Latenza audio

La latenza è il tempo impiegato da un segnale per attraversare un sistema. Di seguito sono riportati i tipi comuni di latenza relativa alle app audio:

  • La latenza dell'uscita audio è il tempo che intercorre tra la generazione di un campione audio da parte di un'app e la riproduzione del campione tramite il jack per cuffie o l'altoparlante integrato.
  • La latenza dell'input audio è il tempo che intercorre tra la ricezione di un segnale audio da parte dell'input audio di un dispositivo, ad esempio il microfono, e la disponibilità degli stessi dati audio per un'app.
  • La latenza di round trip è la somma della latenza di input, del tempo di elaborazione delle app e della latenza di output.

  • La latenza del tocco è il tempo che intercorre tra il tocco dello schermo da parte dell'utente e la ricezione dell'evento di tocco da parte di un'app.
  • La latenza di riscaldamento è il tempo necessario per avviare la pipeline audio la prima volta che i dati vengono accodati in un buffer.

Questa pagina descrive come sviluppare la tua app audio con input e output a bassa latenza e come evitare la latenza di riscaldamento.

Misura la latenza

È difficile misurare la latenza dell'input audio e dell'uscita in modo isolato, poiché è necessario sapere esattamente quando il primo campione viene inviato nel percorso audio (anche se questo può essere fatto utilizzando un circuito di test della luce e un oscilloscopio). Se conosci la latenza audio andata e ritorno, puoi applicare la regola generale: la latenza dell'input audio (e dell'output) è la metà della latenza audio round trip per i percorsi senza elaborazione del segnale.

La latenza audio di round trip varia notevolmente in base al modello di dispositivo e alla build Android. Per farti un'idea della latenza round trip per i dispositivi Nexus, leggi le misure pubblicate.

Puoi misurare la latenza audio di andata e ritorno creando un'app che genera un segnale audio, lo ascolti e misuri il tempo che intercorre tra l'invio e la ricezione.

Poiché la latenza più bassa si ottiene sui percorsi audio con un'elaborazione del segnale minima, ti consigliamo di utilizzare anche una chiavetta di loopback audio, che permette di eseguire il test sul connettore delle cuffie.

Best practice per ridurre al minimo la latenza

Convalidare le prestazioni audio

L'Android Compatibility Definition Document (CDD) elenca i requisiti hardware e software di un dispositivo Android compatibile. Consulta la pagina relativa alla compatibilità con Android per ulteriori informazioni sul programma di compatibilità generale e il documento CDD per consultare il documento CDD effettivo.

Nel CDD, la latenza di round trip è specificata come un valore pari o inferiore a 20 ms (anche se i musicisti in genere richiedono 10 ms). Questo perché esistono casi d'uso importanti abilitati da 20 ms.

Al momento non esiste un'API per determinare la latenza audio su qualsiasi percorso su un dispositivo Android in fase di runtime. Tuttavia, puoi utilizzare i seguenti flag delle funzionalità hardware per scoprire se il dispositivo garantisce la latenza:

I criteri per la segnalazione di questi flag sono definiti nel CDD nelle sezioni 5.6 Latenza audio e 5.10 Audio professionale.

Ecco come verificare queste funzionalità in 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);

Per quanto riguarda la relazione tra le funzionalità audio, la funzionalità android.hardware.audio.low_latency è un prerequisito per android.hardware.audio.pro. Un dispositivo può implementare android.hardware.audio.low_latency e non android.hardware.audio.pro, ma non viceversa.

Non fare ipotesi sulle prestazioni audio

Per evitare problemi di latenza, fai attenzione ai seguenti presupposti:

  • Non dare per scontato che gli altoparlanti e i microfoni utilizzati nei dispositivi mobili abbiano in genere una buona acustica. A causa delle loro dimensioni ridotte, l'acustica è generalmente scarsa, quindi viene aggiunta l'elaborazione del segnale per migliorare la qualità audio. Questa elaborazione degli indicatori introduce la latenza.
  • Non dare per scontato che i callback di input e output siano sincronizzati. Per l'input e l'output simultanei, vengono utilizzati gestori di completamento della coda del buffer separati per ogni lato. Non vi è alcuna garanzia dell'ordine relativo di questi callback o della sincronizzazione degli orologi audio, anche quando entrambe le parti utilizzano la stessa frequenza di campionamento. L'applicazione deve eseguire il buffer dei dati con una corretta sincronizzazione del buffer.
  • Non dare per scontato che la frequenza di campionamento effettiva corrisponda esattamente alla frequenza di campionamento nominale. Ad esempio, se la frequenza di campionamento nominale è di 48.000 Hz, è normale che il clock audio avanza a una frequenza leggermente diversa rispetto alla frequenza del sistema operativo CLOCK_MONOTONIC. Questo perché gli orologi audio e di sistema possono provenire da cristalli diversi.
  • Non dare per scontato che la frequenza di campionamento effettiva della riproduzione corrisponda esattamente alla frequenza di campionamento effettiva dell'acquisizione, soprattutto se gli endpoint si trovano su percorsi separati. Ad esempio, se acquisisci dal microfono sul dispositivo a una frequenza di campionamento nominale di 48.000 Hz e riproduci su audio USB a una frequenza di campionamento nominale di 48.000 Hz, è probabile che le frequenze di campionamento effettive siano leggermente diverse tra loro.

Una conseguenza di orologi audio potenzialmente indipendenti è la necessità di una conversione della frequenza di campionamento asincrona. Una tecnica semplice (anche se non ideale per la qualità audio) per la conversione della frequenza di campionamento asincrona è duplicare o eliminare i campioni secondo necessità vicino a un punto di passaggio zero. Sono possibili conversioni più sofisticate.

