alsa config (crap?) – asoundrc

Encore une belle chose de linux, les choses basiques ne sont pas réalisables facilement. Cas concret tout bête, je veux créer une vidéo de mon bureau, avec le son émis des haut-parleurs et le son (reçu) de mon micro. Pour l’image, on va dire que c’est assez simple, rien à dire là dessus. Pour le son, c’est une autre histoire…

Il y a des serveurs de son sous: linux, Pulseaudio, jack, et des API du kernel: oss, alsa. Imaginer déjà comment doit faire le pauvre développeur si il doit gérer du son dans son application, mais heureusement, il y a des solutions.

Mais cet article n’est pas là pour débattre du bordel de la multitude de système de gestion de son sur linux. Je vais m’attarder sur alsa (mALSAin?) car OSS est obsolète on va dire, JACK ne correspond pas à mes besoins, et PulseAudio malheureusement ne marche pas dans mon cas précis. D’ailleurs, mon cas précis est de créer une vidéo d’un jeu (StarCraft 2, qui produit du son donc) sous Wine. PulseAudio, que j’ai testé, divise par 10 les fps du jeu et c’est un bug connu. PulseAudio colle bien à sa réputation pour le coup. Je me retrouve donc coincé avec ALSA, ce qui est surement le mieux, plutot que de passer par des couches d’abstraction.

L’objectif numéro 1, est donc d’enregistrer le son qui sort du PC. Ce n’est pas très compliqué quand la carte son propose d’elle-même de capturer le son qu’elle émet. Mais même lorsqu’elle le propose, cela ne semble pas si évident (c’est ironique) car j’ai trouvé de multiples articles qui expliquent comment choisir la source de données à capturer. Grosso-modo, c’est choisir dans un menu déroulant la source sonore, pour certaines personnes c’est d’un compliqué de faire ça, surtout quand on passe par la console et que l’on s’y prend mal, à tel point au début j’ai cru que c’était la solution à mon problème. Malheureusement, ma carte son ne propose pas d’elle-même, donc matériellement, d’enregistrer le son qu’elle émet, il me faut une solution logicielle.

asoundrc

Cette satanée solution logicielle, je ne l’ai jamais trouvé dans mes recherches pour ALSA, la solution étant de nos jours d’utiliser PulseAudio ou JACK (qui sont des solutions logicielles). Du coup, j’ai du m’inspirer d’exemples. Pour commencer, il y a un fichier magique, ~/.asoundrc (appelé simplement « asoundrc » par la suite), qui permet de personnaliser ALSA par utilisateur. C’est là dedans que tout va se passer, en fait c’est ça la solution logicielle…

Contrairement aux conneries qu’on peut lire sur Internet, pas besoin de se délogguer de la session à chaque modification du fichier, ni de redémarrer son PC. C’est encore une fois une bonne illustration des bêtises qu’on peut trouver sur Internet. Le fichier asoundrc est lu à chaque lancement par l’application (si celle-ci utilise ALSA). C’est tout simple. C’est aussi perturbant, c’est un gros WTF car on peut changer la configuration à la volée dans une session, mais en fait cela semble très logique.

Il faut comprendre ALSA, on peut appliquer des filtres aux différents périphériques, comme la conversion de fréquence, la conversion de stéréo en mono, etc. Ces filtres, une fois chargés dans l’application, restent actifs jusqu’à la fermeture de l’application. Ce n’est donc pas un problème de modifier asoundrc entre temps, les nouveaux filtres (ou ceux supprimés) seront disponible pour les futures applications qui seront lancées.

Si les tests sont effectués avec VLC par exemple, il ne faudra pas oublier de bien le fermer avant d’ouvrir un autre fichier. L’idéal, est de tester en ligne de commande avec aplay et arecord. Au début c’est bien chiant, mais à la fin c’est bien pratique. D’ailleurs, aplay -L et arecord -L (fonctionne aussi avec un L minuscule pour d’autres infos) vont bien aider par la suite.

Terminologie

