Lançamento do Netflix em TVs e decodificadores. Extra 40 milissegundos

O aplicativo Netflix roda em centenas de smart TVs, dispositivos e decodificadores. Sou um dos engenheiros ajudando os fabricantes a colocar nosso aplicativo em execução em seus dispositivos. Neste artigo, discutiremos uma questão particularmente difícil que impediu um decodificador de entrar no mercado europeu.



Problema misterioso



No final de 2017, recebi uma ligação para discutir um problema com o aplicativo Netflix no novo decodificador. Era uma nova Android TV com capacidade 4K baseada no Android Open Source Project (AOSP) versão 5.0, Lollipop. Trabalhei na Netflix por vários anos e ajudei a lançar alguns dispositivos, mas esta foi minha primeira Android TV.



Todas as quatro partes estiveram em contato: uma grande empresa europeia de TV paga lançando o dispositivo (operadora), um integrador de firmware (integrador), um fornecedor de sistema em um chip (fornecedor de chip) e eu (Netflix).



O integrador e a Netflix já concluíram o rigoroso processo de certificação da Netflix, mas durante um teste interno com a operadora, um executivo da empresa relatou um problema sério: a reprodução da Netflix atrasou, o que significa que o vídeo foi reproduzido por um tempo muito curto, depois pausado, depois pausado e depois pausado. Isso nem sempre acontecia, mas começou a demorar alguns dias após ligar o console. Eles mostraram o vídeo, parecia terrível.



O integrador encontrou uma maneira de reproduzir o problema: inicie o Netflix várias vezes, inicie a reprodução e volte para a IU. Eles forneceram um script para automatizar o processo. Às vezes, demorava até cinco minutos, mas o script sempre reproduzia o bug de maneira confiável.



Enquanto isso, um engenheiro de um fornecedor de chips diagnosticou a causa raiz: um aplicativo Netflix Android TV chamado Ninja não foi capaz de fornecer dados de áudio. Os atrasos são causados ​​por underruns no pipeline de áudio do hardware. A reprodução parava quando o decodificador estava esperando por uma parte do fluxo de áudio do Ninja e, então, continuava quando novos dados chegavam. O integrador, o fornecedor do chip e o operador acharam que o problema estava claro. E todos olharam para mim: Netflix, você tem um bug em seu aplicativo e precisa corrigi-lo. Eu ouvi a tensão na voz do representante da operadora. O lançamento do dispositivo atrasou e ultrapassou o orçamento, e eles esperavam resultados de mim.



Investigação



Eu estava cético. Este mesmo aplicativo Ninja é executado em milhões de dispositivos Android TV, incluindo smart TVs e outros decodificadores. Se há um bug no Ninja, por que isso só acontece neste dispositivo?



Comecei replicando o problema sozinho usando um script do integrador. Entrei em contato com um colega do fabricante do chip e perguntei se ele tinha visto algo assim (não vi). Então comecei a estudar o código-fonte do Ninja. Foi necessário encontrar o código exato responsável pela entrega dos dados de áudio. Descobri muita coisa, mas comecei a me perder no código que é responsável pela reprodução e precisava de ajuda.



Subi as escadas e encontrei o engenheiro que escreveu o pipeline de áudio e vídeo Ninja, ele me apresentou o código. Depois disso, eu mesmo estudei por algum tempo para finalmente entender as partes principais e adicionar meus próprios logs. O aplicativo Netflix é complexo, mas de forma simplificada, ele recupera dados do servidor Netflix, armazena dados de vídeo e áudio no dispositivo por alguns segundos e, em seguida, entrega os quadros de vídeo e áudio um de cada vez aos decodificadores de hardware.





Figura: 1. Pipeline de reprodução simplificado



Vamos falar sobre o pipeline de áudio / vídeo no aplicativo Netflix por um momento. Antes do “buffer do decodificador”, é exatamente o mesmo em todos os decodificadores e TVs, mas mover os dados A / V para o buffer do decodificador de um dispositivo é um procedimento específico do dispositivo. Ele funciona em seu próprio segmento. O objetivo deste procedimento é manter o buffer do decodificador cheio, chamando o próximo quadro de dados de áudio ou vídeo por meio da API Netflix. No Ninja, esse trabalho é feito por um threadAndroid. Há uma máquina de estado simples e alguma lógica para lidar com os vários estados de reprodução, mas na reprodução normal, o stream copia um quadro de dados para a API de reprodução do Android e, em seguida, diz ao agendador de thread para esperar 15 ms antes da próxima chamada do manipulador. Quando você cria um thread Android, pode solicitar que o thread seja reiniciado como um loop, mas é o planejador de thread do Android que chama o manipulador, não seu próprio aplicativo.



