La configuración de GOP no es respetada por el hardware Intel H264 MFT

8

Planteamiento del problema:

El hardware de Intel MFT no respeta la configuración de GOP, lo que resulta en un mayor consumo de ancho de banda en aplicaciones en tiempo real. El mismo código funciona bien en el hardware MFT de Nvidia.

Antecedentes:

Estoy tratando de codificar muestras NV12 capturadas a través de API de DesktopDuplication para transmitir video usando el codificador de hardware MediaFoundation H264 en la máquina Windows10, transmitir y renderizar lo mismo en tiempo real a través de LAN.

Inicialmente, estaba enfrentando demasiado almacenamiento en búfer en el codificador ya que el codificador estaba almacenando en búfer hasta 25 cuadros (tamaño GOP) antes de entregar una muestra de salida. Después de investigar un poco, descubrí que configurar CODECAPI_AVLowLatencyMode reduciría la latencia con el costo de un poco de calidad y ancho de banda.

Establecer la propiedad CODECAPI_AVLowLatencyMode mejoró un poco el rendimiento, pero no hasta los requisitos en tiempo real. Parece que ahora el codificador todavía almacena hasta 15 cuadros al menos antes de producir las muestras (presentando un retraso de alrededor de 2 segundos en la salida). Y este comportamiento solo se nota cuando se configura una baja velocidad de cuadros. A 60FPS, la salida es casi en tiempo real sin demoras visibles.

De hecho, el almacenamiento en búfer es perceptible para el ojo humano solo cuando la velocidad de fotogramas se establece por debajo de 30FPS. Y, el retraso aumenta inversamente proporcional a la configuración de FPS, a 25FPS el retraso es de unos pocos cientos de milisegundos y sube hasta 3 segundos cuando FPS está configurado en 10 (velocidad constante). Supongo que configurar FPS más de 30 (digamos 60FPS) en realidad hace que el búfer del codificador se desborde lo suficientemente rápido como para producir muestras con un retraso imperceptible.

