Skip to content

Decode Opus at 16 bits whatever stream/start's bit_depth says - #197

Merged
kahrendt merged 1 commit into
mainfrom
claude/opus-bit-depth-mismatch-47c52a
Oct 11, 2026
Merged

kahrendt merged 1 commit into
mainfrom
claude/opus-bit-depth-mismatch-47c52a

Conversation

@kahrendt

Copy link
Copy Markdown
Contributor

Summary

roles/player/v1.md "stream/start player object" says bit_depth is ignored for opus, so a compliant server may send any value. handle_stream_start() copied it into the Opus codec header and the published stream params. The decoder always writes opus_decode()'s 16-bit samples, but sizes frames and output bytes from the header, so any other depth broke playback (reproduced with a 20 ms stereo packet):

  • 24: reports 5760 bytes decoded for 3840 written, passing stale buffer bytes on as audio.
  • 8: overflows the decode buffer by about 1920 bytes per packet.
  • 0: divides by zero in bytes_to_frames().

The consumer also configured its output from the wire depth through get_current_stream_params().

  • For an Opus stream, handle_stream_start() sets bit_depth to the new OPUS_DECODED_BITS_PER_SAMPLE (16, in decoder.h) before it builds the codec header and publishes the params. The player is the only producer of the Opus header, so this is the one place.
  • handle_stream_start() takes its param by value so it can adjust it and move it into the params slot (same one copy as before).

sendspin-server and aiosendspin both send 16 for Opus today, so no known server hits this.

Tests

New table test PlayerStreamStart.BitDepthIsTheDecodedDepth: stream/start with bit_depth: 24 puts 16 in both the codec header and the published params for Opus, and keeps 24 for PCM (control). Mutation-checked: dropping the override, or building the header from the wire value, fails it. 701/701 under ASan.

roles/player/v1.md "stream/start player object" ignores bit_depth for
opus, but handle_stream_start() copied it into the Opus codec header and
the published stream params. The decoder always writes opus_decode()'s
16-bit samples while sizing frames and output bytes from that header, so
a server sending another depth broke playback: 24 reported stale buffer
bytes as audio, 8 overflowed the decode buffer, and 0 divided by zero.
The consumer also configured its output from the wire depth.

For an Opus stream, handle_stream_start() now sets bit_depth to
OPUS_DECODED_BITS_PER_SAMPLE (16, in decoder.h) before building the
codec header and publishing the params.
@kahrendt
kahrendt marked this pull request as ready for review October 11, 2026 18:19
@kahrendt
kahrendt requested a balanced review from Copilot October 11, 2026 18:19

Copilot AI 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.

🟡 Changes recommended

The public header and integration guide must document the new Opus bit_depth semantics.

1 open finding
What changed in this PR

Normalizes Opus playback parameters to the decoder’s fixed 16-bit output format.

Changes:

  • Adds a shared Opus decoded-depth constant.
  • Normalizes codec headers and published stream parameters.
  • Tests Opus normalization with PCM as a control.
File Description
src/​decoder.h Defines the 16-bit Opus output depth.
src/​player_role.cpp Normalizes Opus stream parameters.
src/​player_role_impl.h Updates the handler signature.
tests/​test_player_role.cpp Tests normalized and control behavior.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/player_role.cpp
@kahrendt
kahrendt merged commit 24dc47c into main Oct 11, 2026
17 checks passed
@kahrendt
kahrendt deleted the claude/opus-bit-depth-mismatch-47c52a branch October 11, 2026 18:25
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.

2 participants