当前位置: 首页 > 知识库问答 >
问题:

Intel H264硬件MFT不支持GOP设置

酆高翰
2023-03-14

我尝试在Windows10机器上使用MediaFoundation H264硬件编码器将通过DesktopDupplication API捕获的NV12样本编码为视频流,并在局域网上实时流和渲染。

最初,我在编码器面临太多的缓冲,因为编码器在提供输出样本之前要缓冲最多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的实验代码示例中了解到,我还被告知(主要是在SO上,也在其他论坛上),这是推动编码器进行实时输出的唯一方法,而且没有办法绕过它。

上面提到的方法产生了接近实时的输出,但是消耗的数据比我预期的要多,即使我只是将先前保存的样本输入到编码器。

我可以通过调整一些参数来降低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,该行为都是相同的。

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

我是不是漏掉了什么?在没有屏幕内容变化的情况下,有没有其他属性可以让我进行实验,以获得实时性能,同时消耗尽可能少的带宽?

共有1个答案

楚方伟
2023-03-14

奇迹发生了。当我还在玩编码器配置的时候,我不小心在我的机器上改变了我的主监视器,现在这个问题消失了。切换回先前选择的主监视器会导致同样的问题。我怀疑D3D设备是麻烦的制造者。我还不确定为什么这种情况只发生在那个设备/监视器上,还需要更多的实验。

注意:我没有将此标记为答案,因为我还没有找到只在监视器/D3D设备上发生问题的原因。只是把这个贴出来作为其他可能遇到类似情况的人的参考。我将更新答案,一旦我能够找到原因的奇怪行为,在特定的d3d11device实例。

这就是我创建D3D设备的方法,并将其用于桌面复制图像捕获器、用于颜色转换的视频处理器以及通过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);
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;
            }
        }
    }
}
 类似资料:
  • Debian 不会超出 Linux 内核与 GNU tool-sets 支持的硬件范围之外。 因此,任何 Linux 内核,libc, gcc,等移植过的体系或平台, 对 Debian 移植过的平台,都可以运行 Debian。请参考移植页面 http://www.debian.org/ports/i386/ 了解更多已被 Debian 测试过的 Intel x86 体系。 本章包含一些通用的信息与

  • Debian 不会超出 Linux 内核与 GNU 工具集所支持的硬件范围之外。 因此,任何被移植了 Linux 内核,libc, gcc等,并拥有对应的 Debian 移植版的硬件体系或平台都可以运行 Debian。请参考移植页面 http://www.debian.org/ports/ia64/ 以了解更多已被 Debian 测试过的 IA-64 体系。 本章仅包含一些通用的信息以及在何处可以

  • Debian 不会超出 Linux 内核与 GNU 工具集所支持的硬件范围之外。 因此,任何被移植了 Linux 内核,libc, gcc等,并拥有对应的 Debian 移植版的硬件体系或平台都可以运行 Debian。请参考移植页面 http://www.debian.org/ports/powerpc/ 以了解更多已被 Debian 测试过的 PowerPC 体系。 本章仅包含一些通用的信息以及

  • 大家:我是FFMPEG的新手。最近我正在做一个项目,把ffmpeg移植到android设备上。在开始的时候,我按照网上的一些指南,以一种非常正常的方式将ffmpeg编译成共享库(.so文件),加载到我的android应用程序中。它工作得很好。 现在我想让ffmpeg使用libstagefright来完成硬件解码。问题是,我可以编译.so库,但当我启动应用程序并加载这些.so库时,总是会出现java

  • GOP

    GOP 是一个专为在 GOPATH 之外的 Go 应用开发的管理工具。当然他肯定不支持 Go Get 了。GOP 会将所有的依赖项拷贝到 src/vendor 目录下,应用本身的源代码也在 src 下。 一个通常的使用过程如下: git clone xxx@mydata.com:bac/aaa.gitcd aaagop ensure -ggop buildgop test 特性 GOPATH 兼容

  • 原则上,我的应用程序不知道屏幕的纵横比(Android应用程序也应该如此)。现在,推荐的做法是手动添加 进入清单,因为智能手机屏幕的宽高比高于默认支持的1.86的趋势。我的问题是,与其设置一个硬编码的比率(如2.1),有没有更好的方法告诉Android,我不在乎最大宽高比是多少? 如果否,是否可以设置任意高的值,例如3.1甚至5.1?这样做安全吗?我说的不是奇怪的屏幕尺寸,而是未来的主流屏幕和未来