用于 C#/.NET 的实体 FFmpeg 包装器
Posted
技术标签:
【中文标题】用于 C#/.NET 的实体 FFmpeg 包装器【英文标题】:Solid FFmpeg wrapper for C#/.NET 【发布时间】:2011-01-10 21:44:51 【问题描述】:一段时间以来,我一直在网上搜索用于 C#/.NET 的可靠 FFmpeg 包装器。但我还没有想出一些有用的东西。我找到了以下三个项目,但它们似乎都在早期的 alpha 阶段死了。
FFmpeg.NETffmpeg-sharp FFLIB.NET
所以我的问题是,是否有人知道更成熟的包装器项目? 我不是在寻找具有作业队列等的完整转码引擎。 只是一个简单的包装器,因此我不必进行命令行调用然后解析控制台输出,而是可以进行方法调用并使用事件监听器来获取进度。
请随时提及任何活跃的项目,即使它们仍处于早期阶段。
【问题讨论】:
Anyone know of a set of C# bindings for FFMPEG?的可能重复 这有什么新东西吗?你的 wrapper 有进展吗? @Lillemanden 你有没有发布或开源你的包装器? 有趣的是,这个问题已经有将近 6 年的历史了,但 OP (@JacobPoulRichardt) 没有接受任何答案。 我最终使用了自己制作的包装器,因此没有使用任何建议的项目。由于我不再使用 ffmpeg,因此我也没有时间回去尝试其中的任何一个。但是在浏览了大部分答案后,他们对大部分答案都投了赞成票。所以我真的认为我不能说任何答案都比其他答案更“正确”。 【参考方案1】:这是我自己的包装器:https://github.com/AydinAdn/MediaToolkit
MediaToolkit 可以:
将视频文件转换为其他各种视频格式。 执行视频转码任务。 可配置选项:Bit rate
、Frame rate
、Resolution / size
、Aspect ratio
、Duration of video
执行音频转码任务。
可配置选项:Audio sample rate
使用 FILM、PAL 或 NTSC 电视标准将视频转换为物理格式
媒体包括:DVD
、DV
、DV50
、VCD
、SVCD
我正在更新它,欢迎您使用它,您也可以使用包管理器控制台安装它。
PM> Install-Package MediaToolkit
【讨论】:
您的工具包能否将不同的视频和音频剪辑混合/渲染为给定的输出分辨率之一? 不,它是为追求简单转换的人设计的。也就是说,很快就会有 v2 版本,它可以让您完成 FFmpeg 提供的所有功能。 谢谢 Aydin,请让我了解这个新版本。 看起来棒极了!到目前为止做得很好! 嘿Aydin,这也能录屏吗?【参考方案2】:在尝试了几个包装器之后,我选择了这个:FFmpeg auto generated unsafe bindings for C#/.NET and Mono。
它是 FFmpeg 命名空间中每个类的一组低级互操作绑定。 使用起来可能不如实际的包装器方便,但如果您想做一些重要的事情,IMO 它是在 .Net 中使用 FFmpeg 的最佳解决方案。
优点:
作品 值得信赖 - 假设您信任 FFMpeg 本身,没有 3rd 方包装代码会引入错误。 它总是更新到最新版本的 FFmpeg 单个nuget package 用于所有绑定 包含 XML 文档,但您仍然可以使用在线文档 FFmpeg documentation。缺点:
低级:您必须知道如何使用指向 c 结构 的指针。 最初需要一些工作才能使其正常工作。建议向the official examples学习。注意:这个线程是关于使用 FFmpeg API,但对于某些用例,最好简单地使用 ffmpeg.exe 的command line interface。
【讨论】:
您是否设法从针对 .Net Framework(非核心)的项目中使用它?我不确定我在这里缺少什么 @YoavFeuerstein 是的。【参考方案3】:我从 ASP.NET / Windows 服务 (.NET) 应用程序中使用了 FFmpeg。 但我最终使用了命令行,没有解析控制台。 通过使用这个 - 我有一个简单的方法来控制 - 更新 FFmpeg 并在多个核心上运行多个转换。
【讨论】:
好的,我开始做类似的事情。但我仍然希望有人有更好的解决方案。【参考方案4】:试试这个,我想我可能已经写了一些可以用作简单包装器的东西。
http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/
【讨论】:
谢谢,但我开始自己写了。但是,如果我不能提出一些想法,我会的。【参考方案5】:你可以使用这个 nuget 包:
我知道您询问了成熟项目,但我还没有看到任何项目满足我的期望,所以我决定自己做。 您可以轻松地将转换排队并并行运行,将媒体转换为不同格式的方法,将您自己的参数发送到 ffmpeg 并使用当前进度解析来自 ffmpeg + 事件侦听器的输出。
Install-Package Xabe.FFmpeg
我正在尝试制作易于使用的跨平台 FFmpeg 包装器。
您可以在https://xabe.net/product/xabe_ffmpeg/找到更多信息
更多信息在这里:https://xabe.net/product/xabe_ffmpeg/#documentation
转换很简单:
IConversionResult result = await Conversion.ToMp4(Resources.MkvWithAudio, output).Start();
如果你想要进步:
IConversion conversion = Conversion.ToMp4(Resources.MkvWithAudio, output);
conversion.OnProgress += (duration, length) => currentProgress = duration;
await conversion.Start();
【讨论】:
您好...我需要使用 FFMPEG 对来自网页的流数据进行转码并将其发送到 RTMP 服务器。我的 C# winform 程序中有字节数组。我只需要转码并发送到 RTMP 服务器。我可以使用这个包装器来做到这一点吗?我在 Linux 中使用使用 socketio 的 nodejs 服务器做到了这一点。在那个平台上,我通过标准输入发送二进制流,并在标准错误中接收转换状态。我可以使用 Xabe 包装器吗?【参考方案6】:我正在玩一个名为 MediaHandler Pro 的 ffmpeg 包装库
http://www.mediasoftpro.com
目前看来很有希望。
【讨论】:
这对您来说效果如何?另外,MediaHandler
是否将 ffmpeg.exe
作为一个进程来完成其工作,还是有一个实际的 P/Invoke 库?
我最终在几个项目中使用了它。它在高负载下的生产环境中运行良好。我已经有一段时间没有使用它了,但据我记得,是的,它确实将 ffmpeg.exe 作为一个进程生成。【参考方案7】:
我一直在研究同样的东西,最初使用的是 MediaToolKit(在另一个答案中提到),它非常适合转换,但现在我需要一些更强大的东西。
一个看似成熟且仍然有效的选项是: https://github.com/hudl/HudlFfmpeg 您可以在此处阅读更多信息: http://public.hudl.com/bits/archives/2014/08/15/announcing-hudlffmpeg-a-c-framework-to-make-ffmpeg-interaction-simple/
另一个可能不适合许多情况的选项是直接从您的 c# 代码调用 exe: http://www.codeproject.com/Articles/774093/Another-FFmpeg-exe-Csharp-Wrapper
【讨论】:
【参考方案8】:这里还有一个简单的:http://ivolo.mit.edu/post/Metamorph-Convert-Audio-Video-to-Any-Format-on-Windows-Linux-and-Mac.aspx
【讨论】:
感谢您的链接,但据我所知,您是用 Java 编写的,而不是 int C#。 嗨 lillemanden,我提供的链接实际上是用 Java 实现的,如果你下载文章底部的 zip,你会看到里面有一个 jar 存档文件。谢谢,伊利亚 答案中的链接似乎已失效:“无法访问此站点 - ivolo.mit.edu 响应时间过长。”【参考方案9】:你去吧...大部分代码已经有 2 年以上的历史了,所以缺少很多异步的东西,并且使用了过时的命名约定。在生产环境中运行了相当长的一段时间 ~JT
internal static class FFMpegArgUtils
public static string GetEncodeVideoFFMpegArgs(string sSourceFile, MP4Info objMp4Info, double nMbps, int iWidth, int iHeight, bool bIncludeAudio, string sOutputFile)
//Ensure file contains a video stream, otherwise this command will fail
if (objMp4Info != null && objMp4Info.VideoStreamCount == 0)
throw new Exception("FFMpegArgUtils::GetEncodeVideoFFMpegArgs - mp4 does not contain a video stream");
int iBitRateInKbps = (int)(nMbps * 1000);
StringBuilder sbArgs = new StringBuilder();
sbArgs.Append(" -y -threads 2 -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
if (bIncludeAudio == true)
//sbArgs.Append(" -acodec libmp3lame -ab 96k");
sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
else
sbArgs.Append(" -an");
sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(" -vf pad=" + iWidth + ":" + iHeight + ":" + iVideoOffsetX + ":" + iVideoOffsetY);
sbArgs.Append(String.Format(" -vf \"scale=iw*min(0/iw\\,1/ih):ih*min(0/iw\\,1/ih),pad=0:1:(0-iw)/2:(1-ih)/2\"",iWidth, iHeight));
//Output File
sbArgs.Append(" \"" + sOutputFile + "\"");
return sbArgs.ToString();
public static string GetEncodeAudioFFMpegArgs(string sSourceFile, string sOutputFile)
var args = String.Format(" -y -threads 2 -i \"0\" -strict -2 -acodec aac -ar 44100 -ab 96k -vn \"1\"", sSourceFile, sOutputFile);
return args;
//return GetEncodeVideoFFMpegArgs(sSourceFile, null, .2, 854, 480, true, sOutputFile);
//StringBuilder sbArgs = new StringBuilder();
//int iWidth = 854;
//int iHeight = 480;
//sbArgs.Append(" -y -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
//sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
//sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(String.Format(" -vf \"scale=iw*min(0/iw\\,1/ih):ih*min(0/iw\\,1/ih),pad=0:1:(0-iw)/2:(1-ih)/2\"", iWidth, iHeight));
//sbArgs.Append(" \"" + sOutputFile + "\"");
//return sbArgs.ToString();
internal class CreateEncodedVideoCommand : ConsoleCommandBase
public event ProgressEventHandler OnProgressEvent;
private string _sSourceFile;
private string _sOutputFolder;
private double _nMaxMbps;
public double BitrateInMbps
get return _nMaxMbps;
public int BitrateInKbps
get return (int)Math.Round(_nMaxMbps * 1000);
private int _iOutputWidth;
private int _iOutputHeight;
private bool _bIsConverting = false;
//private TimeSpan _tsDuration;
private double _nPercentageComplete;
private string _sOutputFile;
private string _sOutputFileName;
private bool _bAudioEnabled = true;
private string _sFFMpegPath;
private string _sExePath;
private string _sArgs;
private MP4Info _objSourceInfo;
private string _sOutputExt;
/// <summary>
/// Encodes an MP4 to the specs provided, quality is a value from 0 to 1
/// </summary>
/// <param name="nQuality">A value from 0 to 1</param>
///
public CreateEncodedVideoCommand(string sSourceFile, string sOutputFolder, string sFFMpegPath, double nMaxBitrateInMbps, MP4Info objSourceInfo, int iOutputWidth, int iOutputHeight, string sOutputExt)
_sSourceFile = sSourceFile;
_sOutputFolder = sOutputFolder;
_nMaxMbps = nMaxBitrateInMbps;
_objSourceInfo = objSourceInfo;
_iOutputWidth = iOutputWidth;
_iOutputHeight = iOutputHeight;
_sFFMpegPath = sFFMpegPath;
_sOutputExt = sOutputExt;
public void SetOutputFileName(string sOutputFileName)
_sOutputFileName = sOutputFileName;
public override void Execute()
try
_bIsConverting = false;
string sFileName = _sOutputFileName != null ? _sOutputFileName : Path.GetFileNameWithoutExtension(_sSourceFile) + "_" + _iOutputWidth + "." + _sOutputExt;
_sOutputFile = _sOutputFolder + "\\" + sFileName;
_sExePath = _sFFMpegPath;
_sArgs = FFMpegArgUtils.GetEncodeVideoFFMpegArgs(_sSourceFile, _objSourceInfo,_nMaxMbps, _iOutputWidth, _iOutputHeight, _bAudioEnabled, _sOutputFile);
InternalExecute(_sExePath, _sArgs);
catch (Exception objEx)
DispatchException(objEx);
public override string GetCommandInfo()
StringBuilder sbInfo = new StringBuilder();
sbInfo.AppendLine("CreateEncodeVideoCommand");
sbInfo.AppendLine("Exe: " + _sExePath);
sbInfo.AppendLine("Args: " + _sArgs);
sbInfo.AppendLine("[ConsoleOutput]");
sbInfo.Append(ConsoleOutput);
sbInfo.AppendLine("[ErrorOutput]");
sbInfo.Append(ErrorOutput);
return base.GetCommandInfo() + "\n" + sbInfo.ToString();
protected override void OnInternalCommandComplete(int iExitCode)
DispatchCommandComplete( iExitCode == 0 ? CommandResultType.Success : CommandResultType.Fail);
override protected void OnOutputRecieved(object sender, ProcessOutputEventArgs objArgs)
//FMPEG out always shows as Error
base.OnOutputRecieved(sender, objArgs);
if (_bIsConverting == false && objArgs.Data.StartsWith("Press [q] to stop encoding") == true)
_bIsConverting = true;
else if (_bIsConverting == true && objArgs.Data.StartsWith("frame=") == true)
//Capture Progress
UpdateProgressFromOutputLine(objArgs.Data);
else if (_bIsConverting == true && _nPercentageComplete > .8 && objArgs.Data.StartsWith("frame=") == false)
UpdateProgress(1);
_bIsConverting = false;
override protected void OnProcessExit(object sender, ProcessExitedEventArgs args)
_bIsConverting = false;
base.OnProcessExit(sender, args);
override public void Abort()
if (_objCurrentProcessRunner != null)
//_objCurrentProcessRunner.SendLineToInputStream("q");
_objCurrentProcessRunner.Dispose();
#region Helpers
//private void CaptureSourceDetailsFromOutput()
//
// String sInputStreamInfoStartLine = _colErrorLines.SingleOrDefault(o => o.StartsWith("Input #0"));
// int iStreamInfoStartIndex = _colErrorLines.IndexOf(sInputStreamInfoStartLine);
// if (iStreamInfoStartIndex >= 0)
//
// string sDurationInfoLine = _colErrorLines[iStreamInfoStartIndex + 1];
// string sDurantionTime = sDurationInfoLine.Substring(12, 11);
// _tsDuration = VideoUtils.GetDurationFromFFMpegDurationString(sDurantionTime);
//
//
private void UpdateProgressFromOutputLine(string sOutputLine)
int iTimeIndex = sOutputLine.IndexOf("time=");
int iBitrateIndex = sOutputLine.IndexOf(" bitrate=");
string sCurrentTime = sOutputLine.Substring(iTimeIndex + 5, iBitrateIndex - iTimeIndex - 5);
double nCurrentTimeInSeconds = double.Parse(sCurrentTime);
double nPercentageComplete = nCurrentTimeInSeconds / _objSourceInfo.Duration.TotalSeconds;
UpdateProgress(nPercentageComplete);
//Console.WriteLine("Progress: " + _nPercentageComplete);
private void UpdateProgress(double nPercentageComplete)
_nPercentageComplete = nPercentageComplete;
if (OnProgressEvent != null)
OnProgressEvent(this, new ProgressEventArgs( _nPercentageComplete));
#endregion
//public TimeSpan Duration get return _tsDuration;
public double Progress get return _nPercentageComplete;
public string OutputFile get return _sOutputFile;
public bool AudioEnabled
get return _bAudioEnabled;
set _bAudioEnabled = value;
public abstract class ConsoleCommandBase : CommandBase, ICommand
protected ProcessRunner _objCurrentProcessRunner;
protected List<String> _colOutputLines;
protected List<String> _colErrorLines;
private int _iExitCode;
public ConsoleCommandBase()
_colOutputLines = new List<string>();
_colErrorLines = new List<string>();
protected void InternalExecute(string sExePath, string sArgs)
InternalExecute(sExePath, sArgs, null, null, null);
protected void InternalExecute(string sExePath, string sArgs, string sDomain, string sUsername, string sPassword)
try
if (_objCurrentProcessRunner == null || _bIsRunning == false)
StringReader objStringReader = new StringReader(string.Empty);
_objCurrentProcessRunner = new ProcessRunner(sExePath, sArgs);
_objCurrentProcessRunner.SetCredentials(sDomain, sUsername, sPassword);
_objCurrentProcessRunner.OutputReceived += new ProcessOutputEventHandler(OnOutputRecieved);
_objCurrentProcessRunner.ProcessExited += new ProcessExitedEventHandler(OnProcessExit);
_objCurrentProcessRunner.Run();
_bIsRunning = true;
_bIsComplete = false;
else
DispatchException(new Exception("Processor Already Running"));
catch (Exception objEx)
DispatchException(objEx);
protected virtual void OnOutputRecieved(object sender, ProcessOutputEventArgs args)
try
if (args.Error == true)
_colErrorLines.Add(args.Data);
//Console.WriteLine("Error: " + args.Data);
else
_colOutputLines.Add(args.Data);
//Console.WriteLine(args.Data);
catch (Exception objEx)
DispatchException(objEx);
protected virtual void OnProcessExit(object sender, ProcessExitedEventArgs args)
try
Console.Write(ConsoleOutput);
_iExitCode = args.ExitCode;
_bIsRunning = false;
_bIsComplete = true;
//Some commands actually fail to succeed
//if(args.ExitCode != 0)
//
// DispatchException(new Exception("Command Failed: " + this.GetType().Name + "\nConsole: " + ConsoleOutput + "\nConsoleError: " + ErrorOutput));
//
OnInternalCommandComplete(_iExitCode);
if (_objCurrentProcessRunner != null)
_objCurrentProcessRunner.Dispose();
_objCurrentProcessRunner = null;
catch (Exception objEx)
DispatchException(objEx);
abstract protected void OnInternalCommandComplete(int iExitCode);
protected string JoinLines(List<String> colLines)
StringBuilder sbOutput = new StringBuilder();
colLines.ForEach( o => sbOutput.AppendLine(o));
return sbOutput.ToString();
#region Properties
public int ExitCode
get return _iExitCode;
#endregion
public override string GetCommandInfo()
StringBuilder sbCommandInfo = new StringBuilder();
sbCommandInfo.AppendLine("Command: " + this.GetType().Name);
sbCommandInfo.AppendLine("Console Output");
if (_colOutputLines != null)
foreach (string sOutputLine in _colOutputLines)
sbCommandInfo.AppendLine("\t" + sOutputLine);
sbCommandInfo.AppendLine("Error Output");
if (_colErrorLines != null)
foreach (string sErrorLine in _colErrorLines)
sbCommandInfo.AppendLine("\t" + sErrorLine);
return sbCommandInfo.ToString();
public String ConsoleOutput get return JoinLines(_colOutputLines);
public String ErrorOutput get return JoinLines(_colErrorLines);
CommandBase : ICommand
protected IDedooseContext _context;
protected Boolean _bIsRunning = false;
protected Boolean _bIsComplete = false;
#region Custom Events
public event CommandCompleteEventHandler OnCommandComplete;
event CommandCompleteEventHandler ICommand.OnCommandComplete
add if (OnCommandComplete != null) lock (OnCommandComplete) OnCommandComplete += value; else OnCommandComplete = new CommandCompleteEventHandler(value);
remove if (OnCommandComplete != null) lock (OnCommandComplete) OnCommandComplete -= value;
public event UnhandledExceptionEventHandler OnCommandException;
event UnhandledExceptionEventHandler ICommand.OnCommandException
add if (OnCommandException != null) lock (OnCommandException) OnCommandException += value; else OnCommandException = new UnhandledExceptionEventHandler(value);
remove if (OnCommandException != null) lock (OnCommandException) OnCommandException -= value;
public event ProgressEventHandler OnProgressUpdate;
event ProgressEventHandler ICommand.OnProgressUpdate
add if (OnProgressUpdate != null) lock (OnProgressUpdate) OnProgressUpdate += value; else OnProgressUpdate = new ProgressEventHandler(value);
remove if (OnProgressUpdate != null) lock (OnProgressUpdate) OnProgressUpdate -= value;
#endregion
protected CommandBase()
_context = UnityGlobalContainer.Instance.Context;
protected void DispatchCommandComplete(CommandResultType enResult)
if (enResult == CommandResultType.Fail)
StringBuilder sbMessage = new StringBuilder();
sbMessage.AppendLine("Command Commpleted with Failure: " + this.GetType().Name);
sbMessage.Append(GetCommandInfo());
Exception objEx = new Exception(sbMessage.ToString());
DispatchException(objEx);
else
if (OnCommandComplete != null)
OnCommandComplete(this, new CommandCompleteEventArgs(enResult));
protected void DispatchException(Exception objEx)
if (OnCommandException != null)
OnCommandException(this, new UnhandledExceptionEventArgs(objEx, true));
else
_context.Logger.LogException(objEx, MethodBase.GetCurrentMethod());
throw objEx;
protected void DispatchProgressUpdate(double nProgressRatio)
if (OnProgressUpdate != null) OnProgressUpdate(this, new ProgressEventArgs(nProgressRatio));
public virtual string GetCommandInfo()
return "Not Implemented: " + this.GetType().Name;
public virtual void Execute() throw new NotImplementedException();
public virtual void Abort() throw new NotImplementedException();
public Boolean IsRunning get return _bIsRunning;
public Boolean IsComplete get return _bIsComplete;
public double GetProgressRatio()
throw new NotImplementedException();
public delegate void CommandCompleteEventHandler(object sender, CommandCompleteEventArgs e);
public interface ICommand
event CommandCompleteEventHandler OnCommandComplete;
event UnhandledExceptionEventHandler OnCommandException;
event ProgressEventHandler OnProgressUpdate;
double GetProgressRatio();
string GetCommandInfo();
void Execute();
void Abort();
// 对于流程运行程序的内容,请查找 Roger Knapp 的 ProcessRunner
【讨论】:
【参考方案10】: string result = String.Empty;
StreamReader srOutput = null;
var oInfo = new ProcessStartInfo(exePath, parameters)
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
;
var output = string.Empty;
try
Process process = System.Diagnostics.Process.Start(oInfo);
output = process.StandardError.ReadToEnd();
process.WaitForExit();
process.Close();
catch (Exception)
output = string.Empty;
return output;
这个包装器不会让方法陷入循环。 试试这个,它对我有用。
【讨论】:
【参考方案11】:我从 codeplex 分叉了 FFPMEG.net。
仍在积极进行中。
https://github.com/spoiledtechie/FFMpeg.Net
它不使用 dll,而是使用 exe。所以它往往更稳定。
【讨论】:
看起来像我所追求的,但是如何在他们的项目中实现这一点? 将此项目添加到您的项目中,然后确保 FFMPEG 正确位于项目中。它仍在进行中。 我可以使用这个 FFMPEG.net 将帧编码和解码为 byte[] 吗?例如,byte[] encodeh264(byte[]) 和 byte[] decodeh264(byte[])。【参考方案12】:请参阅Auto Generated FFmpeg wrapper for C#/.NET and Mono,这是一个很棒的项目,它似乎是目前唯一真正的、完整的 .NET FFmpeg 互操作包装器。
【讨论】:
以上是关于用于 C#/.NET 的实体 FFmpeg 包装器的主要内容,如果未能解决你的问题,请参考以下文章
用于 JQuery 日期时间选择器的 ASP.Net 包装器控件
是否有用于 OData 协议 Uri 的 .NET 类包装器