ABStemPlayer/AudioCore/Impl/FfmpegPipe.cs

113 lines
3.2 KiB
C#

using System.Diagnostics;
namespace AudioCore.Impl;
public sealed class FfmpegPipe : IDisposable
{
private readonly string _path;
private Process? _proc;
private Stream? _stdout;
public int SampleRate { get; }
public int Channels { get; }
public long TotalSamples { get; }
public FfmpegPipe(string path, int sampleRate = 44100, int channels = 2)
{
_path = path;
SampleRate = sampleRate;
Channels = channels;
// Optional: probe duration
TotalSamples = ProbeTotalSamples(path, sampleRate);
StartProcess(0);
}
private void StartProcess(long startSample)
{
var startSeconds = (double)startSample / SampleRate;
var psi = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments =
$"-hide_banner -loglevel error " +
$"-nostdin " + // prevent console attach
$"-ss {startSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture)} " +
$"-i \"{_path}\" " +
$"-f f32le -ac {Channels} -ar {SampleRate} pipe:1",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true, // prevents console window
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
};
_proc = Process.Start(psi);
_stdout = _proc!.StandardOutput.BaseStream;
}
public int Read(float[] buffer, int offset, int count)
{
var bytesNeeded = count * sizeof(float);
var tmp = new byte[bytesNeeded];
var readBytes = _stdout!.Read(tmp, 0, bytesNeeded);
if (readBytes <= 0)
return 0;
Buffer.BlockCopy(tmp, 0, buffer, offset * sizeof(float), readBytes);
return readBytes / sizeof(float);
}
public void Seek(long sampleIndex)
{
DisposeProcessOnly();
StartProcess(sampleIndex);
}
public void Reset() => Seek(0);
private static long ProbeTotalSamples(string path, int sampleRate)
{
var psi = new ProcessStartInfo
{
FileName = "ffprobe",
Arguments = $"-v error -show_entries format=duration -of csv=p=0 \"{path}\"",
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
using var p = Process.Start(psi);
var s = p!.StandardOutput.ReadToEnd();
p.WaitForExit();
if (double.TryParse(s, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out var seconds))
{
return (long)(seconds * sampleRate);
}
return 0;
}
private void DisposeProcessOnly()
{
try { _stdout?.Dispose(); } catch { }
try { if (_proc != null && !_proc.HasExited) _proc.Kill(); } catch { }
try { _proc?.Dispose(); } catch { }
}
public void Dispose()
{
DisposeProcessOnly();
}
}