AAudio

AAudio es una API de Android C que se introdujo en la versión Android O. Está diseñada para aplicaciones de audio de alto rendimiento que requieren baja latencia. Las apps se comunican con AAudio mediante la lectura y escritura de datos en transmisiones.

La API de AAudio es mínima por diseño, no realiza las funciones siguientes:

  • Enumeración de dispositivos de audio
  • Enrutamiento automático entre los extremos de audio
  • E/S de archivos
  • Decodificación de audio comprimido
  • Presentación automática de todas las entradas/transmisiones en una sola devolución de llamada.

Transmisiones de audio

AAudio transfiere los datos de audio entre tu app y las entradas y salidas de audio en el dispositivo Android. Tu app recibe y envía datos leyendo transmisiones de audio y escribiendo en ellas, y estas se encuentran representadas por la estructura AAudioStream. Estas llamadas de lectura/escritura pueden incluir bloqueo o no.

Una transmisión se define conforme a lo siguiente:

  • El dispositivo de audio que es la fuente o el receptor de los datos de la transmisión.
  • El modo de uso compartido que determina si una transmisión tiene acceso exclusivo a un dispositivo de audio que, de lo contrario, puede compartirse entre varias transmisiones.
  • El formato de los datos de audio en la transmisión.

Dispositivo de audio

Cada transmisión se encuentra vinculada a un único dispositivo de audio.

Un dispositivo de audio es una interfaz de hardware o un extremo virtual que actúa como fuente o receptor de una transmisión continua de datos de audio digital. No confundas un dispositivo de audio (un micrófono integrado o auriculares Bluetooth) con el dispositivo Android (el teléfono o reloj) que ejecuta la app.

Puedes utilizar el método AudioManager getDevices() para descubrir los dispositivos de audio disponibles en tu dispositivo Android. El método devuelve información acerca del type de cada dispositivo.

Cada dispositivo de audio tiene un ID único en el dispositivo Android. Puedes utilizar el ID para vincular una transmisión de audio a un dispositivo de audio específico. Sin embargo, puedes permitir que AAudio elija el dispositivo principal predeterminado en la mayoría de los casos, en lugar de especificarlo tú.

El dispositivo de audio vinculado a una transmisión determina si la transmisión es de entrada o salida. Una transmisión puede transferir datos en una sola dirección. Cuando defines una transmisión, también estableces su dirección. Cuando abres una transmisión, Android comprueba que la dirección del dispositivo de audio y de la transmisión concuerden.

Modo de uso compartido

Una transmisión tiene un modo de uso compartido:

  • AAUDIO_SHARING_MODE_EXCLUSIVE significa que la transmisión tiene acceso exclusivo al dispositivo de audio; ninguna otra transmisión de audio puede utilizar el dispositivo. Si el dispositivo de audio ya está en uso, quizás no sea posible que la transmisión tenga acceso exclusivo. Es probable que las transmisiones exclusivas tengan una menor latencia, pero también es más probable que se desconecten. Debes cerrar las transmisiones exclusivas tan pronto dejes de necesitarlas, de modo que otras apps puedan acceder al dispositivo. Las transmisiones exclusivas proporcionan la menor latencia posible.
  • AAUDIO_SHARING_MODE_SHARED permite que AAudio combine audio. AAudio combina todas las transmisiones compartidas asignadas al mismo dispositivo.

Puedes configurar el modo de uso compartido de forma explícita cuando creas una transmisión. De manera predeterminada, el modo de uso compartido es SHARED.

Formato de audio

Los datos que se transfieren por medio de una transmisión tienen los atributos de audio digital habituales. Son los siguientes:

  • Formato de muestra
  • Muestras por trama
  • Tasa de muestreo

AAudio permite los formatos de muestra siguientes:

aaudio_format_t Tipo de datos C Notas
AAUDIO_FORMAT_PCM_I16 int16_t muestras comunes de 16 bits, formato Q0.15
AAUDIO_FORMAT_PCM_FLOAT float De -1.0 a +1.0

AAudio puede realizar conversiones de muestras por su cuenta. Por ejemplo, si una app está escribiendo datos FLOAT, pero la HAL utiliza PCM_I16, AAudio puede convertir las muestras automáticamente. La conversión puede llevarse a cabo en cualquier dirección. Si tu app procesa entrada de audio, es aconsejable verificar el formato de entrada y, en caso de que resulte necesario, estar listo para convertir datos como en el ejemplo a continuación:

aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
     convertFloatToPcm16(...)
}

Creación de una transmisión de audio