Últimamente, probé la propiedad CODECAPI_AVEncCommonRealTime ( https://docs.microsoft.com/en-us/windows/win32/directshow/avenccommonrealtime-property ) también para verificar si mejora el rendimiento al reducir la velocidad de fotogramas de entrada para evitar el consumo de ancho de banda , pero esa llamada falla con el error "parámetro incorrecto" .

Mis experimentos:

Para mantener una velocidad de cuadro constante, y también para forzar al codificador a producir salidas en tiempo real, estoy alimentando la misma muestra (muestra previamente guardada) al codificador a una velocidad constante de 30FPS / 60FPS. Estoy haciendo esto capturando solo como máximo 10FPS (o en cualquier FPS requerido) y falsificando 30 / 60FPS alimentando la misma muestra tres veces o exactamente a una tasa basada en la proporción EMULATED_FRAME_RATE / ACTUAL_FRAME_RATE (Ex: 30/10, 60/15 , 60/20) para llenar el espacio exactamente a intervalos constantes. Por ejemplo, cuando no ocurre ningún cambio durante 10 segundos, habría alimentado el codificador con la misma muestra 30 * 10 veces (30FPS). Aprendí sobre este enfoque de algunos proyectos de código abierto de Github, también de muestras de código experimental de cromo, también me informaron ( principalmente sobre SO, y también en otros foros) que esta es la única forma de impulsar el codificador para la salida en tiempo real, y no hay forma de evitarlo.

El enfoque mencionado anteriormente produce una salida casi en tiempo real, pero consume más datos de los que esperaba, a pesar de que solo estoy suministrando al codificador la muestra previamente guardada.

La tasa de bits de salida parece mantenerse constantemente entre 350 Kbps y 500 Kbps en Intel MFT, y varía entre 80 Kbps y 400 Kbps en NVidia MFT (con una configuración de tasa de bits de 30 FPS y 500 KB), sin importar si el contenido de la pantalla cambia a 30 FPS o 0 FPS (inactivo). El codificador de hardware NVidia parece ser algo mejor en este caso.

De hecho, durante el tiempo de inactividad de la pantalla, el codificador estaba produciendo muchos más datos por segundo que la velocidad mencionada anteriormente. He podido reducir el consumo de datos en dispositivos NVidia estableciendo un tamaño de GOP más grande (el tamaño de GOP actual configurado es 16K). Pero aún así, el consumo de datos en tiempo de inactividad de la pantalla se mantiene alrededor de 300KBps en el hardware Intel Graphics 620, y de 50KBps a 80KBps en NVidia GTX 1070 (configuración: velocidad de bits de 500KB y 30FPS), lo cual es inaceptable. Supongo que el hardware de Intel MFT no respeta la configuración de GOP o la mejora es imperceptible.

También pude reducir el consumo de datos en tiempo de inactividad a ~ 130 Kbps y ~ 40 Kbps en hardware Intel y Nvidia, respectivamente, estableciendo tasas de bits muy bajas, pero esto todavía es inaceptable, esto también deteriora la calidad del video.

¿Hay alguna forma de configurar el codificador para producir una salida de menos de ~ 10 Kbps cuando no ocurrieron cambios entre las muestras de entrada? De hecho, he apuntado a una salida de ~ 0 KB cuando no ocurre ningún cambio, pero ~ 10 KB es algo aceptable.

Actualizar:

Puedo reducir el consumo de datos de tiempo de inactividad en NVidia MFT ajustando algunos parámetros, a menos de ~ 20KBps con una configuración de velocidad de bits de 400KB , y por debajo de ~ 10KBps con una configuración de velocidad de bits de 100KB . Esto es convincente Pero el mismo código con las mismas configuraciones de codificador produce de 20 a 40 veces más datos en las máquinas Intel. Intel (Intel graphics 620) seguramente no respeta la configuración de GOP. Incluso he intentado variar el GOP entre 256 a INT_MAX, nada parece estar cambiando en la salida del hardware Intel MFT.

Actualización 2:

Después de jugar con las propiedades del codificador (solo configuré CODECAPI_AVEncCommonRateControlMode con eAVEncCommonRateControlMode_UnconstrainedVBR en lugar de eAVEncCommonRateControlMode_CBR), ahora pude ver que Intel MFT produce datos de 3KBps durante el tiempo de inactividad de la pantalla, pero solo durante los primeros 8 segundos (probablemente solo durante los primeros 8 segundos) , luego vuelve a la misma historia. Supongo que después de unos segundos, el codificador está perdiendo la referencia al fotograma clave con el que compara las muestras y parece que no se recupera después de ese punto. El comportamiento es el mismo sin importar si el GOP es 16/128/256/512/1024 o INT_MAX.

Configuraciones de codificador:

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

Intenté recuperar el rango de parámetros admitidos para el tamaño de GOP con el siguiente código, pero solo devuelve el error 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);

¿Me estoy perdiendo de algo? ¿Hay alguna otra propiedad con la que pueda experimentar para obtener un rendimiento en tiempo real y consumir el menor ancho de banda posible cuando no hay cambios en el contenido de la pantalla?

RAM
fuente

Respuestas:

2

Algún milagro ha sucedido. Mientras también jugaba con configuraciones de codificador, accidentalmente cambié mi monitor principal a uno diferente en mi máquina, ahora el problema desapareció. Cambiar de nuevo al monitor primario previamente seleccionado conduce al mismo problema. Sospecho que el dispositivo d3d es el que causa problemas. No estoy seguro de por qué esto sucede solo en ese dispositivo / monitor todavía, tengo que experimentar un poco más.

Nota: No estoy marcando esto como una respuesta debido al hecho de que aún no he descubierto la razón del problema que ocurre solo en ese monitor / d3ddevice. Simplemente publicando esto como referencia para otras personas que pueden encontrarse con una situación similar. Actualizaré la respuesta una vez que pueda encontrar la razón del extraño comportamiento en esa instancia particular del dispositivo d3d11.

Así es como estoy creando el dispositivo d3d y reutilizando el mismo para capturador de imágenes de duplicación de escritorio, procesador de video para conversión de color y también para la transformación de hardware a través de la propiedad MFT_MESSAGE_SET_D3D_MANAGER .

Opciones:

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

Crear 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
fuente