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?