【发布时间】:2021-06-14 15:20:19
【问题描述】:
问题陈述:
英特尔硬件 MFT 不支持 GOP 设置,导致实时应用程序消耗更多带宽。相同的代码在 Nvidia 硬件 MFT 上运行良好。
背景:
我正在尝试使用 Windows10 机器上的 MediaFoundation H264 硬件编码器将通过 DesktopDuplication API 捕获的 NV12 样本编码为视频流,并通过 LAN 实时流式传输和渲染。
最初,我在编码器处面临过多的缓冲,因为编码器在提供输出样本之前缓冲多达 25 帧(GOP 大小)。经过一番研究,我发现设置 CODECAPI_AVLowLatencyMode 会降低延迟,但会降低一些质量和带宽的成本。
设置 CODECAPI_AVLowLatencyMode 属性有点提高性能,但达不到实时要求。看起来现在编码器至少在产生样本之前仍然缓冲多达 15 帧(在输出中引入大约 2 秒的延迟)。并且这种行为只有在配置了低帧速率时才明显。在 60FPS 时,输出几乎是实时的,没有视觉上明显的延迟。
事实上,只有当帧速率设置在 30FPS 以下时,人眼才能看到缓冲。并且,延迟与 FPS 配置成反比增加,在 25FPS 时延迟为几百毫秒,当 FPS 配置为 10(恒定速率)时延迟达到 3 秒。我想,将 FPS 设置为超过 30(比如 60FPS)实际上会导致编码器缓冲区快速溢出,从而产生具有不明显延迟的样本。
最近,我也尝试了 CODECAPI_AVEncCommonRealTime 属性 (https://docs.microsoft.com/en-us/windows/win32/directshow/avenccommonrealtime-property) 来检查它在降低输入帧速率以避免带宽消耗时是否提高了性能,但调用失败并显示 “参数不正确”错误。
我的实验:
为了保持恒定的帧速率,并强制编码器产生实时输出,我将相同的样本(之前保存的样本)提供给 编码器以 30FPS/60FPS 的恒定速率。我这样做是通过 最多仅捕获 10FPS(或任何所需的 FPS)并伪造 30/60FPS 通过三次或完全以基于 EMULATED_FRAME_RATE/ACTUAL_FRAME_RATE 比率(例如:30/10、60/15、60/20)以恒定的间隔准确地填补空白。例如,当 10 秒内没有变化时,我会向编码器提供相同的样本 30*10 次(30FPS)。我从一些开源 Github 项目以及 chromium 的实验代码示例中了解到这种方法,我还被告知(Primarily on SO, 以及其他论坛)这是推动编码器实时输出的唯一方法,并且没办法。
上述方法产生近乎实时的输出,但消耗的数据比我预期的要多,即使我只将先前保存的样本提供给编码器。
无论屏幕内容在 30FPS 还是 0FPS(空闲)。在这种情况下,NVidia 硬件编码器似乎要好一些。
事实上,在屏幕空闲期间,编码器每秒产生的数据比上述速率要多得多。我已经能够通过setting a larger GOP size(当前配置的 GOP 大小为 16K)减少 NVidia 设备上的数据消耗。但是,在英特尔显卡 620 硬件上,屏幕空闲时间数据消耗保持在 300KBps 左右,在 NVidia GTX 1070(配置:500KB 比特率和 30FPS)上保持在 50KBps 到 80KBps 左右,这是不可接受的。我猜,英特尔硬件 MFT 根本不遵守 GOP 设置,或者改进不明显。
我还能够通过设置非常低的比特率将英特尔和英伟达硬件上的空闲时间数据消耗分别降低到 ~130KBps 和 ~40KBps,但这仍然是不可接受的,这也会降低视频质量。
当输入样本之间没有发生变化时,有没有办法将编码器配置为产生小于 ~10KBps 的输出?实际上,我的目标是在没有变化时输出 ~0KB,但 ~10KBps 在某种程度上是可以接受的。
更新:
我可以通过以下方式降低 NVidia MFT 上的空闲时间数据消耗 调整一些参数,在 400KB 比特率配置下小于 ~20KBps,在 100KB 比特率配置下低于 ~10KBps 配置。这很有说服力。但是具有相同编码器配置的相同代码在英特尔机器上产生的数据要多 20 到 40 倍。英特尔(英特尔显卡 620)肯定不尊重 GOP 设置。我什至尝试在 256 到 INT_MAX 之间改变 GOP,英特尔硬件 MFT 的输出似乎没有任何变化。
更新 2:
在玩弄了编码器属性之后(我只使用 eAVEncCommonRateControlMode_UnconstrainedVBR 而不是 eAVEncCommonRateControlMode_CBR 配置了 CODECAPI_AVEncCommonRateControlMode),现在我可以看到英特尔 MFT 在屏幕空闲时间产生 3KBps 数据,但仅在前几秒钟(可能大约 3 到8 秒),然后它又回到同一个故事。我猜几秒钟后,编码器失去了对它比较样本的关键帧的引用,并且在那之后似乎没有恢复。无论 GOP 是 16/128/256/512/1024 还是 INT_MAX,行为都是一样的。
编码器配置:
const int EMULATED_FRAME_RATE = 30;//
const int TARGET_FPS = 10;
const int FPS_DENOMINATOR = 1;
const unsigned long long time_between_capture = 1000 / TARGET_FPS;
const unsigned long long nEmulatedWaitTime = 1000 / EMULATED_FRAME_RATE;
const unsigned long long TARGET_AVERAGE_BIT_RATE = 4000000; // Adjusting this affects the quality of the H264 bit stream.
const LONGLONG VIDEO_FRAME_DURATION = 10ll * 1000ll * 1000ll / ((long long)EMULATED_FRAME_RATE); // frame duration in 100ns units
const UINT32 KEY_FRAME_SPACING = 16384;
const UINT32 GOP_SIZE = 16384;
const UINT32 BPICTURECOUNT = 2;
VARIANT var = { 0 };
//no failure on both Nvidia & Intel, but Intel seems to be not behaving as expected
var.vt = VT_UI4;
var.lVal = GOP_SIZE;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncMPVGOPSize, &var), "Failed to set GOP size");
var.vt = VT_BOOL;
var.ulVal = VARIANT_TRUE;
// fails with "parameter incorrect" error.
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRealTime, &var), "Failed to set realtime mode");
var = { 0 };
var.vt = VT_BOOL;
var.ulVal = VARIANT_TRUE;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVLowLatencyMode, &var), "Failed to set low latency mode");
var = { 0 };
var.vt = VT_BOOL;
var.ulVal = VARIANT_TRUE;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonLowLatency, &var), "Failed to set low latency mode");
var = { 0 };
var.vt = VT_UI4;
var.lVal = 2; // setting B-picture count to 0 to avoid latency and buffering at both encoder and decoder
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncMPVDefaultBPictureCount, &var), "Failed to set B-Picture count");
var = { 0 };
var.vt = VT_UI4;
var.lVal = 100; //0 - 100 (100 for best quality, 0 for low delay)
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonQualityVsSpeed, &var), "Failed to set Quality-speed ratio");
var = { 0 };
var.vt = VT_UI4;
var.lVal = 20;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonQuality, &var), "Failed to set picture quality");
var = { 0 };
var.vt = VT_UI4;
var.lVal = eAVEncCommonRateControlMode_CBR; // This too fails on some hardware
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var), "Failed to set rate control");
var = { 0 };
var.vt = VT_UI4;
var.lVal = 4000000;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var), "Failed to set Adaptive mode");
var = { 0 };
var.vt = VT_UI4;
var.lVal = eAVEncAdaptiveMode_FrameRate;
CHECK_HR(mpCodecAPI->SetValue(&CODECAPI_AVEncAdaptiveMode, &var), "Failed to set Adaptive mode");
我尝试使用以下代码检索 GOP 大小支持的参数范围,但它只返回 E_NOTIMPL 错误。
VARIANT ValueMin = { 0 };
VARIANT ValueMax = { 0 };
VARIANT SteppingDelt = { 0 };
HRESULT hr = S_OK;
if (!mpCodecAPI) {
CHECK_HR(_pTransform->QueryInterface(IID_PPV_ARGS(&mpCodecAPI)), "Failed to get codec api");
}
hr = mpCodecAPI->GetParameterRange(&CODECAPI_AVEncMPVGOPSize, &ValueMin, &ValueMax, &SteppingDelt);
CHECK_HR(hr, "Failed to get GOP range");
VariantClear(&ValueMin);
VariantClear(&ValueMax);
VariantClear(&SteppingDelt);
我错过了什么吗?在没有屏幕内容更改的情况下,我是否可以尝试其他任何属性来获得实时性能,同时消耗尽可能少的带宽?
【问题讨论】:
标签: video-streaming directx h.264 ms-media-foundation