PCM audio for developers: samples, bytes, and API payloads

Developer examining a voice waveform and its discrete samples.
Developer examining a voice waveform and its discrete samples.

When capture, speech services, telephony, and playback disagree on an audio format, valid bytes can become unusable sound. A mismatched sample rate can change speed and pitch. A mismatched byte order can turn speech into noise. An explicit PCM audio contract and a repeatable conversion path make those failures easier to isolate.

What is PCM audio?

Pulse-code modulation (PCM) represents a signal as a sequence of numerical samples. Linear PCM (LPCM) is uncompressed sample data with uniformly spaced quantization levels. A compatible API payload also needs a defined numeric type, stored sample width, sample rate, channel count and layout, byte order, and container.

Sampling measures amplitude at regular time intervals. Quantization maps those measurements to representable values. The PCM format description distinguishes linear, logarithmic, and adaptive variants. In voice integrations, naming the exact variant matters: μ-law and A-law use companding, a nonlinear amplitude mapping. Their telephony bytes cannot be interpreted as signed 16-bit linear samples.

Uncompressed describes the representation. It does not promise perfect recovery of an analog source, repair clipped speech, or restore information lost earlier in the pipeline. More bits or a higher sample rate cannot undo an upstream lossy conversion.

At Dasha, we provide a managed runtime for production voice AI agents. Our browser voice integration carries audio through Web Real-Time Communication (WebRTC) and uses WebSocket signaling. That channel distinction matters before applying any raw-PCM conversion code. The broader voice AI stack separates media handling from speech understanding and conversation control.

The seven properties of a PCM audio format

“PCM” alone is insufficient for decoding bytes. A useful contract names seven properties.

PropertyExample contractWhat it determines
Encoding and numeric typeLinear PCM, signed integerHow stored values represent amplitude
Stored sample width16 bits, 2 bytesBytes occupied by each channel sample
Sample rate16,000 HzSamples per second per channel
Channel count1, monoSamples belonging to each instant
Channel layoutOne channel, sequential samplesSample order and channel meaning
Byte orderLittle-endianOrder of bytes within each multibyte sample
Framing or containerHeaderless raw dataWhether format headers accompany samples

Here, “layout” includes channel order and whether channels are interleaved or stored separately. Interleaved stereo follows L0, R0, L1, R1. Separate, planar channel buffers hold left samples in one array and right samples in another. Concatenating those arrays does not produce interleaved stereo.

Stored width also differs from valid-bit precision in some formats. A sample with 24 valid bits can occupy a wider storage slot. Byte calculations use the stored width, with the contract defining where the valid bits sit.

Signed 16-bit little-endian is one specific contract

Signed 16-bit integer samples range from -32768 to 32767, with zero representing silence. Little-endian places the low-order byte first: the value 4660, hexadecimal 0x1234, becomes the bytes 34 12.

Other PCM conventions exist. Windows waveform audio uses unsigned values for 8-bit PCM and signed values for 16-bit PCM. Unsigned 8-bit silence sits at 128, so filling that format with zero bytes creates a constant offset rather than silence.

Two names that look similar also define different byte orders:

  • Google Speech-to-Text v2 LINEAR16 means headerless, signed 16-bit, little-endian PCM. Its explicit decoding configuration supplies the encoding, sample rate, and channel count.
  • The media type audio/L16 means signed 16-bit, two's-complement samples in network byte order, which is big-endian, with the high-order byte first. Rate and channel parameters accompany the media type in the L16 media type definition.

A LINEAR16 label or an audio/L16 content type does not rearrange the bytes. The serialized samples must already match the receiving contract.

Raw PCM, WAV, and transport solve different problems

Raw PCM contains sample bytes without a file header describing them. The receiver needs the format tuple through configuration, session metadata, or another agreed mechanism. A .pcm or .raw extension cannot supply missing rate, channel, or byte-order information.

