• 基于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)

  • 相关阅读:
    aes加密
    获取当前系统的版本号
    解决eclipse中出现Resource is out of sync with the file system问题
    Mac系统打开命令行终端及查看操作系统版本号的方法
    android短信拦截
    android权限大全
    mac系统下的常用命令
    android 中 系统日期时间的获取
    ubuntu tor浏览器
    Python中的random模块
  • 原文地址:https://www.cnblogs.com/huluwa508/p/10304451.html
Copyright © 2020-2023  润新知