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不存在或者不通畅,程序会死掉很久时间,这里就必须要设置一个超时返回失败的设置,各个网络协议的设置不尽相同:

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

复制代码
    const char *timeout = "1000000";
    if (wcsncmp(m_url, L"rtmp",4)==0)
        av_dict_set(&opts, "rw_timeout", timeout, 0);//rtmp设置超时1秒
    else if (wcsncmp(m_url, L"rtsp", 4) == 0)
        av_dict_set(&opts, "stimeout", timeout, 0);//rtsp设置超时1秒
    else
        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的核心代码:

cpp 复制代码
static int dxva2_create_decoder(AVCodecContext *s)
{
    InputStream  *ist = (InputStream *)s->opaque;
    int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR;
    DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx;
    struct dxva_context *dxva_ctx = (dxva_context *)s->hwaccel_context;
    GUID *guid_list = NULL;
    unsigned guid_count = 0, i, j;
    GUID device_guid = GUID_NULL;
    D3DFORMAT target_format = D3DFMT_UNKNOWN;
    DXVA2_VideoDesc desc = { 0 };
    DXVA2_ConfigPictureDecode config;
    HRESULT hr;
    int surface_alignment;
    int ret;

    hr = ctx->decoder_service->GetDecoderDeviceGuids(&guid_count, &guid_list);
    if (FAILED(hr)) {
        av_log(NULL, loglevel, "Failed to retrieve decoder device GUIDs\n");
        goto fail;
    }

    for (i = 0; dxva2_modes[i].guid; i++) {
        D3DFORMAT *target_list = NULL;
        unsigned target_count = 0;
        const dxva2_mode *mode = &dxva2_modes[i];
        if (mode->codec != s->codec_id)
            continue;

        for (j = 0; j < guid_count; j++) 
		{
            //if (IsEqualGUID(*mode->guid, guid_list[j]))
            //    break;
			if (IsEqualGUID(*mode->guid, guid_list[j]))
			{
				if (s->pix_fmt == AV_PIX_FMT_YUV420P10LE || s->pix_fmt ==AV_PIX_FMT_YUV420P10BE)
				{
					if (*mode->guid == DXVA2_ModeHEVC_VLD_Main10)
						break;
					continue;
				}
				else
					break;
			}

        }
        if (j == guid_count)
            continue;

        hr = ctx->decoder_service->GetDecoderRenderTargets(*mode->guid, &target_count, &target_list);
        if (FAILED(hr)) {
            continue;
        }
        //for (j = 0; j < target_count; j++) {
        //    const D3DFORMAT format = target_list[j];
        //    if (format == MKTAG('N', 'V', '1', '2')) {
        //        target_format = format;
        //        break;
        //    }
        //}
        for (j = 0; j < target_count; j++) {
            const D3DFORMAT format = target_list[j];
			D3DFORMAT f0 = (D3DFORMAT)MKTAG('N', 'V', '1', '2');
			D3DFORMAT f1 = (D3DFORMAT)MKTAG('P', '0', '1', '0');
			if (format == MKTAG('N', 'V', '1', '2') || format == MKTAG('P', '0', '1', '0'))
			{
                target_format = format;
                break;
            }
        }
        CoTaskMemFree(target_list);
        if (target_format) {
            device_guid = *mode->guid;
            break;
        }
    }
    CoTaskMemFree(guid_list);

    if (IsEqualGUID(device_guid, GUID_NULL)) {
        av_log(NULL, loglevel, "No decoder device for codec found\n");
        goto fail;
    }

    desc.SampleWidth = s->coded_width;
    desc.SampleHeight = s->coded_height;
    desc.Format = target_format;

    ret = dxva2_get_decoder_configuration(s, &device_guid, &desc, &config);
    if (ret < 0) {
        goto fail;
    }

    /* decoding MPEG-2 requires additional alignment on some Intel GPUs,
    but it causes issues for H.264 on certain AMD GPUs..... */
    if (s->codec_id == AV_CODEC_ID_MPEG2VIDEO)
        surface_alignment = 32;
    /* the HEVC DXVA2 spec asks for 128 pixel aligned surfaces to ensure
    all coding features have enough room to work with */
    else if (s->codec_id == AV_CODEC_ID_HEVC)
        //surface_alignment = 128;
		surface_alignment = 16;
    else
        surface_alignment = 16;

    /* 4 base work surfaces */
    ctx->num_surfaces = 4;

    /* add surfaces based on number of possible refs */
    if (s->codec_id == AV_CODEC_ID_H264 || s->codec_id == AV_CODEC_ID_HEVC)
        ctx->num_surfaces += 16;
    else if (s->codec_id == AV_CODEC_ID_VP9)
        ctx->num_surfaces += 8;
    else
        ctx->num_surfaces += 2;

    /* add extra surfaces for frame threading */
    if (s->active_thread_type & FF_THREAD_FRAME)
        ctx->num_surfaces += s->thread_count;

    ctx->surfaces = (LPDIRECT3DSURFACE9 *)av_mallocz(ctx->num_surfaces * sizeof(*ctx->surfaces));
    ctx->surface_infos = (surface_info *)av_mallocz(ctx->num_surfaces * sizeof(*ctx->surface_infos));

    if (!ctx->surfaces || !ctx->surface_infos) {
        av_log(NULL, loglevel, "Unable to allocate surface arrays\n");
        goto fail;
    }

    hr = ctx->decoder_service->CreateSurface(FFALIGN(s->coded_width, surface_alignment),
        FFALIGN(s->coded_height, surface_alignment),
        ctx->num_surfaces - 1,
        target_format, D3DPOOL_DEFAULT, 0,
        DXVA2_VideoDecoderRenderTarget,
        ctx->surfaces, NULL);
    if (FAILED(hr)) {
        printf( "Failed to create %d video surfaces\n", ctx->num_surfaces);
        goto fail;
    }

    hr = ctx->decoder_service->CreateVideoDecoder(device_guid,
        &desc, &config, ctx->surfaces,
        ctx->num_surfaces, &ctx->decoder);
    if (FAILED(hr)) {
        printf("Failed to create DXVA2 video decoder\n");
        goto fail;
    }

    ctx->decoder_guid = device_guid;
    ctx->decoder_config = config;

    dxva_ctx->cfg = &ctx->decoder_config;
    dxva_ctx->decoder = ctx->decoder;
    dxva_ctx->surface = ctx->surfaces;
    dxva_ctx->surface_count = ctx->num_surfaces;

    if (IsEqualGUID(ctx->decoder_guid, DXVADDI_Intel_ModeH264_E))
        dxva_ctx->workaround |= FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO;

    return 0;
fail:
    dxva2_destroy_decoder(s);
    return AVERROR(EINVAL);
}

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

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

