Skip to content

Mic send: PCM as a per-profile alternative to Opus (#7) - #9

Merged
jonathans859 merged 1 commit into
mainfrom
pcm-send
Sep 18, 2026
Merged

jonathans859 merged 1 commit into
mainfrom
pcm-send

Conversation

@jonathans859

Copy link
Copy Markdown
Owner

Closes #7.

The receive path has always decoded PCM; the sender was Opus-only. This adds the other half as a choice, not a change of default — Opus stays the default because it is a twelfth of the data for audio that is effectively transparent for a microphone source.

Wire

  • PCM sends 24-bit frames of 120 samples per channel (2.5 ms) — upstream's "Tight" size, and the only one whose encrypted frame (720 + 28 bytes) still fits one datagram. A PCM frame is all-or-nothing on the receiver, so upstream's 5 ms default would spill into a second part and double what a single lost packet costs. The split is still written generically, because the frame size is a constant we may want to move.
  • Frame is packed to int24-LE, encrypted whole, then split with the 6-byte sub-header; each part carries its own audio sequence, frameId increments per frame. Format fields: 48000 / 2 / 24-bit / encoding 1 / blockAlign 6 / 288000 B/s.
  • Changing codec mid-stream rotates the streamId, like the Windows sender's OnCodecChanged — a receiver keys its session on (endpoint, streamId) plus the format it opened with.

App

  • ReceiverSettings.sendCodec, persisted, default Opus, and part of a profile (decoded with a default, like every field after id/name).
  • Picker in the Send section; its footer states the cost — ~290 kB/s per peer against Opus's ~24 — where the choice is made. Codec also appears in the send status line, peer details, and the profile row summary when it is PCM.

Also here

Mirrors upstream's 46-byte format payload: our own capture latency, as the device reports it, now travels with the format so a peer stops substituting its own guess for a stage that happens here. Write-only for now — there is nothing to display it with in the other direction. Cached per capture graph, never polled (pitfall 5).

Testing

Unit tests cover the PCM stream end to end through the real PcmFrameAssembler + decryptor + unpacker, one datagram per frame, the format fields, codec-change stream rotation, the int24 round trip with clipping, the 46-byte payload in both directions, and the profile/settings defaults. Not yet verified against a Windows peer — the 2.5 ms frame is upstream-supported but this port has never sent one.

🤖 Generated with Claude Code

The receive path has always decoded PCM; the sender was Opus-only. Add the
other half, as a choice rather than a switch of default — Opus stays the
default because it is a twelfth of the data for audio that is effectively
transparent for a microphone source.

PCM sends 24-bit frames of 120 samples per channel (2.5 ms): upstream's
"Tight" size, and the only one whose encrypted frame (720 + 28 bytes) still
fits one datagram. A PCM frame is all-or-nothing on the receiver, so
upstream's 5 ms default would spill into a second part and double what one
lost packet costs. The split is still written generically, because the frame
size is a constant we may want to move.

Changing codec mid-stream rotates the streamId, like the Windows sender's
OnCodecChanged: a receiver keys its session on (endpoint, streamId) plus the
format it opened with.

The choice is persisted, saved in profiles (decoded with a default, like
every field after id/name), and the picker's footer states the cost -
~290 kB/s per peer against Opus's ~24 - where the choice is made.

Also mirrors upstream's 46-byte format payload: our own capture latency, as
the device reports it, now travels with the format so a peer stops
substituting its own guess for a stage that happens here. Write-only for
now; we have nothing to display it with in the other direction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jonathans859
jonathans859 merged commit 0511306 into main Sep 18, 2026
3 checks passed
@jonathans859
jonathans859 deleted the pcm-send branch September 18, 2026 13:50
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.

Add PCM Sending Support

1 participant