FFmpegInterop 简介

FFmpegInterop 是微软推出的封装 FFmpeg 的一个开源库,旨在方便在 Windows 10、Windows 8.1 以及 Windows Phone 8.1 应用中使用 FFmpeg 进行媒体内容播放。FFmpegInterop 实现了一个 MediaStreamSource 以便通过 FFmpeg 对媒体内容进行解码后输送到 Windows 多媒体管线进行播放。

FFmpegInterop 项目托管于 Github,项目地址:FFmpegInterop

编译步骤

FFmpegInterop 是对 FFmpeg 的封装,依赖 FFmpeg 库本身。要使用 FFmpegInterop 需要首先手动编译 FFmpeg 和 FFmpegInterop 库。

获取文件到本地

使用 git 命令或任意 git 工具将 FFmpegInterop 项目文件 clone 到本地:

git clone --recursive git://github.com/microsoft/FFmpegInterop.git

获取最新的 FFmpeg 代码:

 git clone git://github.com/microsoft/FFmpegInterop.git
 cd FFmpegInterop
 git clone git://source.ffmpeg.org/ffmpeg.git

FFmpegInterop 的 github 仓库 中链接的是 commit 620197d 的 FFmpeg 代码。不同版本的 FFmpeg 编译后实际表现可能有所不同。

进行以上操作后,你的本地目录结构应该同以下结构相同:

FFmpegInterop\
├ ffmpeg\              - FFmpeg 库代码目录
├ FFmpegInterop\       - FFmpegInterop WinRT 组件代码目录
├ Samples\             - 使用 C++, C# 以及 JavaScript 分别实现的例子
├ Tests\               - FFmpegInterop 的单元测试
├ BuildFFmpeg.bat      - 用于编译 FFmpeg 库的批处理文件
├ FFmpegConfig.sh      - FFmpeg 配置脚本
├ FFmpegWin8.1.sln     - 用于 Windows 8.1 Windows Phone 8.1 开发的 Visual Studio 2013 解决方案
├ FFmpegWin10.sln      - 用于 Windows 10 开发的 Visual Studio 2015 解决方案
├ LICENSE
└ README.md

编译 FFmpeg

编译 FFmpegInterop 之前,我们首先需要编译 FFmpeg 本体。编译 FFmpeg 需要先准备特定的编译环境。

Visual Studio

对于 Windows 8.1,要求使用 Visual Studio 2013 Update 3 RTM 或更新的版本。
对于 Windows 10,要求使用 Visual Studio 2015。

安装配置 MSYS2

MSYS2 是一个用于 Windows 平台的 GNU 编译环境套件。要编译 FFmpeg,必须安装使用 MSYS2。

MSYS2 下载地址:http://msys2.github.io/

下载页面提供了 x86 和 x64 两种架构对应的版本,选择当前计算机对应版本下载即可。下载启动安装程序后选择一个安装路径,注意尽量选择类似 C:\msys32 这样由字母数字构成的简单短路径,路径中不能包含中文、特殊字符、空格等。安装完成后立即运行 MSYS2。

启动 MSYS2 后,需要更新 MSYS2 提供的 GNU 环境,在 MSYS2 的终端中输入命令 update-core 进行更新。更新完毕后,关闭 MSYS2 再通过开始菜单重启 MSYS2。重启后,再输入 pacman -Su 同步 MSYS2 环境的包数据库。

有关 MSYS2 安装使用的更多内容,可参阅 MSYS2 Wiki

安装配置 YASM

YASM 一个完全重写 NASM 编译器的汇编语言编译器,也是编译 FFmpeg 的必要工具之一。有关 YASM 的更多信息,可以访问其官网 yasm.tortall.net

YASM 下载地址:http://yasm.tortall.net/Download.html

截至目前 YASM 的最新版本为 2014 年 8 月 10 日发布的 1.3.0 版。注意 YASM 在其下载页面上列举了多个不同的版本可供下载:

  • Source .tar.gz (源代码)
  • Win32 VS2010 .zip (用于 VS2010+ 和 32 位 Windows)
  • Win64 VS2010 .zip (用于 VS2010+ 和 64 位 Windows)
  • Win32 .exe (32 位 Windows 通用)
  • Win64 .exe (64 位 Windows 通用)
  • CygWin32 .exe (用于 CygWin)
  • DOS .exe (用于纯 DOS 或 DJGPP)

注意我们需要的是上述列表中加粗的两个通用版本。根据自己使用计算器的架构选择对应的通用版本下载即可。下载后,将下载回来的 yasm-1.3.0-win64.exe 改名为 yaml.exe,并放置于 MSYS2 安装目录中。例如,MSYS2 安装在 C:\msys64,则将 yaml.exe 放置到 c:\msys64\usr\bin\ 中。

安装配置 gas-preprocessor

gas-preprocessor 是用于编译 FFmpeg 的 perl 预处理脚本。

gas-preprocessor 下载地址:https://github.com/FFmpeg/gas-preprocessor

下载 gas-preprocessor.pl 文件后放置于 MSYS2 安装目录中。例如,MSYS2 安装在 C:\msys64,则将 gas-preprocessor.pl 放置到 c:\msys64\usr\bin\ 中。