La biblioteca de AAudio respeta un patrón de diseño generador y proporciona AAudioStreamBuilder.

  1. Crea un AAudioStreamBuilder:

    AAudioStreamBuilder *builder;
    aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    

  2. Completa la configuración de la transmisión de audio en el generador mediante las funciones del configurador que corresponden a los parámetros de la transmisión. Se encuentran disponibles las siguientes funciones opcionales de configuración:

    AAudioStreamBuilder_setDeviceId(builder, deviceId);
    AAudioStreamBuilder_setDirection(builder, direction);
    AAudioStreamBuilder_setSharingMode(builder, mode);
    AAudioStreamBuilder_setSampleRate(builder, sampleRate);
    AAudioStreamBuilder_setChannelCount(builder, channelCount);
    AAudioStreamBuilder_setFormat(builder, format);
    AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    Ten en cuenta que estos métodos no informan errores, como una constante sin definir o un valor fuera de rango.

    Si no especificas el deviceId, el valor predeterminado será el dispositivo de salida principal. Si no especificas la dirección de transmisión, el valor predeterminado será una transmisión de salida. En cuanto a los demás parámetros, puede configurar explícitamente un valor o dejar que el sistema asigne el valor óptimo si no especificas ningún parámetro o lo configuras como AAUDIO_UNSPECIFIED.

    Para estar seguro, comprueba el estado de la transmisión de audio luego de crearla, como se explica en el paso 4 a continuación.

  3. Si AAudioStreamBuilder está configurado, utilízalo para crear una transmisión:

    AAudioStream *stream;
    result = AAudioStreamBuilder_openStream(builder, &stream);
    

  4. Luego de crear la transmisión, verifica la configuración. Si especificaste el formato de muestra, la tasa de muestreo o las muestras por trama, no se modificarán. Si especificaste el modo de uso compartido o la capacidad de búfer, es posible que se modifiquen según las capacidades del dispositivo de audio de la transmisión y del dispositivo Android en el cual se está ejecutando. Por una cuestión de buena programación defensiva, debes comprobar la configuración de la transmisión antes de utilizarla. Existen funciones para recuperar la configuración de la transmisión que corresponde a cada configuración del generador:

    AAudioStreamBuilder_setDeviceId() AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection() AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode() AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate() AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount() AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat() AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames() AAudioStream_getBufferCapacityInFrames()
  5. Puedes guardar el generador y volver a utilizarlo en el futuro para crear más transmisiones. Pero si no planeas volver a utilizarlo, deberías borrarlo.

    AAudioStreamBuilder_delete(builder);
    

Uso de una transmisión de audio

Transiciones de estado

Por lo general, una transmisión de AAudio se encuentra en uno de cinco estados estables (el estado de error, Disconnected, se describe al final de esta sección):

  • Open
  • Started
  • Paused
  • Flushed
  • Stopped

Los datos solo fluyen a través de una transmisión cuando esta se encuentra en estado Started. Para cambiar el estado de una transmisión, utiliza una de las funciones que solicitan una transición de estado:

aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);

Ten en cuenta que solo puedes solicitar una pausa o un vaciamiento en una transmisión de salida:

Estas funciones son asíncronas, y el cambio de estado no se produce de inmediato. Cuando solicitas un cambio de estado, la transmisión transfiere uno de los estados transitorios correspondientes:

  • Starting
  • Pausing
  • Flushing
  • Stopping
  • Closing

El diagrama de estados a continuación muestra los estados estables como rectángulos redondeados y los estados transitorios como rectángulos en línea de puntos. Si bien no se muestra, puedes llamar a close() desde cualquier estado.

Ciclo de vida de AAudio

AAudio no proporciona devolución de llamada para alertarte sobre los cambios de estado. Se puede utilizar una función especial, AAudioStream_waitForStateChange(stream, inputState, nextState, timeout) para esperar un cambio de estado.

La función no detecta un cambio de estado por su cuenta ni espera un estado específico. Espera hasta que el estado actual sea diferente del inputState, que especificas.

Por ejemplo, después de solicitar una pausa, una transmisión debe ingresar inmediatamente en el estado transitorio Pausing y luego llegar al estado Paused, si bien no hay garantías de que lo hará. Dado que no puedes esperar el estado Paused, utiliza waitForStateChange() para esperar cualquier estado distinto de Pausing. Aquí te mostramos cómo hacerlo:

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

Si el estado de la transmisión no es Pausing (el inputState, que suponemos que era el estado actual en el momento de la llamada), la función devuelve un resultado de inmediato. De lo contrario, se bloquea hasta que el estado ya no sea Pausing o hasta que se acabe el tiempo de espera. Cuando la función devuelve un resultado, el parámetro nextState muestra el estado actual de la transmisión.

