Contributeurs à la latence audio

Cette page se concentre sur les facteurs contribuant à la latence de sortie, mais une discussion similaire s'applique à la latence d'entrée.

En supposant que le circuit analogique n'y contribue pas de manière significative, les principaux facteurs de latence audio sont les suivants:

  • Application
  • Nombre total de tampons dans le pipeline
  • Taille de chaque tampon, en images
  • Latence supplémentaire après le processeur de l'application, par exemple d'un DSP

Aussi précise que soit la liste des contributeurs ci-dessus, elle peut également être trompeuse. En effet, le nombre et la taille de la mémoire tampon sont davantage un effet qu'une cause. En règle générale, un schéma de tampon donné est implémenté et testé, mais lors des tests, un sous-dépassement ou un dépassement audio se produit sous la forme d'un "clic" ou d'un "pop". Pour compenser, le concepteur du système augmente ensuite les tailles ou les nombres de tampons. Cela permet d'éliminer les sous-dépassements ou les dépassements, mais a également l'effet secondaire indésirable d'augmenter la latence. Pour en savoir plus sur les tailles de mémoire tampon, regardez la vidéo Latence audio: tailles de mémoire tampon.

Une meilleure approche consiste à comprendre les causes des sous-exécutions et des dépassements, puis à les corriger. Cela élimine les artefacts audibles et peut permettre d'utiliser des tampons encore plus petits ou moins nombreux, ce qui réduit la latence.

D'après notre expérience, les causes les plus courantes de sous-temps et de dépassements sont les suivantes:

  • Linux CFS (Completely Fair Scheduler)
  • threads à priorité élevée avec planification SCHED_FIFO
  • inversion de priorité
  • longue latence de planification
  • Gestionnaires d'interruptions de longue durée
  • temps de désactivation d'interruption long
  • gestion de l'alimentation
  • noyaux de sécurité

Planification CFS et SCHED_FIFO sous Linux

Le CFS Linux est conçu pour être équitable envers les charges de travail concurrentes qui partagent une ressource de processeur commune. Cette équité est représentée par un paramètre nice par thread. La valeur nice varie de -19 (moins agréable, ou temps de CPU le plus alloué) à 20 (plus agréable, ou temps de CPU le moins alloué). En règle générale, tous les threads avec une valeur de priorité donnée reçoivent un temps de processeur à peu près égal, et les threads avec une valeur de priorité numériquement inférieure doivent s'attendre à recevoir plus de temps de processeur. Cependant, la méthode CFS n'est "adéquate" que sur des périodes d'observation relativement longues. Sur des périodes d'observation à court terme, CFS peut allouer les ressources de processeur de manière inattendue. Par exemple, il peut retirer le processeur d'un thread dont la priorité est faible pour le placer sur un thread dont la priorité est élevée. Dans le cas de l'audio, cela peut entraîner un sous-dépassement ou un dépassement.

La solution évidente consiste à éviter le CFS pour les threads audio hautes performances. À partir d'Android 4.1, ces threads utilisent désormais la stratégie de planification SCHED_FIFO plutôt que la stratégie de planification SCHED_NORMAL (également appelée SCHED_OTHER) implémentée par CFS.

Priorités SCHED_FIFO

Bien que les threads audio hautes performances utilisent désormais SCHED_FIFO, ils restent susceptibles d'être affectés par d'autres threads SCHED_FIFO de priorité plus élevée. Il s'agit généralement de threads de travail du noyau, mais il peut également y avoir quelques threads utilisateur non audio avec la stratégie SCHED_FIFO. Les priorités SCHED_FIFO disponibles vont de 1 à 99. Les threads audio s'exécutent avec une priorité de 2 ou 3. La priorité 1 est donc disponible pour les threads de priorité inférieure, et les priorités 4 à 99 pour les threads de priorité supérieure. Nous vous recommandons d'utiliser la priorité 1 dans la mesure du possible et de réserver les priorités 4 à 99 pour les threads qui sont garantis de se terminer dans un délai limité, qui s'exécutent avec une période plus courte que celle des threads audio et qui ne sont pas connus pour interférer avec la planification des threads audio.

Planification monotonique de la fréquence

