Latence audio

La latence correspond au temps nécessaire pour qu'un signal transite par un système. Voici les types de latence couramment associés aux applications audio :

  • La latence de sortie audio correspond au délai entre la génération d'un échantillon audio par une application et la lecture de cet échantillon via le connecteur casque ou le haut-parleur intégré.
  • La latence d'entrée audio correspond au délai entre la réception d'un signal audio par l'entrée audio d'un appareil (par exemple, le micro) et la disponibilité de ces mêmes données audio dans une application.
  • La latence aller-retour correspond à la somme de la latence d'entrée, du temps de traitement par l'application et de la latence de sortie.

  • La latence tactile correspond au délai entre le moment où un utilisateur touche l'écran et le moment où l'application reçoit un événement tactile.
  • La latence de préchauffage correspond au temps nécessaire au démarrage du pipeline audio la première fois que les données sont mises en file d'attente dans un tampon.

Cette page explique comment développer une application audio avec des entrées et des sorties à faible latence, et comment éviter la latence de préchauffage.

Mesurer la latence

Il est difficile de mesurer la latence d'entrée et de sortie audio de façon isolée, car cela nécessite de savoir exactement quand le premier échantillon est envoyé dans le chemin audio (bien que cela soit possible en utilisant un circuit de test de lumière et un oscilloscope). Si vous connaissez la latence audio aller-retour, vous pouvez utiliser la règle de base suivante : la latence d'entrée (et de sortie) audio est égale à la moitié de la latence audio aller-retour sur les chemins sans traitement de signal.

La latence audio aller-retour varie considérablement selon le modèle de l'appareil et la version d'Android. Pour vous faire une idée approximative de la latence aller-retour des appareils Nexus, consultez les mesures publiées.

Vous pouvez mesurer la latence audio aller-retour en créant une application qui génère un signal audio, écoute ce signal et mesure le délai entre son envoi et sa réception.

Étant donné que la latence la plus faible est obtenue sur les chemins audio avec un traitement minimal du signal, vous pouvez également utiliser un dongle de rebouclage audio, qui permet d'exécuter le test sur le connecteur casque.

Bonnes pratiques pour réduire la latence

Valider les performances audio

Le document de définition de compatibilité Android (CDD) indique la configuration matérielle et logicielle requise pour un appareil Android compatible. Pour plus d'informations sur le programme de compatibilité global, consultez Compatibilité Android. Vous pouvez également consulter le CDD lui-même.

Dans le CDD, la latence aller-retour est définie sur 20 ms ou moins (même si les musiciens ont généralement besoin qu'elle ne dépasse pas 10 ms). En effet, une valeur de 20 ms convient à certains cas d'utilisation importants.

Il n'existe actuellement aucune API permettant de déterminer la latence audio sur un chemin quelconque sur un appareil Android au moment de l'exécution. Vous pouvez toutefois utiliser les commutateurs de fonctionnalités matérielles suivants pour savoir si l'appareil fournit des garanties concernant la latence :

Les critères de signalement de ces indicateurs sont définis dans les sections 5.6 Audio Latency et 5.10 Professional Audio du CDD.

Pour vérifier ces fonctionnalités en Java, procédez comme suit :

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 ce qui concerne la relation entre les fonctionnalités audio, la fonctionnalité android.hardware.audio.low_latency est une condition préalable à l'utilisation de android.hardware.audio.pro. Un appareil peut intégrer android.hardware.audio.low_latency et non android.hardware.audio.pro, mais pas l'inverse.

Ne faire aucune hypothèse concernant les performances audio

Pour éviter les problèmes de latence, méfiez-vous des hypothèses suivantes :

  • Ne partez pas du principe que les haut-parleurs et les micros des appareils mobiles offrent généralement de bonnes performances acoustiques. Comme ils sont de petite taille, l'acoustique est souvent médiocre. Un traitement du signal est donc ajouté pour améliorer la qualité audio. Ce traitement du signal introduit une latence.
  • Ne partez pas du principe que vos rappels d'entrée et de sortie sont synchronisés. Pour les entrées et les sorties simultanées, des gestionnaires d'achèvement de file d'attente de tampon distincts sont utilisés de chaque côté. Il n'existe aucune garantie quant à l'ordre relatif de ces rappels ni à la synchronisation des horloges audio, même lorsque les deux côtés utilisent le même taux d'échantillonnage. Votre application doit mettre les données en mémoire tampon avec une synchronisation de tampon appropriée.
  • Ne partez pas du principe que le taux d'échantillonnage réel correspond exactement au taux nominal. Par exemple, si le taux d'échantillonnage nominal est de 48 000 Hz, il est normal que l'horloge audio n'avance pas exactement à la même vitesse que l'horloge CLOCK_MONOTONIC du système d'exploitation. En effet, les horloges audio et système peuvent utiliser des cristaux différents.
  • Ne partez pas du principe que le taux d'échantillonnage de lecture réel correspond exactement au taux d'échantillonnage de capture réel, en particulier si les points de terminaison se trouvent sur des chemins distincts. Par exemple, en cas de capture à partir du micro de l'appareil à un taux d'échantillonnage nominal de 48 000 Hz et de lecture par un périphérique audio USB à un taux d'échantillonnage nominal de 48 000 Hz, les taux d'échantillonnage réels seront probablement légèrement différents.

Cette indépendance potentielle des horloges audio entraîne la nécessité d'une conversion de taux d'échantillonnage asynchrone. Une technique simple (mais pas idéale pour la qualité audio) de conversion de taux d'échantillonnage asynchrone consiste à dupliquer ou à supprimer des échantillons selon les besoins, à proximité d'un point de passage à zéro. Des conversions plus sophistiquées sont possibles.

Réduire la latence d'entrée

Cette section propose des suggestions pour réduire la latence d'entrée audio lors de l'enregistrement avec un micro intégré ou un micro de casque externe.

  • Si votre application surveille l'entrée, suggérez aux utilisateurs d'utiliser un casque (par exemple, en affichant un écran Casque recommandé lors de la première exécution). Notez que l'utilisation d'un casque ne suffit pas à garantir la plus faible latence possible. Vous devrez peut-être effectuer d'autres étapes pour supprimer tout traitement de signal indésirable du chemin audio, par exemple en utilisant le préréglage VOICE_RECOGNITION lors de l'enregistrement.
  • Préparez-vous à gérer des taux d'échantillonnage nominaux de 44 100 et 48 000 Hz, tels qu'indiqués par getProperty(String) pour PROPERTY_OUTPUT_SAMPLE_RATE. D'autres taux d'échantillonnage sont possibles, mais rares.
  • Préparez-vous à gérer la taille de mémoire tampon indiquée par getProperty(String) pour PROPERTY_OUTPUT_FRAMES_PER_BUFFER. Les tailles de mémoire tampon usuelles sont de 96, 128, 160, 192, 240, 256 ou 512 trames, mais d'autres valeurs sont possibles.

Réduire la latence de sortie

Utiliser le taux d'échantillonnage optimal lors de la création du lecteur audio

Pour obtenir la latence la plus faible possible, vous devez fournir des données audio correspondant à la taille de mémoire tampon et au taux d'échantillonnage optimaux pour l'appareil. Pour en savoir plus, consultez Conception pour une latence réduite.

En Java, vous pouvez obtenir le taux d'échantillonnage optimal à l'aide d'AudioManager, comme indiqué dans l'exemple de code suivant :

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

Une fois que vous connaissez le taux d'échantillonnage optimal, vous pouvez l'indiquer lorsque vous créez votre lecteur. Cet exemple utilise 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;
   ...
}