Puedes utilizar esta misma técnica después de llamar a la operación de inicio, detención o vaciamiento de una solicitud, con el estado transitorio correspondiente inputState. No llames a waitForStateChange() luego de llamar a AAudioStream_close(), ya que se borrará la transmisión tan pronto como se cierre. Ni llames a AAudioStream_close() mientras waitForStateChange() se está ejecutando en otro subproceso.

Cómo leer una transmisión de audio y escribir en ella

Una vez iniciada la transmisión, puedes leerla o escribir en ella con las funciones AAudioStream_read(stream, buffer, numFrames, timeoutNanos) y AAudioStream_write(stream, buffer, numFrames, timeoutNanos).

Para un bloqueo de lectura o escritura que transfiera la cantidad de tramas especificada, configura timeoutNanos con un valor superior a cero. Para una llamada sin bloqueo, configura timeoutNanos en cero. En este caso, el resultado es el número real de tramas transferidas.

Cuando lees datos de entrada, debes verificar que se haya leído el número correcto de tramas. De lo contrario, el búfer puede contener datos desconocidos que podrían provocar una falla de audio. Puedes rellenar el búfer con ceros para crear un abandono silencioso:

aaudio_result_t result =
    AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
  // Error!
}
if (result != numFrames) {
  // pad the buffer with zeros
  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}

Puedes preparar el búfer de la transmisión antes de comenzar con la transmisión escribiendo datos o silencios en ella. Esto debe realizarse en una llamada sin bloqueo con timeoutNanos configurado en cero.

Los datos del búfer deben coincidir con el formato de datos que devuelve AAudioStream_getDataFormat().

Cómo cerrar una transmisión de audio

Cuando hayas terminado de usar una transmisión, ciérrala:

AAudioStream_close(stream);

Después de cerrar una transmisión, no podrás usarla con ninguna función basada en transmisión de AAudio.

Transmisión de audio desconectada

Una transmisión de audio puede desconectarse en cualquier momento si ocurre alguno de los eventos siguientes:

  • El dispositivo de audio asociado ya no está conectado (por ejemplo, cuando se desconectan los auriculares).
  • Se produce un error interno.
  • Un dispositivo de audio ya no es el dispositivo de audio principal.

Cuando se desconecta una transmisión, su estado es "Disconnected", y cualquier intento de ejecutar la función write() u otra devuelve AAUDIO_ERROR_DISCONNECTED. Cuando se desconecta una transmisión, solo puedes cerrarla.

Si necesitas que se te notifique cuando un dispositivo de audio se desconecta, escribe una función AAudioStream_errorCallback y regístrala con AAudioStreamBuilder_setErrorCallback().

La devolución de llamada debería comprobar el estado de la transmisión como se muestra en el ejemplo siguiente. No debes cerrar ni volver a abrir la transmisión desde la devolución de llamada, en su lugar, utiliza otro subproceso. Ten en cuenta que si abres una transmisión nueva, puede tener características diferentes a las de la transmisión original (por ejemplo, framesPerBurst):

void errorCallback(AAudioStream *stream,
                   void *userData,
                   aaudio_result_t error){

  aaudio_stream_state_t streamState = AAudioStream_getState(stream);
  if (streamState == AAUDIO_STREAM_STATE_DISCONNECTED){
    // Handle stream disconnect on a separate thread
    ...
  }
}

Optimización del rendimiento

Si deseas optimizar el rendimiento de una aplicación de audio, puedes ajustar los búferes internos y utilizar subprocesos de prioridad alta especiales.

Ajuste de los búferes para minimizar la latencia

AAudio envía y recibe datos en los búferes internos que mantiene, uno para cada dispositivo de audio.

Nota: No confundas los búferes internos de AAudio con el parámetro de búfer de las funciones de lectura y escritura de la transmisión de AAudio.

La capacidad del búfer es la cantidad total de datos que este puede contener. Puedes llamar a AAudioStreamBuilder_setBufferCapacityInFrames() para configurar la capacidad. El método limita la capacidad que puedes asignar al valor máximo que permite el dispositivo. Usa AAudioStream_getBufferCapacityInFrames() para verificar la capacidad real del búfer.

Una app no debe utilizar la capacidad total de un búfer. AAudio llenará un búfer hasta un tamaño que puedes configurar. El tamaño de un búfer no puede superar su capacidad y, por lo general, es más pequeño. Mediante el control del tamaño del búfer, determinas la cantidad de picos de actividad necesarios para llenarlo y, por lo tanto, controlas la latencia. Utiliza los métodos AAudioStreamBuilder_setBufferSizeInFrames() y AAudioStreamBuilder_getBufferSizeInFrames() para trabajar con el tamaño del búfer.

