Skip to content

Commit 28a34b3

Browse files
authored
Configure native audio source from device config instead of hardcoded defaults (#304)
* Configure native audio source from device, not hardcoded defaults 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 * Adds microphone start again for iOS, adds a debug log * Trust Unity audio selection, use sample rate detected in mic start
1 parent 58f46a5 commit 28a34b3

5 files changed

Lines changed: 63 additions & 27 deletions

File tree

Runtime/Scripts/BasicAudioSource.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ sealed public class BasicAudioSource : RtcAudioSource
1919
/// Creates a new basic audio source for the given <see cref="AudioSource"/> in the scene.
2020
/// </summary>
2121
/// <param name="source">The <see cref="AudioSource"/> to capture from.</param>
22-
/// <param name="channels">The number of channels to capture.</param>
2322
/// <param name="sourceType">The type of audio source.</param>
24-
public BasicAudioSource(AudioSource source, int channels = 2, RtcAudioSourceType sourceType = RtcAudioSourceType.AudioSourceCustom) : base(channels, sourceType)
23+
/// <remarks>
24+
/// The sample rate and channel count are taken from Unity's audio configuration.
25+
/// </remarks>
26+
public BasicAudioSource(AudioSource source, RtcAudioSourceType sourceType = RtcAudioSourceType.AudioSourceCustom) : base(sourceType)
2527
{
2628
_source = source;
2729
}

Runtime/Scripts/MicrophoneSource.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ sealed public class MicrophoneSource : RtcAudioSource
2828
/// get the list of available devices.</param>
2929
/// <param name="sourceObject">The GameObject to attach the AudioSource to. The object must be kept in the scene
3030
/// for the duration of the source's lifetime.</param>
31-
public MicrophoneSource(string deviceName, GameObject sourceObject) : base(2, RtcAudioSourceType.AudioSourceMicrophone)
31+
public MicrophoneSource(string deviceName, GameObject sourceObject) : base(RtcAudioSourceType.AudioSourceMicrophone)
3232
{
3333
_deviceName = deviceName;
3434
_sourceObject = sourceObject;
@@ -82,7 +82,7 @@ private IEnumerator StartMicrophone()
8282
_deviceName,
8383
loop: true,
8484
lengthSec: 1,
85-
frequency: (int)DefaultMicrophoneSampleRate
85+
frequency: (int)_expectedSampleRate
8686
);
8787
}
8888
catch (Exception e)

Runtime/Scripts/RtcAudioSource.cs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,11 @@ private sealed class PendingAudioFrame
4646
/// </remarks>
4747
public abstract event Action<float[], int, int> AudioRead;
4848

49-
#if UNITY_IOS && !UNITY_EDITOR
50-
// iOS microphone sample rate is 24k
51-
public static uint DefaultMicrophoneSampleRate = 24000;
52-
53-
public static uint DefaultSampleRate = 48000;
54-
#else
55-
public static uint DefaultSampleRate = 48000;
56-
public static uint DefaultMicrophoneSampleRate = DefaultSampleRate;
57-
#endif
58-
public static uint DefaultChannels = 2;
59-
6049
private readonly RtcAudioSourceType _sourceType;
6150
public RtcAudioSourceType SourceType => _sourceType;
6251
private readonly int _debugId = Interlocked.Increment(ref nextDebugId);
63-
private readonly uint _expectedSampleRate;
64-
private readonly uint _expectedChannels;
52+
internal readonly uint _expectedSampleRate;
53+
internal readonly uint _expectedChannels;
6554

6655
internal readonly FfiHandle Handle;
6756
protected AudioSourceInfo _info;
@@ -83,20 +72,33 @@ private sealed class PendingAudioFrame
8372
private volatile bool _disposed = false;
8473
private int _audioReadCount = 0;
8574

