172 lines
5.4 KiB
C#
172 lines
5.4 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace Microsoft.MixedReality.WebRTC.Unity
|
|
{
|
|
/// <summary>
|
|
/// Utility component used to play audio frames obtained from a WebRTC audio source.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Calling <see cref="StartRendering(IAudioSource)"/> and <see cref="StopRendering(IAudioSource)"/>
|
|
/// will start/stop playing the passed <see cref="IAudioSource"/> through a <see cref="UnityEngine.AudioSource"/>
|
|
/// component on the same object, if there is one.
|
|
///
|
|
/// The component will play only while enabled.
|
|
/// </remarks>
|
|
/// <seealso cref="AudioReceiver"/>
|
|
[AddComponentMenu("MixedReality-WebRTC/Audio Renderer")]
|
|
[RequireComponent(typeof(UnityEngine.AudioSource))]
|
|
public class AudioRenderer : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// If true, pad buffer underruns with a sine wave. This will cause artifacts on underruns.
|
|
/// Use for debugging.
|
|
/// </summary>
|
|
public bool PadWithSine = false;
|
|
|
|
// Local storage of audio data to be fed to the output
|
|
private AudioTrackReadBuffer _readBuffer = null;
|
|
|
|
// _readBuffer can be accessed concurrently by audio thread (OnAudioFilterRead)
|
|
// and main thread (StartStreaming, StopStreaming).
|
|
private readonly object _readBufferLock = new object();
|
|
|
|
// Cached sample rate since we can't access this in OnAudioFilterRead.
|
|
private int _audioSampleRate = 0;
|
|
|
|
|
|
// Source that this renderer is currently subscribed to.
|
|
private IAudioSource _source;
|
|
|
|
protected void Awake()
|
|
{
|
|
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
|
|
OnAudioConfigurationChanged(deviceWasChanged: true);
|
|
}
|
|
|
|
protected void OnDestroy()
|
|
{
|
|
AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;
|
|
}
|
|
|
|
protected void OnEnable()
|
|
{
|
|
if (_source != null)
|
|
{
|
|
StartReadBuffer();
|
|
}
|
|
}
|
|
|
|
protected void OnDisable()
|
|
{
|
|
if (_source != null)
|
|
{
|
|
StopReadBuffer();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start rendering the passed source.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Can be used to handle <see cref="AudioReceiver.AudioStreamStarted"/>.
|
|
/// </remarks>
|
|
public void StartRendering(IAudioSource source)
|
|
{
|
|
Debug.Assert(_source == null);
|
|
_source = source;
|
|
|
|
if (isActiveAndEnabled)
|
|
{
|
|
StartReadBuffer();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop rendering the passed source. Must be called with the same source passed to <see cref="StartRendering(IAudioSource)"/>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Can be used to handle <see cref="AudioReceiver.AudioStreamStopped"/>.
|
|
/// </remarks>
|
|
public void StopRendering(IAudioSource source)
|
|
{
|
|
Debug.Assert(_source == source);
|
|
if (isActiveAndEnabled)
|
|
{
|
|
StopReadBuffer();
|
|
}
|
|
_source = null;
|
|
}
|
|
|
|
protected void OnAudioFilterRead(float[] data, int channels)
|
|
{
|
|
var behavior = PadWithSine ?
|
|
AudioTrackReadBuffer.PadBehavior.PadWithSine :
|
|
AudioTrackReadBuffer.PadBehavior.PadWithZero;
|
|
bool hasRead = false;
|
|
bool hasOverrun = false;
|
|
bool hasUnderrun = false;
|
|
|
|
lock (_readBufferLock)
|
|
{
|
|
// Read and use buffer under lock to prevent disposal while in use.
|
|
if (_readBuffer != null)
|
|
{
|
|
_readBuffer.Read(_audioSampleRate, channels, data,
|
|
out int numSamplesRead, out hasOverrun, behavior);
|
|
hasRead = true;
|
|
hasUnderrun = numSamplesRead < data.Length;
|
|
}
|
|
}
|
|
|
|
if (hasRead)
|
|
{
|
|
// Uncomment for debugging.
|
|
//if (hasOverrun)
|
|
//{
|
|
// Debug.LogWarning($"Overrun in track {Track.Name}");
|
|
//}
|
|
//if (hasUnderrun)
|
|
//{
|
|
// Debug.LogWarning($"Underrun in track {Track.Name}");
|
|
//}
|
|
|
|
return;
|
|
}
|
|
|
|
// If there is no track/buffer, fill array with 0s.
|
|
for (int i = 0; i < data.Length; ++i)
|
|
{
|
|
data[i] = 0.0f;
|
|
}
|
|
}
|
|
|
|
private void OnAudioConfigurationChanged(bool deviceWasChanged)
|
|
{
|
|
_audioSampleRate = AudioSettings.outputSampleRate;
|
|
}
|
|
|
|
private void StartReadBuffer()
|
|
{
|
|
Debug.Assert(_readBuffer == null);
|
|
|
|
// OnAudioFilterRead reads the variable concurrently, but the update is atomic
|
|
// so we don't need a lock.
|
|
_readBuffer = _source.CreateReadBuffer();
|
|
}
|
|
|
|
private void StopReadBuffer()
|
|
{
|
|
lock (_readBufferLock)
|
|
{
|
|
// Under lock so OnAudioFilterRead won't use the buffer while/after it is disposed.
|
|
_readBuffer.Dispose();
|
|
_readBuffer = null;
|
|
}
|
|
}
|
|
}
|
|
}
|