WAV is a container. It commonly wraps PCM, but it can also contain other encodings. WAV files based on Resource Interchange File Format (RIFF) have chunks that describe the format and carry audio data. A raw-PCM endpoint expects samples, while a WAV endpoint expects the supported container and encoding.

Removing the first 44 bytes is not a general WAV-to-PCM conversion. Extra chunks, extended format information, and padding can change where the audio starts. A container parser locates the format and audio-data chunks described in the RIFF structure. Extracting the data chunk only yields compatible raw PCM when its encoding already matches the destination.

Transport carries that payload. PCM does not define connection setup, packet timing, retransmission, or session control. A network read boundary is separate from a sample boundary, and a WAV chunk is separate from a streaming message.

PCM sample data shown raw, inside a WAV container with metadata, and across separate transport boundaries.

Browser float samples require numerical conversion

Web Audio exposes script-visible audio as linear 32-bit floating-point PCM, often through a Float32Array. An AudioBuffer provides separate channel arrays. Its nominal amplitude range is [-1, 1], although processing can produce values outside that range. These details follow the Web Audio specification.

An integer payload needs numerical conversion: a defined policy for out-of-range and non-finite values, amplitude scaling, rounding, and serialization in the required byte order. For signed 16-bit output, the final values must fit -32768 through 32767.

Creating an Int16Array view over a float array's buffer reinterprets the floating-point bit patterns. It does not convert amplitudes. Copying normalized floats directly into an integer array also misses amplitude scaling. Neither operation resamples audio or changes the channel layout.

Base64 changes packaging, not audio encoding

Base64 turns binary bytes into text for a transport that requires it. Decoding that text restores the same bytes. It does not turn WAV into raw PCM, float32 into int16, or μ-law into linear PCM.

For example, Twilio Media Streams uses mono, 8 kHz audio/x-mulaw. Audio sent back to Twilio must be base64-encoded without audio-file headers. This is a separate contract from LINEAR16. The Twilio Media Streams format does not establish the format of a Dasha telephony integration.

Prepare a payload from the receiving contract

A PCM preparation pipeline has six responsibilities:

  1. Identify the source. Capture settings or a container parser establish its encoding, actual rate, sample width, channels, and layout. The filename is insufficient.
  2. Define the destination tuple. The endpoint and channel determine whether it accepts a file, raw samples, or negotiated media. A configuration field describes bytes; it does not transform them.
  3. Decode when required. Compressed or companded input needs the appropriate decoder before becoming linear samples. Already-compatible PCM needs no codec conversion.
  4. Convert only mismatches. Channel selection or downmixing, resampling, and numeric conversion are separate operations. Resampling changes the sample sequence; relabeling 48 kHz bytes as 16 kHz changes their interpreted timing. A stereo mix can also cancel opposite-phase signals, while selecting one channel discards the other.
  5. Serialize and package. Sample width, interleaving, and byte order belong to serialization. A WAV wrapper or base64 layer is added only when required by the destination.
  6. Enforce byte and signal invariants. Complete PCM data contains a whole number of audio frames. Decoding the serialized output with the destination tuple should preserve the intended duration, channel assignment, and waveform. Known-value fixtures expose packing errors; a known tone or speech recording exposes timing and amplitude errors that byte length cannot.

This local fixture demonstrates signed 16-bit little-endian serialization. It is not a service request or an audio-conversion pipeline.

Python
import struct samples = (-32768, -1, 0, 1, 4660, 32767) payload = struct.pack("<6h", *samples) assert payload.hex(" ") == "00 80 ff ff 00 00 01 00 34 12 ff 7f" assert struct.unpack("<6h", payload) == samples sample_rate = 16000 channels = 1 stored_bytes_per_sample = 2 frame_bytes = channels * stored_bytes_per_sample assert len(payload) % frame_bytes == 0 frames = len(payload) // frame_bytes assert frames == 6 assert frames / sample_rate == 0.000375