86-
protected RtcAudioSource(int channels = 2, RtcAudioSourceType audioSourceType = RtcAudioSourceType.AudioSourceCustom)
75+
// Device-capture sources (microphone, AudioSource taps) don't know their format ahead of
76+
// time — it is whatever Unity's audio graph delivers. They use this constructor, which
77+
// configures the native source from Unity's current output configuration.
78+
protected RtcAudioSource(RtcAudioSourceType audioSourceType)
79+
: this(audioSourceType, 0, 0) { }
80+
81+
// Sources that generate a fixed, known format (e.g. test signal generators) declare it
82+
// directly. Passing 0 for either value falls back to the device configuration.
83+
protected RtcAudioSource(RtcAudioSourceType audioSourceType, uint sampleRate, uint channels)
8784
{
8885
_sourceType = audioSourceType;
89-
_expectedChannels = (uint)channels;
86+
87+
if (sampleRate > 0 && channels > 0)
88+
{
89+
_expectedSampleRate = sampleRate;
90+
_expectedChannels = channels;
91+
}
92+
else
93+
{
94+
(_expectedSampleRate, _expectedChannels) = ResolveDeviceFormat();
95+
}
9096

9197
using var request = FFIBridge.Instance.NewRequest<NewAudioSourceRequest>();
9298
var newAudioSource = request.request;
9399
newAudioSource.Type = AudioSourceType.AudioSourceNative;
94-
newAudioSource.NumChannels = (uint)channels;
95-
newAudioSource.SampleRate = _sourceType == RtcAudioSourceType.AudioSourceMicrophone ?
96-
DefaultMicrophoneSampleRate : DefaultSampleRate;
97-
_expectedSampleRate = newAudioSource.SampleRate;
98-
99-
Utils.Debug($"NewAudioSource: {newAudioSource.NumChannels} {newAudioSource.SampleRate}");
100+
newAudioSource.NumChannels = _expectedChannels;
101+
newAudioSource.SampleRate = _expectedSampleRate;
100102

101103
newAudioSource.Options = request.TempResource<AudioSourceOptions>();
102104
newAudioSource.Options.EchoCancellation = true;
@@ -109,6 +111,37 @@ protected RtcAudioSource(int channels = 2, RtcAudioSourceType audioSourceType =
109111
Utils.Debug($"{DebugTag} created handle={Handle.DangerousGetHandle()} expectedRate={_expectedSampleRate} expectedChannels={_expectedChannels} sourceType={_sourceType}");
110112
}
111113

114+
// Reads Unity's actual output audio configuration. The capture path delivers buffers at the
115+
// DSP output rate/channel count (see AudioProbe), so this is the format the native source
116+
// must match. Falls back to the platform defaults when Unity cannot report a configuration
117+
// (e.g. batch mode without an audio device).
118+
private (uint sampleRate, uint channels) ResolveDeviceFormat()
119+
{
120+
var config = UnityEngine.AudioSettings.GetConfiguration();
121+
var sampleRate = (uint)config.sampleRate;
122+
var configuredChannels = SpeakerModeChannels(config.speakerMode);
123+
var channels = configuredChannels;
124+
125+
Utils.Info($"Configured native audio source with sampleRate {sampleRate} and channels {channels}");
126+
127+
return (sampleRate, channels);
128+
}
129+
130+
private static uint SpeakerModeChannels(UnityEngine.AudioSpeakerMode mode)
131+
{
132+
switch (mode)
133+
{
134+
case UnityEngine.AudioSpeakerMode.Mono: return 1;
135+
case UnityEngine.AudioSpeakerMode.Stereo: return 2;
136+
case UnityEngine.AudioSpeakerMode.Quad: return 4;
137+
case UnityEngine.AudioSpeakerMode.Surround: return 5;
138+
case UnityEngine.AudioSpeakerMode.Mode5point1: return 6;
139+
case UnityEngine.AudioSpeakerMode.Mode7point1: return 8;
140+
case UnityEngine.AudioSpeakerMode.Prologic: return 2;
141+
default: return 0;
142+
}
143+
}
144+
112145
/// <summary>
113146
/// Begin capturing audio samples from the underlying source.
114147
/// </summary>

Samples~/Meet/Assets/Runtime/MeetManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,9 @@ private IEnumerator PublishLocalMicrophone()
453453
{
454454
if (_audioObjects.ContainsKey(LocalAudioTrackName)) yield break;
455455

456+
// Start the microphone here for early iOS permission request and android getting access to Microphone.devices
456457
Microphone.Start(null, true, 10, 44100);
457-
458+
458459
var audioObject = new GameObject($"My Microphone: {Microphone.devices[0]}");
459460
audioObject.transform.SetParent(_audioTrackParent);
460461

Tests/PlayMode/Utils/SineWaveAudioSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public SineWaveAudioSource(
3131
int sampleRate = 48000,
3232
double frequencyHz = 440.0,
3333
float amplitude = 0.1f)
34-
: base(channels, RtcAudioSourceType.AudioSourceCustom)
34+
: base(RtcAudioSourceType.AudioSourceCustom, (uint)sampleRate, (uint)channels)
3535
{
3636
_channels = channels;
3737
_sampleRate = sampleRate;

0 commit comments

Comments
 (0)