Skip to content

fix(codecs): skip RIFF pad byte after odd-sized WAV chunks#6452

Open
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/wav-inline-decoder-odd-chunk-pad-byte
Open

fix(codecs): skip RIFF pad byte after odd-sized WAV chunks#6452
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/wav-inline-decoder-odd-chunk-pad-byte

Conversation

@chuenchen309

Copy link
Copy Markdown

What

RIFF chunks are word-aligned: a chunk whose size is odd is followed by a single pad byte that is not counted in the chunk size. The inline WAV decoder (_WavInlineDecoder) consumed exactly chunk_size bytes for skipped ancillary chunks (LIST/bext/cue /...) and for the fmt chunk, then read the next chunk header immediately — one byte too early whenever the preceding chunk was odd. The misaligned header parses garbage, so the real data chunk is never found and every frame is silently dropped (0 frames, no error).

This hits any spec-compliant WAV that carries an odd-length metadata chunk before data, which tools like ffmpeg/sox routinely emit.

Repro (before fix):

import asyncio, struct
from livekit.agents.utils.codecs import AudioStreamDecoder

def make_wav(anc_len):          # LIST chunk before data; odd len => 1 RIFF pad byte
    sr, ch, bps, n = 24000, 1, 16, 2400
    fmt = struct.pack("<HHIIHH", 1, ch, sr, sr*ch*bps//8, ch*bps//8, bps)
    anc = b"\x00"*anc_len + (b"\x00" if anc_len % 2 else b"")
    body = (b"fmt " + struct.pack("<I", len(fmt)) + fmt
            + b"LIST" + struct.pack("<I", anc_len) + anc
            + b"data" + struct.pack("<I", n*2) + b"\x00"*(n*2))
    return b"RIFF" + struct.pack("<I", 4+len(body)) + b"WAVE" + body, n

async def total(anc_len):
    wav, n = make_wav(anc_len)
    dec = AudioStreamDecoder(sample_rate=24000, num_channels=1, format="audio/wav")
    dec.push(wav); dec.end_input()
    return n, sum(f.samples_per_channel async for f in dec)

async def main():
    print("even LIST (no pad):", await total(4))   # (2400, 2400) ok
    print("odd  LIST (+pad):  ", await total(3))   # (2400, 0)    all audio lost
asyncio.run(main())

Fix

Fold the pad byte into the skip length for ancillary chunks (chunk_size + (chunk_size & 1)), and skip the trailing pad byte for an odd-sized fmt chunk, so the following chunk header stays word-aligned.

Testing

  • New parametrized regression test test_wav_inline_decoder_odd_ancillary_chunk (even control + odd case, fed byte-by-byte to exercise the incremental state machine across the pad byte) — red on the odd case before the fix, green after.
  • Full tests/test_audio_decoder.py unit suite: 15 passed, 1 skipped (cloud-cred test), no regressions.
  • make check (ruff format-check + ruff lint + strict mypy) clean.

AI disclosure

Investigated and implemented with Claude Code (Claude Opus 4.8); I reviewed the diff, wrote and ran the repro, ran the full test suite and make check myself before opening this PR.

RIFF chunks are word-aligned: a chunk whose size is odd is followed by a
single pad byte that is not counted in the chunk size. The inline WAV
decoder consumed exactly `chunk_size` bytes for skipped ancillary chunks
(LIST/bext/cue/...) and for the fmt chunk, then read the next chunk header
immediately — one byte too early whenever the preceding chunk was odd.

The misaligned header then parses garbage, so the real `data` chunk is
never found and every audio frame is silently dropped (0 frames, no error).
This bites any spec-compliant WAV that carries an odd-length metadata chunk
before `data`, which tools like ffmpeg/sox routinely produce.

Fold the pad byte into the skip length for ancillary chunks, and skip the
trailing pad byte for an odd-sized fmt chunk, so the next header stays
word-aligned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chuenchen309
chuenchen309 requested a review from a team as a code owner July 16, 2026 05:39

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant