问题陈述:
英特尔硬件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)来检查它在降低输入帧频以避免带宽消耗时是否提高了性能,但是该调用失败,并出现“ parametercorrect”错误。
我的实验:
为了保持恒定的帧速率,并迫使编码器产生实时输出,我将相同的样本(以前保存的样本)馈送到
编码器以30FPS / 60FPS的恒定速率传输。我正在这样做
最多只能捕获10FPS(或任何所需的FPS)并伪造30 / 60FPS
通过三次或完全按照
以EMULATED_FRAME_RATE / ACTUAL_FRAME_RATE的比率(例如:30 / 10、60 / 15、60 / 20)精确地以恒定间隔填充空白。例如,当10秒钟没有变化时,我将给编码器提供30 * 10倍(30FPS)的相同样本。我从一些开源Github项目中了解了这种方法,也从Chrome的实验代码示例中学到了这种方法,还被告知(Primarily on SO,以及在其他论坛上),这是推动编码器实现实时输出的唯一方法,因此没办法解决。
上述方法产生近乎实时的输出,但消耗的数据却比我预期的要多,即使我仅将先前保存的样本馈送到编码器。
无论屏幕内容在30FPS还是0FPS(空闲)下变化,Intel MFT上的输出比特率似乎始终保持在350KBps至500KBps之间,而NVidia MFT(具有30FPS和500KB比特率配置)上的输出比特率则介于80KBps至400KBps之间。在这种情况下,NVidia硬件编码器似乎要好一些。
实际上,在屏幕空闲期间,编码器每秒产生的数据量比上述速率要多。我已经能够通过setting a larger GOP size减少NVidia设备上的数据消耗(当前配置的GOP大小为16K)。但是,在英特尔图形620硬件上,屏幕空闲时间数据消耗保持在300KBps左右,而在NVidia GTX 1070(配置:500KB比特率和30FPS)上,屏幕空闲时间数据消耗保持在50KBps至80KBps。我猜想,英特尔硬件MFT根本不遵守GOP设置,或者改进并不明显。
通过设置非常低的比特率,我还能够分别将Intel和Nvidia硬件上的空闲时间数据消耗降低到〜130KBps和〜40KBps,但这仍然是不可接受的,这也会使视频质量下降。
当输入采样之间没有变化时,是否可以将编码器配置为产生小于约10KBps的输出?当没有任何变化但〜10KBps还是可以接受的时候,我实际上的目标是〜0KB输出。
更新:
我可以通过以下方式减少NVidia MFT上的空闲时间数据消耗
调整一些参数,以400KB比特率配置将其调整为〜20KBps以下,以100KB比特率将其调整为〜10KBps以下
组态。这令人信服。但是,具有相同编码器配置的相同代码在Intel机器上产生的数据量将增加20至40倍。英特尔(英特尔图形620)肯定不遵守GOP设置。我什至尝试将GOP从256更改为INT_MAX,英特尔硬件MFT的输出似乎没有变化。
更新2:
在试用了编码器属性之后(我仅使用eAVEncCommonRateControlMode_UnconstrainedVBR而不是eAVEncCommonRateControlMode_CBR配置了CODECAPI_AVEncCommonRateControlMode),现在我可以看到Intel MFT在屏幕空闲时间内产生了3KBps的数据,但是仅在最初的几秒钟(大约3至8秒)内产生了数据。 ,然后回到同一故事。我猜想在几秒钟后,编码器将丢失对关键帧的参考,无法将其与样本进行比较,并且在那一点之后似乎无法恢复。无论GOP是16/128/256/512/1024还是INT_MAX,其行为都是相同的。
编码器配置:
参考:http://alax.info/blog/1586
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);
我想念什么吗?在没有屏幕内容更改的情况下,我还可以尝试使用其他任何属性来获得实时性能,同时消耗尽可能少的带宽吗?
最佳答案
发生了一些奇迹。在同时使用编码器配置的同时,我不小心将主显示器更改为机器上的另一台显示器,现在问题已解决。切换回先前选择的主监视器会导致相同的问题。我怀疑d3ddevice会造成麻烦。我不确定为什么仅在该设备/显示器上会发生这种情况,因此必须进行更多实验。
注意:由于我尚未找出仅在该monitor / d3d设备上发生问题的原因,因此我未将其标记为答案。只是将其发布为可能遇到类似情况的其他人的参考。一旦能够找到特定d3d11device实例上异常行为的原因,我将更新答案。
这就是我创建d3ddevice的方式,并将其重复用于桌面复制图像捕获器,视频处理器以进行颜色转换以及通过MFT_MESSAGE_SET_D3D_MANAGER属性进行硬件转换。
选项:
const D3D_DRIVER_TYPE m_DriverTypes[] = {
//Hardware based Rasterizer
D3D_DRIVER_TYPE_HARDWARE,
//High performance Software Rasterizer
D3D_DRIVER_TYPE_WARP,
//Software Rasterizer (Low performance but more accurate)
D3D_DRIVER_TYPE_REFERENCE,
//TODO: Explore other driver types
};
const D3D_FEATURE_LEVEL m_FeatureLevel[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
//TODO: Explore other features levels as well
};
int m_DriversCount = ARRAYSIZE(m_DriverTypes);
int m_FeatureLevelsCount = ARRAYSIZE(m_FeatureLevel);
创建d3ddevice:
DWORD errorCode = ERROR_SUCCESS;
if (m_FnD3D11CreateDevice == NULL)
{
errorCode = loadD3D11FunctionsFromDll();
}
if (m_Id3d11Device)
{
m_Id3d11Device = NULL;
m_Id3d11DeviceContext = NULL;
}
UINT uiD3D11CreateFlag = (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
if (errorCode == ERROR_SUCCESS)
{
if (m_FnD3D11CreateDevice) {
for (UINT driverTypeIndex = 0; driverTypeIndex < m_DriversCount; ++driverTypeIndex)
{
m_LastErrorCode = D3D11CreateDevice(nullptr, m_DriverTypes[driverTypeIndex], nullptr, uiD3D11CreateFlag,
m_FeatureLevel, m_FeatureLevelsCount, D3D11_SDK_VERSION, &m_Id3d11Device, &m_SelectedFeatureLevel, &m_Id3d11DeviceContext);
if (SUCCEEDED(m_LastErrorCode))
{
break;
}
}
}
}
关于video-streaming - 英特尔H264硬件MFT不支持GOP设置,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59051443/