Huny's Dev Blog
huny.dev

Convert 24-Bit PCM WAV to 16-Bit PCM in Python

Decode signed 24-bit PCM and apply rounding and saturation to write 16-bit WAV. Verify stereo channel order, boundary samples, and truncated frames in Python.

Hun Jang
Hun Jang Nov 10, 2026

To convert a 24-bit PCM WAV recording or TTS output to 16-bit PCM WAV, read each three-byte sample as a signed integer, scale it by 256, then round and clamp it. The Python example below changes the bit depth on a little-endian host. It preserves stereo channel order and the sample rate, and shows how to detect truncated input frames.

A cyan audio waveform passes through an amber conversion point and emerges in coarser layers

Why negative samples turn into large positive values

In packed 24-bit PCM, each sample occupies three bytes. The little-endian two’s-complement value ff ff ff is -1 when read as signed and 16,777,215 when read as unsigned. Interpreting it as unsigned and then fitting it into the 16-bit range can turn a quiet negative sample into a value near the positive maximum. int.from_bytes(sample, "little", signed=True) specifies both byte order and signed interpretation. Python int.from_bytes documentation

A stereo frame contains one sample from each channel. A 24-bit stereo frame occupies six bytes. The code reads complete frames and converts the samples in order, three bytes at a time. To write the 16-bit WAV, it uses the little-endian signed 16-bit format with struct.pack. Python wave documentation · Python struct documentation

Round and clamp to the 16-bit range

The scale factor from 24-bit to 16-bit integers is 256. This example rounds to the nearest integer with ties away from zero. That maps -128 and 128 to -1 and 1 respectively, while -127 and 127 both map to 0. An arithmetic right shift that discards the low byte rounds negative values down and maps -1 to -1, for example. The rounding rule here maps -1 to 0.

Rounding the largest positive input, 8,388,607, produces 32,768. Since signed 16-bit samples have a maximum of 32,767, the final step clamps the result. The most negative input, -8,388,608, maps exactly to -32,768. The scale is a fixed ratio for changing bit depth. There is no gain adjustment based on the file’s peak level or loudness.

Save the code below as pcm24_to_pcm16.py and run python3 pcm24_to_pcm16.py. It uses only the standard library.

Verify boundary samples and damaged input

The example ran on Linux x86-64 with Python 3.12.14 on November 10, 2026. It converts a generated stereo WAV, reopens the result, and checks the channel count, sample width, sample rate, frame count, and the order and value of all eight samples. It also feeds in a WAV with its final byte removed and verifies that the truncated frame is rejected.

(2, 2, 24000, 4) means two channels, two bytes per sample, 24 kHz, and four frames. The frame count and sample rate stay the same, so the playback duration stays the same too. The first two entries in the sample list are the left and right channels of the first frame.

Apply the conversion to real files

You can pass distinct input and output path strings to convert_wav. An error can leave a partial output, so in a batch job, write to a temporary file and move it to the final path after conversion succeeds. The example was verified with ordinary PCM WAV files in memory. It does not preserve the valid-bit count or channel mask from PCM extensible headers supported by wave, and it does not copy ancillary WAV metadata chunks. Handle files whose container width differs from the valid-bit count separately with a tool that can interpret those format fields. Python wave documentation · Microsoft WAVEFORMATEXTENSIBLE documentation

The example uses no dither. The numeric checks verify the conversion rule. They do not assess quantization distortion in low-level signals or perceived audio quality. For work such as mastering where low-level quality matters, compare conversion policies that include dither separately.

한국어 버전 · Python으로 24비트 PCM WAV를 16비트로 변환하기Python으로 24비트 PCM WAV를 16비트로 변환하기

추천 글