Colaboradores con la latencia de audio

En esta página, se analizan los factores que contribuyen a la latencia de salida, pero se aplica un análisis similar a la latencia de entrada.

Si suponemos que el circuito analógico no contribuye de manera significativa, los principales contribuyentes a la latencia de audio a nivel de la superficie son los siguientes:

  • Aplicación
  • Cantidad total de búferes en la canalización
  • Tamaño de cada búfer, en fotogramas
  • Latencia adicional después del procesador de la app, como de un DSP

Por más precisa que sea la lista de colaboradores anterior, también es engañosa. El motivo es que el recuento y el tamaño del búfer son más un efecto que una causa. Lo que suele suceder es que se implementa y prueba un esquema de búfer determinado, pero durante la prueba, se escucha un error de subdesbordamiento o desbordamiento de audio como un "clic" o un "pop". Para compensar, el diseñador del sistema aumenta los tamaños o los recuentos de búferes. Esto tiene el resultado deseado de eliminar las subcargas o sobrecargas, pero también tiene el efecto secundario no deseado de aumentar la latencia. Para obtener más información sobre los tamaños de búfer, consulta el video Latencia de audio: tamaños de búfer.

Un mejor enfoque es comprender las causas de los subdesbordamientos y los desbordamientos, y luego corregirlos. Esto elimina los artefactos audibles y puede permitir incluso menos búferes o más pequeños, lo que reduce la latencia.

Según nuestra experiencia, las causas más comunes de los subdesbordamientos y los desbordamientos son las siguientes:

  • CFS de Linux (programador completamente justo)
  • subprocesos de alta prioridad con programación SCHED_FIFO
  • inversión de prioridad
  • latencia de programación larga
  • Controladores de interrupción de larga duración
  • tiempo de inhabilitación de interrupción larga
  • administración de energía
  • kernels de seguridad

Programación de CFS y SCHED_FIFO de Linux

El CFS de Linux está diseñado para ser justo con las cargas de trabajo en competencia que comparten un recurso de CPU común. Esta equidad se representa con un parámetro nice por subproceso. El valor de nice varía de -19 (menos agradable o con más tiempo de CPU asignado) a 20 (más agradable o con menos tiempo de CPU asignado). En general, todos los subprocesos con un valor de prioridad determinado reciben un tiempo de CPU aproximadamente igual, y los subprocesos con un valor de prioridad numéricamente más bajo deberían recibir más tiempo de CPU. Sin embargo, el CFS es “justo” solo en períodos de observación relativamente largos. En ventanas de observación a corto plazo, CFS puede asignar el recurso de la CPU de formas inesperadas. Por ejemplo, puede quitar la CPU de un subproceso con una prioridad numérica baja a un subproceso con una prioridad numérica alta. En el caso del audio, esto puede provocar una falta o un exceso de datos.

La solución obvia es evitar CFS para subprocesos de audio de alto rendimiento. A partir de Android 4.1, estos subprocesos ahora usan la política de programación SCHED_FIFO en lugar de la política de programación SCHED_NORMAL (también llamada SCHED_OTHER) que implementa CFS.

Prioridades de SCHED_FIFO

Aunque los subprocesos de audio de alto rendimiento ahora usan SCHED_FIFO, aún son susceptibles a otros subprocesos SCHED_FIFO de prioridad más alta. Por lo general, se trata de subprocesos de trabajo del kernel, pero también puede haber algunos subprocesos de usuario que no sean de audio con la política SCHED_FIFO. Las prioridades de SCHED_FIFO disponibles varían de 1 a 99. Los subprocesos de audio se ejecutan con prioridad 2 o 3. Esto deja la prioridad 1 disponible para los subprocesos de prioridad más baja y las prioridades del 4 al 99 para los subprocesos de prioridad más alta. Te recomendamos que uses la prioridad 1 siempre que sea posible y que reserves las prioridades del 4 al 99 para los subprocesos que se completan dentro de un período limitado, se ejecutan con un período más corto que el de los subprocesos de audio y se sabe que no interfieren con la programación de los subprocesos de audio.

Programación monótona de la tasa

Para obtener más información sobre la teoría de la asignación de prioridades fijas, consulta el artículo de Wikipedia sobre programación monótona de tasas (RMS). Un punto clave es que las prioridades fijas deben asignarse estrictamente en función del período, con prioridades más altas asignadas a subprocesos de períodos más cortos, no en función de la "importancia" percibida. Los subprocesos no periódicos se pueden modelar como subprocesos periódicos, con la frecuencia máxima de ejecución y el procesamiento máximo por ejecución. Si un subproceso no periódico no se puede modelar como un subproceso periódico (por ejemplo, podría ejecutarse con una frecuencia ilimitada o un procesamiento ilimitado por ejecución), no se le debe asignar una prioridad fija, ya que eso sería incompatible con la programación de subprocesos periódicos reales.