Quand je dis capturer ou enregistrer cela signifie que le flux (de bits) du périphérique ou interface ou autre peut être lu, c’est une source d’entrée. L’exemple d’interface que l’on peut capturer/enregistrer c’est le micro. On ne peut enregistrer seulement les entrées, sinon je ne me ferai pas chier à mettre en place une interface logicielle pour pouvoir enregistrer ce qui passe par une sortie (le son qui sort de mon PC). A noter que pour les cartes sons qui permettent d’enregistrer leur sortie, c’est parce qu’elles offrent d’elles même une entrée pour ça (ce qui n’est pas mon cas je le rappelle).

Pour résumer: sur une entrée (synonyme: source de données), on lit (synonyme: capture/enregistre) des données, comme lire le flux du micro; sur une sortie on écrit des données, comme écrire vers les haut-parleurs. J’évite le verbe enregistrer car il donne l’impression qu’on sauvegarde les données dans un fichier, ce qui n’est pas notre cas.

Ce n’est pas pour rien si j’utilise capturer comme verbe, en anglais capture est le type d’interface qui permettent d’être lu. Pour l’interface qui permet d’écrire des données, en anglais c’est playback

loopback

L’interface loopback c’est une carte audio logicielle avec plusieurs points d’entrées (sur lesquels il y a du son à capturer) et plusieurs points de sorties (qui peuvent servir de sortie pour le son). Elle correspond au module du kernel snd-aloop et est chargée par défaut de nos jours, il existe plein de documentation sur Internet sur comment la charger sinon.

Cette interface loopback, va créer des tunnels, ou plutot des boucles pour reprendre son appellation (loop): ce qui sera écrit sur une sortie pourra être lu (capturé) sur une entrée. C’est pour cela qu’il y a autant de sorties que d’entrées.

Cela ne semble pas très parlant peut-être, pourtant c’est simple: cela transforme une sortie en entrée. C’est exactement ce que je veux, transformer la sortie des haut-parleurs en entrée.

Dans le cadre de la vidéo que j’ai à réaliser, la source de données sera une des interfaces loopback pour résoudre mon problème de capture du son du PC. Ce ne sera pas l’interface par défaut (default) qui correspond à mon micro. Dans le logiciel d’enregistrement il faudra spécifier correctement cette source de données.

Déclarons déjà juste ces interfaces virtuelles, on prend soin d’utiliser un nom parlant pour ces interfaces.

pcm.loophwOut {
  type hw
  card Loopback
  device 0
  subdevice 0
  hint {
    show on
    description "Loop Output00"
  }
}


pcm.loophwCapt {
  type hw
  card Loopback
  device 1
  subdevice 0
  hint {
    show on
    description "Loop Input10 Rec"
  }
}

loophwOut correspond à la carte logicielle Loopback, mais elle est quand même de type hw (hardware) car c’est une carte (une émulation), elle utilise le périphérique (device) 0 et sous-device 0. Ce qui sera écrit dedans pourra être lu par loophwCapt, qui correspond à la même chose sauf pour le champ device. C’est assez simple, pour un même subdevice, ce qui est écrit dans un device 0, se lit dans un device 1, et il y a jusqu’à 8 subdevice (voir aplay -l).

La source de données pour ma vidéo, sera alors loophwCapt. Tout ce que je veux enregistrer pour la vidéo devra figurer (être envoyer) dans loophwOut.

Dans le nom de périphérique loophwCapt, j’ai préféré utiliser « Capt » pour « Capture » au lieu de « In » pour insister que c’est une périphérique qui va servir à capturer des données, dans ma tête c’est plus clair pour moi. Libre à chacun d’utiliser les mots qui leur parlent le plus, l’essentiel est de ne pas être perdu quand un lit un fichier asoundrc, de ne pas devoir analyser chacune des lignes du fichier pour le comprendre. Car c’est exactement ce qu’il faut faire sur les articles que j’ai pu lire…

Multiplication

Pour capturer le son du PC, il suffit donc que celui-ci soit écrit sur une interface loopback (loophwOut), mais aussi sur la carte son pour pouvoir l’entendre. Un même flux doit être envoyé à deux cartes différentes: une matérielle, une logicielle (le type de carte n’a pas d’importance, c’est juste pour insister que loopback est une carte son, même si elle est logicielle).

