WebRTC no Android: como habilitar a codificação de hardware em vários dispositivos

Para chamadas de vídeo no Badoo, usamos o padrão WebRTC e o codec H.264. Se você acredita na documentação, esse codec deve funcionar sem problemas em qualquer dispositivo Android desde o Android 5.0. Mas, na prática, tudo acabou não sendo bem assim. Neste artigo, falarei sobre os recursos da implementação da codificação de hardware para o codec H.264 no WebRTC e sobre como fazê-lo funcionar em mais dispositivos.



Por que H.264?


Quando conectados via WebRTC, todos os dispositivos que participam da sessão transmitem vários parâmetros de comunicação, incluindo codecs de vídeo e áudio. Se os dispositivos suportarem vários codecs (por exemplo, VP8 e H.264), os codecs prioritários da plataforma serão listados primeiro. Esses dados são usados ​​no estágio de reconciliação no WebRTC, após o qual apenas os codecs suportados por todos os dispositivos permanecem. Um exemplo desses dados com descriptografia pode ser visto neste documento .

No caso de videochamadas, se um dos dispositivos não suportar o codec H.264, os dois dispositivos poderão mudar, por exemplo, para o codec VP8, que não depende da implementação de hardware no dispositivo. Mas nosso aplicativo está disponível em uma variedade de gadgets, incluindo smartphones de gerações anteriores. Portanto, para comunicações por vídeo, queríamos usar a codificação de hardware sempre que possível: reduz a carga no processador e não consome tanto a bateria, o que é fundamental para dispositivos desatualizados. O suporte à codificação de hardware H.264 é implementado em um grande número de dispositivos, ao contrário do mesmo VP8 .

Suporte para H.264 no Android


Se você acredita na descrição do suporte a formatos multimídia , a decodificação do perfil de linha de base H.264 deve funcionar em todos os dispositivos Android e a codificação - começando com o Android 3.0. No Badoo, oferecemos suporte a dispositivos começando no Android 5.0, portanto, não devemos ter problemas. Mas não era tão simples: mesmo em gadgets com a quinta versão, encontramos um grande número de recursos.

Com o que pode ser conectado?

Como você sabe, ao desenvolver um novo dispositivo no Android, qualquer fabricante precisa passar no Compatibility Test Suite. É executado em um PC conectado ao dispositivo e seus resultados devem ser enviados ao Google para confirmar se o dispositivo atende aos requisitos do sistema operacional Android da versão especificada. Somente depois disso o gadget poderá ser lançado no mercado.

Estamos interessados ​​em testes de multimídia nesta suíte de testes e, mais especificamente, em testes de codificação e decodificação de vídeo. Decidi me concentrar nos testes EncodeDecodeTest , MediaCodecTest , DecoderTest e EncoderTest , pois eles estão presentes em todas as versões do Android desde o 4.3. O gráfico do número de linhas de código nesses testes é assim:



Antes da versão 4.3, a maioria desses testes simplesmente não existia e seu aumento significativo caiu nas versões 5 e 7. Portanto, podemos dizer que antes da versão Android 4.3, o Google não verificava a conformidade dos dispositivos com suas especificações para codificação e decodificação de vídeo, mas na versão 5.0 melhorou bastante essa verificação.

Parece que isso indica que a partir da versão 5.0 com a codificação de tudo deve estar em ordem. Mas, dada a minha experiência anterior com decodificação de streaming de vídeo no Android, eu tinha certeza de que não era. Foi o suficiente para analisar o número de tópicos sobre codificação no grupo do Google discuss-webrtc .

Os arquivos de origem do WebRTC, que são de domínio público, nos ajudaram a procurar armadilhas. Vamos considerá-los com mais detalhes.

Suporte H.264 no WebRTC


Vamos começar com o HardwareVideoEncoderFactory .

Existe um método com o nome falante éHardwareSupportedInCurrentSdkH264:

private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) {
  // First, H264 hardware might perform poorly on this model.
  if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) {
    return false;
  }
  String name = info.getName();
  // QCOM H264 encoder is supported in KITKAT or later.
  return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
      // Exynos H264 encoder is supported in LOLLIPOP or later.
      || (name.startsWith(EXYNOS_PREFIX)
             && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}

