基于FFmpeg的Dxva2硬解码及Direct3D显示(四)

初始化硬解码上下文

创建解码数据缓冲区

这一步为了得到 LPDIRECT3DSURFACE9* 实例 m_pSurface,就是之前说过的那个数组。

// m_surfaceNums 为希望创建的缓冲区个数,单路视频一个就够了,太多可能显存不够用
m_pSurface = (LPDIRECT3DSURFACE9*)av_mallocz(m_surfaceNums * sizeof(LPDIRECT3DSURFACE9));
if (!m_pSurface)
{
    return FALSE;
}

// 字节对齐
int surfaceAlignment = 0;
if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
{
    surfaceAlignment = 32;
}
else if (pCodecCtx->codec_id == AV_CODEC_ID_HEVC)
{
    surfaceAlignment = 128;
}
else
{
    surfaceAlignment = 16;
}

// 创建缓冲区
HRESULT hr = m_pDecoderService->CreateSurface(
    FFALIGN(pCodecCtx->coded_width, surfaceAlignment),	// 缓冲区宽
    FFALIGN(pCodecCtx->coded_height, surfaceAlignment),	// 缓冲区高
    m_surfaceNums,					// 缓冲区个数,这里可以设置为0,CreateVideoDecoder里面会重新设置个数
    m_renderFormat,					// 缓冲区格式
    D3DPOOL_DEFAULT,				// 缓冲区位置,D3DPOOL_DEFAULT--显存
    0,								// 资源如何被使用					
    DXVA2_VideoDecoderRenderTarget,	// 缓冲区为视频解码器渲染目标
    m_pSurface,						// 缓冲区数组指针
    NULL);							// 保留字

if (FAILED(hr))
{
    return FALSE;
}

创建IDirectXVideoDecoder视频解码器

  1. 获取当前GPU支持的解码能力等级和渲染格式
