Problema misterioso
A fines de 2017, recibí una llamada para discutir un problema con la aplicación Netflix en el nuevo decodificador. Era un nuevo Android TV con capacidad 4K basado en Android Open Source Project (AOSP) versión 5.0, Lollipop. Trabajé en Netflix durante varios años y ayudé a lanzar algunos dispositivos, pero este fue mi primer Android TV.
Las cuatro partes estaban en contacto: una gran empresa europea de televisión de pago que lanzó el dispositivo (operador), un integrador de firmware (integrador), un proveedor de sistema en un chip (proveedor de chips) y yo mismo (Netflix).
El integrador y Netflix ya completaron el riguroso proceso de certificación de Netflix, pero durante una prueba interna con el operador, un ejecutivo de la compañía informó un problema grave: la reproducción de Netflix se retrasó, lo que significa que el video se reprodujo por un tiempo muy corto, luego se pausó, luego nuevamente, luego se pausó. Esto no siempre sucedió, pero comenzó a retrasarse constantemente unos días después de encender la consola. Mostraron el video, se veía terrible.
El integrador encontró una manera de reproducir el problema: inicie Netflix varias veces, inicie la reproducción y luego vuelva a la interfaz de usuario. Proporcionaron un script para automatizar el proceso. A veces tardaba hasta cinco minutos, pero el script siempre reproducía el error de forma fiable.
Mientras tanto, un ingeniero de un proveedor de chips diagnosticó la causa raíz: una aplicación de Netflix para Android TV llamada Ninja no podía entregar datos de audio. Los retrasos se deben a fallas en la canalización de audio del hardware. La reproducción se detuvo cuando el decodificador estaba esperando una parte del flujo de audio del Ninja, y luego se reanudó cuando llegaron nuevos datos. El integrador, el proveedor de chips y el operador pensaron que el problema estaba claro. Y todos me miraron: Netflix, tienes un error en tu aplicación y necesitas arreglarlo. Escuché la tensión en la voz del representante del operador. El lanzamiento del dispositivo se retrasó y superó el presupuesto, y esperaban resultados de mí.
Investigación
Yo estaba escéptico. Esta misma aplicación Ninja se ejecuta en millones de dispositivos Android TV, incluidos televisores inteligentes y otros decodificadores. Si hay un error en Ninja, ¿por qué solo ocurre en este dispositivo?
Comencé replicando el problema yo mismo usando un script del integrador. Me comuniqué con un colega del fabricante de chips y le pregunté si había visto algo como esto (no visto). Luego comencé a estudiar el código fuente de Ninja. Era necesario encontrar el código exacto que es responsable de la entrega de datos de audio. Descubrí muchas cosas, pero comencé a perderme en el código que se encarga de reproducir y necesitaba ayuda.
Subí las escaleras y encontré al ingeniero que escribió la canalización de audio y video de Ninja, él me presentó el código. Después de eso, yo mismo lo estudié durante un tiempo para finalmente comprender las partes principales y agregar mis propios registros. La aplicación Netflix es compleja, pero de una manera simplificada, recupera datos del servidor Netflix, almacena datos de video y audio en el dispositivo durante unos segundos y luego entrega los cuadros de video y audio uno por uno a los decodificadores de hardware.
Figura: 1. Canalización de reproducción simplificada
Hablemos de la canalización de audio / video en la aplicación Netflix por un momento. Antes del "búfer del decodificador" es exactamente lo mismo en todos los decodificadores y televisores, pero mover datos A / V al búfer del decodificador de un dispositivo es un procedimiento específico del dispositivo. Se ejecuta en su propio hilo. El propósito de este procedimiento es mantener lleno el búfer del decodificador llamando al siguiente cuadro de datos de audio o video a través de la API de Netflix. En Ninja, este trabajo se realiza mediante un hilo.Androide. Hay una máquina de estado simple y algo de lógica para manejar los diversos estados de reproducción, pero en la reproducción normal, la transmisión copia un cuadro de datos a la API de reproducción de Android y luego le dice al programador de subprocesos que espere 15 ms antes de la siguiente llamada al controlador. Cuando crea un hilo de Android, puede solicitar que el hilo se reinicie como un bucle, pero es el programador de hilos de Android el que llama al controlador, no su propia aplicación.
A un máximo de 60 FPS, el dispositivo debería mostrar un nuevo cuadro cada 16.66 ms, por lo que verificar después de 15 ms es suficiente de todos modos. Dado que el integrador determinó que el problema estaba en la transmisión de audio, me centré en el controlador específico que entregaba las muestras de audio al servicio de audio de Android.
Era necesario entender de dónde vienen los rezagos, es decir, el retraso. Supuse que la culpa era de alguna función llamada por el controlador, por lo que dispersé los mensajes de registro por todo el controlador e iba a encontrar fácilmente el código que causaba los retrasos. Pronto quedó claro que no había nada malo con el controlador, y funcionó durante unos milisegundos incluso cuando la reproducción estaba retrasada.
Si, perspicacia
Al final, me concentré en tres números: velocidad en baudios, tiempo de llamada del controlador y tiempo para transferir el control del controlador a Android. Escribí un script para analizar la salida del registro y generé el siguiente gráfico que muestra la respuesta. Figura: 2. Visualización del ancho de banda de transmisión de audio y los tiempos del controlador. La línea naranja es la velocidad a la que los datos viajan desde el búfer de transmisión al sistema de audio Android (bytes por milisegundo). Hay tres escenarios diferentes en este diagrama:
- Dos áreas con picos altos, donde las velocidades de datos alcanzan los 500 bytes por milisegundo. Esta fase se almacena en búfer antes de iniciar la reproducción. El administrador copia los datos lo más rápido que puede.
- — . 45 .
- , 10 . .
Conclusión inevitable: La línea naranja confirma las conclusiones del ingeniero de la empresa de chips. De hecho, Ninja no es lo suficientemente rápido para entregar datos de audio.
Para entender por qué, echemos un vistazo más de cerca a las líneas amarillas y grises.
La línea amarilla muestra el tiempo empleado en el propio procedimiento del controlador, calculado a partir de las marcas de tiempo registradas al comienzo y al final del procedimiento. Tanto en las áreas normales como en las atrasadas, el tiempo en el controlador es el mismo: aproximadamente 2 ms. Las ráfagas muestran casos en los que los tiempos son más lentos debido a que se realizan otras tareas en el dispositivo.
Verdadera causa raíz
La línea gris, el tiempo entre llamadas al gestor, cuenta una historia diferente. En la reproducción normal, el controlador se llama aproximadamente cada 15 ms. En el caso de retrasos a la derecha, el controlador se llama aproximadamente cada 55 ms, hay 40 ms adicionales entre llamadas y, en tal situación, no puede seguir el ritmo de la reproducción. ¿Pero por qué?
Informé de mi descubrimiento al integrador y al proveedor de chips (¡mira, el programador de subprocesos de Android tiene la culpa!), Pero insistieron en que Netflix debería resolver el problema. ¿Por qué no copiar más datos cada vez que se llama al controlador? Fue una crítica justa, pero implementar tal comportamiento implicaría cambios profundos a los que no quería ir, así que continué buscando la causa raíz. Me sumergí en el código fuente de Android y descubrí que los subprocesos de Android son una construcción de espacio de usuario y el programador de subprocesos utiliza una llamada al sistema para sincronizar
epoll()
. Sabía que el rendimiento
epoll()
no estaba garantizado, así que sospeché que algo lo estaba afectando sistemáticamente.
En este punto, fui rescatado por otro ingeniero de un proveedor de chips que descubrió un error que ya estaba arreglado en la próxima versión de Android (Marshmallow). Resulta que el programador de subprocesos de Android cambia el comportamiento de los subprocesos dependiendo de si la aplicación se está ejecutando en primer plano o en segundo plano. A los subprocesos en segundo plano se les asigna una latencia adicional de 40 ms (40 000 000 ns).
Un error en el kernel de Android significó que este valor de temporizador adicional se mantuvo cuando el hilo pasó a primer plano. Normalmente, el subproceso del procesador de audio se creaba cuando la aplicación estaba en primer plano, pero a veces un poco antes, cuando el Ninja todavía estaba en segundo plano. Si esto sucediera, la reproducción comenzaría a retrasarse.
Lecciones aprendidas
Este no es el último error que hemos corregido en la plataforma Android, pero fue el más difícil de localizar. Estaba fuera de la aplicación Netflix e incluso fuera del canal de reproducción, y todos los datos sin procesar indicaban un error en la propia aplicación Netflix.
La historia ilustra un aspecto de mi trabajo que me encanta: es imposible predecir todos los problemas que me plantearán nuestros socios. Y sé que resolverlos requiere comprender muchos sistemas, trabajar con excelentes colegas y esforzarse constantemente para aprender cosas nuevas. Lo que hago tiene un impacto directo en las personas reales y en su disfrute de un gran producto. Cuando la gente disfruta viendo Netflix en su sala de estar, sé que soy parte del equipo que lo hizo posible.