通过 ICodecAPI 为 H.264 IMFSinkWriter 编码器设置属性

Posted

技术标签:

【中文标题】通过 ICodecAPI 为 H.264 IMFSinkWriter 编码器设置属性【英文标题】:Set attributes via ICodecAPI for a H.264 IMFSinkWriter Encoder 【发布时间】:2019-06-03 16:50:49 【问题描述】:

我正在尝试通过检索 ICodecAPI 接口来调整通过 ActivateObject() 创建的 H.264 编码器的属性。 虽然我没有收到错误,但我的设置没有被考虑在内。

代码在 Windows 10 下运行。

我复制了用于创建 IMFSinkWriter 的代码并检索下面的 ICodecAPI。未显示错误处理,但不会产生错误。

我已阅读此thread,这意味着调整 IMFSinkWriter 使用的编码器可能是不可能的,但由于 MSDN 文档上没有任何声明,我想知道是否有人设法将 ICodecAPI 与 IMFSinkWritter 一起使用。

如果不可能,应该怎么做?我需要在 H.264 中编码并流式传输到 MP4。我想更改 GOP、Qp、CABAC 等,这似乎无法通过输出媒体类型进行。 我应该能够单独创建编码器并将其连接到 MP4 文件编写器吗?任何关于如何做到这一点的指针表示赞赏......

hr = encoderToOpen.activate->ActivateObject(__uuidof(IMFTransform), (LPVOID *)&encoderTransform);
hr = encoderTransform->GetAttributes(&attributes);
hr = attributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE);
hr = MFCreateSinkWriterFromURL(fileName, NULL, attributes, &encoder);

// Initialise outputMediaType (code not shown)

hr = encoder->AddStream(outputMediaType, &streamIndex);
hr = encoder->SetInputMediaType(streamIndex, sourceMediaType, NULL);

// Retrieve the ICodecAPI
ICodecAPI *codecApi;
streamIndex = 0;
hr = encoder->GetServiceForStream(streamIndex, GUID_NULL, __uuidof(ICodecAPI), (LPVOID*)&codecApi);

VARIANT v;
hr = codecApi->GetValue(&CODECAPI_AVEncCommonQuality, &v);
v.vt = VT_UI4;
v.ulVal = 8;
hr = codecApi->SetValue(&CODECAPI_AVEncMPVGOPSize, &v);

// Start encoding (code not shown)

【问题讨论】:

C++ 显然只是次要相关。我相信代码是用 c++ 而不是 c# 或 Visual Basic 编写的,这只是偶然。您可以考虑通过删除 c++ 标签来向更广泛的受众公开您的问题,并在问题中说明显示的实际代码是使用 c++ 编写的。 另外,您的代码中没有单一的错误检查。您可能想使用hr 结果来测试您的调用是否成功。 类似问题Change h.264 quality when using SinkWriter to encode video ? Re-C++ - 好的,谢谢。为清楚起见省略了错误检查,但在我的应用程序中没有生成错误。 我也面临同样的问题。你能解决这个问题吗?如果是,请您指导我查看样本或一些文档吗? 【参考方案1】:

Media Foundation 的 Sink Writer 是一个简化的 API,其中省略了编码器配置问题。这里的基本问题是您不拥有编码器 MFT 并且您正在通过编写者的头访问它,然后在设置完所有内容后编码器围绕更改设置的行为取决于实现,在编码器的情况下是供应商特定的实现并且可能因硬件而异。

您更可靠的选择是直接管理编码 MFT 并为 Sink Writer 提供已编码的视频。

让事情变得更轻松的潜在技巧是检索编码器的IMFTransform 并清除,然后在完成ICodecAPI 更新后设置回输入/输出媒体类型。轻推媒体类型,您建议编码器重新配置内部结构,并且已经对您进行了微调。请注意,一般来说,这可能会有一些副作用。

“技巧”似乎适用于某些 ICodecAPI 参数(例如 CODECAPI_AVEncCommonQualityVsSpeed),并且仅适用于 Microsoft 的 h.264 编码器。对 CODECAPI_AVEncH264CABACEnable 没有影响。该文档确实似乎专门针对 Microsoft 的编码器,而不是通用 API。我正在使用 QuickSync 和 NVidia 编解码器,如果我自己创建 MFT,你知道它们是否可以通过 ICodecAPI 进行配置?