Riduci al minimo la latenza di input

Questa sezione fornisce suggerimenti per aiutarti a ridurre la latenza dell'input audio durante la registrazione con un microfono integrato o un microfono con cuffia esterna.

  • Se la tua app monitora l'input, suggerisci agli utenti di utilizzare le cuffie (ad esempio mostrando la schermata Ideale con le cuffie alla prima esecuzione). Tieni presente che il semplice utilizzo delle cuffie non garantisce la latenza più bassa possibile. Potresti dover eseguire altri passaggi per rimuovere l'elaborazione del segnale indesiderato dal percorso audio, ad esempio usando la preimpostazione VOICE_RECOGNITION durante la registrazione.
  • Preparati a gestire frequenze di campionamento nominali di 44.100 e 48.000 Hz come riportato da getProperty(String) per PROPERTY_OUTPUT_CAMPAIGN_RATE. Sono possibili altre frequenze di campionamento, ma sono rare.
  • Preparati a gestire le dimensioni del buffer riportate da getProperty(String) per PROPERTY_OUTPUT_FRAMES_PER_BUFFER. Le dimensioni tipiche del buffer includono frame 96, 128, 160, 192, 240, 256 o 512, ma sono possibili altri valori.

Riduci al minimo la latenza di output

Utilizza la frequenza di campionamento ottimale quando crei il lettore audio

Per ottenere la latenza più bassa, devi fornire dati audio che corrispondano alla frequenza di campionamento e alla dimensione del buffer ottimali del dispositivo. Per ulteriori informazioni, consulta Progetta per ridurre la latenza.

In Java, puoi ottenere la frequenza di campionamento ottimale da AudioManager, come mostrato nel seguente esempio di codice:

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 volta individuata la frequenza di campionamento ottimale, puoi fornirla al momento della creazione del player. In questo esempio viene utilizzato 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 si riferisce alla frequenza di campionamento per canale in millihertz (1 Hz = 1000 mHz).

Usa la dimensione del buffer ottimale per accodare i dati audio

Puoi ottenere la dimensione del buffer ottimale in modo simile alla frequenza di campionamento ottimale utilizzando l'API 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 proprietà PROPERTY_OUTPUT_FRAMES_PER_BUFFER indica il numero di frame audio che il buffer HAL (Hardware Abstraction Layer) può contenere. Dovresti costruire i buffer audio in modo che contengano un multiplo esatto di questo numero. Se utilizzi il numero corretto di frame audio, i callback vengono eseguiti a intervalli regolari, riducendo il trettero.

È importante utilizzare l'API per determinare la dimensione del buffer anziché utilizzare un valore hardcoded, poiché le dimensioni del buffer HAL variano in base al dispositivo e alle build Android.

Non aggiungere interfacce di output che prevedono l'elaborazione degli indicatori

Il mixer veloce supporta solo queste interfacce:

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • VOLUME_SL_IID_VOLUME
  • SL_IID_MUTESOLO

Queste interfacce non sono consentite perché prevedono l'elaborazione degli indicatori e causano il rifiuto della tua richiesta di accesso rapido:

  • BASSBOOST_IID_SL
  • SL_IID_EffectSEND
  • SL_IID_ENVIRONMENTALREVERB
  • Equalizzatore_SL_IID
  • SL_IID_PLAYBACKRATE
  • SL_IID_PREIMPOSTAZIONE REVERB
  • SL_IID_VIRTUALIZZATORE
  • SL_IID_ANDROID EFFETTO
  • SL_IID_ANDROIDEffectSEND

Quando crei il player, assicurati di aggiungere solo interfacce veloci, come mostrato nell'esempio seguente:

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

Verifica di utilizzare un canale a bassa latenza

Completa questi passaggi per verificare di aver ottenuto correttamente un canale a bassa latenza:

  1. Avvia l'app ed esegui questo comando:
  2. adb shell ps | grep your_app_name
    
  3. Prendi nota dell'ID di processo della tua app.
  4. Ora riproduci un po' di audio dall'app. Hai circa tre secondi per eseguire il seguente comando dal terminale:
  5. adb shell dumpsys media.audio_flinger
    
  6. Cerca l'ID di processo. Se vedi una F nella colonna Nome, significa che si trova su un canale a bassa latenza (la F sta per fast track).

Riduci al minimo la latenza di warmup

Quando accodi i dati audio per la prima volta, il riscaldamento del circuito audio del dispositivo richiede un po', ma comunque significativo, tempo. Per evitare questa latenza di warmup, puoi accodare i buffer di dati audio contenenti silenzio, come mostrato nel seguente esempio di codice:

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

Nel momento in cui deve essere prodotto l'audio, puoi passare ad accodare i buffer contenenti dati audio reali.

Nota:l'emissione costante di audio comporta un consumo energetico significativo. Assicurati di interrompere l'output nel metodo onPause(). Valuta inoltre di mettere in pausa l'output silenzioso dopo un certo periodo di inattività dell'utente.

Codice campione aggiuntivo

Per scaricare un'app di esempio che mostra la latenza audio, consulta gli esempi NDK.

Per ulteriori informazioni

  1. Latenza audio per gli sviluppatori di app
  2. contributo alla latenza audio
  3. Misurazione della latenza audio
  4. Riscaldamento audio
  5. Latenza (audio)
  6. Tempo di ritardo di andata e ritorno