Inversión de prioridad

La inversión de prioridad es un modo de falla clásico de los sistemas en tiempo real, en el que una tarea de prioridad más alta se bloquea durante un tiempo ilimitado a la espera de que una tarea de prioridad más baja libere un recurso, como un mutex (estado compartido protegido por un mutex). Consulta el artículo "Cómo evitar la inversión de prioridad" para conocer las técnicas que te ayudarán a mitigarla.

Latencia de programación

La latencia de programación es el tiempo entre el momento en que un subproceso está listo para ejecutarse y el momento en que se completa el cambio de contexto resultante para que el subproceso se ejecute en una CPU. Cuanto menor sea la latencia, mejor, y cualquier valor superior a dos milisegundos causa problemas de audio. Es más probable que la latencia de programación prolongada se produzca durante las transiciones de modo, como iniciar o cerrar una CPU, cambiar entre un kernel de seguridad y el kernel normal, cambiar del modo de máxima potencia al modo de baja potencia o ajustar la frecuencia y el voltaje del reloj de la CPU.

Interrupciones

En muchos diseños, la CPU 0 controla todas las interrupciones externas. Por lo tanto, un controlador de interrupciones de larga duración puede retrasar otras interrupciones, en particular, las interrupciones de finalización de acceso directo a la memoria (DMA) de audio. Diseña controladores de interrupción para que finalicen rápidamente y aplacen el trabajo prolongado a un subproceso (preferentemente, un subproceso de CFS o SCHED_FIFO de prioridad 1).

De manera equivalente, inhabilitar las interrupciones en la CPU 0 durante un período prolongado tiene el mismo resultado de retrasar el servicio de las interrupciones de audio. Los tiempos de inhabilitación de interrupciones largos suelen ocurrir mientras se espera un bloqueo de giro del kernel. Revisa estos bloqueos de giro para asegurarte de que estén delimitados.

Administración de energía, rendimiento y térmica

La administración de energía es un término amplio que abarca los esfuerzos para supervisar y reducir el consumo de energía y, al mismo tiempo, optimizar el rendimiento. La administración térmica y el enfriamiento de computadoras son similares, pero buscan medir y controlar el calor para evitar daños por exceso de calor. En el kernel de Linux, el administrador de la CPU es responsable de la política de bajo nivel, mientras que el modo de usuario configura la política de alto nivel. Entre las técnicas que se usan, se incluyen las siguientes:

  • escalamiento dinámico de voltaje
  • escalamiento dinámico de frecuencia
  • Habilitación del núcleo dinámico
  • cambio de clúster
  • control de acceso de energía
  • Hotplug (cambio en caliente)
  • varios modos de suspensión (detención, parada, inactividad, suspensión, etcétera)
  • migración de procesos
  • afinidad del procesador

Algunas operaciones de administración pueden provocar "interrupciones del trabajo" o períodos durante los cuales el procesador de aplicaciones no realiza ninguna tarea útil. Estas interrupciones del trabajo pueden interferir con el audio, por lo que dicha administración debe diseñarse para una interrupción del trabajo aceptable en el peor de los casos mientras el audio está activo. Por supuesto, cuando la deriva térmica es inminente, evitar el daño permanente es más importante que el audio.

Kernels de seguridad

Un kernel de seguridad para la administración de derechos digitales (DRM) puede ejecutarse en los mismos núcleos de procesadores de aplicaciones que los que se usan para el kernel del sistema operativo principal y el código de la aplicación. Cualquier momento durante el cual una operación del kernel de seguridad esté activa en un núcleo es, en realidad, una detención del trabajo ordinario que normalmente se ejecutaría en ese núcleo. En particular, esto puede incluir trabajo de audio. Por su naturaleza, el comportamiento interno de un kernel de seguridad es inescrutable desde las capas de nivel superior y, por lo tanto, cualquier anomalía de rendimiento causada por un kernel de seguridad es especialmente perniciosa. Por ejemplo, las operaciones del kernel de seguridad no suelen aparecer en los registros de cambio de contexto. Lo llamamos "tiempo oscuro", es decir, el tiempo que transcurre y que aún no se puede observar. Los kernels de seguridad deben diseñarse para una detención del trabajo aceptable en el peor de los casos mientras el audio está activo.