Skip to content

Recreate audio source and republish track on sample-rate change#312

Open
MaxHeimbrock wants to merge 5 commits into
max/mic-restart-on-device-changefrom
max/mic-rate-change-republish
Open

Recreate audio source and republish track on sample-rate change#312
MaxHeimbrock wants to merge 5 commits into
max/mic-restart-on-device-changefrom
max/mic-rate-change-republish

Conversation

@MaxHeimbrock

Copy link
Copy Markdown
Contributor

Stacked on #311 — review/merge that first. This PR's diff is one commit on top of it.

Problem

#311 restarts capture on a device change, but the native audio source's rate is fixed at construction. If the new device has a different sample rate, RtcAudioSource silently drops every captured frame (it rejects frames whose rate/channels don't match the source) — so audio stays dead.

The FFI exposes no in-place source reconfigure (only NewAudioSource), so recovery requires a fresh native source and a re-bound track.

Change

  • Add RtcAudioSource.Reconfigure(sampleRate, channels): disposes the old native handle, rebuilds the source at the new format, and raises a new FormatChanged event. The native source stays alive via the track's reference until the track is dropped, so disposing the handle before the old track is unpublished is safe.
  • MicrophoneSource detects the format change via ResolveDeviceFormat in its config-changed handler and calls Reconfigure inside the restart while capture is paused (no AudioRead callbacks in flight), replacing the warning from Restart microphone capture on audio device change #311.
  • MeetManager subscribes to FormatChanged and republishes the local audio track against the source's new handle (unsubscribing on unpublish/cleanup).

Note

Republishing yields a new track SID, so remote peers resubscribe on a rate change — acceptable but observable.

Testing

  • Scripts~/run_unity.sh build macos → Build SUCCEEDED.
  • Manual runtime verification (device swap that actually changes the DSP rate → audio recovers with a republish log line) still recommended on a real device.

🤖 Generated with Claude Code

MaxHeimbrock and others added 4 commits June 15, 2026 16:23
The native (Rust) audio source was created with a hardcoded sample rate
(48000) and channel count (2). Microphone frames flow through Unity's
audio graph (AudioProbe) at the actual DSP output configuration, which
often differs — e.g. with a Bluetooth headset. The Rust source does not
resample; it rejects frames whose rate/channels don't match, causing the
metadata-mismatch warning and capture failures.

Read the source's sample rate and channel count from Unity's output
configuration (AudioSettings.GetConfiguration) instead of hardcoded
defaults, falling back to the defaults only when Unity can't report one.
The base constructor now exposes a device-mode overload (type only) and an
explicit overload (type, sampleRate, channels) for sources that generate a
fixed format. MicrophoneSource and BasicAudioSource use device mode;
BasicAudioSource drops its unused channels parameter. SineWaveAudioSource
declares its exact format.

If a frame's format still doesn't match (inconsistent Unity report or a
runtime output change), drop it with a throttled warning instead of
sending a mismatch the native side would error on. Also removes the
redundant Microphone.Start in the Meet sample.

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

Remove logging changes
When a capture device disappears mid-call (e.g. unplugging a Bluetooth
headset), the local microphone went silent and never recovered:
MicrophoneSource stayed bound to the gone device name and never
re-registered the AudioProbe tap that Unity detaches when it rebuilds
its audio graph.

Subscribe to AudioSettings.OnAudioConfigurationChanged (mirroring the
playback-side AudioStream handler) and restart capture on a device
change, resolving to the OS default device when the preferred device is
no longer present. Track the active device separately from the preferred
one so Microphone.IsRecording/GetPosition/End target the right device,
and guard against overlapping restarts.

The native source's rate is fixed at construction, so if the device
change moves Unity's DSP output rate, frames are still dropped; warn
clearly in that case. Full rate-change recovery follows separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaxHeimbrock MaxHeimbrock force-pushed the max/mic-restart-on-device-change branch from 37725a6 to d19145d Compare June 15, 2026 14:26
Commit 1 restarts capture on a device change but the native source's
rate stays fixed at construction, so a device whose rate differs from
the original silently has every frame dropped.

Add RtcAudioSource.Reconfigure(sampleRate, channels): it disposes the
old native handle, rebuilds the source at the new format, and raises a
new FormatChanged event. The native source stays alive via the track's
reference until the track is dropped, so disposing the handle before the
old track is unpublished is safe. The FFI exposes no in-place source
reconfigure, so a fresh source (and re-bound track) is required.

MicrophoneSource detects the format change via ResolveDeviceFormat in
its config-changed handler and calls Reconfigure inside the restart
while capture is paused (no AudioRead callbacks in flight). MeetManager
subscribes to FormatChanged and republishes the local audio track
against the source's new handle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaxHeimbrock MaxHeimbrock force-pushed the max/mic-rate-change-republish branch from 9de6396 to 817b1a6 Compare June 15, 2026 14:27
if (sampleRate == 0 || channels == 0) return false;
if (sampleRate == _expectedSampleRate && channels == _expectedChannels) return false;

Utils.Debug($"{DebugTag} reconfigure {_expectedSampleRate}/{_expectedChannels} -> {sampleRate}/{channels}");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Without this change, we can have:

LiveKit: MicrophoneSource: audio device change moved the DSP output rate to 44100Hz, but the native source is fixed at 48000Hz. Captured frames will be dropped until the track is recreated at the new rate.
UnityEngine.Logger:LogWarning (string,object)
LiveKit.Internal.Utils:Warning (object) (at /Users/maxheimbrock/dev/unity/client-sdk-unity/Runtime/Scripts/Internal/Utils.cs:41)
LiveKit.MicrophoneSource:OnAudioConfigurationChanged (bool) (at /Users/maxheimbrock/dev/unity/client-sdk-unity/Runtime/Scripts/MicrophoneSource.cs:248)
UnityEngine.AudioSettings:InvokeOnAudioConfigurationChanged (bool) (at /Users/bokken/build/output/unity/unity/Modules/Audio/Public/ScriptBindings/Audio.bindings.cs:413)

@MaxHeimbrock MaxHeimbrock force-pushed the max/mic-restart-on-device-change branch from d19145d to 442e2b3 Compare June 16, 2026 12:17
@MaxHeimbrock MaxHeimbrock force-pushed the max/mic-restart-on-device-change branch from 442e2b3 to ace30fa Compare June 16, 2026 12:59
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