BOOL GetDxva2FormatAndGuid(AVCodecContext *pCodecCtx, GUID & guid, D3DFORMAT & fmt)
{
	// 获取当前设备支持的解码标准等级标识列表
	GUID *guidList = NULL;
	unsigned guidCount = 0;

	HRESULT hr = m_pDecoderService->GetDecoderDeviceGuids(&guidCount, &guidList);
	if (FAILED(hr))
	{
		VX_LOG_ERROR("Get hardware acclerate device guids failed!");
		return FALSE;
	}

	for (int i = 0; ; i++)
	{
		if (NULL == guid2AVCodecID[i].guidID)
		{
			// 查到最后一个了直接退出循环
			break;
		}

		const Guid2CodecID *mode = &guid2AVCodecID[i];

		if (mode->codecID == pCodecCtx->codec_id)
		{
			for (uint32_t j = 0; j < guidCount; j++)
			{
				if (IsEqualGUID(*mode->guidID, guidList[j]))
				{
					// 获取当前解码标准下渲染器目标格式数组
					D3DFORMAT *targetList = NULL;
					UINT targetCount = 0;
					hr = m_pDecoderService->GetDecoderRenderTargets(*mode->guidID, &targetCount, &targetList);
					if (FAILED(hr))
					{
						VX_LOG_ERROR("Get support render format failed!");
						return FALSE;
					}

					for (uint32_t j = 0; j < targetCount; j++)
					{
						if (targetList[j] == MKTAG('N', 'V', '1', '2'))
						{
							fmt = targetList[j];
							guid = *mode->guidID;
							break;
						}
					}
					// 释放内存资源
					CoTaskMemFree(targetList);
				}
			}
		}
	}

	CoTaskMemFree(guidList);

	if (D3DFMT_UNKNOWN == fmt || GUID_NULL == guid)
	{
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}
  1. 获取当前解码等级下的配置信息
void CDxva2Decode::GetDecoderCfg(AVCodecContext *pCodecCtx, const GUID *pGuid,
								const DXVA2_VideoDesc *pDesc, DXVA2_ConfigPictureDecode *pCfg)
{
	unsigned cfgCount = 0, bestScore = 0;
	DXVA2_ConfigPictureDecode *cfgList = NULL;

	HRESULT hr = m_pDecoderService->GetDecoderConfigurations(*pGuid, pDesc, NULL, &cfgCount, &cfgList);

	for (uint32_t i = 0; i < cfgCount; i++)
	{
		DXVA2_ConfigPictureDecode cfg = cfgList[i];

		unsigned score;

		if (cfg.ConfigBitstreamRaw == 1)
		{
			score = 1;
		}
		else if (pCodecCtx->codec_id == AV_CODEC_ID_H264 && cfg.ConfigBitstreamRaw == 2)
		{
			score = 2;
		}
		else
		{
			continue;
		}

		if (IsEqualGUID(cfg.guidConfigBitstreamEncryption, DXVA2_NoEncrypt))
		{
			score += 16;
		}

		if (score > bestScore)
		{
			bestScore = score;
			*pCfg = cfg;
		}
	}

	CoTaskMemFree(cfgList);
}
  1. 这一步为了得到 IDirectXVideoDecoder* 实例 m_pDxva2Decoder,亦即硬件解码器。
if (!GetDxva2FormatAndGuid(pCodecCtx, m_decoderGuid, m_renderFormat))
{
    // 不支持DXVA2加速
    VX_LOG_ERROR("Do not support Dxva2!");
    return FALSE;
}

// 设置解码后的格式
DXVA2_VideoDesc desc = { 0 };
desc.SampleWidth = pCodecCtx->coded_width;
desc.SampleHeight = pCodecCtx->coded_height;
desc.Format = m_renderFormat;

// 获取支持的配置
GetDecoderCfg(pCodecCtx, &m_decoderGuid, &desc, &m_config);

// 创建解码器设备
HRESULT hr = m_pDecoderService->CreateVideoDecoder(m_decoderGuid,		// 设备标识符
                                                   &desc,				// 视频内容描述
                                                   &m_config,			// 解码器配置
                                                   m_pSurface,			// 渲染目标数组指针(解码后的数据写到这里)
                                                   m_surfaceNums,		// 渲染目标数,必须大于0,
                                                   &m_pDxva2Decoder);	// 解码器

if (FAILED(hr))
{
    return FALSE;
}

设置硬解码上下文

// 这一步为了将解码缓冲区数组传给GetBufferCallBack回调函数
pCodecCtx->opaque = m_pSurface;

// 设置回调
pCodecCtx->get_buffer2 = GetBufferCallBack;
pCodecCtx->get_format = GetHwFormat;

// 单路视频启动多线程解码,理解是启用多个线程将待解码数据送往GPU,因为数据从内存到显存比较慢
pCodecCtx->thread_safe_callbacks = TRUE;
pCodecCtx->thread_count = 2;

// 为解码器上下文申请硬件加速内存
pCodecCtx->hwaccel_context = av_mallocz(sizeof(struct dxva_context));
if (!pCodecCtx->hwaccel_context)
{
    return FALSE;
}

// 设置硬件加速上下文
struct dxva_context *dxva2Ctx = (dxva_context *)pCodecCtx->hwaccel_context;
dxva2Ctx->cfg = &m_config;
dxva2Ctx->decoder = m_pDecoder;
dxva2Ctx->surface = m_pSurface;
dxva2Ctx->surface_count = m_surfaceNums;

// 对老的intel GPU 的支持
if (IsEqualGUID(m_decoderGuid, DXVADDI_Intel_ModeH264_E))
{
    dxva2Ctx->workaround |= FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO;
}

解码回调函数

  1. 解码输出格式回调
static AVPixelFormat GetHwFormat(AVCodecContext * pCodecCtx, const AVPixelFormat * pPixFmt)
{
	// 因为采用的是DXVA2,所以这里直接写死了
	return AV_PIX_FMT_DXVA2_VLD;	
}
  1. 解码数据回调

    此时解码后的数据放在解码缓冲数组里面,这里数组大小为1。单路视频时 pFrame 地址为两个固定地址切换,这些应该都是FFmpeg内部实现的。这里理解的不清楚,希望大神可以指点。

// 个人理解就是将LPDIRECT3DSURFACE9转为(uint8_t *),同时得保证内存不会立即被释放 
static int GetBufferCallBack(AVCodecContext * pCodecCtx, AVFrame * pFrame, int flags)
{
	if (pFrame->format != AV_PIX_FMT_DXVA2_VLD)
	{
		return -1;
	}

	// 获取解码后的数据,出于安全性不可直接访问,这一步没有内存拷贝
	LPDIRECT3DSURFACE9 surface = ((LPDIRECT3DSURFACE9*)(pCodecCtx->opaque))[0];

	// 将LPDIRECT3DSURFACE9转为AVBuffer,内存地址不变,并返回AVBufferRef,并返回AVBufferRef供FFmpeg内部使用,这一步应该发生了内存拷贝
	// 类似于智能指针,增加对surface的引用计数,当计数为0时FFmpeg会认为该帧数据丢弃掉。默认使用av_buffer_default_free释放,
	pFrame->buf[0] = av_buffer_create((uint8_t*)surface, 0, nullptr, nullptr, AV_BUFFER_FLAG_READONLY);
	if (!pFrame->buf[0])
	{
		return AVERROR(ENOMEM);
	}
	
	// 这一步拿到最终可以显示的数据,必须是data[3],此时surface应该是AVBuffer
	pFrame->data[3] = (uint8_t *)surface;

	return 0;
}

[参考链接]:(http://www.cnblogs.com/betterwgo/p/6125507.html)

原文地址:https://www.cnblogs.com/huluwa508/p/10304451.html