Skip to content
Merged
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
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -82,7 +82,7 @@ private IEnumerator StartMicrophone()
_deviceName,
loop: true,
lengthSec: 1,
frequency: (int)DefaultMicrophoneSampleRate
frequency: (int)_expectedSampleRate
);
}
catch (Exception e)
Expand Down
75 changes: 54 additions & 21 deletions Runtime/Scripts/RtcAudioSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,11 @@ private sealed class PendingAudioFrame
/// </remarks>
public abstract event Action<float[], int, int> AudioRead;

#if UNITY_IOS && !UNITY_EDITOR
// iOS microphone sample rate is 24k
public static uint DefaultMicrophoneSampleRate = 24000;

public static uint DefaultSampleRate = 48000;
#else
public static uint DefaultSampleRate = 48000;
public static uint DefaultMicrophoneSampleRate = DefaultSampleRate;
#endif
public static uint DefaultChannels = 2;

private readonly RtcAudioSourceType _sourceType;
public RtcAudioSourceType SourceType => _sourceType;
private readonly int _debugId = Interlocked.Increment(ref nextDebugId);
private readonly uint _expectedSampleRate;
private readonly uint _expectedChannels;
internal readonly uint _expectedSampleRate;
internal readonly uint _expectedChannels;

internal readonly FfiHandle Handle;
protected AudioSourceInfo _info;
Expand All @@ -83,20 +72,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 +111,37 @@ 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()
{
var config = UnityEngine.AudioSettings.GetConfiguration();
var sampleRate = (uint)config.sampleRate;
var configuredChannels = SpeakerModeChannels(config.speakerMode);
var channels = configuredChannels;

Utils.Info($"Configured native audio source with sampleRate {sampleRate} and channels {channels}");

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
3 changes: 2 additions & 1 deletion Samples~/Meet/Assets/Runtime/MeetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,9 @@ private IEnumerator PublishLocalMicrophone()
{
if (_audioObjects.ContainsKey(LocalAudioTrackName)) yield break;

// Start the microphone here for early iOS permission request and android getting access to Microphone.devices
Microphone.Start(null, true, 10, 44100);

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