EVR与Sink Writer对屏幕捕获图像垂直翻转的不一致问题
问题原因
EVR 和 Sink Writer 对 RGB32 格式视频的像素行扫描方向默认处理逻辑不一致:
- EVR 渲染时直接遵循缓冲区像素行的存储顺序(自上而下,屏幕顶部对应缓冲区第一行),和屏幕捕获的原始数据逻辑匹配,所以未反转时显示正常。
- Sink Writer 编码视频时默认遵循传统视频文件的存储规范(自下而上,屏幕顶部对应缓冲区最后一行),会自动将自上而下的缓冲区数据翻转,导致录制视频垂直颠倒。
手动反转图像后,缓冲区数据变为自下而上,Sink Writer 处理逻辑匹配但 EVR 渲染时就会显示翻转。
解决方案
不需要手动修改图像像素数据,通过明确指定媒体类型的扫描方向属性,让两个组件按统一逻辑处理即可,有两种可行方案:
方案一:给两个接收器分别设置适配的媒体类型
- 完善基础媒体类型定义,添加
MF_MT_DEFAULT_STRIDE属性明确扫描方向:
check = MFExtern.MFCreateMediaType(out var mediaType); check = mediaType.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video); check = mediaType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.RGB32); check = mediaType.SetUINT32(MFAttributesClsid.MF_MT_INTERLACE_MODE, (uint)MFVideoInterlaceMode.Progressive); check = mediaType.SetSize(MFAttributesClsid.MF_MT_FRAME_SIZE, (uint)mode.Width, (uint)mode.Height); check = mediaType.SetRatio(MFAttributesClsid.MF_MT_FRAME_RATE, FrameRateNumerator, FrameRateDenominator); check = mediaType.SetRatio(MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO, 1, 1); check = mediaType.SetBoolean(MFAttributesClsid.MF_MT_ALL_SAMPLES_INDEPENDENT, true); // RGB32每个像素占4字节,正步长表示自上而下的扫描顺序 int stride = mode.Width * 4; check = mediaType.SetINT32(MFAttributesClsid.MF_MT_DEFAULT_STRIDE, stride);
- 为 Sink Writer 单独复制并修改媒体类型:
// 复制基础媒体类型,用于Sink Writer check = MFExtern.MFCreateMediaType(out var sinkWriterMediaType); check = mediaType.CopyAllItems(sinkWriterMediaType); // 设置负步长,告诉Sink Writer缓冲区是自上而下的,需要转换为视频规范的自下而上顺序 check = sinkWriterMediaType.SetINT32(MFAttributesClsid.MF_MT_DEFAULT_STRIDE, -stride); // 使用这个修改后的媒体类型初始化Sink Writer
此时 EVR 用正步长媒体类型正常渲染,Sink Writer 用负步长媒体类型自动适配视频存储规范,录制结果正常。
方案二:统一缓冲区数据顺序
如果不想维护两份媒体类型,可以在将 DirectX9 缓冲区数据复制到IMFSample时,主动翻转像素行顺序,再配合统一的媒体类型设置:
// 假设dxBuffer是DirectX9捕获的缓冲区,sampleBuffer是IMFSample的缓冲区指针 int rowSize = mode.Width * 4; byte[] dxData = new byte[mode.Height * rowSize]; // 从DX9缓冲区读取原始数据 dxBuffer.Read(dxData, 0, dxData.Length); // 翻转像素行顺序 byte[] flippedData = new byte[dxData.Length]; for (int y = 0; y < mode.Height; y++) { int srcRowIndex = (mode.Height - 1 - y) * rowSize; int destRowIndex = y * rowSize; Buffer.BlockCopy(dxData, srcRowIndex, flippedData, destRowIndex, rowSize); } // 将翻转后的数据写入IMFSample缓冲区 sampleBuffer.Write(flippedData, 0, flippedData.Length); // 媒体类型设置负步长,匹配翻转后的数据顺序 check = mediaType.SetINT32(MFAttributesClsid.MF_MT_DEFAULT_STRIDE, -rowSize);
这样处理后,EVR 和 Sink Writer 都会基于统一的数据和媒体类型,正确完成渲染与录制。
内容的提问来源于stack exchange,提问作者Javid
相关产品推荐
相关产品推荐

