using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace axXez.TS3.Audio
{
public static class FFmpeg
{
public const int SampleRate = 48000;
public const int Channels = 2;
/// Returns true if ffmpeg is installed and accessible on PATH.
public static bool IsAvailable()
{
try
{
using var p = Process.Start(new ProcessStartInfo("ffmpeg")
{
ArgumentList = { "-version" },
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
});
p?.WaitForExit();
return p?.ExitCode == 0;
}
catch { return false; }
}
/// Converts any audio file to raw PCM (48kHz stereo s16le).
public static async Task ToPcmAsync(string filePath, CancellationToken ct = default)
{
using var p = Start("-i", filePath);
using var ms = new MemoryStream();
_ = p.StandardError.BaseStream.CopyToAsync(Stream.Null, ct); // drain to prevent deadlock
await p.StandardOutput.BaseStream.CopyToAsync(ms, ct);
await p.WaitForExitAsync(ct);
if (p.ExitCode != 0) throw new Exception($"ffmpeg exited with code {p.ExitCode}.");
return ms.ToArray();
}
///
/// Starts ffmpeg streaming PCM from a URL with reconnect support.
/// The caller must dispose the returned when done.
///
public static (Process process, Stream stdout) StartPcmStream(string url)
{
var p = Start("-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", "-i", url);
return (p, p.StandardOutput.BaseStream);
}
private static Process Start(params string[] inputArgs)
{
var psi = new ProcessStartInfo("ffmpeg")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
foreach (var a in inputArgs) psi.ArgumentList.Add(a);
foreach (var a in new[] { "-vn", "-f", "s16le", "-acodec", "pcm_s16le",
"-ar", SampleRate.ToString(), "-ac", Channels.ToString(),
"-loglevel", "error", "pipe:1" })
psi.ArgumentList.Add(a);
return Process.Start(psi) ?? throw new InvalidOperationException("ffmpeg not found.");
}
}
}