Cuando una aplicación reproduce audio, escribe en un búfer y lo bloquea hasta completar la escritura. AAudio lee del búfer en picos de actividad discretos. Cada pico de actividad contiene varias tramas de audio y, por lo general, su tamaño es inferior al del búfer que se lee. El sistema controla la tasa y el tamaño de los picos de actividad; el circuito del dispositivo de audio es el que generalmente determina estas propiedades. Si bien no puedes cambiar el tamaño de un pico de actividad o la tasa de picos de actividad, puedes configurar el tamaño del búfer interno conforme a la cantidad de picos de actividad que contiene. Por lo general, se obtiene la menor latencia si el tamaño de búfer de AAudioStream es múltiplo del tamaño de pico de actividad informado.

      Almacenamiento en búfer de AAudio

Una manera de optimizar el tamaño del búfer es comenzar con un búfer grande y reducirlo gradualmente hasta llegar al agotamiento y luego volver a incrementarlo para corregirlo. Como alternativa, puedes comenzar con un tamaño de búfer pequeño y, si se agota, aumentar el tamaño del búfer hasta que los datos de salida fluyan sin problema nuevamente.

Este proceso puede ser muy rápido, incluso puede completarse antes de que el usuario reproduzca el primer sonido. Es recomendable que realices primero la determinación del tamaño del búfer inicial, con silencio, de modo que el usuario no escuche fallas de audio. Es posible que el rendimiento del sistema cambie con el tiempo (por ejemplo, el usuario puede desactivar el modo de avión). Dado que el ajuste de búfer suma muy poca sobrecarga, tu app puede hacerlo continuamente mientras la app lee o escribe datos en un sistema.

A continuación, te mostramos un ejemplo de un bucle de optimización del búfer:

int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);

int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);

while (go) {
    result = writeSomeData();
    if (result < 0) break;

    // Are we getting underruns?
    if (bufferSize < bufferCapacity) {
        int32_t underrunCount = AAudioStream_getXRunCount(stream);
        if (underrunCount > previousUnderrunCount) {
            previousUnderrunCount = underrunCount;
            // Try increasing the buffer size by one burst
            bufferSize += framesPerBurst;
            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
        }
    }
}

Utilizar esta técnica para optimizar el tamaño del búfer para una transmisión de entrada no conlleva ninguna ventaja. Las transmisiones de entrada se ejecutan lo más rápido posible, intentan minimizar la cantidad de datos almacenados en el búfer y después lo completan cuando se evita la app.

Uso de una devolución de llamada de alta prioridad

Si tu app lee o escribe datos de audio de un subproceso ordinario, es posible que se la evite o experimente fluctuación en el tiempo. Esto puede causar fallas de audio. Los búferes más grandes pueden servir como protección contra dichas fallas, pero también traen aparejada una mayor latencia de audio. En el caso de las aplicaciones que requieren latencia baja, una transmisión de audio puede utilizar una función de devolución de llamada asíncrona para transferir datos hacia la app y desde ella. AAudio ejecuta la devolución de llamada en un subproceso con prioridad más alta y mejor rendimiento.

La función de devolución de llamada tiene el siguiente prototipo:

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

Usa la generación de transmisiones para registrar la devolución de llamada:

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

En el caso más simple, la transmisión ejecuta periódicamente la función de devolución de llamada para adquirir los datos del próximo pico de actividad.

La función de devolución de llamada no debe realizar tareas de lectura ni escritura en la transmisión que la invocó. Si la devolución de llamada pertenece a una transmisión de entrada, el código debe procesar los datos que se suministran en el búfer audioData (especificado como tercer argumento). Si la devolución de llamada pertenece a una transmisión de salida, el código debe colocar los datos en el búfer.

Por ejemplo, podrías utilizar una devolución de llamada para generar continuamente una salida de onda sinusoidal como la siguiente:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    int64_t timeout = 0;

    // Write samples directly into the audioData array.
    generateSineWave(static_cast<float *>(audioData), numFrames);
    return AAUDIO_CALLABCK_RESULT_CONTINUE;
}

Es posible procesar más de una transmisión con AAudio. Puedes utilizar una transmisión como la principal y transferir punteros a otras transmisiones en los datos de usuario. Registra una devolución de llamada para la transmisión principal. Luego, utiliza E/S sin bloqueo en las demás transmisiones. A continuación, te mostramos un ejemplo de una devolución de llamada de ida y vuelta que transfiere una transmisión de entrada a una de salida. La transmisión de llamada principal es la de salida. La de entrada está incluida en los datos del usuario.

