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();
}
}
}