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

mac2022-06-30  26

目录

初始化硬解码上下文 创建解码数据缓冲区创建IDirectXVideoDecoder视频解码器设置硬解码上下文解码回调函数

初始化硬解码上下文

创建解码数据缓冲区

这一步为了得到 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视频解码器

获取当前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; } } 获取当前解码等级下的配置信息 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); } 这一步为了得到 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; }

解码回调函数

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

解码数据回调

此时解码后的数据放在解码缓冲数组里面,这里数组大小为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

相关资源:ffmpeg硬解
最新回复(0)