Pour en savoir plus sur la théorie de l'attribution de priorités fixes, consultez l'article Wikipédia sur la planification monotonique de la fréquence (RMS). Un point clé est que les priorités fixes doivent être attribuées strictement en fonction de la période, avec des priorités plus élevées attribuées aux threads de périodes plus courtes, et non en fonction de l'importance perçue. Les threads non périodiques peuvent être modélisés en tant que threads périodiques, en utilisant la fréquence d'exécution maximale et le calcul maximal par exécution. Si un thread non périodique ne peut pas être modélisé en tant que thread périodique (par exemple, il peut s'exécuter avec une fréquence illimitée ou un calcul illimité par exécution), il ne doit pas être attribué une priorité fixe, car cela serait incompatible avec la planification de véritables threads périodiques.

Inversion de priorité

L'inversion de priorité est un mode d'échec classique des systèmes en temps réel, dans lequel une tâche de priorité supérieure est bloquée pendant une durée illimitée en attendant qu'une tâche de priorité inférieure libère une ressource telle qu'un mutex (état partagé protégé par). Consultez l'article Éviter l'inversion de priorité pour découvrir les techniques permettant de l'atténuer.

Latence de planification

La latence de planification correspond au temps écoulé entre le moment où un thread est prêt à s'exécuter et celui où le changement de contexte qui en résulte est terminé, de sorte que le thread s'exécute réellement sur un processeur. Plus la latence est courte, mieux c'est. Tout ce qui dépasse deux millisecondes entraîne des problèmes pour l'audio. Une latence de planification longue est la plus susceptible de se produire lors des transitions de mode, telles que la mise en route ou l'arrêt d'un processeur, le basculement entre un noyau de sécurité et le noyau normal, le passage du mode pleine puissance au mode basse consommation ou l'ajustement de la fréquence d'horloge et de la tension du processeur.

Interruptions

Dans de nombreuses conceptions, le processeur 0 gère toutes les interruptions externes. Par conséquent, un gestionnaire d'interruption de longue durée peut retarder d'autres interruptions, en particulier les interruptions de fin d'accès direct à la mémoire (DMA) audio. Concevez des gestionnaires d'interruptions pour qu'ils se terminent rapidement et reportez le travail long sur un thread (de préférence un thread CFS ou un thread SCHED_FIFO de priorité 1).

De même, la désactivation des interruptions sur le processeur 0 pendant une longue période a pour effet de retarder le traitement des interruptions audio. Des temps de désactivation des interruptions longs se produisent généralement en attendant un verrou de spin du noyau. Examinez ces verrous de spin pour vous assurer qu'ils sont limités.

Gestion de l'alimentation, des performances et de la température

La gestion de l'alimentation est un terme générique qui englobe les efforts visant à surveiller et à réduire la consommation d'énergie tout en optimisant les performances. La gestion thermique et la climatisation d'ordinateur sont similaires, mais visent à mesurer et à contrôler la chaleur pour éviter les dommages dus à une chaleur excessive. Dans le noyau Linux, le gestionnaire du processeur est responsable de la stratégie de bas niveau, tandis que le mode utilisateur configure la stratégie de haut niveau. Voici quelques-unes des techniques utilisées:

  • scaling dynamique de la tension
  • scaling dynamique de la fréquence
  • activation dynamique des cœurs
  • commutation de cluster
  • gestion de l'alimentation
  • hotplug (hotswap)
  • différents modes de veille (arrêt, arrêt, veille, suspension, etc.)
  • migration de processus
  • affinité de processeur

Certaines opérations de gestion peuvent entraîner des "arrêts de travail" ou des périodes pendant lesquelles le processeur d'application n'effectue aucune tâche utile. Ces arrêts de travail peuvent interférer avec l'audio. Par conséquent, cette gestion doit être conçue pour un arrêt de travail maximal acceptable lorsque l'audio est actif. Bien entendu, lorsque la dérive thermique est imminente, éviter les dommages permanents est plus important que l'audio.

Noyaux de sécurité

Un kernel de sécurité pour la gestion des droits numériques (DRM) peut s'exécuter sur le ou les mêmes cœurs de processeur d'application que ceux utilisés pour le kernel du système d'exploitation principal et le code d'application. Chaque fois qu'une opération de kernel de sécurité est active sur un cœur, il s'agit en fait d'un arrêt du travail ordinaire qui s'exécute normalement sur ce cœur. Cela peut inclure, en particulier, le travail audio. Par nature, le comportement interne d'un kernel de sécurité est insondable à partir des couches de niveau supérieur. Par conséquent, toute anomalie de performances causée par un kernel de sécurité est particulièrement dangereuse. Par exemple, les opérations de noyau de sécurité n'apparaissent généralement pas dans les traces de changement de contexte. Nous appelons cela "temps sombre", c'est-à-dire le temps qui s'écoule, mais qui ne peut pas être observé. Les noyaux de sécurité doivent être conçus pour une interruption de travail acceptable dans le pire des cas lorsque l'audio est actif.