• dxva2+ffmpeg硬件解码(Windows)终结发布


    《dxva2超低延迟视频播放器》演示demo下载URL:

    【免费】dxva2硬解码超低延迟网络+本地播放器资源-CSDN文库

    本地播放 截图:

    rtsp播放截图(推送内容为本地桌面,所以是这样的)

    OK,进入主题:

    前前后后写了4篇文章解决"dxva2+ffmpeg硬件解码"遇到的问题,这篇文章做个demo并发布直播流视频播放器正式测试版本:

    播放器特点:

    1、超低延迟播放器(100ms左右)

    2、秒开技术(1秒)

    3、各种流媒体网络协议支持,如rtmp,rtmp,rtp,udp,tcp,hls,http,srt等

    4、超时断开重连机制

    5、dxva2硬解码+软解码D3D显示,超低CPU占有率

    6、支持4K视频,10bit视频,支持各种视频格式,如mp4,mkv,wmv,mov,ts等等

    7、支持本地各种语言的文件路径及文件名(utf-8)

    8、源码内把dxva2封装为类,可以在进程内打开多个播放器同时进行dxva2硬解码。

    9、提供d3d高速渲染RGB32,I420,NV12数据,并封装为类

    。。。

    《dxva2超低延迟视频播放器》搭建环境:

    windows下,服务器采用ZLMediaKit,推送端采用ffmpeg直接推送桌面,接收端播放器为本播放器

    以下以源码+文字说明的形式一一说明:

    1、超低延迟的实现:

    其实就一句话:

    fc->flags = AV_CODEC_FLAG_LOW_DELAY;

    (fc的定义:AVFormatContext *fc)

    这样设置后,解码器就没缓存了,有数据就直接可以获取

    2、秒开技术:

    就是首帧显示出来的时间。当然越短越好!

    也是一句话:

    fc->max_analyze_duration = 1 * AV_TIME_BASE;

    最大分析持续时间,设置的1秒,这个只能根据经验设置。默认的话这个时间会很长很长。

    3、各种流媒体网络协议支持:(打开URL超时设置)

    支持的协议就不说明了,这是ffmpeg 的功劳,哈哈

    第一点,这里不得不说下各种协议下的打开URL超时设置,如果URL不存在或者不通畅,程序会死掉很久时间,这里就必须要设置一个超时返回失败的设置,各个网络协议的设置不尽相同:

    直接贴代码吧,简单易懂!

    1.     const char *timeout = "1000000";
    2.     if (wcsncmp(m_url, L"rtmp",4)==0)
    3.         av_dict_set(&opts, "rw_timeout", timeout, 0);//rtmp设置超时1
    4.     else if (wcsncmp(m_url, L"rtsp", 4) == 0)
    5.         av_dict_set(&opts, "stimeout", timeout, 0);//rtsp设置超时1
    6.     else
    7.         av_dict_set(&opts, "timeout", timeout, 0);//udp和http设置超时3

    第二点说下,打开过程中的断开判断,直接贴代码:

        第一步,播放器初始化时:

        fc = avformat_alloc_context();
        fc->interrupt_callback.callback = CheckInterrupt;//播放中的超时回调
        fc->interrupt_callback.opaque = 0; 

    m_ntime_timeout=0;

    第二🙅步,获取数据的线程里面:        

    m_ntime_timeout = ::GetTickCount();
            AVPacket pkt = { 0 };
            if (av_read_frame(fc, &pkt) == 0)

    这样在回调函数CheckInterrupt里,如果数据读取超时或失败,那么

    static int CheckInterrupt(void* ctx)
    {
        if (m_ntime_timeout != 0 && ::GetTickCount() - m_ntime_timeout >= 3000)//超时了
        {
            m_bOpened = false;
            g_exitcode = 1;
        }
        else
            g_exitcode = 0;
        return g_exitcode;
    }

    我们就判定这个连接已经断开了,需要重连!接着看下面

    4、超时断开重连机制:

    由于avformat_open_input是同步方式打开URL的模式,所以打开前只能死等,由于没找到异步打开的方式,只能自己实现,其实原理不难,就是创建一个打开URL的线程,每隔1秒判断是否在打开状态,如果不是就尝试打开,一个死循环即可。

    //异步打开线程
    unsigned int __stdcall OpenThread(void * pParam)
    {
        for (;;)
        {
            if (!m_bOpenTheadRuning)
                break;
            if (!m_bOpened)//没打开就打开
            {
                Open2(m_hWnd);
            }

            Sleep(1000);
        }
        return 0;
    }

    5、dxva2硬解码+软解码D3D显示,超低CPU占有率:

    没啥说的,dxva2反正就是爽,因为不需要指定显卡,只要支持dxva2协议的显卡都能进行硬解码。硬解码CPU占用肯定低啦。其他硬解码方式也行但是通用性不强,因为你事先不知道是何种显卡,比如常用的英伟达,intel,AMD,还有不常用的比如有些国产CPU如兆芯带的显卡等,所以DXVA2是通用的和最常用的硬解码方式之一。

    6、支持4K视频,支持各种视频格式,如mp4,mkv,wmv,mov,ts等等

    如果节目源为4K视频,单纯的用CPU解码占有率会很高,而且帧率也达不到30帧,甚至更低。所以此时DXVA2的优势即可体现,实测用INTEL HD630低端显卡,解码4K 10bit HEVC视频,最高解码帧率可以达到300帧左右,CPU占有率3%,GPU72%。

    查看DXVA2协议,应该是支持10bit视频的,网上下载的源码是不支持的。

    修改后支持10bit的核心代码:

    1. static int dxva2_create_decoder(AVCodecContext *s)
    2. {
    3. InputStream *ist = (InputStream *)s->opaque;
    4. int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR;
    5. DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx;
    6. struct dxva_context *dxva_ctx = (dxva_context *)s->hwaccel_context;
    7. GUID *guid_list = NULL;
    8. unsigned guid_count = 0, i, j;
    9. GUID device_guid = GUID_NULL;
    10. D3DFORMAT target_format = D3DFMT_UNKNOWN;
    11. DXVA2_VideoDesc desc = { 0 };
    12. DXVA2_ConfigPictureDecode config;
    13. HRESULT hr;
    14. int surface_alignment;
    15. int ret;
    16. hr = ctx->decoder_service->GetDecoderDeviceGuids(&guid_count, &guid_list);
    17. if (FAILED(hr)) {
    18. av_log(NULL, loglevel, "Failed to retrieve decoder device GUIDs\n");
    19. goto fail;
    20. }
    21. for (i = 0; dxva2_modes[i].guid; i++) {
    22. D3DFORMAT *target_list = NULL;
    23. unsigned target_count = 0;
    24. const dxva2_mode *mode = &dxva2_modes[i];
    25. if (mode->codec != s->codec_id)
    26. continue;
    27. for (j = 0; j < guid_count; j++)
    28. {
    29. //if (IsEqualGUID(*mode->guid, guid_list[j]))
    30. // break;
    31. if (IsEqualGUID(*mode->guid, guid_list[j]))
    32. {
    33. if (s->pix_fmt == AV_PIX_FMT_YUV420P10LE || s->pix_fmt ==AV_PIX_FMT_YUV420P10BE)
    34. {
    35. if (*mode->guid == DXVA2_ModeHEVC_VLD_Main10)
    36. break;
    37. continue;
    38. }
    39. else
    40. break;
    41. }
    42. }
    43. if (j == guid_count)
    44. continue;
    45. hr = ctx->decoder_service->GetDecoderRenderTargets(*mode->guid, &target_count, &target_list);
    46. if (FAILED(hr)) {
    47. continue;
    48. }
    49. //for (j = 0; j < target_count; j++) {
    50. // const D3DFORMAT format = target_list[j];
    51. // if (format == MKTAG('N', 'V', '1', '2')) {
    52. // target_format = format;
    53. // break;
    54. // }
    55. //}
    56. for (j = 0; j < target_count; j++) {
    57. const D3DFORMAT format = target_list[j];
    58. D3DFORMAT f0 = (D3DFORMAT)MKTAG('N', 'V', '1', '2');
    59. D3DFORMAT f1 = (D3DFORMAT)MKTAG('P', '0', '1', '0');
    60. if (format == MKTAG('N', 'V', '1', '2') || format == MKTAG('P', '0', '1', '0'))
    61. {
    62. target_format = format;
    63. break;
    64. }
    65. }
    66. CoTaskMemFree(target_list);
    67. if (target_format) {
    68. device_guid = *mode->guid;
    69. break;
    70. }
    71. }
    72. CoTaskMemFree(guid_list);
    73. if (IsEqualGUID(device_guid, GUID_NULL)) {
    74. av_log(NULL, loglevel, "No decoder device for codec found\n");
    75. goto fail;
    76. }
    77. desc.SampleWidth = s->coded_width;
    78. desc.SampleHeight = s->coded_height;
    79. desc.Format = target_format;
    80. ret = dxva2_get_decoder_configuration(s, &device_guid, &desc, &config);
    81. if (ret < 0) {
    82. goto fail;
    83. }
    84. /* decoding MPEG-2 requires additional alignment on some Intel GPUs,
    85. but it causes issues for H.264 on certain AMD GPUs..... */
    86. if (s->codec_id == AV_CODEC_ID_MPEG2VIDEO)
    87. surface_alignment = 32;
    88. /* the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
    89. all coding features have enough room to work with */
    90. else if (s->codec_id == AV_CODEC_ID_HEVC)
    91. //surface_alignment = 128;
    92. surface_alignment = 16;
    93. else
    94. surface_alignment = 16;
    95. /* 4 base work surfaces */
    96. ctx->num_surfaces = 4;
    97. /* add surfaces based on number of possible refs */
    98. if (s->codec_id == AV_CODEC_ID_H264 || s->codec_id == AV_CODEC_ID_HEVC)
    99. ctx->num_surfaces += 16;
    100. else if (s->codec_id == AV_CODEC_ID_VP9)
    101. ctx->num_surfaces += 8;
    102. else
    103. ctx->num_surfaces += 2;
    104. /* add extra surfaces for frame threading */
    105. if (s->active_thread_type & FF_THREAD_FRAME)
    106. ctx->num_surfaces += s->thread_count;
    107. ctx->surfaces = (LPDIRECT3DSURFACE9 *)av_mallocz(ctx->num_surfaces * sizeof(*ctx->surfaces));
    108. ctx->surface_infos = (surface_info *)av_mallocz(ctx->num_surfaces * sizeof(*ctx->surface_infos));
    109. if (!ctx->surfaces || !ctx->surface_infos) {
    110. av_log(NULL, loglevel, "Unable to allocate surface arrays\n");
    111. goto fail;
    112. }
    113. hr = ctx->decoder_service->CreateSurface(FFALIGN(s->coded_width, surface_alignment),
    114. FFALIGN(s->coded_height, surface_alignment),
    115. ctx->num_surfaces - 1,
    116. target_format, D3DPOOL_DEFAULT, 0,
    117. DXVA2_VideoDecoderRenderTarget,
    118. ctx->surfaces, NULL);
    119. if (FAILED(hr)) {
    120. printf( "Failed to create %d video surfaces\n", ctx->num_surfaces);
    121. goto fail;
    122. }
    123. hr = ctx->decoder_service->CreateVideoDecoder(device_guid,
    124. &desc, &config, ctx->surfaces,
    125. ctx->num_surfaces, &ctx->decoder);
    126. if (FAILED(hr)) {
    127. printf("Failed to create DXVA2 video decoder\n");
    128. goto fail;
    129. }
    130. ctx->decoder_guid = device_guid;
    131. ctx->decoder_config = config;
    132. dxva_ctx->cfg = &ctx->decoder_config;
    133. dxva_ctx->decoder = ctx->decoder;
    134. dxva_ctx->surface = ctx->surfaces;
    135. dxva_ctx->surface_count = ctx->num_surfaces;
    136. if (IsEqualGUID(ctx->decoder_guid, DXVADDI_Intel_ModeH264_E))
    137. dxva_ctx->workaround |= FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO;
    138. return 0;
    139. fail:
    140. dxva2_destroy_decoder(s);
    141. return AVERROR(EINVAL);
    142. }

    7、支持本地各种语言的文件路径及文件名(utf-8)

    这个其实不算什么特点,单独列出来,只是有些初学开发者不知道avformat_open_input的URL路径的字符编码,很多人以为是ansi码,在中文系统下的中文字符没问题,但是你遇到日语,韩语,越南语等其他小语种路径时,却打开失败!其实这里的字符编码应该为UTF-8,需要将unicode转为utf-8字符串,贴代码:

    1. static int U2UTF(WCHAR* szUnicode, char* pDesBuf)
    2. {
    3. DWORD dwNum = WideCharToMultiByte(CP_UTF8, NULL, szUnicode, -1, NULL, 0, NULL, FALSE);
    4. char* psText = new char[dwNum];
    5. if (!psText)
    6. {
    7. delete[]psText;
    8. }
    9. memset(psText, 0, dwNum);
    10. WideCharToMultiByte(CP_UTF8, NULL, szUnicode, -1, psText, dwNum, NULL, FALSE);
    11. memcpy(pDesBuf, psText, dwNum);
    12. delete[]psText;
    13. return dwNum;
    14. }

    打开越南语路径的截图:

    8、源码内把dxva2封装为类,可以在进程内打开多个播放器同时进行dxva2硬解码:

    封装优化后,只有3个接口:

        int Init(AVCodecContext *s, HWND hwnd);
        int    RenderData(AVCodecContext *s,AVFrame *pFrame,RECT *sourceRect=0);
        void dxva2_uninit(AVCodecContext *s) ;

    9、提供d3d高速渲染RGB32,I420,NV12数据,并封装为类:

    对于软解码或者硬解码后获取数据到CPU内存,就需要提供D3D高速渲染显示:

    1. #pragma once
    2. #include "stdafx.h"
    3. #include "d3d9.h"
    4. #include "d3dx9.h"
    5. class CD3DVidRender
    6. {
    7. public:
    8. CD3DVidRender(void);
    9. ~CD3DVidRender(void);
    10. void Cleanup();
    11. BOOL InitD3D_RGB32(HWND hwnd, int img_width, int img_height);
    12. BOOL InitD3D_YUV(HWND hwnd, int img_width, int img_height);
    13. BOOL InitD3D_NV12(HWND hwnd, int img_width, int img_height);
    14. BOOL Render_RGB32(unsigned char* pdata, int width, int height);
    15. BOOL Render_YUV(unsigned char * pdata, int img_width, int img_height);
    16. BOOL Render_NV12(unsigned char * pdata, int img_width, int img_height);
    17. void calculate_display_rect(RECT *rect,int img_width, int img_height, int scr_width, int scr_height) ;
    18. public:
    19. RECT m_rtViewport;
    20. D3DPRESENT_PARAMETERS d3dpp;
    21. IDirect3D9 * m_pDirect3D9;
    22. IDirect3DDevice9 * m_pDirect3DDevice;
    23. IDirect3DSurface9 * m_pDirect3DSurfaceRender;
    24. IDirect3DSurface9 * m_pBackBuffer;
    25. RECT m_rtFont;
    26. ID3DXFont* m_pD3DXFont;
    27. D3DXFONT_DESC m_font_desc;
    28. };

    《dxva2超低延迟视频播放器》演示demo下载URL:

    【免费】dxva2硬解码超低延迟网络+本地播放器资源-CSDN文库icon-default.png?t=N7T8https://download.csdn.net/download/xjb2006/88554080

    可能以后会适时上传源码。不过授之以鱼不如授之以渔,知道了原理和方法,这些问题都迎刃而解!

  • 相关阅读:
    【大数据处理技术】实验12(新建文件夹)
    使用 Hugging Face 微调 Gemma 模型
    【使用JDBC对数据库表进行操作】
    如何用javascript生成网络计算器
    不花钱就能完美解决大型离散非标设备生产行业企业信息化
    【C++项目】高并发内存池第三讲PageCache框架涉及+核心实现(上)
    Redis:redis为什么会分16个库、与数据库关联时是先更新数据库还是先更新缓存、大多数情况为什么删除而不是更新缓存
    JSR303和拦截器
    vue3+ts打开echarts的正确方式
    分布式唯一ID几种生成方案
  • 原文地址:https://blog.csdn.net/xjb2006/article/details/134480767