106 lines
2.9 KiB
C#
106 lines
2.9 KiB
C#
using NAudio.CoreAudioApi;
|
|
using NAudio.Wave;
|
|
|
|
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// Captures the selected microphone (TASK 4 ship step 4) via WASAPI. Resolves
|
|
/// the NAudio device by FriendlyName matching <c>MicSourceName</c> (the app only
|
|
/// persists DisplayName), falling back to the default capture endpoint.
|
|
/// </summary>
|
|
public sealed class WasapiMicAudioSource : IAudioSource
|
|
{
|
|
private readonly Func<string?> _micNameProvider;
|
|
private WasapiCapture? _capture;
|
|
|
|
/// <param name="micNameProvider">Returns the current mic DisplayName; read
|
|
/// at each Start so a device picked mid-session takes effect immediately
|
|
/// (the mixer restarts the mic on pick).</param>
|
|
public WasapiMicAudioSource(Func<string?> micNameProvider)
|
|
{
|
|
_micNameProvider = micNameProvider;
|
|
}
|
|
|
|
public event Action? Started;
|
|
public event Action<AudioSample>? SampleReady;
|
|
public event Action<Exception>? Failed;
|
|
|
|
public void Start()
|
|
{
|
|
if (_capture != null)
|
|
throw new InvalidOperationException("Already started.");
|
|
|
|
try
|
|
{
|
|
var device = ResolveDevice();
|
|
_capture = device != null ? new WasapiCapture(device) : new WasapiCapture();
|
|
_capture.DataAvailable += OnDataAvailable;
|
|
_capture.RecordingStopped += OnRecordingStopped;
|
|
_capture.StartRecording();
|
|
Started?.Invoke();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Stop();
|
|
Failed?.Invoke(ex);
|
|
}
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (_capture == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_capture.StopRecording();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
_capture.Dispose();
|
|
_capture = null;
|
|
}
|
|
|
|
public void Dispose() => Stop();
|
|
|
|
private MMDevice? ResolveDevice()
|
|
{
|
|
var micName = _micNameProvider();
|
|
if (string.IsNullOrWhiteSpace(micName))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
using var enumerator = new MMDeviceEnumerator();
|
|
foreach (var endpoint in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active))
|
|
{
|
|
if (string.Equals(endpoint.FriendlyName, micName, StringComparison.OrdinalIgnoreCase))
|
|
return endpoint;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void OnDataAvailable(object? sender, WaveInEventArgs e)
|
|
{
|
|
if (e.BytesRecorded <= 0)
|
|
return;
|
|
|
|
var format = _capture?.WaveFormat ?? WaveFormat.CreateIeeeFloatWaveFormat(48000, 1);
|
|
var samples = WaveToFloat.Convert(e.Buffer, e.BytesRecorded, format);
|
|
if (samples.Length > 0)
|
|
SampleReady?.Invoke(new AudioSample(samples, format.SampleRate, format.Channels));
|
|
}
|
|
|
|
private void OnRecordingStopped(object? sender, StoppedEventArgs e)
|
|
{
|
|
if (e.Exception != null)
|
|
Failed?.Invoke(e.Exception);
|
|
}
|
|
}
|