与Nvidia相比,Intel H264硬件MFT性能较差

Ram*_*san 7 c++ windows directx video-streaming ms-media-foundation

我正在尝试使用Windows10机器上的MediaFoundation H264硬件编码器将NV12样本编码为视频,并在LAN内实时传输和呈现它们。

最初,我在编码器处面临太多缓冲,因为编码器在提供输出样本之前最多缓冲25帧(GOP大小)。经过一些研究,我发现设置CODECAPI_AVLowLatencyMode可以减少延迟,但要花一些质量和带宽。

设置CODECAPI_AVLowLatencyMode属性有点改善了性能,但没有达到实时要求。现在看来,编码器至少在生成样本之前仍至少缓冲15帧(在输出中引入大约2秒钟的延迟)。并且仅当配置了低帧速率时,此行为才明显。在60FPS时,输出几乎是实时的,没有视觉上明显的延迟。

实际上,只有当帧频设置为低于30FPS时,人眼才能看到缓冲。并且,延迟与FPS配置成反比增加,在25FPS时,延迟在几百毫秒内,而当FPS配置为10(恒定速率)时,延迟增加到3秒。我猜想,将FPS设置为大于30(说60FPS)实际上会导致编码器缓冲区足够快地溢出,以产生明显延迟的采样。

但是问题是,如果需要,我希望FPS可以配置为低至10FPS(恒定速率),同时由于我的输入源是台式机并且仅在有台式机的情况下才产生样本,因此还具有实时流式传输的经验。屏幕内容有变化吗?

我的实验:

为了保持恒定的帧速率,并强制编码器产生实时输出,我以30FPS / 60FPS的恒定速率将相同的样本(以前保存的样本)馈送到编码器。我这样做的方式是,最多只能捕获10FPS(或任何所需的FPS),并通过以相同的三次或完全基于EMULATED_FRAME_RATE / ACTUAL_FRAME_RATE的比率(例如:30 / 10、60 / 15)提供相同的样本来伪造30 / 60FPS (例如60/20)以完全恒定的间隔填充空白。例如,如果10秒钟内没有任何变化,我将给编码器提供30 * 10次(30FPS)的相同样本。这将产生近乎实时的输出,但消耗的数据却比我预期的要多,即使我仅将先前保存的样本馈入编码器。

无论屏幕内容以30FPS还是0F​​PS更改,在Intel上,输出比特率似乎一直保持在350KBps至500KBps之间,而在NVidia GTX 1070(具有30FPS和500KB比特率配置)上,输出比特率一直在80KBps至400KBps之间变化。NVidia编码器似乎要好一些。

实际上,编码器消耗的带宽超过了上述带宽。通过设置更大的GOP大小(当前配置的GOP大小为16K),我已经能够减少NVidia机器上的数据消耗。但是,在具有“ 500KB比特率和30FPS”配置的NVidia GTX 1070上,数据消耗在Intel图形620硬件上大约保持300KBps,在NVidia GTX 1070上从50KBps到80KBps,输入样本之间几秒钟没有发生任何变化(我猜英特尔硬件不是完全遵守GOP设置,否则改进不明显)。

通过设置非常低的比特率,我还能够将Intel和Nvidia硬件上的数据消耗分别降低到100KBps和40KBps(当屏幕内容没有变化时),但这仍然是不可接受的,并且还会降低视频质量。

当样本之间没有变化时,是否可以将编码器配置为产生小于10KBps的输出?当没有变化但10KBps可以接受时,我实际上的目标是0KB输出。

更新: 我做了很多尝试。我想,无论是否发生了变化,NVidia编码器仍会产生某种额外的信息或其他信息,从而导致高数据消耗,但比英特尔好几个数量级。我完全不知道可能会有什么额外的数据。并且还认为Intel编码器根本不遵守GOP设置或不擅长压缩,我什至尝试将GOP在256到INT_MAX之间进行更改,似乎在Intel硬件上没有任何改变。

这是Microsoft网站(https://docs.microsoft.com/zh-cn/windows/win32/medfound/codecapi-avlowlatencymode)上的CODECAPI_AVLowLatencyMode的描述

“低延迟模式对于实时通信或实时捕获很有用,应将延迟最小化。但是,低延迟模式也可能会降低解码或编码质量。

由于编码过程中的帧重新排序,预计编码器不会增加任何采样延迟,并且一个输入采样将产生一个输出采样。只要没有在编码器中引入任何帧重新排序,就可以存在B条/帧。”

但是,这不能提供所描述的实时性能。

最近,我还尝试了CODECAPI_AVEncCommonRealTime属性(https://docs.microsoft.com/en-us/windows/win32/directshow/avenccommonrealtime-property)来检查它在降低输入帧速率以避免带宽消耗时是否提高了性能。 ,但该调用失败,并显示 “参数不正确”错误。

编码器配置:

参考: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");
Run Code Online (Sandbox Code Playgroud)

我想念什么吗?在没有屏幕内容更改的情况下,我还可以尝试使用其他任何属性来获得实时性能,同时消耗尽可能少的带宽吗?

更新2:

通过调整一些参数,我能够将NVidia机器上的空闲时间数据消耗降低到100KB比特率配置时低于10KBps,而400KB比特率配置时低于20KBps。这令人信服。但是,具有相同编码器配置的相同代码在Intel机器上产生的数据量将增加20至40倍。英特尔(英特尔图形620)肯定不遵守GOP设置。

Ram*_*san 2

一些奇迹发生了。在尝试编码器配置时,我不小心将我的主显示器更改为机器上的另一台显示器,现在问题消失了。切换回之前选择的主显示器会导致同样的问题。我怀疑 d3ddevice 是麻烦制造者。我不确定为什么这种情况只发生在该设备/显示器上,必须进行更多实验。

注意:我不会将此标记为答案,因为我还没有找出仅在该监视器/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);
Run Code Online (Sandbox Code Playgroud)

创建 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)