Latencia de audio

La latencia es el tiempo que demora una señal en atravesar un sistema. Estos son los tipos de latencia más comunes relacionados con apps de audio:

  • La latencia de salida de audio es el tiempo que transcurre entre el momento en que una app genera una muestra de audio y la reproducción de la muestra mediante un conector para audífonos o una bocina incorporada.
  • La latencia de entrada de audio es el tiempo que transcurre entre el momento en que la entrada de audio de un dispositivo, como un micrófono, recibe una señal de audio y el momento en que esos datos de audio están disponibles para una app.
  • La latencia de ida y vuelta es la suma de la latencia de entrada, el tiempo de procesamiento de la app y la latencia de salida.

  • La latencia de toque es el tiempo que transcurre entre el momento en que un usuario toca la pantalla y el momento en que la app recibe ese evento de toque.
  • La latencia de preparación es el tiempo que tarda el inicio de la canalización de audio la primera vez que se ponen datos en cola en un búfer.

En esta página, se describe cómo desarrollar tu app de audio con entrada y salida de baja latencia, y cómo evitar la latencia de preparación.

Medición de la latencia

Es difícil medir la latencia de entrada y salida de audio de forma aislada, ya que se debe conocer exactamente el momento en que se envía la primera muestra a la ruta de acceso de audio (aunque es posible saberlo si se usa un circuito de prueba de luz y un osciloscopio). Si conoces la latencia de audio de ida y vuelta, puedes usar la regla general: La latencia de entrada (y de salida) de audio es la mitad de la latencia de audio completa en las rutas de acceso sin procesamiento de señal.

La latencia de audio de ida y vuelta varía notablemente según el modelo del dispositivo y la versión de Android. Puedes obtener una idea general de la latencia de ida y de vuelta para dispositivos Nexus si consultas las mediciones publicadas.

Para medir la latencia de audio de ida y vuelta, puedes crear una app que genere una señal de audio, reciba esa señal y mida el tiempo transcurrido entre el envío y la recepción de la señal. Como alternativa, puedes instalar esta app de prueba de latencia, que realiza una prueba de latencia de ida y vuelta mediante la prueba de Larsen. También puedes ver el código fuente para la app de prueba de latencia.

Como la latencia más baja se logra mediante de rutas de acceso de audio con procesamiento de señal mínimo, también puedes usar un Dongle de bucle de retorno de audio, que permite la ejecución de la prueba por medio del conector de los auriculares.

Prácticas recomendadas para minimizar la latencia

Validación del rendimiento de audio

En el documento de Definición de compatibilidad de Android (CDD), se enumeran los requisitos de hardware y software para un dispositivo Android compatible. Consulta Compatibilidad con Android para obtener más información sobre el programa de compatibilidad general y el CDD a fin de acceder al documento real del CDD.

En este último, se especifica la latencia de entrada y salida en 20 ms o un valor inferior (aunque los músicos generalmente requieren 10 ms), porque hay casos prácticos que se producen en 20 ms.

Actualmente no hay una API que permita determinar la latencia de audio por medio de una ruta de acceso en un dispositivo Android en el tiempo de ejecución. Sin embargo, puedes usar las siguientes marcas de función de hardware para saber si el dispositivo tiene especificaciones con respecto a la latencia:

Los criterios para informar estos indicadores se definen en las secciones 5.6 Latencia de audio y 5.10 Audio profesional del CDD.

A continuación, te mostramos la manera de comprobar estas características en Java:

Kotlin

    val hasLowLatencyFeature: Boolean =
            packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)

    val hasProFeature: Boolean =
            packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO)
    

Java

    boolean hasLowLatencyFeature =
        getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);

    boolean hasProFeature =
        getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);
    

En cuanto a la relación de las funciones de audio, android.hardware.audio.low_latency es un requisito previo para android.hardware.audio.pro. Un dispositivo puede implementar android.hardware.audio.low_latency y no implementar android.hardware.audio.pro, pero no a la inversa.

No realices suposiciones respecto del rendimiento del audio.

