Initial commit: RemSound v1.0
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
using NAudio.CoreAudioApi;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace RemSound.Sender;
|
||||
|
||||
/// <summary>
|
||||
/// Pins a continuous silent render stream on a WASAPI device so the device stays "warm".
|
||||
/// Some USB audio interfaces (Audient EVO8, RME, Focusrite, etc.) only fire WASAPI loopback
|
||||
/// callbacks when something is actively rendering to the device — when the render endpoint
|
||||
/// goes idle, the loopback path stops delivering frames until an application starts rendering
|
||||
/// again. By pinning a zero-volume silent render stream on the same device we capture from,
|
||||
/// loopback callbacks keep firing regardless of whether other apps are playing audio.
|
||||
///
|
||||
/// Same idea the legacy "silence.exe" used; folded into the sender so it's automatic, sized
|
||||
/// to the device's actual mix format (no resampler stage), and ties to capture lifetime.
|
||||
/// We do not own the MMDevice — AudioSender does — so we never dispose it.
|
||||
/// </summary>
|
||||
internal sealed class SilentRenderKeepAlive : IDisposable
|
||||
{
|
||||
private readonly WasapiOut output;
|
||||
private readonly Action<string>? onDiagnostic;
|
||||
|
||||
public SilentRenderKeepAlive(MMDevice device, Action<string>? onDiagnostic = null)
|
||||
{
|
||||
this.onDiagnostic = onDiagnostic;
|
||||
// Shared mode with a 50 ms buffer. Latency doesn't matter for silence; longer buffers
|
||||
// mean fewer wakeups per second. Event sync still gives us efficient blocking turnover.
|
||||
output = new WasapiOut(device, AudioClientShareMode.Shared, useEventSync: true, latency: 50);
|
||||
output.Init(new SilenceProvider(output.OutputWaveFormat));
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
output.Play();
|
||||
onDiagnostic?.Invoke($"silence keepalive started ({output.OutputWaveFormat.SampleRate} Hz, {output.OutputWaveFormat.Channels} ch)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
onDiagnostic?.Invoke($"silence keepalive start failed: {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { output.Stop(); } catch { /* ignore */ }
|
||||
try { output.Dispose(); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
private sealed class SilenceProvider(WaveFormat format) : IWaveProvider
|
||||
{
|
||||
public WaveFormat WaveFormat { get; } = format;
|
||||
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Array.Clear(buffer, offset, count);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user