验证 FFmpeg 编译环境

进行以上步骤之后,编译 FFmpeg 的环境已经基本准备就绪。我们还需要对环境进行一下验证,以保证环境确实准备完毕能够顺利进行编译。

通过开始菜单找到 Visual Studio 2013 或 Visual Studio 2015 菜单组,在其中找到 VS2015 x86 ARM Cross Tools Command Prompt 启动。注意,菜单组中可能存在多个名称类似的命令行快捷方式,需要选择 x86 ARM Cross Tools启动 VS2015 x86 ARM Cross Tools Command Prompt 后,在命令行中定位到 MSYS2 的安装目录,启动 MSYS2:C:\msys64\msys2_shell.bat。(这样通过 VS 提供的命令行启动 MSYS2 的目的在于让 MSYS2 能够检测到部分由 VS 提供的编译工具。)

在启动的 MSYS2 终端中分别运行一下命令观察各便于工具组件是否被正确找到:

$ which cl
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/x86_ARM/cl

$ which link
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/x86_ARM/link

$ which armasm
/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/BIN/x86_ARM/armasm

$ which yasm
/usr/bin/yasm

$ which cpp
/usr/bin/cpp

$ which gas-preprocessor.pl
/usr/bin/gas-preprocessor.pl

如果所有组件均在指定位置被找到,则表示 FFmpeg 编译环境已经准备就绪,可以进入下一步骤编译 FFmpeg。如果没有通过 VS 提供的 x86 ARM Cross Tools 命令行启动 MSYS2,则 cl, link, armasam 这几个组件有可能定位不到。也可以选择将 c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_ARM 这个目录加入系统的环境变量。

编译 FFmpeg

在 FFmpegInterop 中,微软已经提供了方便的编译批处理用于自动编译 FFmpeg,如果你想手动编译 FFmpeg,可以参阅 Compile and Use FFmpeg Libraries for Windows Runtime

FFmpegInterop 项目中提供了一个名为 BuildFFmpeg.bat 的批处理文件,借助该批处理,可以轻松进行 FFmpeg 的编译工作。BuildFFmpeg.bat 接受两个可选参数,第一个参数表明目标平台,第二个参数表明目标架构,例如:

BuildFFmpeg.bat win10                     - 为 Windows 10 的 ARM, x64 和 x86 编译
BuildFFmpeg.bat phone8.1 ARM              - 为 Windows Phone 8.1 的 ARM 编译
BuildFFmpeg.bat win8.1 x86 x64            - 为 Windows 8.1 的 x86 和 x64 编译
BuildFFmpeg.bat phone8.1 win10 ARM        - 为 Windows 10 和 Windows Phone 8.1 的 ARM 编译
BuildFFmpeg.bat win8.1 phone8.1 win10     - 为 所有平台所有架构编译

编译时间较长,编译完成后批处理会自动退出。编译后的输出的文件位于项目的 ffmpeg/Build/目标平台/架构 目录内。

编译 FFmpegInterop

打开 Win 8.1 或 Win 10 对应的 项目解决方案文件。可见到 FFmpegInterop 解决方案整体的结构:

