Last active
January 28, 2024 11:08
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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