Journal Exposer un ou des modules Python sur D-Bus [proof of concept]

Posté par  (site web personnel) . Licence CC By‑SA.
41
2
mar.
2013

Sommaire

Réfléchissant au moyen d'utiliser un module python2 dans mon projet python3 (le module pyalsa en fait), m'est venue l'idée saugrenue de lister toutes les méthodes et des les exposer avec D-Bus, afin d'y avoir accès depuis Python 3, ou n'importe quoi qui cause avec D-Bus. Ce n'est certainement pas la solution que je vais retenir, mais j'ai trouvée l'idée amusante et ça m'a donné envie de jouer ce week-end.

L'idée, c'est donc de parcourir avec python tous les modules qui nous intéressent, et de générer du code python à partir de leur description, code python qui, exécuté, jouera le rôle d'intermédiaire entre les fonctions exposées sur D-Bus et les fonctions des modules.

Nous nommons le générateur de code alsadbusgen.py, et le code généré alsadbus.py.

Introspection Python

Python permet d'interroger des modules, des classes, des fonctions… enfin tout type d'objet avec le module inspect

Comme son nom le laisse deviner, la fonction inspect.getmembers reçoit un objet en paramètre et retourne une liste d'objets fils.
En fait, chaque objet fils est associé à un nom, ainsi on peut écrire :

for name, obj in inspect.getmembers(something):

Pour parcourir la liste des objets contenus dans un module, associé à son nom.
Ensuite, on peut tester le type d'objet avec inspect.ismodule, inspect.isfunction, inspect.isbuiltin, inspect.isclass, inspect.ismethod, etc.

Parcourir les modules

Ainsi, pour faire une action arbitraire pour tous les modules parcourus :

for name, obj in inspect.getmembers(something):
    if inspect.ismodule(obj):
        pass

Si something est pyalsa_ et que j'ai préalablement importé pyalsa.alsacard, pyalsa.alsacontrol, pyalsa.alsahcontrol, pyalsa.alsamixer, pyalsa.alsaseq, mon code va donc parcourir tous ces modules.

