在.NET中使用FFmpeg?

76

我知道这是一个相当大的挑战,但我想用C#和FFmpeg库编写一个基本的电影播放器/转换器。然而,我需要克服的第一个障碍是将FFmpeg库包装在C#中。我已经下载了ffmpeg,但无法在Windows上编译它,所以我为自己下载了预编译版本。好极了。然后我开始寻找C#包装。

我查看了一些包装器,比如SharpFFmpeg (http://sourceforge.net/projects/sharpffmpeg/)和ffmpeg-sharp (http://code.google.com/p/ffmpeg-sharp/)。首先,我想使用ffmpeg-sharp,因为它是LGPL许可证,而SharpFFmpeg则是GPL。然而,它有很多编译错误。结果发现它是为mono编译器编写的,我尝试使用mono编译它,但无法弄清楚如何操作。然后我开始手动修复编译器错误,但遇到了一些可怕的错误,觉得最好不要去碰那些。所以我放弃了使用ffmpeg-sharp。

然后我看了一下SharpFFmpeg,它看起来就是我想要的,所有函数都已经为我进行了P/Invoke。但它是GPL? AVCodec.cs和AVFormat.cs文件看起来像是avcodec.c和avformat.c的移植版本,我认为我可以自己进行移植?然后就不必担心许可证问题。

但在开始编码之前,我想先确定下来。我应该:

  1. 编写自己的C++库与ffmpeg交互,然后让我的C#程序与C++库通信以播放/转换视频等。

还是

  1. 通过使用大量的DllImports将avcodec.h和avformat.h(这就是我需要的全部吗?)移植到C#中,并完全用C#编写它?
首先,要考虑到我不是一位C++的专家,因为我很少使用它,但我知道足够让自己转来转去。我认为#1可能是更好的选择,原因是大多数FFmpeg教程都是用C++编写的,而且我可以比使用c#时拥有更多的内存管理控制权。
你认为呢?另外,你是否有任何有用的链接(也许是教程)可以介绍如何使用FFmpeg?

可能是用于C#/.NET的Solid FFmpeg封装程序的重复问题。 - Nikolay Shmyrev
为什么不尝试像C# -> DirectShow -> FFMPEG这样的方案呢?这可能与主题无关,但也要确保您不会出现在http://ffmpeg.org/shame.html上。 - Adam Baxter
5个回答

32

原始问题已经超过5年了。与此同时,现在有一个来自FFmpeg的WinRT解决方案以及来自Microsoft的集成示例。


1
这只适用于Windows,有关于Linux版本的任何消息吗? - Christo S. Christov
4
请提供需要翻译的完整句子。 - CyberFox
1
这还是最新的吗? :) - AlexandreG

27

以下还有一些你可以查看的托管封装器:

.NET中编写自己的互操作包装器可能是一个耗时且困难的过程。使用C++库进行交互可能有其优点-特别是它允许您大大简化C#代码的接口。然而,如果您只需要库的子集,则在C#中执行interop可能会更加容易。


难道没有像 SWIG 这样的库可以自动包装互操作细节吗? - rogerdpack
1
@Shimmy 如果你只想转换文件,最简单的方法是使用ffmpeg命令行。 - David Chappelle
@DavidChappelle 这就是我最终做的,我使用.NET包装器并带有异步进度报告。 - Shimmy Weitzhandler
我们可以下载FFv1 DLL进行图像转换吗? - thevan
FFMpeg-Sharp似乎没有用于编写视频的API。 - Dmitri Nesteruk
显示剩余2条评论

8
您可以使用这个NuGet包:
Install-Package Xabe.FFmpeg

我正在尝试制作易于使用的跨平台FFmpeg包装器。
您可以在Xabe.FFmpeg上找到更多信息。
有关更多信息,请参阅文档
转换很简单:
var conversion = await FFmpeg.Conversions.FromSnippet.ToMp4(Resources.MkvWithAudio, output);
await conversion.Start();

我插入了 using Xabe.FFmpeg; 但是我的IDE中的 Resources 是红色的。 - silvalli
资源仅用于该项目中的测试。您应该将文件路径传递给那里。 - Tomasz Żmuda

6

如果要在商业项目中使用GPL编译的ffmpeg,只能将其作为命令行实用程序在单独的进程中调用;所有与ffmpeg库链接的包装器(包括Microsoft的FFMpegInterop)只能使用LGPL构建的ffmpeg。

您可以尝试我的.NET包装器FFMpeg:Video Converter for .NET(我是这个库的作者)。它将FFMpeg.exe嵌入DLL中以便于部署,并且不违反GPL规则(FFMpeg未被链接,包装器通过System.Diagnostics.Process在单独的进程中调用它)。


