A configuração GOP não é respeitada pela MFT de hardware Intel H264

8

Declaração do problema:

A MFT de hardware da Intel não está respeitando a configuração GOP, resultando em mais consumo de largura de banda em aplicativos em tempo real. O mesmo código funciona bem no hardware da Nvidia MFT.

Fundo:

Estou tentando codificar amostras do NV12 capturadas pelas APIs do DesktopDuplication para transmitir vídeo usando o codificador de hardware MediaFoundation H264 na máquina Windows10, transmitir e renderizar o mesmo em tempo real pela LAN.

Inicialmente, eu estava enfrentando muito buffer no codificador, já que o codificador estava armazenando buffer até 25 quadros (tamanho GOP) antes de entregar uma amostra de saída. Após algumas pesquisas, descobri que definir o CODECAPI_AVLowLatencyMode reduziria a latência com o custo de um pouco de qualidade e largura de banda.

A configuração da propriedade CODECAPI_AVLowLatencyMode melhorou um pouco o desempenho, mas não os requisitos em tempo real. Parece que agora o codificador ainda armazena em buffer até 15 quadros pelo menos antes de produzir as amostras (introdução de um atraso de cerca de 2 segundos na saída). E esse comportamento é perceptível apenas quando uma taxa de quadros baixa é configurada. A 60FPS, a saída é quase em tempo real, sem atraso visualmente visível.

De fato, o buffer é perceptível ao olho humano somente quando a taxa de quadros é definida abaixo de 30FPS. E, o atraso aumenta inversamente proporcional à configuração do FPS, a 25FPS o atraso é de algumas centenas de milissegundos e sobe para 3 segundos quando o FPS é configurado para 10 (taxa constante). Eu acho que definir FPS acima de 30 (digamos 60FPS) faz com que o buffer do encoder transborde rápido o suficiente para produzir amostras com atraso imperceptível.

