前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用C#+FFmpeg+DirectX+dxva2硬件解码播放h264流

使用C#+FFmpeg+DirectX+dxva2硬件解码播放h264流

作者头像
梁规晓
发布2019-11-28 19:52:53
3.1K0
发布2019-11-28 19:52:53
举报
文章被收录于专栏:DotNet程序园DotNet程序园

本文门槛较高,因此行文看起来会乱一些,如果你看到某处能会心一笑请马上联系我开始摆龙门阵 如果你跟随这篇文章实现了播放器,那你会得到一个高效率,低cpu占用(单路720p视频解码播放占用1%左右cpu),且代码和引用精简(无其他托管和非托管的dll依赖,更无需安装任何插件,你的程序完全绿色运行);并且如果硬解不可用,切换到软件是自动过程

  首先需要准备好visual studio/msys2/ffmpeg源码/dx9sdk。因为我们要自己编译ffmpeg,并且是改动代码后编译,ffmpeg我们编译时会裁剪。

  • ffmpeg源码大家使用4.2.1,和我保持同步,这样比较好对应,下载地址为ffmpeg-4.2.1.tar.gz
  • msys2安装好后不需要装mingw和其他东西,只需要安装make(见下方图片;我们编译工具链会用msvc而非mingw-gcc)

msys2安装make

  • visual studio版本按道理是不需要新版本的,应该是2008-2019都可以(不过还是得看看ffmpeg代码里是否用了c99 c11等低版本不支持的东西),vs需要安装c++和c#的模块(见下方图片;应该也不需要特意去打开什么功能)