Remarque : samplesPerSec fait référence au taux d'échantillonnage par canal en millihertz (1 Hz = 1 000 mHz).

Utiliser la taille de mémoire tampon optimale pour mettre en file d'attente les données audio

Vous pouvez obtenir la taille de mémoire tampon optimale de la même façon que le taux d'échantillonnage optimal, à l'aide de 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 propriété PROPERTY_OUTPUT_FRAMES_PER_BUFFER indique le nombre de trames audio que le tampon HAL (couche d'abstraction matérielle) peut contenir. Vous devez créer vos tampons audio de sorte qu'ils contiennent un multiple exact de ce nombre. Si vous utilisez le bon nombre de trames audio, vos rappels se produisent à intervalles réguliers, ce qui réduit la gigue.

Pour déterminer la taille de la mémoire tampon, il est important d'utiliser l'API plutôt qu'une valeur codée en dur, car les tailles de mémoire tampon HAL diffèrent selon les appareils et les versions d'Android.

Ne pas ajouter des interfaces de sortie impliquant un traitement du signal

Seules les interfaces suivantes sont compatibles avec le mélangeur rapide :

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

Les interfaces suivantes ne sont pas autorisées, car elles impliquent un traitement du signal et entraîneront le refus de votre demande de création d'une piste rapide :

  • 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

Lorsque vous créez votre lecteur, veillez à n'ajouter que des interfaces rapides, comme indiqué dans l'exemple suivant :

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

Vérifier que vous utilisez une piste à faible latence

Pour vérifier que vous avez bien obtenu une piste à faible latence, procédez comme suit :

  1. Lancez votre application, puis exécutez la commande suivante :
  2. adb shell ps | grep your_app_name
    
  3. Notez l'identifiant de processus de votre application.
  4. Lisez un contenu audio à partir de votre application. Vous disposez d'environ trois secondes pour exécuter la commande suivante à partir du terminal :
  5. adb shell dumpsys media.audio_flinger
    
  6. Recherchez votre identifiant de processus. Si la lettre F figure dans la colonne Nom, cela signifie qu'il s'agit d'une piste à faible latence (le F signifie fast track, c.-à-d. piste rapide).

Réduire la latence de préchauffage

Lorsque vous mettez des données audio en file d'attente pour la première fois, un laps de temps court, mais non négligeable, est nécessaire au préchauffage du circuit audio de l'appareil. Pour éviter cette latence de préchauffage, vous pouvez mettre en file d'attente des tampons de données audio contenant du silence, comme illustré dans l'exemple de code suivant :

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

Au moment où le son doit être produit, vous pouvez passer à des files d'attente contenant des données audio réelles.

Remarque : La sortie constante de contenu audio entraîne une consommation d'énergie importante. Veillez à arrêter la sortie dans la méthode onPause(). Pensez également à mettre en veille la sortie silencieuse après une certaine période d'inactivité de l'utilisateur.

Exemple de code supplémentaire

Pour télécharger une application exemple mettant en évidence la latence audio, consultez les exemples de NDK.

En savoir plus

  1. Latence audio pour les développeurs d'applications
  2. Contributeurs à la latence audio
  3. Mesure de la latence audio
  4. Préchauffage audio
  5. Latency (audio)
  6. Round-trip delay time