|
Programmation
audio sous LINUX
Pierre
Ficheux (pficheux@com1.fr)
Janvier
2000
0.
Résumé
Cette
article est une introduction à la réalisation d'applications audios
sous LINUX. Nous présenterons successivement la configuration
du noyau LINUX pour le support audio, l'utilisation des fichiers
spéciaux (devices) utilisés par le noyau et une introduction à
la programmation en C de l'API audio de LINUX. Dans une dernière
partie, nous présenterons un petit exemple d'application réalisant
du streaming audio. Les archives des programmes d'exemples
sont disponibles sur http://www.com1.fr/~pficheux/articles/lmf/audio/exemples/exemples.tar.gz
1.
Principe d'une carte son
La
carte son est un périphérique effectuant des conversions entre
des données analogiques (un signal audio en entrée) et des données
numériques (un fichier informatique de stockage). On a alors affaire
à un échantillonnage du signal audio.
La conversion peut également se faire dans l'autre sens, lorsque
l'on veut jouer un fichier audio sur la sortie audio
du PC.
Pour
effectuer cela, la carte audio est basée sur une paire de circuits
électroniques appelés Convertisseur Analogique Numérique
et Convertisseur Numérique Analogique, ou en anglais
ADC (Analog to Digital Converter) et DAC (Digital
to Analog Converter).
2.
Configuration du noyau LINUX
Le
multimédia est une partie importante de l'informatique d'aujourd'hui.
Les développeurs du noyau LINUX ont réalisés des prouesses en
permettant d'utiliser une bonne partie des chipsets audios
actuels. La configuration du noyau LINUX passe comme d'habitude
par la suite de commande:
cd /usr/src/linux
make config
On
pourra avantageusement remplacer le make config par un
make menuconfig (configuration en mode texte plein écran)
ou un make xconfig (configuration par des scripts TCL/TK).
Si
on prend pour exemple le support d'une bonne vieille carte Sound
Blaster de Creative Labs, on devra valider les options
suivantes:
Sound card support (CONFIG_SOUND) [M/n/y/?] m
ProAudioSpectrum 16 support (CONFIG_PAS) [N/y/?]
Sound Blaster (SB, SBPro, SB16, clones) support (CONFIG_SB) [Y/n/?]
...
/dev/dsp and /dev/audio support (CONFIG_AUDIO) [Y/n/?]
MIDI interface support (CONFIG_MIDI) [N/y/?]
FM synthesizer (YM3812/OPL-3) support (CONFIG_YM3812) [N/y/?]
I/O base for SB Check from manual of the card (SBC_BASE) [220]
Sound Blaster IRQ Check from manual of the card (SBC_IRQ) [5]
Sound Blaster DMA 0, 1 or 3 (SBC_DMA) [1]
Sound Blaster 16 bit DMA (_REQUIRED_for SB16, Jazz16, SMW) 5, 6 or 7 (use 1 for 8 bit cards) (SB_DMA2) [1]
MPU401 I/O base of SB16, Jazz16 and ES1688 Check from manual of the card (SB_MPU_BASE) [0]
SB MPU401 IRQ (Jazz16, SM Wave and ES1688) Use -1 with SB16 (SB_MPU_IRQ) [-1]
Audio DMA buffer size 4096, 16384, 32768 or 65536 (DSP_BUFFSIZE) [65536]
Dans le cas présent, la configuration est la suivante:
- Support
audio en module
- La
carte est un SB ou une SB16
- On
supporte les fichiers spéciaux /dev/audio et /dev/dsp
- La
carte est à l'adresse de base 220, interruption 5
et canal DMA 1
La
compilation du noyau se fera ensuite par:
make dep; make clean; make zlilo; make modules; make modules_install
3.
Les fichiers spéciaux de l'API audio de LINUX
Nous
nous bornerons ici à décrire les principaux fichiers spéciaux
utilisés par le noyau LINUX, en l'occurence:
/dev/dsp |
Ce
device permet de lire/écrire les échantillons traités
par la carte son. Dans ce cas la les données sont échantillonnées
sur 8 bits. |
/dev/dspW |
Identique
au device précédent, sauf que les données sont échantillonnées
sur 16 bits, lorque la carte le permet. |
/dev/audio |
Ce
device permet de lire/écrire des données au format mu-Law
(encodage logarithmique). Pour info, ce device est compatible
avec celui des stations de travail de type SUN. |
/dev/mixer |
Ce
device permet de controler l'entrée/sortie audio, par
exemple règler le volume de sortie, lorsque la carte le
permet. |
/dev/sndstat |
Ce
device permet d'obtenir des infos concernant la configuration
audio; |
L'utilisation de ces devices dans une session shell est extrèmement
simple. Par exemple pour jouer un fichier audio mu-Law
(en général suffixé .au) on fera:
cat fichier.au > /dev/audio
et pour enregistrer:
cat /dev/audio > fichier.au
^C
Pour obtenir les infos concernant la configuration audio:
[root@dc2000 /root]# cat /dev/sndstat
Sound Driver:3.5.4-960630 (mar jan 18 17:35:32 CET 2000 root,
Linux atkins.local.com1.fr 2.0.36 #5 jeu sep 16 18:38:52 CEST 1999 i686 unknown)
Kernel: Linux dc2000.local.com1.fr 2.0.36 #67 mer jan 19 09:13:18 CET 2000 i486
Config options: 0
Installed drivers:
Type 2: Sound Blaster
Card config:
Sound Blaster at 0x220 irq 10 drq 1,5
Audio devices:
0: Sound Blaster 16 (4.12)
Synth devices:
Midi devices: NOT ENABLED IN CONFIG
Timers:
0: System clock
Mixers:
0: Sound Blaster
4.
Programmation de l'API audio
Comme
habituellement sous LINUX, l'API audio sera utilisable à travers
les devices décrits ci dessus. On devra donc utiliser les appels
systèmes classiques comme:
- open
- close
- read
- write
- ioctl
L'ouverture d'un device audio en lecture se fera par une séquence
du type:
int fd_audio;
if ((fd_audio = open ("/dev/audio", O_RDONLY)) < 0) {
perror ("/dev/audio");
exit (1);
}
/* Le device audio est disponible */
...
La lecture
des échantillons se fera par:
#define BUF_SIZE 4096
int n;
char buf[BUF_SIZE];
if ((n = read (fd_audio, buf, sizeof(buf))) < 0) {
perror ("audio read");
exit (1);
}
/* Les échantillons sont disponibles dans buf */
...
La fermeture du device se fera par un simple:
close (fd_audio);
De même, on pourra jouer les échantillons mu-Law en utilisant
la portion de code ci-dessous
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#define BUF_SIZE 4096
main (int ac, char **av)
{
int fd_file, fd_audio, n, nleft, nwritten;
char buf[BUF_SIZE], *p;
if ((fd_file = open (av[1], O_RDONLY)) < 0) {
perror (av[1]);
exit (1);
}
if ((fd_audio = open ("/dev/audio", O_WRONLY)) < 0) {
perror ("/dev/audio");
exit (1);
}
/* Lecture de l'échantillon */
while ((n = read (fd_file, buf, sizeof(buf))) > 0) {
nleft = n;
p = buf;
/* On envoit l'échantillon... */
while (nleft) {
if ((nwritten = write (fd_audio, p, nleft)) < 0)
perror ("/dev/audio");
else
printf ("%d/%d written.\n", nwritten, nleft);
nleft -= nwritten;
p += nwritten;
}
}
close (fd_file);
close (fd_audio);
}
On
pourra controler des paramètres tels que le volume de sortie en
utilisant le device /dev/mixer. Pour lire le volume de
sortie, on utilisera:
int fd_mixer, vol;
if ((fd_mixer = open ("/dev/mixer", O_RDWR)) < 0) {
perror ("/dev/mixer");
exit (1);
}
if (ioctl (fd_mixer, SOUND_MIXER_READ_VOLUME, &vol)==-1) {
perror ("ioctl");
}
else
printf ("vol= %d %d\n", vol&255, (vol >> 8)&255);
close (fd_mixer);
et pour l'écrire:
int fd_mixer, vol, v;
if ((fd_mixer = open ("/dev/mixer", O_RDWR)) < 0) {
perror ("/dev/mixer");
exit (1);
}
v = atoi(av[1]);
vol = (v << 8) | v;
if (ioctl (fd_mixer, SOUND_MIXER_WRITE_VOLUME, &vol)==-1) {
perror ("ioctl");
}
close (fd_mixer);
On pourra
de même visualiser et modifier le canal d'enregistrement (par
exemple mic ou line):
static char *dev_names[] = SOUND_DEVICE_NAMES;
int fd_mixer, mask, i;
if ((fd_mixer = open ("/dev/mixer", O_RDWR)) < 0) {
perror ("/dev/mixer");
exit (1);
}
if (ioctl (fd_mixer, SOUND_MIXER_READ_RECSRC, &mask)==-1) {
perror ("ioctl");
}
for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) {
if (mask & (1 << i))
printf ("Enregistrement sur: %s\n", dev_names[i]);
}
close (fd_mixer);
Idem pour la sélection du canal d'enregistrement en utilisant:
static char *dev_names[] = SOUND_DEVICE_NAMES;
int fd_mixer, mask, i;
if ((fd_mixer = open ("/dev/mixer", O_RDWR)) < 0) {
perror ("/dev/mixer");
exit (1);
}
mask = 0;
for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) {
if (!strcmp (av[1], dev_names[i])) {
mask |= (1 << i);
break;
}
}
if (!mask) {
fprintf (stderr, "Device %s inconnu !\n", av[1]);
close (fd_mixer);
exit (1);
}
if (ioctl (fd_mixer, SOUND_MIXER_WRITE_RECSRC, &mask) == -1) {
perror ("ioctl");
}
close (fd_mixer);
5.
Un petit exemple de streaming audio
De
plus en plus de sites Internet fournissent des services de streaming
vidéo ou audio. Le principe est simple: il s'agit de jouer en
continu grace à un navigateur Internet ou bien une application
spécialisée un flux de données provenant d'un serveur. Un exemple
est la possibilité d'écouter des stations de radios à travers
un réseau de type TCP/IP et à l'aide d'un lecteur spécifique ou
bien d'un navigateur Internet type NS Communicator équipé d'un
plugin.
L'application
qui suit est un petit serveur audioserver permettant
de diffuser à travers le réseau les échantillons audios provenant
de l'entrée d'une carte son d'un PC LINUX. Le principe de fonctionnement
est le suivant:
- Lorsque
le client se connecte sur le port associé au service
audio, l'application ouvre le device /dev/audio .
-
-
Si
le client est un navigateur, ce dernier envoit une requète
HTTP (de type GET) et le client doit répondre en
acquittant cette demande et en renvoyant le type des données
transmises (ici audio/ulaw). C'est la reception de
ce type MIME qui provoquera le chargement du plugin
coté navigateur.
-
Lorque
le dialogue HTTP est terminé, le serveur copie en temps réel
les données audios sur la connexion réseau.
-
Lorque
le client coupe la connexion, le device audio est fermé
Pour
installer une application de ce type on devra:
- Modifier
le fichier /etc/services en ajoutant une ligne:
audio 5555/tcp
si le
port à utiliser est le 5555.
-
-
Modifier
le fichier /etc/inetd.conf en ajoutant une ligne:
audio stream tcp nowait root /usr/local/bin/audioserver audioserver
-
Forcer
le démon inetd à relire sa config en faisant:
kill -1 `cat /var/run/inetd.pid`
Cette
application à but pédagogique est volontairement simplifiée:
- Elle
utilise le démon inetd et n'accepte donc qu'une
seule connexion.
-
-
Le
données audios ne sont pas compressées, ce qui n'est pas
envisageable dans le cas d' une application réelle.
-
Elle
utilise le protocole TCP qui n'est pas le mieux
adapté au transfert de données en streaming.
Dans, ce cas il est plutôt conseillé d'utiliser le protocole
UDP beaucoup moins gourmant en bande passante.
-
Le
code source commenté du serveur est donné ci-dessous:
Cette
première partie comprends les includes et la définition de
quelques constantes. La réponse HTTP est principalement constituée
du code de réponse correcte HTTP/1.0 200 OK et du
type des données Content-Type: audio/ulaw
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/soundcard.h>
extern char *basename();
#define HTTP_REPLY "HTTP/1.0 200 OK\r\nPragma: no-cache\r\nContent-Type: audio/ulaw\r\n\r\n"
#define MAXREQ 1024
#define BUF_SIZE 1024
La fonction ci-dessous effectue la lecture de la requête HTTP
du navigateur
static char http_request[MAXREQ];
/* Lecture de la requête HTTP du client */
int get_http_request (int fd, char *request, int max_length)
{
char end, c, last_char;
int length = 0;
end = 0;
c = last_char = 0;
while (!end) {
if (read (fd, &c, 1) < 0)
return -1;
/* Test de fin de commande */
if (c == '\r' && last_char == '\n') {
if (read (fd, &c, 1) < 0)
return -1;
if (c == '\n')
end = 1;
else {
syslog (LOG_ERR, "get_http_request: bad end of request!\n");
return -1;
}
}
else {
if (request)
*(request+length) = c;
if (length < max_length-1)
length++;
/* Dépassement de taille de requète ou pas de marqueur de fin */
else {
syslog (LOG_ERR, "get_http_request: request too long!\n");
return -1;
}
}
last_char = c;
}
if (request)
*(request+length) = 0;
return length;
}
Cette
fonction appelle la fonction précédente puis envoit la réponse
HTTP au client.
/* Dialogue HTTPD ? */
static void http_dialog (int fd_in, int fd_out)
{
int n;
/* Lecture requête */
if ((n = get_http_request (fd_in, http_request, sizeof(http_request))) <= 0)
return;
/* Envoi réponse HTTP */
write (fd_out, HTTP_REPLY, strlen(HTTP_REPLY));
}
La suite décrit le programme principal. On commence par ouvrir
de device audio.
main (int ac, char **av)
{
int n, nleft, nwritten, fd_audio;
char buf[BUF_SIZE], *p, *progname = basename(av[0]);
openlog (progname, LOG_PID | LOG_CONS, LOG_DAEMON);
if ((fd_audio = open ("/dev/audio", O_RDONLY)) < 0) {
syslog (LOG_ERR, "Can't open audio device, exiting.\n");
exit (1);
}
Puis
on effectue le dialogue HTTP.
/* Dialogue HTTP */
http_dialog (0, 1);
Ensuite,
on lit des données audios et on les envoit sur le réseau.
La condition d'arrêt est en général la coupure de la connexion
coté client.
while ((n = read (fd_audio, buf, sizeof(buf))) > 0) {
nleft = n;
p = buf;
/* On envoit l'échantillon... */
while (nleft) {
if ((nwritten = write (1, p, nleft)) < 0) {
syslog (LOG_ERR, write: %m");
exit (1);
}
nleft -= nwritten;
p += nwritten;
}
}
close (fd_audio);
}
6.
Autres drivers audios sous LINUX
Le
driver audio fourni avec le noyau LINUX 2.0 ou 2.2 supporte
un bon nombre de chipsets. Il existe cependant d'autres alternatives.
On pourra citer:
- Le
driver OSS, qui est une version commerciale du
driver audio standard. Il est développé par la société
4 Front Technologies fondée par l'auteur du driver
LINUX. Ce driver supporte un très grand nombre de chipsets
du marché et coute entre $10 et $30 suivant les options.
L'installation est très simple et pas mal de chipsets
sont détectés automatiquement. On peut télécharger une
version d'essai sur http://www.opensound.com/
-
-
Le
driver ALSA (Advanced Linux Sound Architecture)
est un driver complètement indépendant du driver standard.
Il est diffusé sous GPL sur http://www.alsa-project.org/.
Il faut noter que l'API de programmation est différente
de celle du driver standard ou bien d'OSS.
7.
Bibliographie
-
-
-
Copyright
(c) 2001 Linux Magazine France. Permission is granted
to copy, distribute and/or modify this document under
the terms of the GNU Free Documentation License, Version
1.1 or any later version published by the Free Software
Foundation; A copy of the license is included in the section
entitled "GNU Free Documentation License".
|
|