英特尔 H264 硬件 MFT 不支持 GOP 设置

GOP setting is not honored by Intel H264 hardware MFT

问题陈述:

英特尔硬件 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) 以及检查它是否在降低输入帧速率时提高性能以避免带宽消耗,但该调用失败"parameter incorrect" 错误。

我的实验:

To maintain a constant frame rate, and also to force the encoder to produce real-time outputs, I'm feeding the same sample (previously saved sample) to the encoder at a constant rate of 30FPS/60FPS. I'm doing this by capturing only at most 10FPS (or at any required FPS) and faking 30/60FPS by feeding the same sample thrice or exactly at a rate based on EMULATED_FRAME_RATE/ACTUAL_FRAME_RATE ratio (Ex: 30/10, 60/15, 60/20) to fill the gap exactly at constant intervals. For example, when no change happens for 10 seconds, I would have fed the encoder with the same sample 30*10 times (30FPS). I learned about this approach from some opensource Github projects, also from chromium's experimental code samples, I was also informed ( and also on other forums) that this is the only way to push the encoder for real-time output, and there is no way around it.

上述方法产生近乎实时的输出,但消耗的数据比我预期的要多,即使我只将之前保存的样本提供给编码器。

在 Intel MFT 上输出比特率似乎始终保持在 350KBps 到 500KBps 之间,在 NVidia MFT 上(比特率配置为 30FPS 和 500KB)在 80KBps 到 400KBps 之间变化,无论屏幕内容以 30FPS 还是 0FPS 变化(虚度)。在这种情况下,NVidia 硬件编码器似乎要好一些。

事实上,在屏幕空闲期间,编码器每秒产生的数据比上述速率多得多。我已经能够通过 减少 NVidia 设备上的数据消耗(当前配置的 GOP 大小为 16K)。但是,屏幕空闲时间数据消耗在 Intel Graphics 620 硬件上保持在 300KBps 左右,在 NVidia GTX 1070(配置:500KB 比特率和 30FPS)上保持在 50KBps 到 80KBps,这是不可接受的。我猜,英特尔硬件 MFT 根本不遵守 GOP 设置,或者改进不明显。

通过设置非常低的比特率,我还能够将 Intel 和 Nvidia 硬件上的空闲时间数据消耗分别降低到 ~130KBps 和 ~40KBps,但这仍然是不可接受的,这也会降低视频质量。

有没有一种方法可以将编码器配置为在输入样本之间没有发生变化时产生小于 ~10KBps 的输出?实际上,我的目标是在没有发生变化时输出 ~0KB,但 ~10KBps 在某种程度上是可以接受的。

更新:

我能够通过以下方式降低 NVidia MFT 上的空闲时间数据消耗 调整一些参数,在 400KB 比特率配置下小于 ~20KBps,在 100KB 比特率下小于 ~10KBps 配置。这是有说服力的。但具有相同编码器配置的相同代码在英特尔机器上产生的数据要多 20 到 40 倍。 Intel(Intel 显卡 620)肯定不支持 GOP 设置。我什至尝试将 GOP 在 256 到 INT_MAX 之间变化,英特尔硬件 MFT 的输出似乎没有任何变化。

更新二:

玩转编码器属性后(我只将 CODECAPI_AVEncCommonRateControlMode 配置为 eAVEncCommonRateControlMode_UnconstrainedVBR 而不是 eAVEncCommonRateControlMode_CBR),现在我可以看到 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 大小的支持参数范围,但只是 returns 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 是麻烦制造者。我不确定为什么这种情况只发生在 device/monitor 上,还得再做一些实验。

注意:我没有将此标记为答案,因为我还没有找出问题发生的原因 monitor/d3ddevice.仅将此发布作为其他可能遇到类似情况的人的参考。一旦我能够找到该特定 d3d11 设备实例上出现奇怪行为的原因,我将更新答案。

这就是我创建 d3d 设备的方式,并将其重复用于桌面复制图像捕获器、用于颜色转换的视频处理器以及通过 MFT_MESSAGE_SET_D3D_MANAGER[=24 进行的硬件转换=] 属性.

选项:

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);

创建 d3d 设备:

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;
            }
        }
    }
}