No máximo 60 FPS, o dispositivo deve exibir um novo quadro a cada 16,66 ms, portanto, a verificação após 15 ms é suficiente de qualquer maneira. Como o integrador determinou que o problema estava no fluxo de áudio, concentrei-me no manipulador específico que estava entregando as amostras de áudio para o serviço de áudio Android.



Era preciso entender de onde vêm as defasagens, ou seja, o atraso. Presumi que alguma função chamada pelo manipulador era a culpada, então espalhei as mensagens de log por todo o manipulador e encontraria facilmente o código que causou os atrasos. Logo ficou claro que não havia nada de errado com o manipulador e ele funcionou por alguns milissegundos, mesmo quando a reprodução estava atrasada.



Sim, visão



No final, concentrei-me em três números: taxa de transmissão, tempo de chamada do manipulador e tempo para transferir o controle do manipulador de volta para o Android. Eu escrevi um script para analisar a saída do log e gerei o gráfico abaixo mostrando a resposta. Figura: 2. Visualização da largura de banda de streaming de áudio e tempos do manipulador A linha laranja é a taxa na qual os dados viajam do buffer de streaming para o sistema de áudio Android (bytes por milissegundo). Existem três cenários diferentes neste diagrama:













  1. Duas áreas com altos picos, onde as taxas de dados chegam a 500 bytes por milissegundo. Esta fase é armazenada em buffer antes de iniciar a reprodução. O manipulador copia os dados o mais rápido possível.

  2. — . 45 .

  3. , 10 . .


Conclusão inevitável: a linha laranja confirma as conclusões do engenheiro da empresa de chips. Na verdade, o Ninja não é rápido o suficiente para fornecer dados de áudio.



Para entender por quê, vamos examinar mais de perto as linhas amarelas e cinzas.



A linha amarela mostra o tempo gasto no próprio procedimento do manipulador, calculado a partir dos timestamps registrados no início e no final do procedimento. Nas áreas normais e atrasadas, o tempo no manipulador é o mesmo: cerca de 2 ms. Bursts mostram casos em que os tempos são mais lentos devido a outras tarefas sendo realizadas no dispositivo.



Verdadeira causa raiz



A linha cinza - o tempo entre as chamadas para o manipulador - conta uma história diferente. Na reprodução normal, o manipulador é chamado aproximadamente a cada 15ms. No caso de atrasos à direita, o manipulador é chamado aproximadamente a cada 55 ms. Há um extra de 40 ms entre as chamadas e, em tal situação, ele não consegue acompanhar a reprodução. Mas por que?



Relatei minha descoberta ao integrador e ao fornecedor do chip (veja, o agendador de stream do Android é o culpado!), Mas eles insistiram que a Netflix deveria resolver o problema. Por que não copiar mais dados cada vez que o manipulador é chamado? Era uma crítica justa, mas implementar esse comportamento implicaria em mudanças profundas que eu não queria realizar, então continuei a buscar a causa raiz. Eu mergulhei no código-fonte do Android e descobri que os threads do Android são uma construção do espaço do usuário e o planejador de thread usa uma chamada de sistema para sincronizar epoll()



. Eu sabia que o desempenho epoll()



não era garantido, então suspeitei que algo o estava afetando sistematicamente.



Nesse ponto, fui resgatado por outro engenheiro de um fornecedor de chips que descobriu um bug que já foi corrigido na próxima versão do Android (Marshmallow). Acontece que o agendador de threads do Android altera o comportamento das threads dependendo se o aplicativo está sendo executado em primeiro ou segundo plano. Threads em segundo plano são atribuídos a uma latência adicional de 40 ms (40.000.000 ns).



Um bug profundo no kernel do Android significava que esse valor de cronômetro extra persistia quando o thread era trazido à tona. Normalmente, o thread do processador de áudio era criado quando o aplicativo estava em primeiro plano, mas às vezes um pouco antes, quando o Ninja ainda estava em segundo plano. Se isso acontecesse, a reprodução começaria a atrasar.



Lições aprendidas



Este não é o último bug que corrigimos na plataforma Android, mas foi o mais difícil de rastrear. Estava fora do aplicativo Netflix e até mesmo fora do pipeline de reprodução, e todos os dados brutos indicavam um erro no próprio aplicativo Netflix.



A história ilustra um aspecto do meu trabalho que adoro: é impossível prever todos os problemas que nossos parceiros vão lançar sobre mim. E eu sei que resolvê-los requer a compreensão de muitos sistemas, trabalhar com ótimos colegas e se esforçar constantemente para aprender coisas novas. O que faço tem um impacto direto nas pessoas reais e em seu prazer de um ótimo produto. Quando as pessoas gostam de assistir Netflix na sala de estar, sei que faço parte da equipe que tornou isso possível.



All Articles