Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Runtime/Scripts/BasicAudioSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ sealed public class BasicAudioSource : RtcAudioSource
/// Creates a new basic audio source for the given <see cref="AudioSource"/> in the scene.
/// </summary>
/// <param name="source">The <see cref="AudioSource"/> to capture from.</param>
/// <param name="channels">The number of channels to capture.</param>
/// <param name="sourceType">The type of audio source.</param>
public BasicAudioSource(AudioSource source, int channels = 2, RtcAudioSourceType sourceType = RtcAudioSourceType.AudioSourceCustom) : base(channels, sourceType)
/// <remarks>
/// The sample rate and channel count are taken from Unity's audio configuration.
/// </remarks>
public BasicAudioSource(AudioSource source, RtcAudioSourceType sourceType = RtcAudioSourceType.AudioSourceCustom) : base(sourceType)
{
_source = source;
}
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Scripts/MicrophoneSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ sealed public class MicrophoneSource : RtcAudioSource
/// get the list of available devices.</param>
/// <param name="sourceObject">The GameObject to attach the AudioSource to. The object must be kept in the scene
/// for the duration of the source's lifetime.</param>
public MicrophoneSource(string deviceName, GameObject sourceObject) : base(2, RtcAudioSourceType.AudioSourceMicrophone)
public MicrophoneSource(string deviceName, GameObject sourceObject) : base(RtcAudioSourceType.AudioSourceMicrophone)
{
_deviceName = deviceName;
_sourceObject = sourceObject;
Expand Down
81 changes: 72 additions & 9 deletions Runtime/Scripts/RtcAudioSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,33 @@ private sealed class PendingAudioFrame
private volatile bool _disposed = false;
private int _audioReadCount = 0;

protected RtcAudioSource(int channels = 2, RtcAudioSourceType audioSourceType = RtcAudioSourceType.AudioSourceCustom)
// Device-capture sources (microphone, AudioSource taps) don't know their format ahead of
// time — it is whatever Unity's audio graph delivers. They use this constructor, which
// configures the native source from Unity's current output configuration.
protected RtcAudioSource(RtcAudioSourceType audioSourceType)
: this(audioSourceType, 0, 0) { }

// Sources that generate a fixed, known format (e.g. test signal generators) declare it
// directly. Passing 0 for either value falls back to the device configuration.
protected RtcAudioSource(RtcAudioSourceType audioSourceType, uint sampleRate, uint channels)
{
_sourceType = audioSourceType;
_expectedChannels = (uint)channels;

if (sampleRate > 0 && channels > 0)
{
_expectedSampleRate = sampleRate;
_expectedChannels = channels;
}
else
{
(_expectedSampleRate, _expectedChannels) = ResolveDeviceFormat();
}

using var request = FFIBridge.Instance.NewRequest<NewAudioSourceRequest>();
var newAudioSource = request.request;
newAudioSource.Type = AudioSourceType.AudioSourceNative;
newAudioSource.NumChannels = (uint)channels;
newAudioSource.SampleRate = _sourceType == RtcAudioSourceType.AudioSourceMicrophone ?
DefaultMicrophoneSampleRate : DefaultSampleRate;
_expectedSampleRate = newAudioSource.SampleRate;

Utils.Debug($"NewAudioSource: {newAudioSource.NumChannels} {newAudioSource.SampleRate}");
newAudioSource.NumChannels = _expectedChannels;
newAudioSource.SampleRate = _expectedSampleRate;

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

// Reads Unity's actual output audio configuration. The capture path delivers buffers at the
// DSP output rate/channel count (see AudioProbe), so this is the format the native source
// must match. Falls back to the platform defaults when Unity cannot report a configuration
// (e.g. batch mode without an audio device).
private (uint sampleRate, uint channels) ResolveDeviceFormat()
{
uint sampleRate = _sourceType == RtcAudioSourceType.AudioSourceMicrophone
? DefaultMicrophoneSampleRate
: DefaultSampleRate;
uint channels = DefaultChannels;

try
{
var config = UnityEngine.AudioSettings.GetConfiguration();
if (config.sampleRate > 0)
sampleRate = (uint)config.sampleRate;
var configuredChannels = SpeakerModeChannels(config.speakerMode);
if (configuredChannels > 0)
channels = configuredChannels;
}
catch (Exception e)
{
Utils.Warning($"{DebugTag} could not read Unity audio configuration, using defaults: {e.Message}");
}

return (sampleRate, channels);
}

private static uint SpeakerModeChannels(UnityEngine.AudioSpeakerMode mode)
{
switch (mode)
{
case UnityEngine.AudioSpeakerMode.Mono: return 1;
case UnityEngine.AudioSpeakerMode.Stereo: return 2;
case UnityEngine.AudioSpeakerMode.Quad: return 4;
case UnityEngine.AudioSpeakerMode.Surround: return 5;
case UnityEngine.AudioSpeakerMode.Mode5point1: return 6;
case UnityEngine.AudioSpeakerMode.Mode7point1: return 8;
case UnityEngine.AudioSpeakerMode.Prologic: return 2;
default: return 0;
}
}

/// <summary>
/// Begin capturing audio samples from the underlying source.
/// </summary>
Expand Down Expand Up @@ -153,9 +209,16 @@ private void OnAudioRead(float[] data, int channels, int sampleRate)
return;
}

// The native source rejects frames whose rate/channels differ from how it was
// configured (it does not resample). This should not happen now that the source is
// configured from the device, but if Unity reports an inconsistent format — or the
// output configuration changes at runtime — we drop the frame instead of sending a
// mismatch the native side would error on.
if ((uint)sampleRate != _expectedSampleRate || (uint)channels != _expectedChannels)
{
Utils.Warning($"{DebugTag} audio frame #{frameIndex} metadata mismatch actualRate={sampleRate} actualChannels={channels} expectedRate={_expectedSampleRate} expectedChannels={_expectedChannels} sourceType={_sourceType}");
if (frameIndex == 1 || frameIndex % 100 == 0)
Utils.Warning($"{DebugTag} dropping audio frame #{frameIndex}: format {sampleRate}/{channels} does not match source {_expectedSampleRate}/{_expectedChannels} (sourceType={_sourceType})");
return;
}

var pendingBeforeSend = PendingFrameCount();
Expand Down
3 changes: 1 addition & 2 deletions Samples~/Meet/Assets/Runtime/MeetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,7 @@ private IEnumerator PublishLocalMicrophone()
{
if (_audioObjects.ContainsKey(LocalAudioTrackName)) yield break;

Microphone.Start(null, true, 10, 44100);

// MicrophoneSource starts the device itself, so we only need the device name here.
var audioObject = new GameObject($"My Microphone: {Microphone.devices[0]}");
audioObject.transform.SetParent(_audioTrackParent);

Expand Down
2 changes: 1 addition & 1 deletion Tests/PlayMode/Utils/SineWaveAudioSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public SineWaveAudioSource(
int sampleRate = 48000,
double frequencyHz = 440.0,
float amplitude = 0.1f)
: base(channels, RtcAudioSourceType.AudioSourceCustom)
: base(RtcAudioSourceType.AudioSourceCustom, (uint)sampleRate, (uint)channels)
{
_channels = channels;
_sampleRate = sampleRate;
Expand Down
Loading