問題ステートメント:
インテルのハードウェア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)でのみキャプチャし、同じサンプルを3回またはEMULATED_FRAME_RATE/ACTUAL_FRAME_RATE比に基づいたレートで正確に供給して30/60FPSを偽造することでこれを行っています(例:30/10、60/15 、60/20)一定の間隔でギャップを正確に埋めます。たとえば、10秒間変化がない場合、同じサンプルを30 * 10回(30FPS)エンコーダーに供給しました。このアプローチについて、いくつかのオープンソースGithubプロジェクトから、またchromiumの実験的なコードサンプルからも学びました。これは、エンコーダーをプッシュする唯一の方法であることも通知されました( 主にSO および他のフォーラムでも)。リアルタイム出力のため、それを回避する方法はありません。
上記のアプローチはほぼリアルタイムの出力を生成しますが、以前に保存したサンプルのみをエンコーダーに供給している場合でも、予想よりも多くのデータを消費します。
出力ビットレートは、Intel MFTでは常に350KBpsから500KBpsの範囲にとどまり、NVidia MFT(30FPSおよび500KBビットレート構成)では80KBpsから400KBpsの間で変化します。画面のコンテンツが30FPSで変化するか0FPS(アイドル)で変化するかは関係ありません。この場合、NVidiaハードウェアエンコーダの方がいくらか優れているようです。
実際、画面のアイドル時間中に、エンコーダーは前述の速度よりも毎秒多くのデータを生成していました。 より大きなGOPサイズの設定 (現在のGOPサイズは16Kです)を使用して、NVidiaデバイスのデータ消費を削減できました。ただし、画面のアイドル時のデータ消費量はIntelグラフィックス620ハードウェアでは約300KBps、NVidia GTX 1070(構成:500KBビットレートおよび30FPS)では50KBps〜80KBpsのままですが、これは許容できません。おそらく、インテルのハードウェアMFTはGOP設定をまったく順守していないか、改善が目立たないようです。
また、非常に低いビットレートを設定することで、IntelとNvidiaのハードウェアでアイドル時間のデータ消費をそれぞれ〜130KBpsと〜40KBpsに下げることもできましたが、これも許容できないため、ビデオ品質も低下します。
入力サンプル間で変更が発生しなかった場合に、最大10KBps未満の出力を生成するようにエンコーダーを構成する方法はありますか?変更が発生しない場合、私は実際には〜0KBの出力を目指していますが、〜10KBpsはある程度許容可能です。
更新:
一部のパラメーターを調整して、NVidia MFTのアイドル時間のデータ消費量を〜20KBps未満、400KBビットレート構成以下に下げることができます。 〜10KBps、100KBビットレート構成。これは説得力があります。ただし、同じコードで同じエンコーダ構成を使用すると、Intelマシンでは20〜40倍のデータが生成されます。インテル(インテルグラフィックス620)はGOP設定を順守していません。私はGOPを256からINT_MAXの間で変化させてみましたが、IntelハードウェアMFTの出力では何も変化していないようです。
更新2:
エンコーダーのプロパティを試してみた後(eAVEncCommonRateControlMode_CBRではなくeAVEncCommonRateControlMode_UnconstrainedVBRでCODECAPI_AVEncCommonRateControlModeを構成しただけです)、画面のアイドル時にインテル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がトラブルメーカーであると疑っています。これがそのデバイス/モニターでのみ発生する理由はまだわかりません。さらに実験する必要があります。
注:このモニター/ 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;
}
}
}
}