vs所需功能模块

  • dx9的sdk理论上是不用安装的(如果你是高手,可以用c#的ilgenerator直接写calli;亦或者写unsafe代码直接进行内存call,文章最后我会为大家揭秘如何用c#调用c++甚至com组件)。我用了directx的managecode,由官方为我们做了dx的调用(见下方图片)

安装好dx的sdk后我们得到c#的托管引用dll

  第二步是修改ffmpeg源码并编译,我们要修改的源码只有一个文件的十余行,而且是增量修改。

修改的文件位于libavutil/hwcontext_dxva2.c文件,我先将修改部分贴出来然后再给大家解释

hwcontext_dxva2.c修改部分

  代码中dxva2_device_create9_extend函数是我新加入的,并且在dxva2_device_create函数(这个函数是ffmpeg原始流程中的,我的改动不影响原本任何功能)中适时调用;简单来说,原来的ffmpeg也能基于dxva2硬件解码,但是它没法将解码得到的surface用于前台播放,因为它创建device时并未指定窗口和其他相关参数,大家可以参考我代码实现,我将窗口句柄传入后创建过程完全改变(其他人如果使用我们编译的代码,他没有传入窗口句柄,就执行原来的创建,因此百分百兼容)。

原始文件(版本不一致,仅供参考)

  (ps:在这里我讲一下网络上另外一种写法(两年前我也用的他们的,因为没时间详细看ffmpeg源码),他们是在外面创建的device和surface然后想办法传到ffmpeg内部进行替换,这样做有好处,就是不用自己修改和编译ffmpeg,坏处是得自己维护device和surface。至于二进制兼容方面考虑,两种做法都不是太好)

代码修改完成后我们使用msys2编译

  • 首先是需要把编译器设置为msvc,这个步骤通过使用vs的命令行工具即可,如下图

打开vs的编译工具

  • 然后是设置msys2继承环境变量(这样make时才能找到cl/link)

设置msys继承环境变量

将msys自带link重命名避免冲突

  • 打开msys,查看变量是否正确

检查变量正确性

  • 编译ffmpeg
代码语言:javascript
复制
./configure --enable-shared --enable-small --disable-all --disable-autodetect --enable-avcodec --enable-decoder=h264 --enable-dxva2 --enable-hwaccel=h264_dxva2 --toolchain=msvc --prefix=host
make && make install

cmake和make语句

编译完成后头文件和dll在host文件夹内(编译产出的dll也是clear的,不依赖msvc**.dll)

编译产出

  在C#中使用我们产出的方式需要使用p/invoke和unsafe代码。

我先贴出我针对ffmpeg写的一个工具类,然后给大家稍微讲解一下

FFHelper.cs

上文中主要有几个地方是知识点,大家做c#的如果需要和底层交互可以了解一下

  • 结构体的使用   结构体在c#与c/c++基本一致,都是内存连续变量的一种组合方式。与c/c++相同,在c#中,如果我们不知道(或者可以规避,因为结构体可能很复杂,很多无关字段)结构体细节只知道结构体整体大小时,我们可以用Pack=1,SizeConst=来表示一个大小已知的结构体。
  • 指针的使用   c#中,有两种存储内存地址(指针)的方式,一是使用interop体系中的IntPtr类型(大家可以将其想象成void*),一是在不安全的上下文(unsafe)中使用结构体类型指针(此处不讨论c++类指针)
  • unsafe和fixed使用   简单来说,有了unsafe你才能用指针;而有了fixed你才能确保指针指向位置不被GC压缩。我们使用fixed达到的效果就是显式跳过了结构体中前部无关数据(参考上文中AVCodecContext等结构体定义),后文中我们还会使用fixed。

  现在我们开始编写解码和播放部分(即我们的具体应用)代码

FFPlayer.cs

下面讲解代码最主要的三个部分

  • 初始化ffmpeg   主要在静态块和构造函数中,过程中我没有将AVPacket和AVFrame局部化,很多网上的代码包括官方代码都是局部化这两个对象。我对此持保留意见(等我程序报错了再说)
  • 将收到的数据送入ffmpeg解码并将拿到的数据进行展示   这里值得一提的是get_format,官方有一个示例,下图

官方的硬解码示例

它有一个get_format过程(详见215行和63行),我没有采用。这里给大家解释一下原因:

这个get_format的作用是ffmpeg给你提供了多个解码器让你来选一个,而且它内部有一个机制,如果你第一次选的解码器不生效(初始化错误等),它会调用get_format第二次(第三次。。。)让你再选一个,而我们首先认定了要用dxva2的硬件解码器,其次,如果dxva2初始化错误,ffmpeg内部会自动降级为内置264软解,因此我们无需多此一举。

  • 发现解码和播放过程中出现异常的解决办法
    • 不支持硬解 代码中已经做出了一部分兼容,因为baseline的判定必须解出sps/pps才能知道,因此这个错误可能会延迟爆出(不过不用担心,如果此时报错,ffmpeg会自动降级为软解)
    • 窗体大小改变 基于DirectX中设备后台缓冲的宽高无法动态重设,我们只能在控件大小改变时推倒重来。如若不然,你绘制的画面会进行意向不到的缩放
    • 网络掉包导致硬件解码器错误 见代码
    • 其他directx底层异常 代码中我加了一个try-catch,捕获的异常类型是DirectXException,在c/c++中,我们一般是调用完函数后会得到一个HRESULT,并通过FAILED宏判定他,而这个步骤在c#自动帮我们做了,取而代之的是一个throw DirectXException过程,我们通过try-catch进行可能的异常处理(实际上还是推倒重来)

  番外篇:C#对DiretX调用的封装 上文中我们使用DirectX的方式看起来即非COM组件,又非C-DLL的P/Invoke,难道DirectX真有托管代码? 答案是否定的,C#的dll当然也是调用系统的d3d9.dll。不过我们有必要一探究竟,因为这里面有一个隐藏副本

首先请大家准备好ildasm和visual studio,我们打开visual studio,创建一个c++工程(类型随意),然后新建一个cpp文件,然后填入下面的代码

测试代码

如果你能执行,你会发现输出是88;然后我们使用ildasm找到StrechRectangle的代码

ildasm中的呈现

你会发现也有一个+88的过程,那么其实道理就很容易懂了,c#通过calli(CLR指令)可以执行内存call,而得益于微软com组件的函数表偏移量约定,我们可以通过头文件知道函数对于对象指针的偏移(其实就是一个简单的ThisCall)。具体细节大家查阅d3d9.h和calli的网络文章即可。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet程序园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档