Il faut donc dupliquer le son sur deux cartes, cela tombe bien, il y a plein d’exemples sur Internet pour ça. Mal expliqué malheureusement, en fait, c’est généralement un bloc de code à mettre dans asoundrc, avec une explication de comment s’en servir, mais rien n’explique le fonctionnement du code qui a été collé. C’est une chose que je déteste, faire quelque chose sans comprendre, et du coup sans être capable d’adapter à mon besoin précis ou de corriger si c’est buggé ou pas à jour.

En fait, ce que l’on écrit dans asoundrc, ce sont des « plug-ins », qui se connectent en eux, le premier plugin de cet article était loophwOut qui se « connecte » (plug) sur la carte audio loopback. Un plugin possède des données en entrée, et des données en sortie, entrée/sortie qui n’ont rien avoir avec la notion de « capturer/enregistrer », le plugin c’est une boite de transition, qui peut effectuer une transformation, servir de redirection ou d’alias, ou autre.

Cette fois-ci, on va faire un plug-in qui duplique un flux sur deux cartes audios, appelés loopOutMix (loopback) et speakersOutMix (la carte son). Ces plug-ins n’existent pas encore, en réalité ils ne sont pas nécessaire, on peut directement spécifier un plug-in hardware (ou équivalent comme loopback), sauf que j’anticipe le problème de mixage auquel je me suis confronté pour éviter d’écrire du code qui marche mal. Par exemple, loopOutMix devrait correspondre à loophwOut créé plus haut, et speakersOutMix devrait correspondre naturellement à default (ou hw:0,0), les haut-parleurs.

Donc créons déjà le plug-in qui duplique le flux, appelons le « speakersMulti » puisqu’il sert à dupliquer le son des haut-parleurs (en fait il effectue seulement des branchements, la duplication se fait avant). Appeler correctement les plug-ins est une chose indispensable pour trouver les choses logiques en lisant le fichier asoundrc, sur l’article original, le nom utilisé était « mdev », très pratique…

pcm.speakersMulti {
  type multi

  # première sortie
  slaves.a.pcm "loopOutMix"
  slaves.a.channels 2

  # seconde sortie
  slaves.b.pcm "speakersOutMix"
  slaves.b.channels 2

  # configuration des liaisons
  bindings.0.slave a
  bindings.0.channel 0
  bindings.1.slave a
  bindings.1.channel 1

  # seconde sortie
  bindings.2.slave b
  bindings.2.channel 0
  bindings.3.slave b
  bindings.3.channel 1
}

Le fonctionnement ne semble pas très compliqué, on définit une première sortie avec 2 canaux (car stéréo), on définit la seconde sortie de la même manière, puis on effectue les liaisons… Sauf qu’il semble y avoir 4 canaux en entrée. C’est parce qu’à mon avis, on ne peut pas lier un seul flux d’entrée à deux flux sortie, il est alors nécessaire de dupliquer ce flux avant de le passer au plugin qu’on a créé.

Le flux c’est du son en stéréo, donc deux canaux. Si on duplique le flux, il y aura 4 canaux en entrée du plugin speakersMulti. Ce sont ces 4 canaux que l’on lit. Les chiffres 0 à 3 situés après bindings correspondent au numéro du canal en entrée, que l’on lie aux sorties. Le canal 0 et 2 sont identiques, et 1 et 3 également. Pourquoi ? Parce que l’on va utiliser un autre plugin pour réaliser cette duplication. Mais ça, c’est rarement expliqué dans les articles que j’ai lu. C’est pour cela que j’ai volontairement expliqué dans cet ordre, pour montrer la démarche, les problèmes.

Donc c’est partie, dupliquons les canaux et connectons le. Appelons ce plug-in intelligemment, ce plug-in sera celui qui recevra tous les sons du PC pour les transmettre à speakersMulti et donc aux haut-parleurs ensuite, je vais utiliser le nom « speakersOut ». Sur les articles que j’ai lu, c’était mdevplug

pcm.speakersOut {
  type plug
  slave.pcm speakersMulti
  route_policy "duplicate"
}

Tout simplement. Les canaux en entrées sont dupliqués avec route_policy sur la destination (slave) qui est speakersMulti.

