基于D3D11+MediaFoundation的MP4录制:RGB32转NV12疑问
关于D3D11+MediaFoundation录制MP4时RGB32转NV12的兼容性与实现方案
我来结合MediaFoundation和D3D11的实际开发经验,给你拆解这些问题:
一、是否必须将MFVideoFormat_RGB32转换为MFVideoFormat_NV12?
结论:从兼容性和稳定性角度,强烈建议转换为NV12
你当前的方案在测试机器上能跑,是因为这些机器的MediaFoundation环境(系统版本、硬件编码器)恰好支持RGB32输入到H264编码器,但这不是通用情况:
- 部分老硬件(比如Win7时代的低端显卡)的硬件H264编码器可能只接受YUV类格式(尤其是NV12),不支持RGB32直接输入。
- 某些精简版Win7/8系统可能缺失了RGB转YUV的系统MFT,导致编码器无法处理RGB32输入,直接返回错误。
- NV12是目前MediaFoundation生态中支持最广泛的视频格式,Intel、AMD、NVIDIA的硬件编码器对它的硬件加速支持都非常完善,是录制场景的最优选择。
二、RGB32转NV12的实现方案
结合你的D3D11场景,推荐两种高效的实现方式:
1. 利用MediaFoundation内置转换MFT(自动/手动)
自动方式(最简单)
你可以保持当前的InitVideoStreamHelper代码不变,但去掉手动注册CColorConvertDMO的逻辑(这个是旧版DMO,效率不如新的MFT)。当SinkWriter发现输入类型(RGB32)和输出类型(H264要求的YUV)不匹配时,会自动尝试加载系统内置的视频转换MFT来完成格式转换。
手动方式(更可控)
如果需要更稳定的转换流程,可以手动创建转换MFT:
// 示例:创建RGB32转NV12的转换MFT HRESULT CreateColorConvertMFT(IMFTransform** ppMFT, UINT32 width, UINT32 height) { HRESULT hr = S_OK; MFT_REGISTER_TYPE_INFO inputType = { MFMediaType_Video, MFVideoFormat_RGB32 }; MFT_REGISTER_TYPE_INFO outputType = { MFMediaType_Video, MFVideoFormat_NV12 }; // 查找支持硬件加速的转换MFT CComPtr<IMFActivate> pActivate; hr = MFTEnumEx(MFT_CATEGORY_VIDEO_PROCESSOR, MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SYNCMFT, &inputType, &outputType, NULL, &pActivate); if (FAILED(hr)) { // fallback到软件MFT hr = MFTEnumEx(MFT_CATEGORY_VIDEO_PROCESSOR, MFT_ENUM_FLAG_SYNCMFT, &inputType, &outputType, NULL, &pActivate); } if (SUCCEEDED(hr)) { hr = pActivate->ActivateObject(__uuidof(IMFTransform), (void**)ppMFT); } return hr; }
之后在MakeVideoFrame中,先把RGB32的媒体帧输入到MFT,获取NV12的输出帧,再传给SinkWriter。
2. D3D11硬件加速转换(性能最优)
因为你的纹理都是D3D11资源,用GPU直接转换效率最高,步骤如下:
- 创建NV12格式的目标纹理:
D3D11_TEXTURE2D_DESC nv12Desc = {}; nv12Desc.Width = uiWidth; nv12Desc.Height = uiHeight; nv12Desc.Format = DXGI_FORMAT_NV12; nv12Desc.ArraySize = 1; nv12Desc.MipLevels = 1; nv12Desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; nv12Desc.Usage = D3D11_USAGE_DEFAULT; nv12Desc.SampleDesc.Count = 1; hr = _d3d_device->CreateTexture2D(&nv12Desc, NULL, &_nv12_texture2d);
- 使用D3D11视频处理器完成转换:
// 初始化视频处理器(只需要做一次) CComPtr<ID3D11VideoDevice> pVideoDevice; hr = _d3d_device->QueryInterface(&pVideoDevice); D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc = {}; contentDesc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; contentDesc.InputWidth = uiWidth; contentDesc.InputHeight = uiHeight; contentDesc.OutputWidth = uiWidth; contentDesc.OutputHeight = uiHeight; contentDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; CComPtr<ID3D11VideoProcessorEnumerator> pEnumerator; hr = pVideoDevice->CreateVideoProcessorEnumerator(&contentDesc, &pEnumerator); CComPtr<ID3D11VideoProcessor> pVideoProcessor; hr = pVideoDevice->CreateVideoProcessor(pEnumerator, 0, &pVideoProcessor); // 转换纹理 CComPtr<ID3D11DeviceContext> pContext; _d3d_device->GetImmediateContext(&pContext); D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputViewDesc = {}; inputViewDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D; inputViewDesc.Texture2D.MipSlice = 0; CComPtr<ID3D11VideoProcessorInputView> pInputView; hr = pVideoDevice->CreateVideoProcessorInputView(_capture_texture2d, pEnumerator, &inputViewDesc, &pInputView); D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputViewDesc = {}; outputViewDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D; outputViewDesc.Texture2D.MipSlice = 0; CComPtr<ID3D11VideoProcessorOutputView> pOutputView; hr = pVideoDevice->CreateVideoProcessorOutputView(_nv12_texture2d, pEnumerator, &outputViewDesc, &pOutputView); // 执行转换 pContext->VideoProcessorBlt(pVideoProcessor, pOutputView, 0, 1, &pInputView, NULL, NULL);
- 将NV12纹理包装成MediaFoundation媒体帧,传给SinkWriter即可(类似你当前
MakeVideoFrame中Win8+的逻辑,用MFCreateDXGISurfaceBuffer)。
三、Win7/8/10系统的内置支持情况
| 系统版本 | 硬件加速转换支持 | 备注 |
|---|---|---|
| Win10 | ✅ 完全支持 | 内置的视频转换MFT和D3D11视频处理器都支持RGB32→NV12的硬件加速,只要显卡支持DXGI 1.2+ |
| Win8/8.1 | ✅ 支持 | D3D11视频处理器支持硬件加速转换,系统内置转换MFT也兼容 |
| Win7 | ⚠️ 有限支持 | 需要满足两个条件: 1. 安装KB2670838补丁,启用D3D11.1支持; 2. 显卡驱动支持DXGI_FORMAT_NV12的硬件转换; 如果不满足,只能使用软件转换(比如CColorConvertDMO) |
代码修改建议
如果你决定转成NV12,需要修改InitVideoStreamHelper中的输入媒体类型:
// 修改输入类型为NV12 hr = pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
然后在MakeVideoFrame中加入上述的转换逻辑,将_capture_texture2d(RGB32)转成NV12纹理,再包装成媒体帧。
内容的提问来源于stack exchange,提问作者fredirty2017
相关产品推荐
相关产品推荐

