using System.IO.Pipes; using System.Runtime.InteropServices; namespace ytLive.Services.Audio; /// /// The mixer's sink for the live loop: interleaved stereo PCM float at 48 kHz /// written to a named pipe, byte-identical to what ffmpeg expects from /// -f f32le -ar 48000 -ac 2 -i \\.\pipe\<name>. The encoder side is /// handled by FfmpegArgs/EncoderOptions; this side just owns the server end. /// public interface IAudioPipeWriter : IDisposable { /// Creates the named pipe server and waits (async) for the client. void Start(string pipeName); /// True once ffmpeg has connected and the pipe is writable. bool IsConnected { get; } /// Writes interleaved float samples; dropped until the client connects. Task WriteAsync(ReadOnlyMemory samples, CancellationToken cancellationToken = default); /// Closes the server end — ffmpeg sees EOF and ends the audio input. void Stop(); } public sealed class NamedPipeAudioWriter : IAudioPipeWriter { private readonly object _sync = new(); private NamedPipeServerStream? _pipe; public void Start(string pipeName) { lock (_sync) { if (_pipe != null) throw new InvalidOperationException("Already started."); _pipe = new NamedPipeServerStream( pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); } _ = Task.Run(WaitForConnectionAsync); } public bool IsConnected { get { lock (_sync) { return _pipe is { IsConnected: true }; } } } public async Task WriteAsync(ReadOnlyMemory samples, CancellationToken cancellationToken = default) { NamedPipeServerStream? pipe; lock (_sync) { pipe = _pipe; } if (pipe == null || !pipe.IsConnected || samples.IsEmpty) return; var bytes = MemoryMarshal.AsBytes(samples.Span).ToArray(); await pipe.WriteAsync(bytes, cancellationToken).ConfigureAwait(false); } public void Stop() { NamedPipeServerStream? pipe; lock (_sync) { pipe = _pipe; _pipe = null; } pipe?.Dispose(); } public void Dispose() => Stop(); private async Task WaitForConnectionAsync() { NamedPipeServerStream? pipe; lock (_sync) { pipe = _pipe; } if (pipe == null) return; try { await pipe.WaitForConnectionAsync().ConfigureAwait(false); } catch { Stop(); } } }