这确实调用了 ffmpeg.exe,而不是使用 DLL。 - James
1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- 来自审查 - dingo_d
1
@dingo_d 感谢您的提醒,我已经在答案中添加了更多细节。 - Vitaliy Fedorchenko

3
一种适用于Linux和Windows的解决方案是在代码中使用控制台ffmpeg。我堆叠线程,编写一个简单的线程控制器类,然后您可以轻松地利用ffmpeg的任何功能。
例如,这包含了使用ffmpeg从我指定的时间创建缩略图的部分。
在线程控制器中,您可以有类似以下的内容:
List<ThrdFfmpeg> threads = new List<ThrdFfmpeg>();

您正在运行的线程列表是哪些,我使用计时器来轮询这些线程,如果轮询对您的应用程序不适用,您也可以设置事件。在这种情况下,Thrdffmpeg类包含以下内容:

public class ThrdFfmpeg
{
    public FfmpegStuff ffm { get; set; }
    public Thread thrd { get; set; }
}

FFmpegStuff包含各种ffmpeg功能,thrd显然是线程。

FfmpegStuff中的一个属性是FilesToProcess类,用于向调用的进程传递信息,并在线程停止后接收信息。

public class FileToProcess
{
    public int videoID { get; set; }
    public string fname { get; set; }
    public int durationSeconds { get; set; }
    public List<string> imgFiles { get; set; }
}

VideoID(我使用数据库)告诉线程处理程序从数据库中取出哪个视频。 fname在我的其他函数中用于使用FilesToProcess,但此处未使用。 durationSeconds-由仅收集视频持续时间的线程填充。 imgFiles用于返回创建的任何缩略图。

我不想在代码中陷入泥潭,因为这是鼓励使用易于控制的线程中的ffmpeg的目的。

现在我们有了我们的组件,我们可以添加到我们的线程列表中,在我们的控制器中执行以下操作,例如,

        AddThread()
        {
        ThrdFfmpeg thrd;
        FileToProcess ftp;

        foreach(FileToProcess ff in  `dbhelper.GetFileNames(txtCategory.Text))`
        {
            //make a thread for each
            ftp = new FileToProcess();
            ftp = ff;
            ftp.imgFiles = new List<string>();
            thrd = new ThrdFfmpeg();
            thrd.ffm = new FfmpegStuff();
            thrd.ffm.filetoprocess = ftp;
            thrd.thrd = new   `System.Threading.Thread(thrd.ffm.CollectVideoLength);`

         threads.Add(thrd);
        }
        if(timerNotStarted)
             StartThreadTimer();
        }

现在,对我们的线程进行Pole操作已经变得很简单。
private void timerThreads_Tick(object sender, EventArgs e)
    {
        int runningCount = 0;
        int finishedThreads = 0;
        foreach(ThrdFfmpeg thrd in threads)
        {
            switch (thrd.thrd.ThreadState)
            {
                case System.Threading.ThreadState.Running:
                    ++runningCount;


 //Note that you can still view data progress here,
    //but remember that you must use your safety checks
    //here more than anywhere else in your code, make sure the data
    //is readable and of the right sort, before you read it.
                    break;
                case System.Threading.ThreadState.StopRequested:
                    break;
                case System.Threading.ThreadState.SuspendRequested:
                    break;
                case System.Threading.ThreadState.Background:
                    break;
                case System.Threading.ThreadState.Unstarted:


//Any threads that have been added but not yet started, start now
                    thrd.thrd.Start();
                    ++runningCount;
                    break;
                case System.Threading.ThreadState.Stopped:
                    ++finishedThreads;


//You can now safely read the results, in this case the
   //data contained in FilesToProcess
   //Such as
                    ThumbnailsReadyEvent( thrd.ffm );
                    break;
                case System.Threading.ThreadState.WaitSleepJoin:
                    break;
                case System.Threading.ThreadState.Suspended:
                    break;
                case System.Threading.ThreadState.AbortRequested:
                    break;
                case System.Threading.ThreadState.Aborted:
                    break;
                default:
                    break;
            }
        }


        if(flash)
        {//just a simple indicator so that I can see
         //that at least one thread is still running
            lbThreadStatus.BackColor = Color.White;
            flash = false;
        }
        else
        {
            lbThreadStatus.BackColor = this.BackColor;
            flash = true;
        }
        if(finishedThreads >= threads.Count())
        {

            StopThreadTimer();
            ShowSample();
            MakeJoinedThumb();
        }
    }

将自己的事件放入控制器类中效果很好,但在视频工作中,当我的代码实际上没有处理任何视频文件时,在控制类中轮询并调用事件同样有效。使用这种方法,我已经逐渐构建了几乎所有可能需要使用的视频和静态功能,并将它们都包含在一个类中,而该类作为文本文件可以在Lunux和Windows版本中使用,只需要一小部分预处理指令即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接