Skip to content
31 changes: 31 additions & 0 deletions Runtime/Scripts/AudioStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ public sealed class AudioStream : IDisposable
private const int CrossfadeFrames = 128; // ~2.7ms @ 48kHz
private int _skipCooldown = 0;

// --- Temporary receive diagnostics (Info level, emitted ~every 2s) ---
// Reveals whether choppiness is a buffer-starvation problem (underruns/low fill) versus a
// clean stream, and what rate/channels we are actually playing/requesting.
private long _diagWindowStartTicks;
private int _diagCallbacks;
private int _diagUnderruns;
private int _diagFramesReceived;

/// <summary>
/// Creates a new audio stream from a remote audio track, attaching it to the
/// given <see cref="AudioSource"/> in the scene.
Expand Down Expand Up @@ -147,6 +155,8 @@ private void OnAudioRead(float[] data, int channels, int sampleRate)

lock (_lock)
{
MaybeLogReceiveDiagnostics(channels, sampleRate);

// Single gate covering first-create and runtime format changes (e.g. after a
// system audio device switch). When the FFI stream is missing or what we asked
// Rust for no longer matches what Unity is delivering, post a (re)create to the
Expand Down Expand Up @@ -214,6 +224,7 @@ static float S16ToFloat(short v)
if (valuesAvailableToRead < data.Length)
{
_isPrimed = false;
_diagUnderruns++;
Utils.Debug($"AudioStream underrun detected, re-priming (got {valuesAvailableToRead} samples but want to read {data.Length})");

// Output silence immediately instead of playing partial/choppy samples.
Expand Down Expand Up @@ -370,6 +381,7 @@ private void OnAudioStreamEvent(AudioStreamEvent e)
var data = new ReadOnlySpan<byte>(frame.Data.ToPointer(), frame.Length);
_buffer.Write(data);
}
_diagFramesReceived++;
}
}

Expand Down Expand Up @@ -427,6 +439,25 @@ private void Dispose(bool disposing)
Dispose(false);
}

// Temporary diagnostic: ~every 2s logs buffer fill, underrun count, callback count and
// frames received so we can tell starvation (choppy) from a clean stream. Called under _lock.
private void MaybeLogReceiveDiagnostics(int channels, int sampleRate)
{
_diagCallbacks++;
var now = System.Diagnostics.Stopwatch.GetTimestamp();
if (_diagWindowStartTicks == 0) _diagWindowStartTicks = now;
var elapsed = (now - _diagWindowStartTicks) / (double)System.Diagnostics.Stopwatch.Frequency;
if (elapsed < 2.0) return;

float fill = _buffer != null ? _buffer.AvailableReadInPercent() : 0f;
Utils.Info($"AudioStream#{_trackHandleId} diag: out={sampleRate}Hz/{channels}ch ffi={_ffiSampleRate}Hz/{_ffiNumChannels}ch " +
$"bufferFill={fill * 100f:F0}% callbacks={_diagCallbacks} underruns={_diagUnderruns} framesRecv={_diagFramesReceived} over={elapsed:F1}s");
_diagWindowStartTicks = now;
_diagCallbacks = 0;
_diagUnderruns = 0;
_diagFramesReceived = 0;
}

// For testing and debugging
internal float GetBufferFill()
{
Expand Down
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
Loading
Loading