Solution "FFmpegWin10"
├ FFmpegInterop (Universal Windows, C++)
├ MediaPlayerCPP (Universal Windows, C++)
├ MediaPlayerCS (Universal Windows, C#)
├ MediaPlayerJS (Universal Windows, Javascript)
└ UnitTest

而解决方案的文件目录结构为

FFmpegInterop-master
├ FFmpegWin10.sln/ FFmpegWin8.1.sln (Visual Studio Solution)
├ BuildFFmpeg.bat (Build script)
├ FFmpegConfig.sh (Build script)
├ FFmpegInterop (Project folder of FFmpegInterop)
└ Samples (Project folder of sample players)
  ├ SamplesWin10
  └ SamplesWin8.1

需要注意的是,在项目中使用 FFmpegInterop 时需要按照 FFmpegInterop 的文件目录结构对项目文件进行安放。如果没有直接使用 FFmpegInterop 提供的项目文件,记得配置 interop 项目对 FFmpeg 的引用:

另外 FFmpeg 编译后是区分 x86, x64, ARM 三种不同目标架构的,不同架构输出的目录并不相同,例如面向 ARM 平台的 Windows 10 版本的 FFmpeg 编译输出的文件位于 FFmpegInterop-master\ffmpeg\Build\Windows10\ARM 目录中。在 C# 版的播放器示例项目中,FFmpeg 的几个 .dll 文件是以链接的方式直接从 FFmpeg 的 build 目录引入项目的,这样在应用打包时,会将对应架构的 FFmpeg 库文件自动封入应用包中。

FFmpegInterop 提供的 MediaPlayerCS 项目已经做好了相关配置,如果需要在自己的项目中使用如上文所述的链接方式为项目添加 FFmpeg 库文件,需要手动配置项目文件:

C# 项目

使用文本编辑器(推荐 Sublime Text/Atom,不要使用记事本)或以 Visual Studio 文本模式(只打开文件不打开整个项目)打开项目的 .csproj 文件,找到 <ItemGroup></ItemGroup> 节点,在其中添加以下内容:

<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avcodec-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avdevice-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avfilter-5.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avformat-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avutil-54.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swresample-1.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swscale-3.dll" />

<ItemGroup> 节点代表项目包含的文件组,<Content> 代表项目中的“内容”类型文件。$(SolutionDir)$(PlatformTarget) 均为 Visual Studio 所用生成器可以识别的宏,$(SolutionDir) 代表解决方案目录;$(PlatformTarget) 代表目标平台。采用以上配置,C# 项目即可引入对应平台的 FFmpeg 库文件了。项目配置文件全文可参考 MediaPlayerCS.csproj

Javascript 项目

Javascript 项目与 C# 项目类似,使用文本编辑器(推荐 Sublime Text/Atom,不要使用记事本)或以 Visual Studio 文本模式(只打开文件不打开整个项目)打开项目的 .jsproj 文件,找到 <ItemGroup></ItemGroup> 节点,在其中 <AppxManifest></AppxManifest> 节点之后添加以下内容:

<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avcodec-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avdevice-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avfilter-5.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avformat-56.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avutil-54.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swresample-1.dll" />
<Content Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swscale-3.dll" />

项目配置文件全文可参考 MediaPlayerJS.jsproj

C++ 项目

C++ 项目与 C# 和 Javascript 项目稍有不同,使用文本编辑器(推荐 Sublime Text/Atom,不要使用记事本)或以 Visual Studio 文本模式(只打开文件不打开整个项目)打开项目的 .vcxproj 文件,找到 <ItemGroup></ItemGroup> 节点,在其中 <AppxManifest></AppxManifest> 节点之后添加以下内容:

<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avcodec-56.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avdevice-56.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avfilter-5.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avformat-56.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\avutil-54.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swresample-1.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>
<None Include="$(SolutionDir)ffmpeg\Build\Windows10\$(PlatformTarget)\bin\swscale-3.dll">
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
  <DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</None>

完整的 C++ 项目配置文件可以参考 MediaPlayerCPP.vcxproj

可以看到 FFmpeg 的几个 .dll 文件名中都有数字,例如 avformat-56.dll,不同版本的 FFmpeg 编译出来的文件名中这个版本号数字是不一样的,如果手动获取了不同版本的 FFmpeg 代码进行编译,注意在项目中添加 FFmpeg 的 .dll 时正确填写文件名。

如果在之前获取代码文件到本地的步骤中,你获取了最新版本的 FFmpeg 代码,则需要对 FFmpegInterop 项目进行一些改动才能够顺利编译。

在最新版本的 FFmpeg 代码中,FFmpegInterop 在 FFmpegReader.cpp 中调用的 av_free_packet 已被弃用,FFmpeg 在 commit ce70f28a1732c74a9cd7fec2d56178750bd6e457 中已经使用 av_packet_unref 替换了 av_free_packet,因此我们需要在 FFmpegReader.cpp 中改为使用 av_packet_unref。相关讨论可参见 Build error: av_free_packet deprecated

使用 FFmpegInterop

FFmpegInterop 的工作流程是:

  1. 读取媒体文件流。
  2. 通过 FFmpegInteropMSS.CreateFFmpegInteropMSSFromStream() 方法创建一个 FFmpegInteropObject,并为其传递媒体文件流和强制软解设置。
  3. 调用 FFmpegInteropObject 互操作对象的 GetMediaStreamSource() 方法获得 MediaStreamSource
  4. MediaStreamSource 设置给 MediaElement(C#) 或 VideoTag(Javascript)进行播放。

FFmpegInteropMSS 中提供了两个用于创建 FFmpegInteropObject

  1. CreateFFmpegInteropMSSFromStream
  2. CreateFFmpegInteropMSSFromUri

CreateFFmpegInteropMSSFromStream 方法接收三个参数 IRandomAccessStream^ stream, bool forceAudioDecode, bool forceVideoDecodestream 即输入的待播放媒体文件流;forceAudioDecode 用于设置是否强制使用 FFmpeg 对音频进行软解;forceVideoDecode 用于设置是否强制使用 FFmpeg 对视频进行软解。如果不设置强制使用 FFmpeg 进行软解,那么 MediaStreamSource 会把压缩数据直接送入 MediaElement 进行播放,目前只有 mp3、aac 和 H.264 支持硬解播放。

关于 Windows 10 系统本身支持硬解的格式,可以参考 Supported codecs

除了上述三个参数,CreateFFmpegInteropMSSFromStream 方法还有一个重载接收第四个参数 PropertySet^ ffmpegOptionsffmpegOptions 用于设置 FFmpeg 中 libavformat 库所使用的访问资源时要求的协议。所有属性列表可以参阅 FFmpeg Protocols

CreateFFmpegInteropMSSFromUri 方法用于播放一个 URI 提供的媒体流,其接收参数为 String^ uri, bool forceAudioDecode, bool forceVideoDecode,并且同样有一个接收 PropertySet^ ffmpegOptions 参数的重载用于指定协议设置。