La devolución de llamada realiza una lectura sin bloqueo de la transmisión de entrada colocando los datos en el búfer de la transmisión de salida:

aaudio_data_callback_result_t myCallback(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames) {
    AAudioStream *inputStream = (AAudioStream *) userData;
    int64_t timeout = 0;
    aaudio_result_t result =
        AAudioStream_read(inputStream, audioData, numFrames, timeout);

  if (result == numFrames)
      return AAUDIO_CALLABCK_RESULT_CONTINUE;
  if (result >= 0) {
      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
      return AAUDIO_CALLBACK_RESULT_CONTINUE;
  }
  return AAUDIO_CALLBACK_RESULT_STOP;
}

Ten en cuenta que, en este ejemplo, se asume que las transmisiones de entrada y salida poseen igual cantidad de canales, formato y tasa de muestreo. El formato de las transmisiones puede no coincidir; siempre y cuando el código realice las traducciones correctamente.

Configuración del modo de rendimiento

Cada AAudioStream tiene un modo de rendimiento con un gran efecto sobre el comportamiento de la app. Existen tres modos:

  • AAUDIO_PERFORMANCE_MODE_NONE es el modo predeterminado. Emite una transmisión básica que equilibra el ahorro de latencia y energía.
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY utiliza búferes más pequeños y una ruta de acceso de datos optimizada para una menor latencia.
  • AAUDIO_PERFORMANCE_MODE_POWER_SAVING utiliza búferes internos más grandes y una ruta de acceso de datos que intercambia latencia por menor energía.

Puedes seleccionar el modo de rendimiento llamando a setPerformanceMode(), y descubrir el modo actual llamando a getPerformanceMode().

Si la latencia baja tiene prioridad por sobre el ahorro de energía en tu aplicación, utiliza AAUDIO_PERFORMANCE_MODE_LOW_LATENCY. Este modo es útil para las apps muy interactivas, como juegos o sintetizadores con teclado.

Si el ahorro de energía tiene prioridad por sobre la latencia baja en tu aplicación, utiliza AAUDIO_PERFORMANCE_MODE_POWER_SAVING. Este modo es el típico para apps que reproducen música ya generada, como es el caso de la transmisión de audio o los reproductores de archivos MIDI.

En la versión actual de AAudio, para lograr la menor latencia posible, debes utilizar el modo de rendimiento AAUDIO_PERFORMANCE_MODE_LOW_LATENCY junto con una devolución de llamada de alta prioridad. Sigue este ejemplo:

// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);

// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);

Seguridad del subproceso

La API de AAudio no es completamente segura para el subproceso. No puedes llamar a algunas funciones de AAudio conjuntamente desde más de un subproceso a la vez. Esto se debe a que AAudio evita utilizar exclusiones mutuas, que pueden provocar fallas en los subprocesos y evitarlos.

Como medida de seguridad, no llames a AAudioStream_waitForStateChange() ni realices tareas de lectura o escritura en la misma transmisión desde dos subprocesos diferentes. Del mismo modo, no cierres una transmisión en un subproceso mientras completas en ella tareas de lectura o escritura desde otro subproceso.

Las llamadas que devuelven configuraciones de transmisión, como AAudioStream_getSampleRate() y AAudioStream_getChannelCount(), son seguras para el subproceso.

Estas llamadas también son seguras para el subproceso:

  • AAudio_convert*ToText()
  • AAudio_createStreamBuilder()
  • AAudioStream_get*() excepto por AAudioStream_getTimestamp()

Nota: Cuando una transmisión utiliza una función de devolución de llamada, es seguro leer/escribir desde la transmisión de devolución de llamada al mismo tiempo que se cierra la transmisión desde el subproceso en el cual se ejecuta.

Ejemplos de código

Existen dos pequeñas apps de demostración de AAudio en nuestra página de GitHub:

  • Hello-Audio genera una onda sinusoidal y reproduce audio.
  • Echo demuestra cómo implementar un bucle de audio de ida y vuelta de entrada/salida. Asigna una función de devolución de llamada que sincroniza dos transmisiones y realiza ajustes continuos del búfer.

Para obtener más información sobre cómo lograr una latencia de salida óptima y evitar fallas de audio con OpenSL ES, consulta Simple Synth.

Problemas conocidos

  • La latencia de audio es alta para bloquear la función write() debido a que la versión de Android O DP2 no utiliza una pista RÁPIDA. Utiliza una devolución de llamada para obtener menor latencia.