Ultimamente, tentei a propriedade CODECAPI_AVEncCommonRealTime ( https://docs.microsoft.com/en-us/windows/win32/directshow/avenccommonrealtime-property ) também para verificar se melhora o desempenho ao diminuir a taxa de quadros de entrada para evitar o consumo de largura de banda , mas essa chamada falha com o erro "parâmetro incorreto" .

Minhas experiências:

Para manter uma taxa de quadros constante e também forçar o codificador a produzir saídas em tempo real, estou alimentando a mesma amostra (amostra salva anteriormente) no codificador a uma taxa constante de 30FPS / 60FPS. Estou fazendo isso capturando apenas no máximo 10FPS (ou em qualquer FPS necessário) e fingindo 30 / 60FPS, alimentando a mesma amostra três vezes ou exatamente a uma taxa baseada na razão EMULATED_FRAME_RATE / ACTUAL_FRAME_RATE (Ex: 30/10, 60/15 , 60/20) para preencher a lacuna exatamente em intervalos constantes. Por exemplo, quando nenhuma alteração ocorre por 10 segundos, eu teria alimentado o codificador com a mesma amostra 30 * 10 vezes (30FPS). Aprendi sobre essa abordagem em alguns projetos de código aberto do Github, também a partir de exemplos de código experimental do cromo, e também fui informado ( principalmente no SO, e também em outros fóruns) de que essa é a única maneira de enviar o codificador para saída em tempo real, e não há como contorná-lo.

A abordagem acima mencionada produz saída quase em tempo real, mas consome mais dados do que eu esperava, embora eu esteja alimentando apenas a amostra salva anteriormente no codificador.

A taxa de bits de saída parece permanecer consistentemente entre 350KBps e 500KBps na Intel MFT e varia entre 80KBps e 400KBps na NVidia MFT (com configuração de taxa de bits de 30FPS e 500KB), independentemente do conteúdo da tela ser alterado a 30FPS ou 0FPS (inativo). O codificador de hardware NVidia parece ser um pouco melhor nesse caso.

De fato, durante o tempo ocioso da tela, o codificador produzia muito mais dados por segundo do que a taxa acima mencionada. Consegui reduzir o consumo de dados nos dispositivos NVidia definindo um tamanho GOP maior (o tamanho atual do GOP configurado é 16K). Mas, ainda assim, o consumo de dados em tempo ocioso da tela permanece em torno de 300KBps no hardware Intel graphics 620 e de 50KBps a 80KBps no NVidia GTX 1070 (configuração: taxa de bits de 500KB e 30FPS), o que é inaceitável. Eu acho que o hardware MFT da Intel não está respeitando a configuração do GOP ou a melhoria é imperceptível.

Também pude reduzir o consumo de dados em tempo ocioso para ~ 130KBps e ~ 40KBps no hardware Intel e Nvidia, respectivamente, definindo taxas de bits muito baixas, mas isso ainda é inaceitável, mas também deteriora a qualidade do vídeo.

Existe uma maneira de configurar o codificador para produzir uma saída inferior a ~ 10KBps quando nenhuma alteração ocorreu entre as amostras de entrada? Na verdade, eu apontei para uma saída de ~ 0 KB quando nenhuma mudança acontece, mas ~ 10 KB é um tanto aceitável.

Atualizar:

Sou capaz de reduzir o consumo de dados de tempo ocioso na NVidia MFT, ajustando alguns parâmetros, para menos de ~ 20KBps com configuração de taxa de bits de 400KB e abaixo de ~ 10KBps com configuração de taxa de bits de 100KB . Isso é convincente. Mas o mesmo código com as mesmas configurações de codificador produz 20 a 40 vezes mais dados em máquinas Intel. A Intel (Intel graphics 620) certamente não está respeitando a configuração GOP. Eu até tentei variar o GOP entre 256 e INT_MAX, nada parece estar mudando na saída do hardware da Intel MFT.

Atualização 2:

Depois de brincar com as propriedades do codificador (apenas configurei CODECAPI_AVEncCommonRateControlMode com eAVEncCommonRateControlMode_UnconstrainedVBR em vez de eAVEncCommonRateControlMode_CBR), agora eu pude ver que o Intel MFT produz 3KBps durante os primeiros segundos, provavelmente durante apenas oito segundos , então ele volta à mesma história. Acho que depois de alguns segundos, o codificador está perdendo a referência ao quadro-chave com o qual compara as amostras e parece não estar se recuperando depois desse ponto. O comportamento é o mesmo, independentemente de o GOP ser 16/128/256/512/1024 ou INT_MAX.

Configurações do codificador:

Referência: 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");

Tentei recuperar o intervalo de parâmetros suportados para o tamanho GOP com o código a seguir, mas ele apenas retorna erro 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);

Estou esquecendo de algo? Existem outras propriedades com as quais eu poderia experimentar para obter desempenho em tempo real e consumir a menor largura de banda possível quando não houver alteração no conteúdo da tela?

RAM
fonte

Respostas:

2

Algum milagre aconteceu. Enquanto também brincava com as configurações do codificador, mudei acidentalmente meu monitor principal para outro na minha máquina, agora o problema se foi. Voltar ao monitor principal selecionado anteriormente leva ao mesmo problema. Suspeito que o d3ddevice seja o causador de problemas. Não sei por que isso acontece apenas nesse dispositivo / monitor ainda, tenho que experimentar um pouco mais.

Nota: Não estou marcando isso como resposta devido ao fato de que ainda estou para descobrir o motivo do problema acontecendo apenas nesse monitor / d3ddevice. Basta postar isso como uma referência para outras pessoas que podem se deparar com uma situação semelhante. Atualizarei a resposta assim que conseguir encontrar o motivo do comportamento estranho nessa instância específica do d3d11device.

É assim que estou criando o dispositivo d3d e reutilizando o mesmo para o capturador de imagens de duplicação de desktop, processador de vídeo para conversão de cores e também para a transformação de hardware através da propriedade MFT_MESSAGE_SET_D3D_MANAGER .

Opções:

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

Crie o 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
fonte