Skip to content

fix(publish): honor requested audio channel count on macOS#1616

Merged
kixelated merged 1 commit into
mainfrom
claude/sweet-bartik-172f4a
Jun 3, 2026
Merged

fix(publish): honor requested audio channel count on macOS#1616
kixelated merged 1 commit into
mainfrom
claude/sweet-bartik-172f4a

Conversation

@kixelated
Copy link
Copy Markdown
Collaborator

Summary

The audio encoder produced stereo Opus from a mono microphone on macOS, wasting bitrate. This forces the WebAudio capture graph to honor the requested channel count instead of trusting the platform's (unreliable) reporting.

Root cause

Source.Microphone already forwards constraints.channelCount to getUserMedia, so the constraint itself wasn't being dropped. The problem is downstream in the encoder:

  • On macOS, track.getSettings().channelCount is undefined and MediaStreamAudioSourceNode.channelCount defaults to 2, so encoder.ts fell back to 2 channels.
  • The AudioWorkletNode used the default channelCountMode: "max", which just follows the input graph rather than the channelCount passed to it. So even a correctly-requested mono mic was carried (and encoded) as duplicated stereo.

Because the platform misreports the track after getUserMedia, the { channelCount: 1 } constraint alone can't fix it at the source.

Changes

  • Add a channelCount override prop on Audio.Encoder (mirrors the existing sampleRate prop). Reachable through Broadcast via audio: { channelCount: 1 }.
  • Recover the requested count from the track's applied constraint via getConstraints().channelCount, which echoes what was requested and survives the macOS misreport (unlike getSettings()).
  • Force channelCountMode: "explicit" when a channel count is requested, so WebAudio deterministically downmixes before the capture worklet sees the frames. The worklet then reports the correct count, so the Opus config and AudioData frames both come out mono automatically.
  • Non-mic sources and callers that don't request a count keep the existing "max" behavior, so there's no regression and no default change.

With this, new Source.Microphone({ constraints: { channelCount: 1 } }) produces correct mono end-to-end. Consuming apps can drop both the JS getUserMedia bypass and any server-side downmix workaround.

Cross-package sync

No paired updates needed. This is browser-only WebAudio capture (the Rust moq-audio path handles PCM channels natively and separately), and demo/web doesn't set a channel count, so the additive prop needs no demo change.

Test plan

  • tsc --noEmit passes for js/publish
  • Biome clean on the changed file
  • Manual: mono mic on macOS now encodes as mono (1 channel in the catalog config) instead of stereo

No unit test added: js/publish has no test harness and Web Audio / getUserMedia aren't mockable in this package.

🤖 Generated with Claude Code

(Written by Claude)

The audio encoder trusted the platform's channel-count reporting when
building the WebAudio capture graph. On macOS getSettings().channelCount
is undefined and MediaStreamAudioSourceNode.channelCount defaults to 2,
so a mono mic was carried (and Opus-encoded) as duplicated stereo,
wasting bitrate. The AudioWorkletNode used the default channelCountMode
"max", which just follows the input, so the requested channel count
never took effect.

Add a channelCount override prop on the encoder (mirroring sampleRate),
recover the requested count from the track's applied getUserMedia
constraint via getConstraints() (which survives the macOS misreport,
unlike getSettings()), and force channelCountMode "explicit" when a
count is requested so WebAudio deterministically downmixes before
capture. Non-mic sources and callers that don't request a count keep
the existing "max" behavior, so there's no regression.

Source.Microphone already forwarded constraints.channelCount to
getUserMedia, so `new Source.Microphone({ constraints: { channelCount: 1 } })`
now produces correct mono end-to-end with no extra plumbing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fecf063-00c4-4a8d-a8fe-df1e22852af4

📥 Commits

Reviewing files that changed from the base of the PR and between e7f53c0 and 6e32462.

📒 Files selected for processing (1)
  • js/publish/src/audio/encoder.ts

Walkthrough

This PR adds optional channelCount signal/prop support to EncoderProps and Encoder to control audio capture channels. The encoder derives a requestedChannels value from the new prop/signal; if absent, it falls back to a constraint-based helper. During AudioWorkletNode creation, the requested channel count and channelCountMode are set explicitly to ensure consistent downmix/upmix behavior, bypassing platform-specific audio settings misreporting.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(publish): honor requested audio channel count on macOS' clearly and concisely summarizes the main change: fixing audio channel count handling on macOS.
Description check ✅ Passed The description provides comprehensive detail about the issue, root cause, and changes, all directly related to the changeset addressing audio channel count handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/sweet-bartik-172f4a

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kixelated kixelated merged commit e5beba5 into main Jun 3, 2026
1 check passed
@kixelated kixelated deleted the claude/sweet-bartik-172f4a branch June 3, 2026 23:35
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