Pour chacun, nous allons donc générer un code python qui monte un service D-Bus avec comme service name « org.alsa.Service », comme object_path « /org/alsa/ », et comme interface « org.alsa.Controller ». Comme on bidouille, on va faire ça avec des print, et on se souciera peu de la beauté du code généré (on mélangera imports, code et définitions, tant pis, c'est une démo pour voir si ça marche).

Chose pratique, chaque objet semble avoir un objet __name__ qui contient son nom, ainsi, si l'on reçoit un module en paramètre, on peut tout de même savoir son nom.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import pyalsa.alsacard
import pyalsa.alsacontrol
import pyalsa.alsahcontrol
import pyalsa.alsamixer
import pyalsa.alsaseq

import inspect
import re

SERVICE_NAME_BASE = "org.alsa"
OBJECT_PATH_BASE = "/org/alsa/"
SERVICE_NAME = SERVICE_NAME_BASE + '.Service'
CONTROLLER_NAME = SERVICE_NAME_BASE + '.Controller'

print("#!/usr/bin/env python")
print("# -*- coding: utf-8 -*-")
print('')
print("from gi.repository import GObject")
print("import dbus")
print("import dbus.service")
print("from dbus.mainloop.glib import DBusGMainLoop")
print('')

def FindModulesIn(something):
    print("import " + something.__name__)
    print('')
    for name, obj in inspect.getmembers(something):
        if inspect.ismodule(obj):
            print("import " + obj.__name__)
            print('')
            print("class Service_" + name + "(dbus.service.Object):")
            print("\tdef __init__(self):")
            print("\t\tself.service_name = \"" + SERVICE_NAME + "\"")
            print("\t\tself.object_path = \"" + OBJECT_PATH_BASE + name + "\"")
            print("\t\tself.main_loop = DBusGMainLoop()")
            print("\t\tself.bus=dbus.SessionBus(mainloop = self.main_loop)")
            print("\t\tself.bus_name = dbus.service.BusName(self.service_name, self.bus)")
            print("\t\tdbus.service.Object.__init__(self, self.bus_name, self.object_path)")
            # inspect.getmembers(obj) here
            print('')

FindModulesIn(pyalsa)

print("mainloop = GObject.MainLoop()")
print("mainloop.run()")

Nous enregistrons ça dans alsadbusgen.py, puis nous faisons :

python alsadbusgen.py > alsadbus.py

Nous obtenons un code qui semble interprétable par Python, c'est bon ça !

Parcourir les fonctions

En fait nous n'utiliserons pas inspect.isfunction, puisque nous inspectons un module écrit en C (pyalsa), il faut donc tester avec inspect.isbuiltin.

for name, obj in inspect.getmembers(module):
    if inspect.isbuiltin(obj):
        pass

Mais il manque une chose importante, il faut définir les fonctions (def …) avec les bons paramètres. Normalement, inspect.getargspec est fait pour décrire les arguments des fonctions, mais ça ne marche pas avec les fonction du module pyalsa parce que ce module n'est pas écrit en Python !

In [16]: def mafonction(a, b):
   ....:     pass
   ....: 

In [17]: inspect.getargspec(mafonction)
Out[17]: ArgSpec(args=['a', 'b'], varargs=None, keywords=None, defaults=None)

In [18]: inspect.getargspec(pyalsa.alsacard.card_list)
TypeError: <built-in function card_list> is not a Python function

Nous allons donc utiliser l'objet __doc__ que nous allons découper à coup de regexp !

In [19]: pyalsa.alsacard.card_list.__doc__
Out[19]: 'card_list() -- Get a card number list in tuple.'

In [20]: pyalsa.alsacard.card_get_name.__doc__
Out[20]: 'card_get_name(card) -- Get card name string using card index.'

Je vous épargne les regexp, surtout que j'ai écris les miennes à la va-vite pour voir si ça marche, je n'ai pas du tout cherché l'élégance, et que de toute manière, les regexp, c'est du code en écriture seule !

Aussi, certaines méthodes ont des paramètres optionnels, nous n'allons pas nous en préoccuper, nous allons tous les indiquer comme s'ils étaient obligatoires, à nous de savoir quelle est la valeur par défaut à passer !

Mais donc voilà, nous pouvons donc générer un code qui ressemble à :

@dbus.service.method("org.alsa.Controller")
def card_get_name(self, card):
    return str(pyalsa.alsacard.card_get_name(card))

@dbus.service.method("org.alsa.Controller")
def card_list(self):
    return Py2Dbus(pyalsa.alsacard.card_list())

Parcourir les classes et les méthodes.

Plus compliqué cette fois-ci, nous exposerons sur D-Bus des fonctions qui correspondent à des méthodes de classe.
En fait pour chaque module nous parcourons les classes :

for name, obj in inspect.getmembers(module):
    if inspect.isclass(obj):
        pass

Et pour chaque classe, nous parcourons les méthodes :

for name, obj in inspect.getmembers(a_class):
    if inspect.ismethoddescriptor(obj):
        pass

Nous utilisons la fonction inspect.ismethoddescriptor, je n'ai pas cherché à vérifier, mais je suppose que ismethoddescriptor est à ismethod ce que isbuiltin est à isfunction : notre module est un module en C, et non en python.

Là nous ça se corse un peu. Nous allons déclarer une fonction dont le nom est composé du nom de la classe suivi du nom de la méthode, qui prendra en paramètre à la fois les paramètres du « constructeur », et à la fois les paramètres de la méthode. Pour éviter les conflits de nom (si le constructeur prend un paramètre qui a le même nom que le paramètre d'une de ses méthodes), nous allons précéder le nom de chaque paramètre de la classe par son nom de classe.

Ensuite, après une telle définition, nous instancions un objet depuis ladite classe avec les paramètres de classe, et nous appliquons la méthode sur ledit objet avec les paramètres de la méthode.

Ainsi, nous générons un code qui, par exemple, ressemble à cela :

@dbus.service.method("org.alsa.Controller")
def Control_card_info(self,  Control_name, Control_ode):
    obj = pyalsa.alsacontrol.Control(Control_name, Control_ode)
    return Py2Dbus(obj.card_info())

@dbus.service.method("org.alsa.Controller")
def Element_get_volume(self,  Element_mixer, Element_name, Element_index, channel, capture):
    obj = pyalsa.alsamixer.Element(Element_mixer, Element_name, Element_index)
    return Py2Dbus(obj.get_volume(channel, capture))

@dbus.service.method("org.alsa.Controller")
def Sequencer_create_queue(self,  Sequencer_name, Sequencer_clientname, Sequencer_streams, Sequencer_mode, Sequencer_maxreceiveevents, name):
    obj = pyalsa.alsaseq.Sequencer(Sequencer_name, Sequencer_clientname, Sequencer_streams, Sequencer_mode, Sequencer_maxreceiveevents)
    return Py2Dbus(obj.create_queue(name))

Noter que sur le dernier exemple, il y avait à l'origine deux paramètres nommés name.

Retourner les valeurs retour

Les fonctions et méthodes retournent parfois des types qui ne sont pas compris par D-Bus, libre à nous d'écrire une fonction Py2Dbus qui fasse une conversion.

Pour faire simple (c'est juste une démo), nous transformons tout retour en chaîne de caractère.

def Py2Dbus(var):
    return str(var)

Générer le code, l'exécuter.

Nous avons donc quelque chose de fonctionnel !

Générons le code :

python alsadbusgen.py > alsadbus.py 

Exécutons le :

python alsadbus.py 

Maintenant nous allons parcourir les services D-Bus avec l'outil graphique d-feet.

d-feet

Nous trouvons très vite un service nommé org.alsa.Service qui contient cinq objets :

  • /org/alsa/alsacard
  • /org/alsa/alsacontrol
  • /org/alsa/alsahcontrol
  • /org/alsa/alsamixer
  • /org/alsa/alsaseq

Chacun de ces objets possède bien une interface nommée org.alsa.Controllers, et ces interfaces contiennent bien des méthodes au nom des fonctions du module pyalsa, ou bien dont le nom est bien un assemblage de classe et de méthode.

Tester

J'identifie dans l'objet /org/alsa/alsacard, l'interface org.alsa.Controller, la méthode card_list sans paramètres que j'appelle sans paramètre. Je reçois « (0, 29) ».

J'identifie dans l'objet /org/alsa/alsacontrol, l'interface org.alsa.Controller, la méthode Control_card_info avec les paramètres Control_name et Control_ode. J'appelle donc cette méthode avec les paramètres « "hw:0", 1 », je reçois « {'name': 'HDA-Intel', 'longname': 'HDA Intel at 0xf8220000 irq 46', 'driver': 'HDA-Intel', 'components': 'HDA:11d41984,17aa20dd,00100400', 'mixername': 'Analog Devices AD1984', 'id': 'Intel', 'card': 0} ».

J'appelle la même méthode mais avec comme paramètres « "hw:29", 1 », je reçois « {'name': 'ThinkPad EC', 'longname': 'ThinkPad Console Audio Control at EC reg 0x30, fw 7RHT16WW-1.02', 'driver': 'ThinkPad EC', 'components': '', 'mixername': 'ThinkPad EC 7RHT16WW-1.02', 'id': 'ThinkPadEC', 'card': 29} ».

Et oui, je suis bien l'heureux possesseur d'un Thinkpad X61 Tablet !

Et ensuite…

Tout cela est donc fonctionnel. Vous pouvez trouver mon alsadbusgen.py ici et le fichier alsadbus.py . Évidemment, à ne pas utiliser en production, et ne pas être trop regardant, c'est du code écrit pour le plaisir de la bidouille, pas pour l'élégance.

Pour aller plus loin, il serait peut-être même possible d'exposer les méthodes via D-Bus sans passer par la génération de code et son interprétation, que le même code qui fait l'introspection se charge d'exposer les méthodes. Mais pour cela, il faudrait le faire sans utiliser les décorateurs, ce qui complique la tâche.

Pour mon projet, je ne sais toujours pas ce que je vais faire… Mais en attendant, si ce journal peut donner des idées aux petits gars du projet Alsa ! Les gars de Jackd ont déjà fourni une API D-bus, donc je peux contrôler jack directement via D-Bus, j'aimerai tant pouvoir faire la même chose avec Alsa !

Et tout ça c'est rigolo, mais ça ne répond pas à mon premier problème, parler à Alsa depuis Python 3 !
En plus de la méthode exposée ici (que je me refuse à utiliser en prod, c'est juste un jeu), je pourrais essayer une autre méthode décrite dans un article GLMF, où deux threads, l'un en Python 2 l'autre en Python 3 partagent leurs objets avec PyRO. Mais l'idée de lancer deux machines virtuelles Python me chiffonne un peu ! Si vous avez des idées pour discuter avec Alsa autrement qu'en grepant la sortie d'aplay, je suis preneur ! ;)

  • # Avertissement (attention les yeux)

    Posté par  (site web personnel) . Évalué à 10.

    Et pardonnez l'horreur du code générateur ainsi que l'horreur du code généré, j'ai certainement passé plus de temps à écrire le journal qu'à écrire le code, on va dire que c'est tombé en marche. XD

    ce commentaire est sous licence cc by 4 et précédentes

    • [^] # Re: Avertissement (attention les yeux)

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 03 mars 2013 à 08:13.

      Merci pour ton journal, fond et forme. J'ai une question de béotien :

      Pourquoi implémenter une api dbus dans alsa aujourd'hui ? dbus n'est il pas voie de dépréciation ? Même si ce n'est pas «demain la veille» que dbus disparaîtra il est fort probable que les premiers endroits/logiciels mis à jour soient ceux concernant le son, justement (et la virtualisation). Parce que le «dbus dans le noyau», alsa sera probablement mis à jour en conséquence, et parmi les tout premiers logiciels à switcher, vue sa position (tandisque que les autres n'auront pas besoin de faire évoluer leur code, puisqu'une libdbus sera probablement là, assurant la translation entre le «dbus du noyau» et les logiciels utilisant habituellement dbus). Donc voilà : pourquoi s'enquiquiner à implémenter une api dbus dans alsa ? Gourrage ? Les plus gros intéressés étant kvm et alsa.

      * \«dbus du noyau» : probablement une évolution ou ré-écriture de AF_BUS, écrit dans le cadre du projet Genivi (Automotive), qui a été refusé en l'état dans la branche principal. A moins qu'il ne s'agisse d'un kdbus second ? Bref, peu importe l'origine : c'est sûr, un «dbus dans le noyau» va voir le jour, Greg Kroah confirme bosser dessus.

      • [^] # Re: Avertissement (attention les yeux)

        Posté par  . Évalué à 3.

        Si j'ai bien compris, DBus existera, il utisera 'Dbus dans le noyau' pour être plus efficace.
        Il faudra toujours un demon en espace utilisateur pour que les programmes discutent entre eux.

      • [^] # Re: Avertissement (attention les yeux)

        Posté par  (site web personnel) . Évalué à 3.

        J'ai lu deux trois trucs sur cette inclusion de D-Bus dans le noyau, mais je ne sais pas trop ce que ça changera vraiment pour l' userspace.

        Mais quelque soit l'avenir, il serait bon qu'Alsa soit interrogeable officiellement via D-Bus, ce qui donnerait un accès à Alsa à tout langage dès qu'il existerai un module D-Bus pour ce langage !

        D-Bus dans le noyau ou pas, faut-il encore qu'Alsa parle D-Bus ! :)

        ce commentaire est sous licence cc by 4 et précédentes

        • [^] # Re: Avertissement (attention les yeux)

          Posté par  . Évalué à 5.

          Mais quelque soit l'avenir, il serait bon qu'Alsa soit interrogeable officiellement via D-Bus, ce qui donnerait un accès à Alsa à tout langage dès qu'il existerai un module D-Bus pour ce langage !

          Tu confond plusieurs choses.

          L'affaire de D-Bus dans le noyau, c'est pour avoir un bus contenant des données arbitraires avec des (hum) envois multicast fiables et ordonnés¹ (hum), pour permettre d'implémenter des bus arbitraires entre les applications.

          Or D-Bus, c'est un bus avec un protocole maison, qui est un peu bizarre et chiant à la fois. Personne (à ma connaissance) veut d'implémenter ça dans le noyau. Ou en tout cas, personne de sensé: le protocole D-Bus est totalement inadapté pour parler avec le noyau; D-Bus nécessite que les interfaces soient stables entre l'appelant et l'appelé, or le noyau évolue et l'userspace aussi, et les deux doivent être rétrocompatible avec des anciennes versions, et ça D-Bus le propose pas.

          Et surtout, le noyau possède déjà un protocole pour parler avec l'utilisateur: generic netlink. C'est un protocole simple et extensible (les deux parties peuvent ignorer des attributs qu'ils ne connaissent pas) qui est implémenté depuis belle lurette.

          Et Alsa ? c'est à la fois une API pour passer du son au noyau, et une bibliothèque utilisateur (c'est pas le noyau qui lit ~/.asoundrc) qui applique une configuration définie par l'administrateur ou l'utilisateur et qui passe ça au noyau.

          Si tu veux parler Alsa via D-Bus, ça veut dire qu'il va falloir un démon qui applique la configuration qui était appliqué par libasound avant, et qui lui, parle au noyau. Franchement, il y a déjà pulseaudio pour ça.

          • [^] # Re: Avertissement (attention les yeux)

            Posté par  (site web personnel) . Évalué à 5.

            Tu confond plusieurs choses

            Justement, j'évite de trop m'avancer sur ce sujet de « D-Bus dans le noyau » parce que

            1. je n'y connais pas grand chose au noyau
            2. ça m'a l'air franchement lointain
            3. je ne vois pas ce que ça changerai pour moi, et c'est peut-être la seule chose que j'ai vraiment dite, en fait.

            Quand j'écris « Mais quelque soit l'avenir, il serait bon qu'Alsa soit interrogeable officiellement via D-Bus », ça n'a rien à voir avec le noyau, et ça reste vrai.

            Je ne parle de D-Bus dans le noyau que parce que tankey< en a parlé (il doit faire référence à ça. Ce à quoi je répond prudemment « mais je ne sais pas trop ce que ça changera », et je termine en disant « faut-il encore qu'Alsa parle D-Bus », bref mon problème n'est pas « D-Bus et le noyau » mais bien « D-Bus et Alsa ». Il n'y a pas de confusion ici.

            Donc merci pour tes explications sur D-Bus qui sont intéressantes, mais je ne vois pas où tu vois de la confusion quand je ne fais que répéter que « D-Bus et le noyau » ne me concerne pas.

            Si tu veux parler Alsa via D-Bus, ça veut dire qu'il va falloir un démon qui applique la configuration qui était appliqué par libasound

            Je n'en suis même pas à vouloir configurer quoi que ce soit ! Je veux juste une description de ce qui est accessible via Alsa :
            Combien de cartes, leur nom, les canaux, les échantillonnages etc. Bref, un peu comme un un /proc/cpuinfo mais pour les cartes sons.

            Franchement, il y a déjà pulseaudio pour ça

            Euh, s'il faut chercher une confusion, elle est là ! Je veux une interface accessible depuis Python 3 pour savoir quel matériel géré par Alsa est disponible sur mon système.

            D'une, je ne configure rien sur les cartes, de deux, ce n'est pas moi qui produit du son (je délègue ça), donc je ne parle pas aux cartes sons.
            J'ai besoin de connaître les cartes gérées par Alsa, pas de connaître les hypothétiques cartes vues par PulseAudio, à travers PulseAudio.

            PulseAudio est complètement hors-sujet ici. Je n'ai rien à faire de savoir ce que PulseAudio expose comme interface. Et surtout je n'ai pas besoin de PulseAudio pour envoyer le son aux cartes son (et surtout j'ai besoin de ne pas passer par PulseAudio pour le faire).

            Alsa marche très bien, tout ce que je veux c'est une interface qui me dise ce qui est exposé par Alsa, pas ce qui est exposé par une abstraction au dessus de Alsa. Utiliser PulseAudio est la meilleure manière de ne pas répondre à ma problématique. J'aurai plein d'informations mais elles me seront inutiles.

            Mon projet gère des entrées et sorties sons (cartes sons, flux réseau) et leurs connections. PulseAudio, ce sont des entrées et sorties sons comme d'autres, à coté de Alsa. En gros, Depuis PulseAudio arrive le son mixé des applis qui savent causer PulseAudio (totem, rhythmbox…), et vers PulseAudio part le son vers des applis de capture sonore qui savent causer PulseAudio.

            PulseAudio peut envoyer le son des applis à Alsa, mais peut aussi ne pas le faire. PulseAudio peut fonctionner sans aucune carte son, PulseAudio peut être branché sur Jack au lieu de Alsa par exemple (Le même Jack pouvant hypothétiquement ne pas être connecté à Alsa, ça existe (cf. FFADO), ou même juste pour essayer, voir le pilote dummy).

                              entrées ←·→ sorties 
                                       ·
             [‾‾‾‾‾‾‾‾‾‾‾‾‾‾]          ·
             [ hypothétique ]          ·
             [ bureau, vlc, ]          ·
             [ audacity…    ]          ·
             [______________]          ·
               \                       ·
                [ pulse ][ c0 ]-----[‾‾‾‾‾‾]
                [ audio ][ c1 ]-----[      ]                       ↑ fonctionnalités
                                    [      ]                       | possibles
            ------------------------>------<-----------------------|
                                    [      ]                       | fonctionnalités
                 [ alsa ][ c0 ]     [      ]                       ↓ nécessaire
                 [ hw:0 ][ c1 ]-----[ bras ]-----[ c0 ][ net0 ]
                                    [ sage ]
                 [ alsa ][ c0 ]-----[      ]
                 [ hw:1 ][ c1 ]     [      ]
                         [ c3 ]-----[      ]-----[ c0 ][ alsa ]
                         [ c4 ]     [      ]     [ c1 ][ hw:0 ]
                                    [      ]
                 [ net0 ][ c0 ]-----[      ]
                                    [______]
                                       ·
                                       ·
                                       ·
                                       ·
            
            

            Maintenant faisons une analogie. Imagine quelqu'un qui pose la question « Bonjour, je suis musicien et je voudrais configurer jackd, mais je ne sais pas si ma carte peut-être gérée sans latence avec l'option -hwmon, comment puis-je le savoir ? », et toi tu réponds. « Franchement il y a PulseAudio pour ça ». Confusion.

            La confusion vient peut-être du fait qu'Alsa fait trop de choses, choses qui peuvent être comparées à des choses similaires dans PulseAudio, dans Jackd, et autres systèmes qui se branchent au dessus d'Alsa. Quand je parle d'Alsa, je ne parle pas de l'échantillonneur et convertisseur plug, je ne parle pas du mixeur dmix/dsnoop, non, je parle uniquement de la partie « pilotes de carte son » et « description du matériel », cette partie qui est utilisée par Jackd et PulseAudio.

            Voilà, pour faire simple, un schéma juste après.
            J'ai besoin de savoir ce qu'il y a au dessous de la ligne pour pouvoir connecter ce qu'il y a au dessus.
            PulseAudio ne m'est pas d'une grande aide, ce n'est qu'une fonctionnalité supplémentaire que je peux choisir de gérer.

                               ALSA          Jackd      PulseAudio
                            [‾‾‾‾‾‾‾‾]    [‾‾‾‾‾‾‾‾]    [‾‾‾‾‾‾‾‾]  }
                            [ plug,  ]    [        ]    [        ]  }
                            [ dmix,  ]    [        ]    [        ]  } mixage¹, conversion,
                            [ dsnoop ]    [        ]    [        ]  } échantillonnage,
                            [ …      ]    [        ]    [        ]  } routage…
                FFADO       [        ]    [        ]    [        ]  }
            ‾‾[‾‾‾‾‾‾‾‾]‾‾‾‾[‾‾‾‾‾‾‾‾]‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾}‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
              [        ]    [        ]                              } pilote
              [________]    [________]                              }
            
            

            ¹ mixage : ici il ne s'agit pas du mixage du volume, mais du mixage de plusieurs sons simultanément

            ce commentaire est sous licence cc by 4 et précédentes

            • [^] # Re: Avertissement (attention les yeux)

              Posté par  (site web personnel) . Évalué à 9. Dernière modification le 05 mars 2013 à 17:52.

              (note : si j'ai causé de cette nouvelle de «dbus dans le noyau» c'est simplement parceque le montage alsa - dbus - alsa (et passer par udev pour avoir un module py3 avec un py2 en //) semble bizarre par construction, pour simplement obtenir des informations sur la carte son. Parceque la distribution ne propose pas encore ce script en python3. Cela m'a semblé incroyablement compliqué pour faire quelque chose de simple. Ceci dit je n'y connait rien non plus :)

              Alsacap ?
              Exemple de sortie de aplay, suivi de la sortie de alsacap, pour la même carte basique :

                  [muny@hpg72 tmp]$ aplay -l
                  **** Liste des Périphériques Matériels PLAYBACK ****
                  carte 0: MID [HDA Intel MID], périphérique 0: ALC270 Analog [ALC270 Analog]
                    Sous-périphériques: 0/1
                    Sous-périphérique #0: subdevice #0
                  carte 0: MID [HDA Intel MID], périphérique 3: HDMI 0 [HDMI 0]
                    Sous-périphériques: 1/1
                    Sous-périphérique #0: subdevice #0
              
              
                  [muny@hpg72 tmp]$ ./alsacap 
                  *** Scanning for playback devices ***
                  Card 0, ID `MID', name `HDA Intel MID'
                    Device 0, ID `ALC270 Analog', name `ALC270 Analog', 1 subdevices (1 available)
                      2 channels, sampling rate 44100..192000 Hz
                      Sample formats: S16_LE, S32_LE
                        Subdevice 0, name `subdevice #0'
                    Device 3, ID `HDMI 0', name `HDMI 0', 1 subdevices (1 available)
                      2..8 channels, sampling rate 32000..192000 Hz
                      Sample formats: S16_LE, S32_LE, IEC958_SUBFRAME_LE
                        Subdevice 0, name `subdevice #0`
              
              

              C'est vrai que c'est nettement mieux, par défaut, en terme d'informations utiles… mais je ne sais pas si cela t'aidera. Le code source de alsacap.

              • [^] # Re: Avertissement (attention les yeux)

                Posté par  (site web personnel) . Évalué à 3.

                Oh ça c'est bon !
                S'il faut compiler quelque chose, je préfère compiler alsacap que tout un module python !
                Aussi, ça me fournit un exemple concis d'asoundlib, et je pourrais peut-être reproduire les quelques fonctionnalité qui m'intéressent en utilisant ctypes, cité plus bas !

                ce commentaire est sous licence cc by 4 et précédentes

                • [^] # Re: Avertissement (attention les yeux)

                  Posté par  (site web personnel) . Évalué à 4.

                  pour ceux qui cherchent, la compilation c'est gcc alsacap.c -o alsacap -lasound (j'ai cherché un moment le -lasound, trouvé avec pkg-config --libs alsa).

                  Voilà ce que ça me donne sur une machine de test :

                  $ ./alsacap | grep '^Card' -A 3 | grep -v 'Subdevice\|--\|Device' | sed -e 's/, (*[0-9]* .*//;s/, sampling rate/\n    rate:/;s/Sample //' | sed -e 's/[0-9]\.\.\([0-9]*\) chann/\1 chann/;s/\([0-9]*\) channels/channels: \1/;s/ Hz//'
                  Card 0, ID `XFi', name `Creative X-Fi'
                      channels: 8
                      rate: 8000..192000
                      formats: U8, S16_LE, S32_LE, FLOAT_LE, S24_3LE
                  Card 1, ID `CODEC', name `USB Audio CODEC'
                      channels: 2
                      rate: 32000..48000
                      formats: S8, U8, S16_LE
                  Card 2, ID `LT3', name `Muse Pocket LT3'
                      channels: 8
                      rate: 44100..48000
                      formats: S16_LE
                  Card 3, ID `Intel', name `HDA Intel'
                      channels: 8
                      rate: 44100..192000
                      formats: S16_LE, S32_LE
                  Card 4, ID `Juli', name `ESI Juli@'
                      channels: 2
                      rate: 16000..192000
                      formats: S32_LE
                  Card 5, ID `Live', name `SB PCI512 [CT4790]'
                      channels: 2
                      rate: 4000..96000
                      formats: U8, S16_LE
                  Card 6, ID `Live1', name `SB Live! Value [CT4832]'
                      channels: 2
                      rate: 4000..96000
                      formats: U8, S16_LE
                  
                  

                  ce commentaire est sous licence cc by 4 et précédentes

  • # PyAlsaAudio python3

    Posté par  (site web personnel) . Évalué à 3. Dernière modification le 03 mars 2013 à 11:53.

    je pars du principe que le mixage est bon et les switchs logiciels convenablement sélectionnés
    mes cartes sons et leurs capacités (…)
    lister mes cartes audio
    interroger quelques caractéristiques : Marque et modèle
    que je puisse lister tout cela depuis le logiciel

    A moins que tu souhaites vraiment faire des torsions avec les bindings python de libXtract (ça marche, ça ?) (voir PySndObj ou Py-PA parceque ça obsolètiserai ce bullshitisme :p). Tout part de ce besoin : avoir les caractéristiques, pour cela tu as choisi d'utiliser pyalsa, et tu cherches un moyen élégant de l'utiliser avec python3 ? Mais il est marqué python3, portage par l'auteur initial, confirmé ici aussi. J'ai probablement rien compris ! :-)

    • [^] # Re: PyAlsaAudio python3

      Posté par  (site web personnel) . Évalué à 6.

      A moins que tu souhaites vraiment faire des torsions avec les bindings python de libXtract (ça marche, ça ?) (voir PySndObj ou Py-PA

      En fait, je ne produis ni ne capture moi-même du son, je laisse cette tâche à Gstreamer. Mais pour construire mes pipelines Gstreamer, j'ai besoin d'en savoir un peu plus sur ce que font mes cartes.

      Je veux éviter ce genre de pipeline :

      [something] ! audioconvert ! audioresample ! alsasink device='default:hw:0'"
      
      

      Avec l'usage d'un module de conversion de format binaire (S16_LE, S32_LE…) nommé audioconvert et un module de resampling (44100, 48000…) nommé audioresample, tandis que Alsa charge lui aussi le module plug (qui fait la reconversion binaire et le rééchantillonnage) et le module dmix pour faire le mixage (dont je n'ai pas besoin).

      Le problème, c'est que c'est ce que font les logiciels, aujourd'hui. Ils prennent ceinture et bretelle. Tel logiciel utilise Gstreamer parce que c'est cool, et pour être sûr de ne pas être bloqué avec un format ou un échantillonnage improbable, glissent les deux modules audioconvert et audioresample au cas où, notamment dans le cas où l'utilisateur indique dans ses préférences que sa carte son est hw:0 et non default:hw:0 ni plug:hw:0.
      Mais en fait, dans la majorité des cas, la carte par défaut de l'utilisateur est default, c'est à dire default:hw:0.

      Alors quand l'appli est un logiciel de lecture de musique (rhythmbox, banshee…) ça ne pose aucun problème. Quand tu ne fait que capturer le son pour le monter de manière non linéaire plus tard, la latence ne pose pas problème.

      C'est pourquoi malgré qu'Alsa soit un gros merdier difficilement exploitable, il n'y a pas grand monde pour râler parce que dans 99% des cas, les gens utilisent une abstraction sur une abstraction sur une abstraction, par exemple Gstreamer sur PulseAudio sur Alsa, trois couches où peuvent être faits du rééchantillonage et de la conversion de format, deux couches où peut être fait le mixage. Et oui, quand vous écoutez votre musique préférée avec Rhythmbox, il se passe certainement tout cela sur votre ordinateur.

      Même quand vous faites « _aplay monfichier.wav_ », vous passez par deux étapes de rééchantillonnage, celui d' aplay, et celui d' alsa, parce que aplay rééchantillonne par défaut, et utilise par défaut la carte son default et donc le module plug. Tout ça « au cas où », toujours.

      Bref cette façon de faire, avec ceintures et bretelles est cool pour celui qui ne se soucie pas de la latence, il sait qu'il peut parler comme il veut, la carte sortira le son. Il peut faire de 5 canal en F32_LE à 48000 Hz sur une carte son stéréo qui ne supporte que le S16_LE à 44100, le son sortira, l'utilisateur sera content.

      Sauf que… moi je fais de la radio FM, je transfère du son sur le réseau, je relie des studios entre eux pour faire des duplex, ou un studios mobile au départ émetteur du studio pour faire un direct en extérieur. Je veux être au plus strict sur la latence. Donc, pas de audioconvert inutile, pas de audioresample inutile, pas de plug, ni de dmix/dsnoop parce que je mixe déjà les sons en amont. Mais pour faire ça, il faut que je connaisse mon matériel.

      Aussi, je veux pouvoir indiquer dans l'interface utilisateur ce qu'il a le droit de faire, du genre « OK gars, tu as 2 stéréo (4 entrées mono) sur telle carte en S32_LE et pas moins, avec de l'échantillonnage jusqu'à 192Khz, mais sur cette autre carte tu as 1 entrée stéréo (2 entrées mono) en S16_LE et pas plus, avec de l'échantillonnage jusqu'à 44100 ». Et quand l'utilisateur demande « je veux 5 canaux de capture », mon logiciel configure le pipeline de capture de la première carte en capturant tous les canaux avec un module audioconvert mais de pas de audioresample parce que derrière il y a un jackd en F32_LE, tandis que le pipeline de la seconde carte utilise à la fois le module audioconvert et le module audioresample, mais ne capture qu'un seul canal, le gauche.

      La plupart des logiciels demandent à l'utilisateur « quel son veux tu lire ? », mais mon logiciel demande à l'utilisateur « que veux-tu faire avec ta carte son ? ». Et pour faire ça, c'est la galère.

      ce commentaire est sous licence cc by 4 et précédentes

    • [^] # Re: PyAlsaAudio python3

      Posté par  (site web personnel) . Évalué à 5.

      Tout part de ce besoin : avoir les caractéristiques, pour cela tu as choisi d'utiliser pyalsa, et tu cherches un moyen élégant de l'utiliser avec python3 ? Mais il est marqué python3, portage par l'auteur initial, confirmé ici aussi. J'ai probablement rien compris ! :-)

      En fait, le besoin des caractéristiques des cartes audio et l'incapacité d'avoir pyalsa pour Python 3 sur ma machine m'a donné l'envie de jouer avec Python et son introspection, et m'a donné aussi le prétexte d'un journal, ce qui est déjà pas mal, ça a occupé geekement une partie de ma soirée d'hier, et ça n'a pas de prix ! :)

      Alors par contre, le port de pyalsa vers Python 3 est une excellente nouvelle qui donne espoir !
      Malheureusement, dans la vraie vie, je suis loin de le voir…

      Pour prendre Debian comme exemple, distribution susceptible de faire tourner mon logiciel, ce ne sera pas pour Wheezy, et ce n'est pas encore pour Sid.

      pyalsa est un module en C, il me faut donc le compiler pour l'utiliser avec Python 3. Ça je peux faire. Mais ce que je ne peux pas faire, c'est dire à l'utilisateur « bon en fait, pour utiliser mon logiciel interprété, il faut que tu compiles un paquet qui n'est même pas dans Sid ».

      Je pourrais demander de faire du pinning ou d'utiliser les backports, mais faire dépendre mon logiciel d'une compilation manuelle… je ne peux pas. Hors mon logiciel il doit servir aujourd'hui, pas quand Debian aura intégré python3-pyalsa dans stable (dans deux ou trois ans minimum ?).

      L'idée avec PyRO, même si elle a l'énorme défaut de lancer deux versions d'interpréteur différent, c'est que j'utiliserai effectivement le module pyalsa pour Python 2 directement depuis Python 3. Ainsi, quand le module python2-pyalsa est disponible chez l'utilisateur, il suffit de tomber la couche PyRO.

      ce commentaire est sous licence cc by 4 et précédentes

      • [^] # Re: PyAlsaAudio python3

        Posté par  . Évalué à 5.

        Hors mon logiciel il doit servir aujourd'hui, pas quand Debian aura intégré python3-pyalsa dans stable (dans deux ou trois ans minimum ?).

        Pourquoi ne pas utiliser un virtualenv ou assimilé ? C'est fait pour. Si tu ne veux pas avoir à demander d'accès réseaux pour l'instalation ou ne pas être vulnérable aux failles de sécurité de pip/pypi tu livres un pybundle en static.

    • [^] # Re: PyAlsaAudio python3

      Posté par  (site web personnel) . Évalué à 2.

      Hum pardon, j'ai confondu, tu parles de pyalsaaudio, et non de pyalsa !

      En fait il existe pyalsaaudio (dit python-pyalsa chez Debian) :

      Et moi j'aimerai utiliser pyalsa (dit python-pyalsa chez Debian) :

      À ne pas confondre avec pyalsa :

      Bref, je préfèrerai utiliser python-pyalsa à cause de son coté « officiel », mais pour Python 3 seul python-alsaaudio est actuellement disponible, et rien n'est dans Debian. Dur dur !

      ce commentaire est sous licence cc by 4 et précédentes

  • # Pyro

    Posté par  (site web personnel) . Évalué à 2.

    Pour mon projet, je ne sais toujours pas ce que je vais faire…

    Il y a quelque temps j'ai lu dans un magazine un article à ce sujet (GNU/Linux Magazine de mémoire).

    Il utilisait PYthon Remote Object (https://pypi.python.org/pypi/Pyro4) pour appeler des objets python2.x à partir de python3. La subtilité était de forcer pyro à fonctionner en python2.

    Je te retrouverai les détails ce soir…

    Matthieu Gautier|irc:starmad

    • [^] # Re: Pyro

      Posté par  (site web personnel) . Évalué à 2.

      J'ai déjà ledit article en fait (Utiliser le meilleur de Python 2.x et de Python 3.x au sein d’une seule et même application (intro), GNU/LINUX Magazine France n°145 de Janvier 2012, en page 75) dans ma collec de Linux Mag. Mais merci tout de même pour l'intention :).

      ce commentaire est sous licence cc by 4 et précédentes

      • [^] # Re: Pyro

        Posté par  (site web personnel) . Évalué à 1.

        Et par curiosité, pourquoi cette solution ne te va pas ?

        À moins que tu n'y ais pas vraiment réfléchit et que tu te sois lancé dans dbus directement (mode POC)

        Matthieu Gautier|irc:starmad

        • [^] # Re: Pyro

          Posté par  (site web personnel) . Évalué à 2.

          Je n'ai pas besoin de tout dans Alsa, seulement lister les cartes et quelques unes de leurs caractéristiques (format binaire, échantillonnage, nombre de canaux, et c'est tout je crois). Si je trouve un moyen d'obtenir tout ces informations simplement en lisant /proc ou /sys, je me ferai plutôt un pyalsa-light plutôt que de faire dépendre mon projet de Python 3 ET Python 2. L'idée de l'export dbus présentée dans le journal est seulement une idée rigolote mais que je n'envisage pas du tout sérieusement.

          En gros j'ai plusieurs possibilité :

          • régresser tout le projet en Python 2
          • rester en Python 3 et ajouter une compilation manuelle en dépendance
          • rester en Python 3 et ajouter PyRO et Python 2 en dépendance, et devoir gérer un process supplémentaire.
          • réinventer la roue parce que je n'ai besoin que d'une roue, pas d'une voiture complète.

          Je préfèrerai (en attendant la disponibilité de python3-pyalsa dans nos distros bien-aimées) la dernière solution (lire /proc ou /sys), sauf qu'en fait tout ça me semble inutilisable, c'est spécifique à chaque carte et chaque chipset. En gros /proc ou /sys ça ressemble à un faire un strings sur le charabia binaire que crache la carte, sans abstraction.

          ce commentaire est sous licence cc by 4 et précédentes

          • [^] # Re: Pyro

            Posté par  (site web personnel) . Évalué à 6.

            Une idée comme ça… s'il y a une librairie en .so pour Alsa, tu dois pouvoir faire rapidement un binding vers les fonctions qu'elle contient en utilisant ctypes.

            Ça devrait couvrir Python2 et Python3.

            Ça ne nécessite rien d'autre qu'Alsa installé avec cette lib dynamique.

            Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

            • [^] # Re: Pyro

              Posté par  (site web personnel) . Évalué à 2.

              Ah merci pour l'idée, c'est pas bête, je vais voir ce que je peux faire avec ça ! :)

              ce commentaire est sous licence cc by 4 et précédentes

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.