如何在DX12中基于AVFrame创建并初始化DXGI_FORMAT_NV12资源
DXGI_FORMAT_NV12资源配置与YUV420P转NV12流程(DX12视频编码器输入)
核心结论
DXGI_FORMAT_NV12在DX12中是单资源双平面结构,不需要通过多数组或MIP层级实现平面分离,系统会根据格式自动识别两个子资源(Y平面和UV交错平面)。
D3D12_RESOURCE_DESC 配置细节
创建NV12资源时,关键参数设置如下:
D3D12_RESOURCE_DESC desc = {}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Width = 视频宽度; // 对应Y平面的分辨率 desc.Height = 视频高度; // 对应Y平面的分辨率 desc.Format = DXGI_FORMAT_NV12; desc.MipLevels = 1; // 视频编码场景无需MIP层级 desc.ArraySize = 1; // 单帧输入用1,批量编码可设为数组大小 desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; // CPU上传场景用这个,GPU生成可设为UNKNOWN desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_SHADER_RESOURCE | D3D12_RESOURCE_FLAG_ALLOW_VIDEO_ENCODER_INPUT; // 注意:视频编码器输入必须设置ALLOW_VIDEO_ENCODER_INPUT标志
- 重点:
DXGI_FORMAT_NV12本身就定义了双平面布局,子资源0对应Y分量,子资源1对应交错的U/V分量,无需额外配置数组或MIP。
从AV_PIX_FMT_YUV420P转NV12的CPU端处理流程
因为AVFrame的YUV420P是平面存储(Y、U、V各占一个独立缓冲区),转NV12需要手动交错U/V分量:
- 计算内存大小:
- Y平面大小:
width * height - UV平面大小:
(width * height) / 2(每两个像素共用一组U/V,交错存储占2字节)
- Y平面大小:
- 映射DX12资源:
调用ID3D12Resource::Map(0, nullptr, reinterpret_cast<void**>(&pYData))获取Y平面指针;
调用ID3D12Resource::Map(1, nullptr, reinterpret_cast<void**>(&pUVData))获取UV平面指针;
注意:要使用D3D12_RANGE来指定映射范围,避免不必要的内存操作。 - 拷贝Y分量:
按行拷贝AVFrame的data[0]到pYData,注意DX12的RowPitch(资源描述中的Width对齐后的值)可能和AVFrame的linesize[0]不一致,需逐行处理:for (int y = 0; y < height; y++) { memcpy(pYData + y * desc.RowPitch, avFrame->data[0] + y * avFrame->linesize[0], width); } - 交错拷贝U/V分量:
遍历UV平面的每一行(共height/2行),每次取U的一个字节和V的一个字节,依次写入UV缓冲区:int uvHeight = height / 2; int uvWidth = width / 2; for (int y = 0; y < uvHeight; y++) { uint8_t* pU = avFrame->data[1] + y * avFrame->linesize[1]; uint8_t* pV = avFrame->data[2] + y * avFrame->linesize[2]; uint8_t* pDst = pUVData + y * desc.RowPitch; for (int x = 0; x < uvWidth; x++) { pDst[2*x] = pU[x]; pDst[2*x+1] = pV[x]; } } - 解除映射:调用
ID3D12Resource::Unmap(0, nullptr)和Unmap(1, nullptr)。
SRV创建注意事项
你提到的两个SRV配置是对的,分别映射Y和UV分量:
- Y分量SRV:格式设为
DXGI_FORMAT_R8_UNORM,PlaneSlice设为0,将Y值映射到红通道; - UV分量SRV:格式设为
DXGI_FORMAT_R8G8_UNORM,PlaneSlice设为1,将U映射到红通道、V映射到绿通道; - SRV的
ViewDimension必须设为D3D12_SRV_DIMENSION_TEXTURE2D,且MipLevels设为1。
常见误区
- 不要用
ArraySize来区分平面:NV12的两个平面是同一资源的子资源,不是数组元素,ArraySize用于多帧批量处理; - CPU映射时必须对齐RowPitch:DX12的资源行宽会按硬件要求对齐,不能直接用AVFrame的linesize做整内存拷贝;
- 视频编码器输入资源必须设置
D3D12_RESOURCE_FLAG_ALLOW_VIDEO_ENCODER_INPUT,否则编码器无法识别。
内容的提问来源于stack exchange,提问作者mike
相关产品推荐
相关产品推荐