The < selects little-endian byte order and h selects a two-byte signed integer in Python's binary packing format. The six samples occupy 12 bytes and represent 0.375 ms at 16 kHz. They are a packing fixture, not a meaningful listening clip.

Calculate frames, duration, and streaming payload size

An audio frame contains one sample from each channel at the same time instant. A stereo frame has two channel samples. Here, frame means an audio sample frame, not a network packet or a codec frame.

For byte-aligned, fixed-width PCM:

frame bytes = channels × stored bytes per sample raw bytes per second = sample rate × frame bytes duration in seconds = payload bytes / raw bytes per second

These correspond to the frame-size and transfer-rate relationships in the wave format fields. Container headers, message envelopes, base64 expansion, and network overhead are excluded.

For 16 kHz, 16-bit mono PCM:

QuantityCalculationResult
One audio frame1 × 22 bytes
Frames in 20 ms16000 × 0.020320 frames
Audio in 20 ms320 × 2640 bytes
Raw byte rate16000 × 232,000 bytes/s
Raw bit rate32000 × 8256 kbit/s

The 20 ms duration is an arithmetic example. It is not a universal API requirement or an optimal buffering recommendation. A voice activity detector may require its own fixed analysis windows, independent of incoming transport messages.

Preserve partial frames across reads

For a byte-stream reader, incoming fragments can end partway through a sample or frame. The parser combines a retained remainder with the next fragment, processes only complete frames, and retains the new remainder.

For the mono fixture above, a first fragment containing only 00 is incomplete. Once the next fragment begins with 80, the pair 00 80 represents -32768. Dropping the first byte shifts every later sample boundary. In 16-bit stereo, the same principle applies to four-byte frames.

This parser behavior does not authorize sending unaligned API messages. Some interfaces require each message to contain complete samples or frames. Their message-level contract controls the sender. A leftover partial frame at the end of a complete stream indicates truncation or a format mismatch and should fail validation.

Diagnose PCM distortion and timing errors

Symptoms narrow the investigation, but rarely identify one cause uniquely.

SymptomPossible format mismatchEvidence that distinguishes it
Harsh noise or distorted speechWrong byte order; float bits read as integers; companded bytes read as linear PCMKnown sample values and exact encoding at the boundary
Speech is slow and low-pitched, or fast and high-pitchedSample rate is declared or played incorrectlyFrame count, declared rate, and reference duration
Duration or channel content is wrongStereo interpreted as mono; planar data read as interleavedFrame size, channel order, and separate channel waveforms
A burst or corrupt sound at the beginningWAV header or metadata interpreted as samplesContainer identification and audio-data offset
Quiet output, clipping, or a constant offsetWrong amplitude scaling or signednessNumeric range, zero level, and output peaks
Corruption begins at fragment boundariesPartial samples or frames are droppedByte continuity and retained remainder across reads

A one-second, 48 kHz mono recording contains 48,000 frames. Interpreting those frames at 16 kHz produces three seconds of playback and lowers pitch. This is a rate mismatch, even if every sample byte is intact.

Use the actual Dasha channel boundary

Our managed runtime supports production voice integrations through defined browser and phone paths:

  • Browser: our browser voice implementation uses WebRTC for media and WebSocket for signaling. A signaling socket is not a generic raw-PCM upload interface.
  • Customer-held inbound call: our inbound call registration returns a call ID and Session Initiation Protocol (SIP) address. Your telephony bridges the live call to that destination.

A generic PCM fixture supplies neither a Dasha request schema nor a channel-specific rate or chunk size. Those details belong to an explicit media contract. Raw sample handling is useful when your own capture, processing, or speech-service boundary requires it; a negotiated voice channel has a different integration path.

For technical teams that want to build and operate voice agents on a managed runtime, evaluate our voice backend with the browser or telephony path your product will use.

Share

Subscribe

Sign up to our e-mail list to get the best of the Dasha blog sent directly to your inbox.

We use cookies for functional and analytical purposes. Please refer to our Privacy Policy for details.