Ten en cuenta las siguientes suposiciones para evitar problemas de latencia:

  • No supongas que las bocinas y los micrófonos empleados en dispositivos móviles generalmente tienen buena acústica. Debido a su pequeño tamaño, la acústica no suele ser buena. Por lo tanto, se agrega procesamiento de señal para mejorar la calidad del sonido. Ese procesamiento de señal introduce latencia.
  • No supongas que las devoluciones de llamada de entrada y salida están sincronizadas. Para lograr entrada y salida simultáneas, se usan controladores de finalización de la cola de búfer independientes para cada lado. No se garantiza el orden relativo de estas devoluciones de llamada ni la sincronización de los relojes de audio, incluso cuando ambos extremos usen la misma tasa de muestreo. Tu app debe almacenar los datos en el búfer con la sincronización de búfer adecuada.
  • No supongas que la tasa de muestreo real coincide exactamente con la nominal. Por ejemplo, si la tasa de muestreo nominal es de 48,000 Hz, es normal que el reloj de audio avance a una velocidad ligeramente diferente de la de CLOCK_MONOTONIC del sistema operativo. Esto ocurre porque los relojes de audio y del sistema pueden provenir de cristales diferentes.
  • No supongas que la tasa de muestreo de reproducción real coincide exactamente con la de captura real, en especial si los extremos se encuentran en diferentes rutas de acceso. Por ejemplo, si realizas una captura del micrófono del dispositivo a una tasa de muestreo nominal de 48,000 Hz y la reproducción en audio USB a una tasa de muestreo nominal de 48,000 Hz, es probable que las tasas de muestreo reales difieran ligeramente entre sí.

Una consecuencia de los relojes de audio posiblemente independientes es la necesidad de conversión asíncrona de la tasa de muestreo. Una técnica sencilla (aunque no ideal en términos a la calidad de audio) para la conversión asincrónica de la tasa de muestreo es duplicar, o bien omitir muestras, según sea necesario, cerca de un punto de cruce en cero. También se pueden realizar conversiones más sofisticadas.

Cómo minimizar la latencia de entrada

En esta sección, se proporcionan recomendaciones para ayudarte a reducir la latencia de entrada de audio durante la grabación con un micrófono incorporado al dispositivo o un micrófono integrado a auriculares externos.

  • Si tu app controla la entrada, recomienda a los usuarios que usen auriculares (por ejemplo, muéstrales la pantalla Se recomienda el uso de auriculares en la primera ejecución). Ten en cuenta que el uso de auriculares no garantiza la menor latencia posible. Es posible que debas tomar otras medidas para quitar el procesamiento de señales no deseadas de la ruta de acceso de audio, como usar el ajuste predeterminado VOICE_RECOGNITION durante la grabación.
  • Debes prepararte para admitir tasas de muestreo nominales de 44,100 y 48,000 Hz, como lo indica getProperty(String) para PROPERTY_OUTPUT_SAMPLE_RATE. Puede haber otras tasas de muestreo, aunque son poco comunes.
  • Debes prepararte para admitir el tamaño del búfer, como informa getProperty(String) con PROPERTY_OUTPUT_FRAMES_PER_BUFFER. Los tamaños del búfer típicos son de 96, 128, 160, 192, 240, 256 o 512 marcos, pero también se pueden usar otros valores.

Cómo minimizar la latencia de salida

Cuando crees tu reproductor de audio, usa la tasa de muestreo óptima.

Para obtener la latencia más baja, debes proporcionar datos de audio que coincidan con la tasa de muestreo y el tamaño de búfer óptimos del dispositivo. Para obtener más información, consulta Diseños para lograr latencia reducida.

En Java, puedes obtener la tasa de muestreo óptima de AudioManager como se muestra en el siguiente ejemplo de código:

Kotlin

    val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val sampleRateStr: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
    var sampleRate: Int = sampleRateStr?.let { str ->
        Integer.parseInt(str).takeUnless { it == 0 }
    } ?: 44100 // Use a default value if property not found
    

Java

    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    String sampleRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
    int sampleRate = Integer.parseInt(sampleRateStr);
    if (sampleRate == 0) sampleRate = 44100; // Use a default value if property not found
    

Una vez que conozcas la tasa de muestreo óptima, puedes proporcionarla cuando crees tu reproductor. En este ejemplo, se usa OpenSL ES:

    // create buffer queue audio player
    void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer
            (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer)
    {
       ...
       // specify the audio source format
       SLDataFormat_PCM format_pcm;
       format_pcm.numChannels = 2;
       format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000;
       ...
    }
    