cpp 复制代码
static int U2UTF(WCHAR* szUnicode, char* pDesBuf)
{
	DWORD dwNum = WideCharToMultiByte(CP_UTF8, NULL, szUnicode, -1, NULL, 0, NULL, FALSE);
	char* psText = new char[dwNum];
	if (!psText)
	{
		delete[]psText;
	}
	memset(psText, 0, dwNum);
	WideCharToMultiByte(CP_UTF8, NULL, szUnicode, -1, psText, dwNum, NULL, FALSE);
	memcpy(pDesBuf, psText, dwNum);
	delete[]psText;
	return dwNum;
}

打开越南语路径的截图:

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高速渲染显示:

cpp 复制代码
#pragma once
#include "stdafx.h"
#include "d3d9.h"
#include "d3dx9.h"

class CD3DVidRender
{
public:
	CD3DVidRender(void);
	~CD3DVidRender(void);
	
	void Cleanup();

	BOOL InitD3D_RGB32(HWND hwnd, int img_width, int img_height);

	BOOL InitD3D_YUV(HWND hwnd, int img_width, int img_height);

	BOOL InitD3D_NV12(HWND hwnd, int img_width, int img_height);

	BOOL Render_RGB32(unsigned char* pdata, int width, int height);

	BOOL Render_YUV(unsigned char * pdata, int img_width, int img_height);

	BOOL Render_NV12(unsigned char * pdata, int img_width, int img_height);

	void calculate_display_rect(RECT *rect,int img_width, int img_height, int scr_width, int scr_height) ;

public:
	RECT m_rtViewport;
	D3DPRESENT_PARAMETERS d3dpp; 
	IDirect3D9 * m_pDirect3D9;  
	IDirect3DDevice9 * m_pDirect3DDevice;  
	IDirect3DSurface9 * m_pDirect3DSurfaceRender; 
	IDirect3DSurface9 * m_pBackBuffer;

	RECT m_rtFont;
	ID3DXFont* m_pD3DXFont;
	D3DXFONT_DESC m_font_desc;
};

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

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

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

相关推荐
lucky67071 小时前
Windows 上彻底卸载 Node.js
windows·node.js
编程小白20262 小时前
从 C++ 基础到效率翻倍:Qt 开发环境搭建与Windows 神级快捷键指南
开发语言·c++·windows·qt·学习
浩瀚之水_csdn2 小时前
av_packet_alloc详解
ffmpeg
凯子坚持 c3 小时前
CANN 性能剖析实战:从原始事件到交互式火焰图
windows·microsoft
开开心心就好4 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
獨枭4 小时前
PyCharm 跑通 SAM 全流程实战
windows
仙剑魔尊重楼5 小时前
音乐制作电子软件FL Studio2025.2.4.5242中文版新功能介绍
windows·音频·录屏·音乐·fl studio
PHP小志5 小时前
Windows 服务器怎么修改密码和用户名?账户被系统锁定如何解锁
windows
专注VB编程开发20年7 小时前
vb.net datatable新增数据时改用数组缓存
java·linux·windows
仙剑魔尊重楼7 小时前
专业音乐制作软件fl Studio 2025.2.4.5242中文版新功能
windows·音乐·fl studio