Pour résumer: pour un logiciel configuré avec « speakersOut » comme périphérique de son, il va écrire dessus, le flux stéréo sera dupliqué en 4 canaux, puis passés à speakersMulti. Deux de ces canaux iront dans loopOutMix, les deux autres dans speakersOutMix.

Si l’on remplace loopOutMix par la carte son, en général hw:0,0, et speakersOutMix par loophwOut comme on a vu en haut, cela devrait marcher. Et bien… oui, ça marche! Mais pour un seul logiciel… cela ne gére pas plusieurs logiciels simultanément. Grand moment de déception sur le moment, car c’est une chose tellement évidente que je pensais que c’était géré par défaut dans le système, masqué aux logiciels et à nous utilisateurs. Linux :-/

Mixage

Un périphérique c’est une ressource, cette ressource ne peut-être utilisée que par une seule chose. Par exemple, si un logiciel qui utilise le micro est en cours d’exécution, et bien on ne pourra pas se servir du même micro en même temps dans une autre application… débile ? oui. Autre exemple, si un logiciel diffuse du son sur la carte son, c’est pourtant normal qu’un autre logiciel puisse faire de même. Dans ce dernier cas, le problème est juste masqué car géré par défaut, sinon tout le monde crierai au scandale. Car j’ai testé comme on vu ci-dessus, en écrivant du son directement sur la carte son (hw:0,0 dans mon cas), cela réserve la ressource (la carte son) et aucune autre application ne peut écrire du son.

Pour que plusieurs logiciels puissent écrire du son simultanément, il faut un mixeur. Le son sera mixé par un intermédiaire et envoyé à la carte son. Cet intermédiaire s’appelle dmix. Il ne faudra pas l’oublier pour écrire sur les cartes sons, dont l’interface loopback qui est aussi une carte son (virtuelle).

Si il y a plusieurs logiciels, ils vont tous écrire sur speakersOut, et donc tous transmettre sur speakersMulti. Deux solutions, soit mixer avant d’envoyer sur speakersMulti, soit mixer à la sortie de speakersMulti.

Pour ma part, j’ai mixé à la sortie de speakersMulti. Parce qu’en général, le mixeur est l’avant dernier plugin de la chaine, le dernier étant le hardware. Dans notre cas, il semble logique de mixer avant, sauf qu’ensuite, il faudra aussi mixer le micro… donc commençons par mixer en fin de chaîne, le reste c’est de l’optimisation qui pourra se faire plus tard (en fait non, j’ai testé et ça ne marche pas, le mixeur doit se trouver toujours avant une carte son).

Ajoutons le plugin de type dmix sur l’interface loopback:

pcm.loopOutMix  {
   type dmix
   ipc_key 1024
   slave {
      pcm "loophwOut"
      format S32_LE
      period_time 0
      period_size 1024
      buffer_size 8192

      rate 44100
      channels 2
      format S16_LE
   }
   bindings {
      0 0
      1 1
   }
}

L’ipc_key doit être unique, la sortie (slave) est le périphérique logiciel loophwOut, sur lequel on configure un buffer de mixage. Du coup on ne doit jamais écrire le son sur loophwOut mais toujours passer par loopOutMix. Il suffit de faire la même chose pour la carte son:

pcm.speakersOutMix  {
   type dmix
   ipc_key 1024
   slave {
      # à adapter en fonction de votre matériel
      pcm "hw:0,0"
      #format S32_LE
      period_time 0
      period_size 1024
      buffer_size 8192

      rate 48000
      channels 2
      format S16_LE
   }
   bindings {
      0 0
      1 1
   }
}

A ce stade, je suis en mesure d’enregistrer le son qui sort de mon PC (avec loophwCapt), mais pas celui qui rentre.

Le micro

Le son du micro n’est pas émis aux haut-parleurs, il ne sort pas du PC, il ne fait pas partie du flux de sortie, il faut donc une astuce encore. De plus, nous pouvons enregistrer (avec le logiciel pour faire la vidéo) qu’une seule source de données, or il y en a deux: le micro et l’interface loopback utilisé pour le son qui sort du PC.

