TASK 4 ship step 4: WASAPI audio capture — NAudio loopback + mic behind an IAudioSource seam, AudioMixer driving AudioLevel while live, pure level meter + WaveToFloat, NAudio.Wasapi 2.2.1 (MIT, notices item 9) — 139 tests passing, 0 warnings
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Services.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Owns the mic + desktop/game capture sources (TASK 4 ship step 4) and drives
|
||||
/// the live mic meter. Runs only while live: started when go-live succeeds,
|
||||
/// stopped on end-stream. Mic samples are level-metered and forwarded; loopback
|
||||
/// samples are currently dropped (consumed by the encoder mix in a later step).
|
||||
/// </summary>
|
||||
public sealed class AudioMixer : IDisposable
|
||||
{
|
||||
private readonly IAudioSource _mic;
|
||||
private readonly IAudioSource _loopback;
|
||||
private readonly AudioLevelMeter _meter;
|
||||
private readonly Action<string>? _log;
|
||||
private bool _started;
|
||||
|
||||
public AudioMixer(IAudioSource mic, IAudioSource loopback, Action<string>? log = null)
|
||||
{
|
||||
_mic = mic;
|
||||
_loopback = loopback;
|
||||
_meter = new AudioLevelMeter();
|
||||
_log = log;
|
||||
|
||||
_mic.SampleReady += OnMicSample;
|
||||
_loopback.SampleReady += OnLoopbackSample;
|
||||
_mic.Failed += OnMicFailed;
|
||||
_loopback.Failed += OnLoopbackFailed;
|
||||
}
|
||||
|
||||
/// <summary>Current smoothed mic level (0..1).</summary>
|
||||
public float MicLevel => _meter.Level;
|
||||
|
||||
/// <summary>Raised whenever the smoothed mic level changes.</summary>
|
||||
public event Action<float>? MicLevelChanged;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_started)
|
||||
return;
|
||||
|
||||
_started = true;
|
||||
_meter.Reset();
|
||||
_loopback.Start();
|
||||
_mic.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!_started)
|
||||
return;
|
||||
|
||||
_started = false;
|
||||
_mic.Stop();
|
||||
_loopback.Stop();
|
||||
_meter.Reset();
|
||||
MicLevelChanged?.Invoke(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
_mic.SampleReady -= OnMicSample;
|
||||
_loopback.SampleReady -= OnLoopbackSample;
|
||||
_mic.Failed -= OnMicFailed;
|
||||
_loopback.Failed -= OnLoopbackFailed;
|
||||
_mic.Dispose();
|
||||
_loopback.Dispose();
|
||||
}
|
||||
|
||||
private void OnMicSample(AudioSample sample)
|
||||
{
|
||||
MicLevelChanged?.Invoke(_meter.Push(sample));
|
||||
}
|
||||
|
||||
private void OnLoopbackSample(AudioSample sample)
|
||||
{
|
||||
// Desktop/game audio: captured for the future encoder mix; no UI yet.
|
||||
}
|
||||
|
||||
private void OnMicFailed(Exception ex)
|
||||
{
|
||||
_log?.Invoke($"Mic capture failed: {ex.Message}");
|
||||
MicLevelChanged?.Invoke(0);
|
||||
}
|
||||
|
||||
private void OnLoopbackFailed(Exception ex)
|
||||
{
|
||||
_log?.Invoke($"Desktop audio capture failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user