Latencia de audio

La latencia es el tiempo que tarda 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 la generación de una muestra de audio por la app y la reproducción de la muestra mediante un conector para auriculares o una bocina incorporada.
  • La latencia de entrada de audio es el tiempo que transcurre entre la recepción de una señal de audio por un dispositivo de entrada de audio, como un micrófono, y cuando 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 que un usuario toca la pantalla y 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.

Cómo medir la latencia

Es difícil medir la latencia de entrada y salida de audio de forma aislada, ya que se debe conocer exactamente cuándo se envía la primera muestra a la ruta de acceso de audio (aunque se puede saber con un circuito de prueba de luz y un osciloscopio). Si conoces la latencia de audio de ida y vuelta, puedes usar una regla general: la latencia de entrada (y de salida) de audio es la mitad de la latencia de audio de ida y vuelta 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 compilación de Android. Consulta las mediciones publicadas para obtener una idea general de la latencia de ida y de vuelta en dispositivos Nexus.

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 la latencia más baja se logra con rutas de acceso de audio con procesamiento de señal mínimo, también puedes usar una llave de bucle de retorno de audio que permita 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 el CDD, se especifica la latencia de entrada y salida como 20 ms o menos (aunque los músicos en general requieren 10 ms), porque hay casos prácticos importantes que se admiten con 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 durante el tiempo de ejecución. Sin embargo, puedes usar las siguientes marcas de función de hardware para saber si el dispositivo ofrece garantías con respecto a la latencia:

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

A continuación, te mostramos cómo 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 hagas suposiciones sobre el rendimiento del audio

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

  • No supongas que las bocinas y los micrófonos usados en dispositivos móviles suelen tener buena acústica. Por su tamaño pequeñ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 hay garantías sobre el orden relativo de las devoluciones de llamada ni la sincronización de los relojes de audio, incluso cuando ambos extremos usen la misma tasa de muestreo. Tu aplicación debería 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 que 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 están en diferentes rutas de acceso. Por ejemplo, si capturas el micrófono del dispositivo a una tasa de muestreo nominal de 48,000 Hz y reproduces 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 de la calidad de audio) para la conversión asincrónica de la tasa de muestreo es duplicar muestras, o bien omitirlas, según sea necesario, cerca de un punto de cruce en cero. También se pueden realizar conversiones más sofisticadas.

Minimiza la latencia de entrada

En esta sección, hay recomendaciones para ayudarte a reducir la latencia de entrada de audio en grabaciones con un micrófono incorporado al dispositivo o un micrófono de auriculares externos.

  • Si tu app supervisa 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 solo el uso de auriculares no garantiza la menor latencia posible. Es posible que debas tomar otras medidas para quitar el procesamiento de señales indeseadas 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) con PROPERTY_OUTPUT_SAMPLE_RATE. Puede haber otras tasas de muestreo, aunque son poco comunes.
  • Debes prepararte para admitir el tamaño del búfer informado por 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 hay otros valores posibles.

Minimiza la latencia de salida

Usa la tasa de muestreo óptima cuando crees tu reproductor de audio

Para obtener la latencia más baja, debes proporcionar datos de audio que coincidan con la tasa de muestreo y el tamaño del búfer óptimos del dispositivo. Si quieres 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 = 1,000 mHz).

Usa el tamaño del búfer óptimo para colocar datos de audio en una cola

Puedes obtener el tamaño del búfer óptimo de forma similar a la tasa de muestreo óptima con 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 capa de abstracción de hardware (HAL) 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 marcos 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 la HAL difieren según el dispositivo y la versión de Android.

No agregues interfaces de salida que requieran procesamiento de señales

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 tu solicitud de pista rápida se rechace:

  • 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 la terminal:
  5. adb shell dumpsys media.audio_flinger
    
  6. Busca el ID del proceso. Si ves una F en la columna Name, está en una pista de latencia baja (la F significa pista rápida).

Minimiza la latencia de preparación

Cuando colocas datos de audio en una cola por primera vez, el circuito de audio del dispositivo tardará un tiempo breve, pero importante, 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;
    }

Cuando se debe reproducir audio, puedes cambiar y colocar en cola búferes con 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 muestre 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