Como podemos ver, o suporte à codificação de hardware no Android é implementado apenas para os chipsets Qualcomm e Exynos. Por que não há suporte para outros chipsets na implementação padrão do WebRTC? Provavelmente, isso se deve às peculiaridades da implementação dos fabricantes de codecs de hardware. E muitas vezes é possível identificar esses recursos apenas na produção, pois nem sempre é possível encontrar dispositivos específicos.

Todas as descrições de codec no dispositivo são armazenadas no arquivo media_codecs.xml. Aqui, por exemplo, está este arquivo para o Pixel XL e o HUAWEI P8 lite . Quando você obtém uma lista de codecs usando o método getCodecInfos () do objeto MediaCodecList, esse arquivo é analisado - e os codecs armazenados nele são retornados. Esta operação e o preenchimento correto deste arquivo pelo fabricante são abordados no teste CTS.O MediaCodecListTest , que também aumentou de 160 linhas de código no Android 4.3 para 740 no Android 10.

No Badoo, alteramos o código do método isHardwareSupportedInCurrentSdkH264, rejeitando a lista de codecs "brancos" e substituindo-a por uma lista "negra" de prefixos de codecs de programas listados no WebRTC:

static final String[] SOFTWARE_IMPLEMENTATION_PREFIXES = {"OMX.google.", "OMX.SEC."};

Mas você não pode simplesmente assumir e implementar o suporte a todos os codecs sem prestar atenção aos recursos dos fabricantes. Dos nomes dos tópicos dedicados à codificação de hardware no Android no grupo discuss-webrtc , podemos entender que, neste caso, definitivamente encontraremos erros. Basicamente, eles aparecem no estágio de configuração do codec.

Opções de configuração de codec


A inicialização do codec para codificação é a seguinte:

MediaCodec mediaCodec = createByCodecName(codecName);
MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, targetBitrateBps);
format.setInteger(MediaFormat.KEY_BITRATE_MODE, VIDEO_ControlRateConstant);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
format.setInteger(MediaFormat.KEY_FRAME_RATE, targetFps);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec);
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

É fácil cometer erros em alguns desses parâmetros, o que gera uma exceção durante a configuração do codec e atrapalha o aplicativo. Além disso, ao trabalhar com um codec, pode ser necessário ajustar sua taxa de bits dependendo de vários fatores, pois o próprio codec faz isso errado. Por isso, no WebRTC, a classe BaseBitrateAdjuster é responsável , que possui dois descendentes:


Portanto, para cada codec, você deve escolher seu próprio mecanismo de ajuste de taxa de bits. Vamos considerar com mais detalhes os recursos da configuração de parâmetros de inicialização para codecs de hardware.

Resolução de transmissão


Depois de receber o objeto MediaCodecInfo para o codec, você pode examiná-lo com mais detalhes, obtendo seus recursos na classe CodecCapabilities . Neles, você pode descobrir se o codec suporta a resolução e a taxa de quadros selecionadas. Se suportar esses parâmetros, eles podem ser definidos com segurança.

No entanto, algumas vezes essa regra não funciona. Estamos diante do fato de que codecs com o prefixo "OMX.MARVELL". codificado incorretamente, mostrando listras verdes nas bordas da tela se a resolução do fluxo for diferente de 4: 3. Ao mesmo tempo, o próprio codec afirmou que a resolução e a taxa de quadros selecionadas são suportadas.

Modo de taxa de bits


O modo padrão para todos os codecs de vídeo é uma taxa de bits constante. No entanto, uma vez que tivemos que usar uma taxa de bits variável:

format.setInteger(MediaFormat.KEY_BITRATE_MODE, VIDEO_ControlRateVariable);

Isso aconteceu em um dispositivo Lenovo A1000 com o chipset Spreadtrum (agora Unisoc) começando com o prefixo "OMX.sprd." Uma pesquisa na Internet nos levou a uma postagem de seis anos no Firefox OS que descreve esse problema e como resolvê-lo.