供应商提供的编码器符合 Certified Hardware Encoder 要求,因此它们必须支持 MSDN 文章中提到的 ICodecAPI 值。重要的是没有定义配置调用的顺序是什么。如果您自己管理编码器,您将在设置媒体类型之前进行ICodecAPI 设置。在 Sink Writer 场景中,它已经配置了媒体类型,然后您可以进行微调。因此,我的技巧建议包括重置现有媒体类型的部分。因为这个技巧对实现细节很敏感,我建议获取当前的媒体类型,然后在 MFT 上清除它们,做ICodecAPI 的事情并取回类型。我认为这应该适用于更多的场景,而不仅仅是 MS 编码器。然而,它仍然是一个不可靠的 hack。

IMO Nvidia 的编码器实现很糟糕(在供应商中最差),英特尔的更好,但它仍然有自己的问题。同样,IMO 提供 MFT 只是为了满足硬件视频编码的最低认证要求,因此它们的实施并没有很好地对齐。各种软件包更喜欢通过供应商 SDK 而不是 Media Foundation Transform 接口来实现视频编码。在其中一个项目中,我也跳过了利用 MFT 进行编码的想法,并在供应商 SDK 之上实现了我自己的 MFT。

这篇文章中的类工厂方法是否适用于IMFSinkWriter?这样可以避免编写太多代码...

我想是的,这应该可以工作,即使我觉得以这种方式修补它不是一件令人愉快的工作。此外,您可能需要考虑对硬件编码器的支持,因为 Sink Writer 在某些情况下也倾向于使用硬件辅助编码,包括给定 DXGI 设备的场景。

另一种类似的 hack 方法是在 Sink Writer 初始化范围内重新定义供应商特定的编码器 CLSID,它与此类似但可能侵入性较小(尽管在其实现中您必须对内部有更好的了解)。只有三个编码器(AMD、Intel、Nvidia;好吧,还有第四个来自上海兆芯半导体,但它并不是很受欢迎),并且它们的 CLSID 是已知的。如果您以聪明的方式CoRegisterClassObject,您可以挂钩 MFT 实例化,让 Media Foundation 决定选择哪个编码器。不过,这只是另一个想法,所以它可能取决于其他因素的最佳做法。

【讨论】:

“技巧”似乎适用于一些的 ICodecAPI 参数(例如 CODECAPI_AVEncCommonQualityVsSpeed)并且仅适用于 Microsoft 的 h.264 编码器。对 CODECAPI_AVEncH264CABACEnable 没有影响。 The doc 确实似乎是专门针对微软的编码器而不是通用 API。我正在使用 QuickSync 和 NVidia 编解码器,如果我自己创建 MFT,你知道它们是否可以通过 ICodecAPI 进行配置? post 中的类工厂方法是否可以与 IMFSinkWritter 一起使用?这样可以避免编写太多代码...【参考方案2】:

它不完全是来自客户端的ICodecAPI,但CODECAPI_AVEncMPVGOPSizeCODECAPI_AVEncCommonQuality 可以使用IMFSinkWriter->SetInputMediaType(/* ... */,, IMFAttributes pEncodingParameters) 传递给h.264 编码器。我怀疑其他 CODECAPI_ 值也会起作用。

CComPtr<IMFAttributes> pEncAttrs;
ATLENSURE_SUCCEEDED(MFCreateAttributes(&pEncAttrs, 1));
ATLENSURE_SUCCEEDED(pEncAttrs->SetUINT32(CODECAPI_AVEncCommonRateControlMode, eAVEncCommonRateControlMode_Quality));
ATLENSURE_SUCCEEDED(pEncAttrs->SetUINT32(CODECAPI_AVEncCommonQuality, 40));
ATLENSURE_SUCCEEDED(pEncAttrs->SetUINT32(CODECAPI_AVEncMPVGOPSize, 4));
ATLENSURE_SUCCEEDED(writer->SetInputMediaType(sink_stream, mtSource, pEncAttrs));
//                                                                   ^^^^^^^^^

可以验证 GOP 设置已在MP4Box.js / ISOBMFF Box Structure Viewer 上查看生效。可以观察到质量会影响文件大小。

【讨论】:

以上是关于通过 ICodecAPI 为 H.264 IMFSinkWriter 编码器设置属性的主要内容,如果未能解决你的问题,请参考以下文章

将 RGB 编码为 H.264

使用 FFmpeg 通过 RTMP 发送 H.264 编码流

确定 H.264 帧的持续时间

使用 libavformat 通过 RTP 流式传输 H.264

如何在 WMP 上通过 RTSP 运行 H.264?

使用 FFmpeg 编码 H.264 CBR 视频