Windows Media Foundation 使用 IMFTransform 将 mp4 电影帧解码为 2D 纹理
Posted
技术标签:
【中文标题】Windows Media Foundation 使用 IMFTransform 将 mp4 电影帧解码为 2D 纹理【英文标题】:Windows Media Foundation using IMFTransform to decode mp4 movie frames to 2D textures 【发布时间】:2016-05-26 12:51:41 【问题描述】:我正在尝试使用 Windows Media Foundation 类解码 mp4 视频,并将帧转换为 2D 纹理,供 DirectX 着色器用于渲染。我已经能够使用 MFCreateSourceReaderfromURL 读取源流,并且能够读取其主要类型为 MFMEdiaType_Video 和次要类型为 的流媒体类型MFVideoFormat_H264 符合预期。
我现在需要将此格式转换为可用于初始化 D3D11_TEXTURE2D 资源和资源视图的 RGB 格式,然后可以将其传递给 HLSL 像素着色器进行采样。我已经厌倦了使用 IMFTransform 类为我进行转换,但是当我尝试将转换上的输出类型设置为任何 MFVideoFormat_RGB 变体时,我得到一个错误。我还尝试在源阅读器上设置一个新的输出类型,然后只是采样希望以正确的格式获得样本,但我还是没有运气。
所以我的问题是:
这种类型的转换可行吗?
这可以通过 IMFTransform/SourceReader 类来完成,就像我上面已经厌倦的那样,我只需要调整代码还是需要手动进行这种类型的转换?
这是将视频纹理数据输入着色器进行采样的最佳方法,还是有一个我没有想到的更简单的替代方法。
使用的操作系统是 Windows 7,因此我无法使用 SourceReaderEx 或 ID3D11VideoDevice 接口,因为据我所知,这些解决方案似乎仅在 Windows 8 上可用。
任何正确方向的帮助/指针将不胜感激,如有必要,我还可以提供一些源代码。
【问题讨论】:
【参考方案1】:这种类型的转换可能吗?
是的,这是可能的。 Stock H.264 Video Decoder MFT 是“Direct3D 感知”,这意味着它可以利用 DXVA 将视频解码为 Direct3D 9 表面/Direct3D 11 纹理。或者,如果硬件能力不足,也有软件回退模式。出于性能原因,您有兴趣将输出直接传递到纹理中(否则您必须自己加载这些数据,花费 CPU 和视频资源)。
这可以通过 IMFTransform/SourceReader 类来完成,就像我上面已经厌倦的那样,我只需要调整代码还是需要手动进行这种类型的转换?
IMFTransform
是抽象接口。它由 H.264 解码器(以及其他 MFT)实现,您可以直接使用它,也可以使用更高级别的 Source Reader API 来管理从文件中读取视频并使用此 MFT 进行解码。
也就是说,MFT 和 Source Reader 实际上并不是独占的替代选项,而是更高和更低级别的 API。 MFT 接口由解码器提供,您负责将 H.264 输入并排出解码输出。 Source Reader 管理相同的 MFT 并添加文件读取功能。
Source Reader 本身在 Windows 7 中可用,顺便说一句(即使在 Vista 上,与较新的操作系统相比,功能集也可能受到限制)。
【讨论】:
感谢您的反馈和为我解决这些问题的欢呼。我猜 SourceReaders 的工作是提供可以输入 MFT 的样本,对吧?尽管来自 SourceReader 的 ReadSample 返回 S_OK 并且 MFT 上的 ProcessInput 返回 S_OK,但我在转换的 ProcessOutput 阶段遇到问题 如果您提供相应的源阅读器配置,基本上您可以将解码 MFT 嵌入到源阅读器中。在这种情况下,MFT 由源阅读器管理,您可以获得已解码的样本。否则,您可能希望更喜欢原始样本,然后您自己使用 MFT 执行 ProcessInput、ProcessOutput 和其余调用。在前一种情况下,解决问题当然更容易,因为您看到阅读原始样本是可以的。【参考方案2】:我发现您对 Media Foundation 的理解有些错误。您想从 MFVideoFormat_H264 获取 RGB 格式的图像,但您不使用解码器 H264。你写了“我已经厌倦了使用 IMFTransform 类” - IMFTransform 不是类。它是转换 COM 对象的接口。您必须创建 COM 对象 Media Foundation H264 解码器。 Microsoft 软件 H264 解码器的 CLSID 是 CLSID_CMSH264DecoderMFT。但是,您可以从该解码器获得以下格式的输出图像: 输出类型
MFVideoFormat_I420
MFVideoFormat_IYUV
MFVideoFormat_NV12
MFVideoFormat_YUY2
MFVideoFormat_YV12
您可以从其中之一创建 D3D11_TEXTURE2D。或者你可以从我的项目 CaptureManager SDK 中做这样的事情:
CComPtrCustom<IMFTransform> lColorConvert;
if (!Result(lColorConvert.CoCreateInstance(__uuidof(CColorConvertDMO))))
lresult = MediaFoundationManager::setInputType(
lColorConvert,
0,
lVideoMediaType,
0);
if (lresult)
break;
DWORD lTypeIndex = 0;
while (!lresult)
CComPtrCustom<IMFMediaType> lOutputType;
lresult = lColorConvert->GetOutputAvailableType(0, lTypeIndex++, &lOutputType);
if (!lresult)
lresult = MediaFoundationManager::getGUID(
lOutputType,
MF_MT_SUBTYPE,
lSubType);
if (lresult)
break;
if (lSubType == MFVideoFormat_RGB32)
LONG lstride = 0;
MediaFoundationManager::getStrideForBitmapInfoHeader(
lSubType,
lWidth,
lstride);
if (lstride < 0)
lstride = -lstride;
lBitRate = (lHight * (UINT32)lstride * 8 * lNumerator) / lDenominator;
lresult = MediaFoundationManager::setUINT32(
lOutputType,
MF_MT_AVG_BITRATE,
lBitRate);
if (lresult)
break;
PROPVARIANT lVarItem;
lresult = MediaFoundationManager::getItem(
*aPtrPtrInputMediaType,
MF_MT_FRAME_RATE,
lVarItem);
if (lresult)
break;
lresult = MediaFoundationManager::setItem(
lOutputType,
MF_MT_FRAME_RATE,
lVarItem);
if (lresult)
break;
(*aPtrPtrInputMediaType)->Release();
*aPtrPtrInputMediaType = lOutputType.detach();
break;
您可以设置 ColorConvertDMO 将 H264 解码器的输出格式转换为您需要的格式。
另外,您可以通过链接查看代码:videoInput。此代码从网络摄像头获取实时视频并将其解码为 RGB。如果您在 mp4 视频文件源上替换网络摄像头源,您将获得接近您需要的解决方案。
问候
【讨论】:
太好了,非常感谢您的反馈,我非常感谢。我已经完成了第一步,并通过 CoCreateInstance 获得了 Media Foundation H264 解码器,并且我已经按照您的建议设置了有效的输出格式。我现在遇到的问题是 ProcessOutput 总是返回 MF_E_TRANSFORM_NEED_MORE_INPUT。我一直调用 ProcessInput 直到它返回 MF_E_NOTACCEPTING ,从我读到的意思是输出阶段已经准备好,所以我可以调用 ProcessOutput?但仍然没有运气。对此有何建议?我也会看看你提供的那个样本,干杯。 嗨,IMFTransform 的 ProcessOutput 方法是用 MFT_OUTPUT_DATA_BUFFER 结构上的指针调用的。在我的代码中,它看起来像这样: MFT_OUTPUT_DATA_BUFFER loutputDataBuffer; initOutputDataBuffer(lTransform, loutputDataBuffer); DWORD lprocessOutputStatus = 0; lresult = lTransform->ProcessOutput(0, 1, &loutputDataBuffer, &lprocessOutputStatus);您必须定义方法 initOutputDataBuffer。 试试 videoInput。它具有通过 OpenGL 从网络摄像头渲染实时视频的示例。您可以为 DirectX 修改它。通过链接CreateObjectFromURL,您将找到为视频文件创建 MediaSource 的代码。它已声明签名 - CreateMediaSource。在文件 MediaFoundation 的videoInput 中有方法 getSorceBySymbolicLink。两个函数都有相似的签名 - 尝试替换,它将适用于视频文件。 感谢您的持续反馈,关于您提到的 initOutputDataBuffer 函数,我知道用作 ProcessOutput 结果的样本需要在调用 ProcessOutput 之前分配和设置,我猜测这是我的代码失败的地方。我现在的问题是如何知道需要在输出样本上分配的缓冲区大小以及需要设置哪些其他属性?我猜在解码后输入样本可能比原始样本大/小? 嗨,解码后输出样本根据格式有固定大小:RGB24、RGB32、NV1、YVY或其他。有一个函数——MFCalculateImageSize,它接受 SubType、Width、Height,并计算 SizeImage——特定未压缩图像的字节大小。【参考方案3】:可以通过下一段代码执行解码:
MFT_OUTPUT_DATA_BUFFER loutputDataBuffer;
initOutputDataBuffer(
lTransform,
loutputDataBuffer);
DWORD lprocessOutputStatus = 0;
lresult = lTransform->ProcessOutput(
0,
1,
&loutputDataBuffer,
&lprocessOutputStatus);
if ((HRESULT)lresult == E_FAIL)
break;
函数 initOutputDataBuffer 分配所需的内存。那里提供了该功能的示例:
Result initOutputDataBuffer(IMFTransform* aPtrTransform,
MFT_OUTPUT_DATA_BUFFER& aRefOutputBuffer)
Result lresult;
MFT_OUTPUT_STREAM_INFO loutputStreamInfo;
DWORD loutputStreamId = 0;
CComPtrCustom<IMFSample> lOutputSample;
CComPtrCustom<IMFMediaBuffer> lMediaBuffer;
do
if (aPtrTransform == nullptr)
lresult = E_POINTER;
break;
ZeroMemory(&loutputStreamInfo, sizeof(loutputStreamInfo));
ZeroMemory(&aRefOutputBuffer, sizeof(aRefOutputBuffer));
lresult = aPtrTransform->GetOutputStreamInfo(loutputStreamId, &loutputStreamInfo);
if (lresult)
break;
if ((loutputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0 &&
(loutputStreamInfo.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES) == 0)
lresult = MFCreateSample(&lOutputSample);
if (lresult)
break;
lresult = MFCreateMemoryBuffer(loutputStreamInfo.cbSize, &lMediaBuffer);
if (lresult)
break;
lresult = lOutputSample->AddBuffer(lMediaBuffer);
if (lresult)
break;
aRefOutputBuffer.pSample = lOutputSample.Detach();
else
lresult = S_OK;
aRefOutputBuffer.dwStreamID = loutputStreamId;
while (false);
return lresult;
它需要通过 IMFTransform 的 GetOutputStreamInfo 方法获取输出样本的信息。 MFT_OUTPUT_STREAM_INFO 包含有关输出媒体样本所需内存大小的信息 - cbSize。它需要分配该大小的内存,将其添加到 MediaSample 中并将其附加到 MFT_OUTPUT_DATA_BUFFER。
因此,您会发现,通过直接调用 MediaFoundation 函数来编写编码和解码视频的代码可能很困难,并且需要大量相关知识。从您的任务描述中,我看到您只需要解码视频并呈现它。我可以建议您尝试使用媒体基础会话功能。它由微软的工程师开发,已经包含了使用所需编码器的算法并进行了优化。在videoInput 项目中,媒体基础会话用于为媒体源找到合适的解码器,该解码器是为网络摄像头创建的,并以未压缩格式抓取帧。它已经完成了所需的处理。您只需将来自网络摄像头的媒体源替换为来自视频文件的媒体源。它可以比直接调用 IMFTransform 进行解码更容易编写代码,并允许简化许多问题(例如 - 稳定帧速率。如果代码在解码后立即渲染图像然后解码新帧,那么它可以渲染 1 分钟的视频几秒钟内的剪辑,或者如果视频和其他内容的渲染可能需要超过一帧的持续时间,视频可以以“慢动作”样式呈现,并且 1 分钟视频剪辑的渲染可能需要 2、3 或 5 分钟。 我不知道您需要对哪个项目进行视频解码,但是您应该有充分的理由使用直接调用媒体基础函数和接口的代码。
问候。
【讨论】:
以上是关于Windows Media Foundation 使用 IMFTransform 将 mp4 电影帧解码为 2D 纹理的主要内容,如果未能解决你的问题,请参考以下文章
Windows Media Foundation:获取 AAC 解码数据
Windows Media Foundation MFCreateSourceReaderFromURL 函数中的内存泄漏。任何替代功能?
在 Windows 10 上为 Qt 构建 WMF(Windows Media Foundation)媒体驱动程序插件
Microsoft Windows Server 2008 R2 中的 Microsoft Media Foundation
Windows 7 CoreAudio Media Foundation - IID_IAudioStreamVolume 的 uuidof