Die GOP-Einstellung wird von der Intel H264-Hardware-MFT nicht berücksichtigt

8

Problemstellung:

Intel Hardware MFT berücksichtigt die GOP-Einstellung nicht, was zu einem höheren Bandbreitenverbrauch in Echtzeitanwendungen führt. Der gleiche Code funktioniert gut auf Nvidia-Hardware-MFT.

Hintergrund:

Ich versuche, NV12-Beispiele, die über DesktopDuplication-APIs erfasst wurden, mithilfe des MediaFoundation H264-Hardware-Encoders auf einem Windows10-Computer in einen Videostream zu codieren, zu streamen und in Echtzeit über LAN zu rendern.

Anfangs hatte ich zu viel Pufferung am Encoder, da der Encoder bis zu 25 Frames (GOP-Größe) pufferte, bevor er ein Ausgabe-Sample lieferte. Nach einigen Recherchen habe ich herausgefunden, dass das Festlegen des CODECAPI_AVLowLatencyMode die Latenz mit den Kosten für ein bisschen Qualität und Bandbreite reduzieren würde.

Durch das Festlegen der CODECAPI_AVLowLatencyMode-Eigenschaft wurde die Leistung etwas verbessert, jedoch nicht den Echtzeitanforderungen. Es sieht so aus, als ob der Encoder noch mindestens 15 Frames puffert, bevor er die Samples erzeugt (Einführung einer Verzögerung von ca. 2 Sekunden in der Ausgabe). Und dieses Verhalten macht sich nur bemerkbar, wenn eine niedrige Bildrate konfiguriert ist. Bei 60 FPS erfolgt die Ausgabe fast in Echtzeit ohne visuell wahrnehmbare Verzögerung.

Tatsächlich ist die Pufferung für das menschliche Auge nur dann wahrnehmbar, wenn die Bildrate unter 30 fps eingestellt ist. Und die Verzögerung steigt umgekehrt proportional zur FPS-Konfiguration an. Bei 25 FPS beträgt die Verzögerung einige hundert Millisekunden und beträgt bis zu 3 Sekunden, wenn FPS auf 10 konfiguriert ist (konstante Rate). Ich denke, wenn Sie FPS von mehr als 30 (z. B. 60 FPS) einstellen, läuft der Encoderpuffer schnell genug über, um Samples mit nicht wahrnehmbarer Verzögerung zu erzeugen.

In letzter Zeit habe ich auch versucht, die CODECAPI_AVEncCommonRealTime-Eigenschaft ( https://docs.microsoft.com/en-us/windows/win32/directshow/avenccommonrealtime-property ) zu überprüfen, ob sie die Leistung beim Verringern der Eingabe- Framerate verbessert, um den Bandbreitenverbrauch zu vermeiden , aber dieser Aufruf schlägt mit dem Fehler "Parameter falsch" fehl .

Meine Experimente:

Um eine konstante Bildrate aufrechtzuerhalten und den Encoder zu zwingen, Echtzeitausgaben zu erzeugen, füge ich dem Encoder das gleiche Sample (zuvor gespeichertes Sample) mit einer konstanten Rate von 30 FPS / 60 FPS zu. Ich mache dies, indem ich höchstens 10 FPS (oder eine der erforderlichen FPS) aufzeichne und 30/60 FPS fälsche, indem ich dieselbe Probe dreimal oder genau mit einer Rate füttere, die auf dem Verhältnis EMULATED_FRAME_RATE / ACTUAL_FRAME_RATE basiert (Beispiel: 30/10, 60/15) , 60/20), um die Lücke in konstanten Intervallen genau zu füllen. Wenn zum Beispiel 10 Sekunden lang keine Änderung erfolgt, hätte ich den Encoder 30 * 10 Mal (30 FPS) mit demselben Sample gespeist. Ich habe diesen Ansatz aus einigen OpenSource-Github-Projekten erfahren, auch aus experimentellen Codebeispielen von Chrom. Ich wurde auch darüber informiert ( hauptsächlich über SO, und auch in anderen Foren), dass dies der einzige Weg ist, den Encoder für die Echtzeitausgabe zu drücken, und es gibt keinen Weg daran vorbei.

Der oben erwähnte Ansatz erzeugt eine nahezu Echtzeitausgabe, verbraucht jedoch mehr Daten als erwartet, obwohl ich dem Encoder nur das zuvor gespeicherte Sample zuführe.

Die Ausgangsbitrate scheint bei Intel MFT konstant zwischen 350 KBit / s und 500 KBit / s zu bleiben und variiert bei NVidia MFT (mit 30-Bit / s- und 500-KB-Bitratenkonfiguration) zwischen 80 KBit / s und 400 KBit / s, unabhängig davon, ob sich der Bildschirminhalt bei 30 Bildern pro Sekunde oder 0 Bildern pro Sekunde (Leerlauf) ändert. Der NVidia-Hardware-Encoder scheint in diesem Fall etwas besser zu sein.