Pour ce problème, c’est très simple, il suffit de rediriger le flux du micro vers la sortie de l’interface loopback, notre loopOutMix. Le son du micro sera ainsi mixé avec le son du pc. Cela se fera à la demande, avec la commande alsaloop, qui prend l’interface (le plugin) d’entrée, et l’interface de sortie. L’ensemble mixé pourra être enregistré avec loophwCapt.

Alors qu’on pense y être arrivé, il y a encore un problème à résoudre. Le micro est capturé deux fois: une fois par le logiciel de conférence, et une fois par alsaloop. En effet, j’ai oublié de préciser que je serai en conférence et qu’on sera plusieurs à commenter la vidéo. Il faut donc faire en sorte que plusieurs logiciels puissent capturer le micro. Ca encore ce n’est pas courant et cela vaut la peine de l’expliquer.

Tout comme dmix pour mixer plusieurs sources en une seule, il existe dsnoop pour dupliquer logiciellement parlant une seule source en plusieurs. Même si dans l’immédiat j’aurai pensé à réutiliser la même astuce de duplication route_policy, dsnoop est le plus approprié et dédié à cet usage (cela évite la duplication de canaux, etc). Tout comme plusieurs applications peuvent écrire dans un plugin dmix sans se poser de question, la même chose en capture est possible avec dsnoop.

Déclarons ce nouveau plugin, appelé microMix (mixage du son du micro pour créer plusieurs sorties), sur les articles trouvés, le nom « dsnooped » avait été utilisé, vraiment pas pratique pour comprendre.

pcm.microMix {
  type dsnoop
  ipc_key 11834
  slave {
    # le périphérique du micro, à adapter si besoin
    pcm "hw:0,0"
    period_size 256
    periods 16
    buffer_size 16384
  }
  bindings {
     0 0
  }
}

Rien d’extraordinaire, c’est comme dmix, sauf qu’il y a qu’un seul canal (mono), d’où un seul canal dans bindings. ipc_key doit toujours être unique. Dans le logiciel de conférence, il faudra utiliser microMix comme source de données… ou pas.

Voici maintenant la commande alsaloop puisque toutes les interfaces sont connues:

$ alsaloop -P loopOutMix -C microMix -t 200000

On capture de microMix pour écrire dans loopOutMix, avec une latence de 200000 microsecondes (200ms). On peut baisser la latence ou l’augmenter, le CPU sera plus ou moins chargé en fonction de cette valeur.

Par défaut

On peut regrouper tous ces changements dans default pour ne pas avoir à changer la configuration de chacune des applications:

pcm.!default {
  type asym
  playback.pcm "speakersOut"
  capture.pcm "microMix"
}

Assez simple à comprendre, default est un périphérique asymétrique dans lequel on peut donc écrire (playback) du son et capturer du son. On précise quel plugin est à utiliser pour les deux cas. Et le tour est joué.

Si plusieurs logiciels doivent enregistrer à partir de loophwCapt, il faut faire comme pour le micro (microMix) et ajouter un plugin dsnoop. Ce n’est pas mon cas, mais c’est possible à faire.

Mais quand on pense avoir fini, il reste les bugs logiciels… mon logiciel de conférence (mumble), n’affiche pas les interfaces alsa, il faut la spécifier dans le fichier de configuration (source). Wine a un problème avec ALSA, impossible d’utiliser l’interface par défaut, j’ai du adapter Wine. Seul VLC a fonctionné comme sur des roulettes. Le coût de la diversité encore…

Au final, l’étape du mirco n’a servi à rien. Le son capturé est le flux brut du micro, et à moins d’avoir un micro de qualité, il faut nettoyer le son pour enlever les parasites, les petits claquements, etc. Ce que fait par exemple très bien mon logiciel de conférence. Il a fallu tricher et tricher encore et encore pour arriver au bon résultat. Certains appellent ça hacker, cela fait plus hype que de dire qu’on galère, comme quoi il y a du marketing aussi du coté de linux…

Publicités

One Response to alsa config (crap?) – asoundrc

  1. linuxfr says:

    Voici le lien que j’ai retrouvé sur le son sur linux:
    http://insanecoding.blogspot.fr/2009/06/state-of-sound-in-linux-not-so-sorry.html

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :