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

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

相关推荐
美酒没故事°1 天前
Open WebUI安装指南。搭建自己的自托管 AI 平台
人工智能·windows·ai
一个欠登儿程序员1 天前
在国产服务器上通过 Docker 部署 Windows 虚拟机
服务器·windows·docker
紫金修道1 天前
【编解码】RK3588 平台基于 FFmpeg RKMPP 硬解的多路 RTSP 抓帧插件实战
ffmpeg·rkmpp
爱宇阳1 天前
WSL2 隔离 Windows PATH 实战指南
windows·环境变量
ALex_zry1 天前
C++模板元编程实战技巧
网络·c++·windows
I疯子1 天前
2026-04-08 打卡第 5 天
开发语言·windows·python
一个人旅程~1 天前
旧笔记本电脑安装win10精简版LTSB&win10LTSC&linuxmint作为三系统的操作指导书(以DELL n4020为例)
linux·windows·经验分享·电脑
开开心心就好1 天前
支持自定义名单的实用随机抽签工具
windows·计算机视觉·计算机外设·excel·散列表·启发式算法·csdn开发云
dyj0951 天前
OpenClaw小龙虾本地部署【Windows系统 + 接入飞书】
windows·飞书
CresCent_Charles1 天前
(已解决)踩坑记录:Windows 11安装pointops编译时报错
windows