Skip to content

Instantly share code, notes, and snippets.

@VapidLinus
Last active January 28, 2024 11:08
Show Gist options
  • Save VapidLinus/af737c702d102a181b47bc4f2a6f7639 to your computer and use it in GitHub Desktop.
Save VapidLinus/af737c702d102a181b47bc4f2a6f7639 to your computer and use it in GitHub Desktop.
Transcode single-channel PCM audio data to dual-channel and output it to D#+'s voice sink
private static async Task TransmitAudioData(byte[] audioData, VoiceTransmitSink sink)
{
var startInfo = new ProcessStartInfo("ffmpeg")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = string.Join(" ", [
"-loglevel panic",
"-f s16le",
"-ac 1",
"-ar 48000",
"-i pipe:0",
"-f s16le",
"-ac 2",
"-ar 48000",
"pipe:1"
])
};
using var process = Process.Start(startInfo)!;
process.EnableRaisingEvents = true;
process.ErrorDataReceived += (_, args)
=> Debug.WriteLine($"Error while transcoding audio data: {args.Data}");
var stdin = process.StandardInput.BaseStream;
var stdout = process.StandardOutput.BaseStream;
try
{
// We need to write and read concurrently from ffmpeg to not deadlock when its internal buffer fills up.
// Achieving concurrency by running writing and reading in two different tasks seems to work fine.
var writer = Task.Run(async () =>
{
// *Maybe*, we could benefit from using chunking here,
// but I haven't run into a scenario when it's needed.
// The data we transmit isn't bigger than a couple hundred kilo-bytes anyway.
// Chunking could very well be done internally by WriteAsync for all I know.
await stdin.WriteAsync(audioData);
await stdin.FlushAsync();
stdin.Close(); // Stream needs to close for ffmpeg to exit
});
var reader = Task.Run(async () =>
{
// ffmpeg seems to only output 8192 bytes at a time, so let's use that chunk size
const int chunkSize = 8192;
var arrayPool = ArrayPool<byte>.Shared;
var buffer = arrayPool.Rent(chunkSize);
try
{
int l;
while ((l = await stdout.ReadAsync(buffer)) > 0)
{
await sink.WriteAsync(buffer.AsMemory(0, l));
}
}
finally
{
arrayPool.Return(buffer);
}
});
await process.WaitForExitAsync();
await Task.WhenAll(writer, reader);
}
finally
{
stdin.Close();
stdout.Close();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment