ABStemPlayer/AudioCore/Impl/FfmpegProcess.cs

123 lines
3.7 KiB
C#

using System.Diagnostics;
namespace AudioCore.Impl;
public sealed class FfmpegProcess : IDisposable
{
public Process? Proc { get; private set; }
public Stream? Stdout { get; private set; }
public Stream? Stdin { get; private set; }
private string _name;
private string _commandLine;
private bool _redirectOutput;
private bool _redirectInput;
public FfmpegProcess(string name, string commandLine, bool redirectOutput = true, bool redirectInput = true)
{
_name = name;
_commandLine = commandLine;
_redirectOutput = redirectOutput;
_redirectInput = redirectInput;
}
public void StartProcess()
{
var psi = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = _commandLine,
UseShellExecute = false,
RedirectStandardOutput = _redirectOutput,
RedirectStandardError = true,
RedirectStandardInput = _redirectInput,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
};
Proc = Process.Start(psi) ?? throw new InvalidOperationException("Failed to start ffmpeg process");
if ( _redirectInput)
Stdin = Proc.StandardInput.BaseStream;
if (_redirectOutput)
Stdout = Proc.StandardOutput.BaseStream;
// Start draining stderr immediately
_ = Task.Run(() => DrainStderr(Proc));
}
private void DrainStderr(Process proc)
{
try
{
var reader = proc.StandardError;
// ffmpeg writes short lines, so ReadLine is fine
// If you want zero allocations, use ReadAsync into a rented buffer.
string? line;
while ((line = reader.ReadLine()) != null)
{
Debug.WriteLine($"{_name}: {line}");
}
}
catch
{
// ignore exceptions during stderr drain, as the process may have exited
}
}
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 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 { Stdin?.Dispose(); } catch { }
try { Proc?.StandardError.BaseStream?.Dispose(); } catch { }
try { if (Proc != null && !Proc.HasExited) Proc.Kill(); } catch { }
try { Proc?.Dispose(); } catch { }
}
public void Dispose()
{
DisposeProcessOnly();
}
}