Formato de cor


Ao usar o codec no modo buffer de bytes, você deve selecionar o formato correto. Isso geralmente é feito usando uma função da seguinte forma:

@Nullable
static Integer selectColorFormat(int[] supportedColorFormats, CodecCapabilities capabilities) {
  for (int supportedColorFormat : supportedColorFormats) {
    for (int codecColorFormat : capabilities.colorFormats) {
      if (codecColorFormat == supportedColorFormat) {
        return codecColorFormat;
      }
    }
  }
  return null;
}

Grosso modo, o primeiro dos formatos de cores suportados é sempre selecionado.

No entanto, no caso de codecs do HUAWEI, começando com os prefixos "OMX.IMG.TOPAZ.", "OMX.hisi". e "OMX.k3.", não funcionou e, após uma longa pesquisa, encontramos uma solução: independentemente do formato que esses codecs retornam, você precisa usar o formato COLOR_FormatYUV420SemiPlanar . O tópico nos ajudou a descobrir isso em um fórum chinês.

Ajuste de taxa de bits


O código padrão do WebRTC contém o seguinte :

private BitrateAdjuster createBitrateAdjuster(VideoCodecMimeType type, String codecName) {
  if (codecName.startsWith(EXYNOS_PREFIX)) {
    if (type == VideoCodecMimeType.VP8) {
        // Exynos VP8 encoders need dynamic bitrate adjustment.
        return new DynamicBitrateAdjuster();
      } else {
        // Exynos VP9 and H264 encoders need framerate-based bitrate adjustment.
        return new FramerateBitrateAdjuster();
    }
  }
  // Other codecs don't need bitrate adjustment.
  return new BaseBitrateAdjuster();
}

Como você pode ver neste código, para todos os chipsets, exceto o Exynos, o ajuste de taxa de bits está desativado. Mas isso se aplica apenas à Qualcomm, já que apenas Exynos e Qualcomm são suportados no código padrão. Experimentando vários valores dessa configuração, bem como pesquisando na Internet, descobrimos que para codecs com prefixos "OMX.MTK". ele também precisa estar ligado. Também é necessário fazer isso para codecs HUAWEI começando com o prefixo "OMX.IMG.TOPAZ.", "OMX.hisi". ou "OMX.k3". Isso ocorre pelo fato de esses codecs não usarem carimbos de data / hora dos quadros para ajustar a taxa de bits, assumindo que todos os quadros tenham a mesma frequência definida ao configurar o codec.

Concluindo, darei uma lista de codecs que recebemos para dispositivos no Android 5.0 e 5.1. Eles nos interessaram principalmente porque nas versões mais recentes do Android a situação está melhorando e os codecs fora do padrão estão ficando menores.

Isso pode ser visto no gráfico abaixo. A escala logarítmica para melhor mostrar casos raros.


Como podemos ver, a maioria dos dispositivos possui chipsets Spreadtrum, MediaTek, HUAWEI e MARVELL - portanto, nossas alterações ajudaram a ativar a codificação de hardware nesses dispositivos.

Resultado


Embora tenhamos assumido que alguns dispositivos teriam problemas ao trabalhar com o H.264, o Android foi novamente capaz de nos surpreender. Como podemos ver nas estatísticas do usuário do Badoo, ainda existem muitos dispositivos nas mãos dos usuários em 2014-2016, que eles não desejam ou não podem atualizar. E embora a situação com o lançamento das atualizações do Android para novos dispositivos já seja muito melhor do que há alguns anos atrás, a proporção de gadgets da geração anterior está diminuindo muito lentamente e terá que ser mantida por algum tempo.

Agora, o WebRTC está sendo desenvolvido ativamente pelo Google devido ao seu uso no projeto Stadia (aqui está o vídeocom detalhes sobre este tópico), para que se torne cada vez melhor e, provavelmente, se torne o padrão para a implementação de comunicações por vídeo. Espero que este artigo o ajude a entender os recursos do trabalho com o H.264 no WebRTC e a usá-lo em seus projetos.

All Articles