Tatsächlich erzeugte der Codierer während der Leerlaufzeit des Bildschirms weit mehr Daten pro Sekunde als die oben erwähnte Rate. Ich konnte den Datenverbrauch auf NVidia-Geräten durch Einstellen einer größeren GOP-Größe senken (die derzeit konfigurierte GOP-Größe beträgt 16 KB). Trotzdem bleibt der Datenverbrauch im Leerlauf des Bildschirms bei Intel Graphics 620-Hardware bei 300 KBit / s und bei NVidia GTX 1070 (Konfiguration: 500 KB Bitrate und 30 Bildern pro Sekunde) bei 50 KBit / s bis 80 KBit / s, was nicht akzeptabel ist. Ich denke, Intel Hardware MFT berücksichtigt die GOP-Einstellung überhaupt nicht oder die Verbesserung ist nicht bemerkbar.

Ich konnte auch den Datenverbrauch im Leerlauf auf ~ 130 KBit / s und ~ 40 KBit / s auf Intel- bzw. Nvidia-Hardware senken, indem ich sehr niedrige Bitraten einstellte. Dies ist jedoch immer noch nicht akzeptabel. Dies verschlechtert auch die Videoqualität.

Gibt es eine Möglichkeit, den Encoder so zu konfigurieren, dass weniger als ~ 10 KBit / s ausgegeben werden, wenn zwischen den Eingangsabtastwerten keine Änderungen vorgenommen wurden? Ich habe tatsächlich eine Ausgabe von ~ 0 KB angestrebt, wenn keine Änderung erfolgt, aber ~ 10 KBit / s etwas akzeptabel sind.

Aktualisieren:

Ich kann den Datenverbrauch im Leerlauf auf NVidia MFT senken, indem ich einige Parameter auf weniger als ~ 20 KBit / s bei einer Konfiguration mit 400 KB Bitrate und unter ~ 10 KBit / s bei einer Konfiguration mit 100 KBit / s optimiere . Das überzeugt. Der gleiche Code mit den gleichen Encoder-Konfigurationen erzeugt jedoch 20- bis 40-mal mehr Daten auf Intel-Computern. Intel (Intel Graphics 620) hält die GOP-Einstellung sicherlich nicht ein. Ich habe sogar versucht, die GOP zwischen 256 und INT_MAX zu variieren. An der Ausgabe von Intel Hardware MFT scheint sich nichts zu ändern.

Update 2:

Nachdem ich mit den Encoder-Eigenschaften herumgespielt habe (ich habe nur CODECAPI_AVEncCommonRateControlMode mit eAVEncCommonRateControlMode_UnconstrainedVBR anstelle von eAVEncCommonRateControlMode_CBR konfiguriert), konnte ich jetzt sehen, dass der Intel MFT nur 3 Sekunden lang (während des ersten Bildschirms 3 Sekunden) dann geht es zurück auf die gleiche Geschichte. Ich denke, nach ein paar Sekunden verliert der Encoder den Verweis auf den Keyframe, mit dem er die Samples vergleicht, und scheint sich nach diesem Zeitpunkt nicht mehr zu erholen. Das Verhalten ist das gleiche, unabhängig davon, ob die GOP 16/128/256/512/1024 oder INT_MAX ist.

Encoder-Konfigurationen:

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

Ich habe versucht, den unterstützten Parameterbereich für die GOP-Größe mit dem folgenden Code abzurufen, aber es wird nur der Fehler E_NOTIMPL zurückgegeben.

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

Vermisse ich etwas Gibt es andere Eigenschaften, mit denen ich experimentieren könnte, um Echtzeitleistung zu erzielen und dabei so wenig Bandbreite wie möglich zu verbrauchen, wenn sich der Bildschirminhalt nicht ändert?

RAM
quelle

Antworten:

2

Ein Wunder ist geschehen. Während ich auch mit Encoder-Konfigurationen herumspielte, habe ich versehentlich meinen primären Monitor auf meinem Computer auf einen anderen umgestellt. Jetzt ist das Problem behoben. Das Zurückschalten auf den zuvor ausgewählten primären Monitor führt zu demselben Problem. Ich vermute, dass das d3ddevice der Unruhestifter ist. Ich bin mir nicht sicher, warum dies nur auf diesem Gerät / Monitor passiert, muss noch etwas experimentieren.

Hinweis: Ich markiere dies nicht als Antwort, da ich den Grund für das Problem, das nur auf diesem Monitor / d3d-Gerät auftritt, noch nicht herausgefunden habe. Veröffentlichen Sie dies einfach als Referenz für andere Personen, die möglicherweise auf eine ähnliche Situation stoßen. Ich werde die Antwort aktualisieren, sobald ich den Grund für das seltsame Verhalten auf dieser bestimmten d3d11device-Instanz gefunden habe.

Auf diese Weise erstelle ich das d3ddevice und verwende es für die Erfassung von Desktop-Duplizierungsbildern, den Videoprozessor für die Farbkonvertierung und auch für die Hardware-Transformation über die Eigenschaft MFT_MESSAGE_SET_D3D_MANAGER .

Optionen:

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

Erstellen Sie 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;
            }
        }
    }
}
RAM
quelle