Nota: samplesPerSec hace referencia a la tasa de muestreo por canal en milihertz (1 Hz = 1000 mHz).

Usa el tamaño de búfer óptimo al disponer en cola datos de audio

Puedes obtener el tamaño de búfer óptimo de forma similar a la tasa de muestreo óptima, mediante la API de AudioManager:

Kotlin

    val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val framesPerBuffer: String? = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
    var framesPerBufferInt: Int = framesPerBuffer?.let { str ->
        Integer.parseInt(str).takeUnless { it == 0 }
    } ?: 256 // Use default
    

Java

    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    int framesPerBufferInt = Integer.parseInt(framesPerBuffer);
    if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default
    

La propiedad PROPERTY_OUTPUT_FRAMES_PER_BUFFER indica el número de marcos de audio que el búfer de la HAL (capa de abstracción de hardware) puede contener. Debes construir tus búferes de audio de modo que contengan un múltiplo exacto de ese número. Si usas la cantidad correcta de tramas de audio, tus devoluciones de llamada se realizan en intervalos regulares, lo que reduce el jitter.

Es importante que uses la API para determinar el tamaño del búfer en lugar de usar un valor codificado, ya que los tamaños del búfer de HAL difieren según el dispositivo y la versión de Android.

Evita agregar interfaces de salida que requieran procesamiento de señal

El mezclador rápido solo admite las siguientes interfaces:

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

Estas interfaces no están permitidas, ya que requieren procesamiento de señal y harán que se rechace tu solicitud de pista rápida:

  • SL_IID_BASSBOOST
  • SL_IID_EFFECTSEND
  • SL_IID_ENVIRONMENTALREVERB
  • SL_IID_EQUALIZER
  • SL_IID_PLAYBACKRATE
  • SL_IID_PRESETREVERB
  • SL_IID_VIRTUALIZER
  • SL_IID_ANDROIDEFFECT
  • SL_IID_ANDROIDEFFECTSEND

Cuando crees un reproductor, asegúrate de agregar solo interfaces fast, como se muestra en el siguiente ejemplo:

    const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
    

Verifica que estés usando una pista de baja latencia

Realiza estos pasos para verificar que hayas obtenido correctamente una pista de baja latencia:

  1. Inicia tu app y luego ejecuta el siguiente comando:
  2.     adb shell ps | grep your_app_name
        
  3. Toma nota del ID del proceso de tu app.
  4. Ahora, reproduce parte del audio desde tu app. Tienes aproximadamente tres segundos para ejecutar el siguiente comando desde el terminal:
  5.     adb shell dumpsys media.audio_flinger
        
  6. Busca el ID del proceso. Si ves una F en la columna Nombre, está en una pista de latencia baja (la F significa pista rápida).

Cómo minimizar la latencia de preparación

Cuando colocas datos de audio en una cola por primera vez, el circuito de audio del dispositivo tardará poco tiempo, aunque no sea insignificante, en prepararse. Para evitar esta latencia de preparación, puedes colocar en cola búferes de datos de audio que contengan silencio, como se muestra en el siguiente ejemplo de código:

    #define CHANNELS 1
    static short* silenceBuffer;
    int numSamples = frames * CHANNELS;
    silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples);
        for (i = 0; i<numSamples; i++) {
            silenceBuffer[i] = 0;
        }
    

En el momento en que se debe producir audio, puedes realizar un cambio y colocar en cola búferes que contengan datos de audio reales.

Nota: La salida constante de audio consume mucha energía. Asegúrate de detener la salida en el método onPause(). Considera también la posibilidad de pausar la salida silenciosa después de un tiempo determinado de inactividad del usuario.

Código de ejemplo adicional

Para descargar una app de muestra que presente la latencia de audio, consulta Muestras de NDK.

Más información

  1. Latencia de audio para desarrolladores de apps
  2. Colaboradores con la latencia de audio
  3. Cómo medir la latencia de audio
  4. Preparación de audio
  5. Latencia (audio)
  6. Tiempo de demora de ida y vuelta