¡... haciendo Linux un poco más divertido!
Por Shuveb Hussain
Traducción al español por José Gregorio del Sol Cobos
el día 25 de Octubre de 2004, para La Gaceta de Linux
La multimedia en Linux ha sido un campo de hirviente actividad. Muchas mentes abiertas han contribuido ahora a hacer de Linux una plataforma multimedia decente. Hoy día existen muchos proyectos que hacen posible un gran número de actividades multimedia bajo Linux. Si usted necesita una buena intoducción a las varias utilidades de sonido bajo Linux, debería leer un excelente artículo previo en Linux Gazette que se encuentra aquí.
Quería incluir sonido en una de las aplicaciones que estaba escribiendo y empecé a googlear por la red. Aprendí que hay muchas librerías que hacen posible descodificar formatos comunes de archivos de sonido comprimido como Ogg Vorbis o MP3. Recrear sonido en sus altavoces ya no es el mejor trato. En este artículo, veremos cómo descodificar archivos Ogg Vorbis y MP3 escribiendo programas sencillos. Este artículo se ha escrito pensando en los que comienzan, así que a los gurús de Linux, perdón por las excesivas explicaciones del material sencillo.
Antes de que efectivamente nos metamos en los detalles de la programación de la tarjeta de sonido, sería efectivamente útil saber cómo se graban y almacenan los datos de sonido en nuestros ordenadores y cómo se reproducen.
Cuando se tiene que grabar el sonido, deberíamos elegir la calidad a la que queremos que se haga. Un criterio principal que de termina la calidad del sonido es la tasa de muestreo. Tome por ejemplo música, no es más que frecuencia que varía con el tiempo. Si queremos grabar sonido digitalmente, apuntamos la frecuencia del sonido a una tasa fija. Ésta se conoce como tasa de muestreo. A mayor tasa de muestreo, mejor calidad de la música. Por ejemplo, el audio con calidad de CD tiene una tasa de muestreo de 44100 por segundo, o 44100 Hz. Esta técnica se usa por dispositivos llamados ADC, o Conversores Analógico a Digital, y se llama Modulación del Código del Pulso, o PCM.
Cuando se muestrea el sonido, se usa un número fijo de bits por muestra y hay uno o más canales. El número de bits a la que se muestrean los datos se conoce como resolución del muestreo. La forma más común es un byte sin signo de 8 bits o de 16 bits. Los datos de canales diferentes se mantienen apartados. Por ejemplo, el audio de CD tiene dos canales, Izquierda y Derecha (estéreo). Si uno hace un simple cálculo, grabar un segundo de audio con calidad de CD con una resolución de 8 bits llevaría:
2 x 44100 = 88200 bytes (Número de canales por la tasa de muestreo)
Ahora que tenemos suficiente información sobre la teoría, empecemos con la programación de verdad. Hay varios modos de programar la tarjeta de sonido. En los primeros días de Linux, cuando se estaban escribiendo los controladores, se formaron dos grupos. Open Sound System (Sistema de Sonido Abierto), u OSS fue un grupo que escribió los controladores de muchos dispositivos y además incluyó módulos binarios de los fabricantes de tarjetas de sonido que eran remisos a proporcionar especificaciones de hardware, o proporcionaron especificaciones, pero no sin que el desarrollador del controlador firmase un acuerdo de confidencialidad. Como los tipos del hardware eran remisos a sacar especificaciones para individuos, OSS creó 4Front Technologies, una compañía. Un grupo más, ALSA, o Arquitectura de Sonido Avanzado para Linux, escribió controladores sólo para aquellas tarjetas de sonido cuyos fabricantes estaban preparados para sacar las especificaciones. Los controladores ALSA son ahora muy populares. También se han incluido en el kernel de prueba 2.6.
Recuerde que bajo UNIX/Linux, todos los controladores de dispositivo tienen una interfaz se sistema de archivos, habitualmente bajo el directorio /dev. La mayoría de las llamadas al sistema relacionadas con archivos como open, read, write, lseek, cierran el trabajo sobre esos dispositivos. Los archivos de los dispositivos se pueden considerar ganchos en el sistema de archivos desde los que los controladores de los dispositivos se mantienen a la escucha de peticiones. Como un ejemplo, el archivo /dev/hda1 es el que representa la interfaz para la primera partición del disco duro IDE maestro primario. Si usted lo abre utilizando la llamada de sistema open, se le devuelve un fichero como en todas la operaciones de archivo. Si ahora intenta leerlo, ¡estaría leyendo datos del primer sector de la partición! Análogamente un lseek hacia adelante moverá hacia adelante el puntero del archivo. Para saltarse un sector, usted haría lseek hacia adelante al tamaño de un sector. Por favor no juguetee con el archivo de dispositivo de su disco duro, es bastante peligroso. Escribir basura sobre la partición la hará inutilizable. Tendrá acceso directo a los dispositivos de hardware sólo como root la mayoría de las veces.
Habiendo dicho esto, movámonos a algo de código. Este código debería correr como no root sin problemas. En caso de que haya problemas al acceder al archivo de dispositivo, su como root y chmod y se garantiza el acceso. Desde luego debería tener una tarjeta de sonido que sepa trabajar bajo Linux. Ahórrese el escribir obteniendo la fuente desde aquí. No se olvide de descargar el archivo demo.pcm.
/*
oss.c reproduce una muestra de sonido en bruto PCM a 22 kHz en la tarjeta de sonido
Importante - Asegúrese de que el archivo demo.pcm está en el directorio actual
antes de intentar ejecutar este programa
*/
#include < sys/types.h >
#include < sys/stat.h >
#include < sys/soundcard.h >
#include < sys/ioctl.h >
#include < unistd.h >
#include < fcntl.h >
#include < errno.h >
#include < stdlib.h >
#define SECONDS 5 //segundos de playback
int main()
{
int fd;
int handle = -1;
int channels = 1; // 0=mono 1=stereo
int format = AFMT_U8;
int rate = 22000;
unsigned char* data;
/*
Abre el archivo correspondiente a la tarjeta de sonido para escritura, DSP es Procesador de Señal Digital
*/
if ( (handle = open("/dev/dsp",O_WRONLY)) == -1 )
{
perror("open /dev/dsp");
return -1;
}
/* Le dice a la tarjeta de sonido que el sonido a punto de reproducirse es estéreo. 0=mono 1=stereo */
if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 )
{
perror("ioctl stereo");
return errno;
}
/* Informa a la tarjeta de sonido del formato del audio */
if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
{
perror("ioctl format");
return errno;
}
/* Establece la tasa de playback de DSP, tasa de muestreo del audio PCM en bruto */
if (ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
{
perror("ioctl sample rate");
return errno;
}
// rate * 5 seconds * two channels
data = malloc(rate*SECONDS*(channels+1));
if((fd=open("demo.pcm",O_RDONLY))==-1)
{
perror("open file");
exit(-1);
}
/* lee los datos contenidos en el archivo demo a la memoria reservada */
read(fd,data,rate*SECONDS*(channels+1));
close(fd);
/* ¡Sólo escribe los datos en la tarjeta de sonido!just write the data to the sound card! Esto lo reproducirá. */
write(handle, data, rate*SECONDS*(channels+1));
if (ioctl(handle, SNDCTL_DSP_SYNC) == -1)
{
perror("ioctl sync");
return errno;
}
free(data); //Sé bueno y limpia.
close(handle);
printf("\nDone\n");
return 0;
}
El programa listado arriba reproduce un archivo de datos de PCM en bruto, estéreo a 22 kHz durante cinco segundos. El programa hace tres cosas sencillas:
a. Abre el dispositivo de sonido
b. Establece los parámetros del playback.
c. Escribe los datos en el dispositivo.
Las llamadas de sistema open()/write()/close() son las mismas que se usan para
ficheros normales. Para establecer los parámetros del playback, usamos la
llamada de sistema ioctl() (Input/Output Control, Control de Entrada/Salida).
La llamada de sistema ioctl() se usa para comunicar con los dispositivos de
E/S y establecer sus parámetros. Se podría ver también como un truco para
disminuir el número de llamadas de sistema individuales. Es conocida como
la navaja del ejército suizo del programador. La firma de las llamadas de
sistema ioctl() es:
int ioctl(int fd, int command, ...)
El primer parámetro es el archivo descriptor del dispositivo, el segundo
parámetro es la petición/orden pasado al dispositivo y el resto de parámetros
son específicos del comando [Vea man ioctl_list para una lista de comandos].
Examine esto:
ioctl(handle, SNDCTL_DSP_SPEED,&rate)
Esta llamada a ioctl() establece la tasa de playback DSP, el tercer parámetro es de hecho la tasa pasada al controlador del dispositivo.
Hay tres llamadas principales a ioctl() en el programa ejemplo. La primera establece el número de canales a usar para el playback, la segunda establece el formato PCM y la tercera, la tasa. Dado que esto es todo lo que el dispositivo necesita saber, seguido escribimos los datos PCM directamente en el archivo de dispositivo y el dispositivo lo reproduce para nosotros. Después "limpiamos" el dispositivo ("flush") con la ioctl() final y entregamos la memoria reservada para los datos PCM. Y ya está, hemos acabado.
Tío, sólo para tocar cinco segundos de datos de sonido a 22 kHz en el ejemplo anterior, se necesitaron 220 kB de datos PCM en bruto.
22,000 x 5 x 2 = 220,000 (muestra x segundos x canales) Así que para reproducir 1 minuto de audio con calidad de CD, uno necesitaría: 44,100 x 60 x 2 = 5292000 bytes, ¡ó aproximadamente 5MB!.
Los CDs de audio almacenan datos PCM en bruto, pero para almacenar música eficientemente en los ordenadores, y distribuirla y compartirla por Internet, la música se comprime para reducir el tamaño de fichero y descomprimida al vuelo durante la reproducción. Si lo puede ver, la tasa de muestreo es fija, no importa la naturaleza de la música a reproducir. Imagine que se muestrea el silencio durante unos segundos, aún así consumiría la misma cantidad de espacio que un número de rock bajito durante el mismo tiempo. Los archivos de audio en bruto no comprimen bien dado que hay muy poca repetición de datos, que es algo importante para la buena compresión. Diferentes formatos de audio usan diferentes técnicas para reducir el tamaño del archivo resultante. La técnica más común es quitar el sonido que el oído humano no capta, o comprimir la corriente de sonido dependiendo de la música tocada. Indiscutiblemente el formato más popular para los amantes de la música es el MP3. Dado que MP3 está ahora envuelto en asuntos de patentes, Ogg Vorbis es ahora bienvenido entre los miembros de la comunidad de la fuente libre. Ambos formatos consiguen la misma reducción en tamaño y son capaces de mantener la calidad del audio con pérdida mínima.
Veamos ahora cómo reproducir ficheros Ogg Vorbis en nuestro sistema, dado que sabemos cómo decirle a la tarjeta de sonido que toque datos de audio en bruto. Para que podamos reproducir ficheros con formato Ogg Vorbis, primero necesitamos descodificarlo, o, en otras palabras, convertirlo a datos PCM en bruto. Esto se hará mediante libvorbisfile. Esta librería presenta la descodificación a un nivel mucho más alto que libvorbis, que está a bastante bajo nivel, pero permite un control relativamente más granular. A partir de aquí es realmente sencillo: proporcionamos a la librería con los datos de entrada desde un fichero Ogg Vorbis y la librería nos proporciona los datos en bruto en formato PCM descodificados, que llevamos directamente a la tarjeta de sonido. Fuente disponible aquí
#include < sys/types.h >
#include < sys/stat.h >
#include < sys/soundcard.h >
#include < sys/ioctl.h >
#include < unistd.h >
#include < fcntl.h >
#include < errno.h >
#include < stdlib.h >
#include "vorbis/codec.h"
#include "vorbisfile.h"
int setup_dsp(int fd,int rate, int channels);
char pcmout[4096]; // Tira de 4 kB para mantener los datos PCM descodificados.
int dev_fd;
int main(int argc, char **argv)
{
OggVorbis_File vf;
int eof=0;
int current_section;
FILE *infile,*outfile;
if(argc<2)
{
printf("proporcione el argumento archivo\n");
exit(0);
}
if ( (dev_fd = open("/dev/dsp",O_WRONLY)) == -1 )
{
perror("open /dev/dsp");
return -1;
}
infile=fopen(argv[1],"r");
if(infile==NULL)
{
perror("fopen");
exit(-1);
}
if(ov_open(infile, &vf, NULL, 0) < 0)
{
fprintf(stderr,"La entrada no parece ser un flujo de datos Ogg Vorbis.\n");
exit(1);
}
char **ptr=ov_comment(&vf,-1)->user_comments;
vorbis_info *vi=ov_info(&vf,-1);
while(*ptr)
{
fprintf(stderr,"%s\n",*ptr);
++ptr;
}
fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
fprintf(stderr,"\nDecoded length: %ld samples\n",(long)ov_pcm_total(&vf,-1));
fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor);
if(setup_dsp(dev_fd,vi->rate,vi->channels-1))
{
printf("error de instalación dsp.Abortando\n");
exit(-1);
}
int count=0;
while(!eof)
{
long ret=ov_read(&vf,pcmout,sizeof(pcmout),0,2,1,¤t_section);
if (ret == 0)
{
/* EOF */
eof=1;
}
else if (ret < 0)
{
/* error en el flujo. No es problema, sólo informarlo en caso de que nos preocupemos. en este caso, no lo hacemos */
}
else
{
printf("Escribiendo %d bytes por %d vez.\n",ret,++count);
write(dev_fd,pcmout,ret);
}
}
ov_clear(&vf);
fclose(infile);
if (ioctl(dev_fd, SNDCTL_DSP_SYNC) == -1)
{
perror("ioctl sync");
return errno;
}
close(dev_fd);
fprintf(stderr,"Done.\n");
return(0);
}
int setup_dsp(int handle,int rate, int channels)
{
int format;
if ( ioctl(handle, SNDCTL_DSP_STEREO,&channels) == -1 )
{
perror("ioctl stereo");
return errno;
}
format=AFMT_U8;
if ( ioctl(handle, SNDCTL_DSP_SETFMT,&format) == -1 )
{
perror("ioctl format");
return errno;
}
if ( ioctl(handle, SNDCTL_DSP_SPEED,&rate) == -1 )
{
perror("ioctl sample rate");
return errno;
}
return 0;
}
gcc oggplay.c -oggplay -lvorbisfile -I/path/to/vorbis/header/files -L/path/to/vorbis/lib/files
Si el archivo de la librería o los archivos de cabecera están en los lugares habituales como /usr/lib y /usr/include, entonces puede ahorrarse las opciones -I y -L de la línea de comandos. Todas las distribuciones principales vienen con la librería vorbisfile, si su sistema parece no tenerla instalada, siempre puede descargarla desde aquí. También puede visitar el sitio de Ogg Vorbis aquí. Necesitará los archivos de cabecera para compilar el programa.
La función setup_dsp abre el dispositivo dsp, establece los tres parámetros principales, canales, formato y tasa. Tiene que recordar que éstos son los tres parámetros más importantes a establecer para la reproducción de audio. En la función principal, más que la comprobación de errores, sólo tenemos dos cosas. El archivo Ogg Vorbis proporcionado como un argumento de la shell se abre y se saca su información y los comentarios del usuario. La tasa de muestreo y la información de canal se extrae de la estructura de la información rellenada por la función de librería ov_open() y es pasada a la función setup_dsp. El programa entra entonces en un bucle donde los bytes descodificados desde el archivo Ogg Vorbis fluyen y se sitúan en una tira ("buffer"). Estos datos PCM en bruto contenidos en la tira son después reproducidos escribiendo sobre el dispositivo dsp que se ha instalado ya. Este bucle continúa hasta que se alcanza el final del archivo, finalmente se limpia todo y ocurre la salida. ¿No era tan difícil, no? Gracias a libvorbisfile, la descodificación es fácil y directa.
Para descodificar archivos Ogg Vorbis, la elección obvia es libvorbisfile, pero si quiere descodificar y reproducir archivos MP3, hay muchas librerías que pueden llevar a cabo la tarea. Una librería popular es smpeg, que es capaz de descodificar tanto flujos de audio como de vídeo. Otra librería con la que me he encontrado es libmad. Seleccionaremos smpeg y usaremos sólo sus capacidades de descodificación de audio. Usted puede explorar más allá sus capacidades de descodificación de vídeo MPEG si está interesado. También se proporciona una aplicación de muestra plaympeg que demuestra el uso de la librería. Para la reproducción de vídeo, smpeg emplea SDL, que es una librería de programación de gráficos muy sencilla así como muy capaz. Hay muchas más librerías por ahí, pero smpeg se proporciona en muchas distribuciones de Linux y eso me hizo elegirla. El popular reproductor de vídeo MPEG gtv es una parte del paquete smpeg.
Aquí viene un ejemplo que reproduce archivos MP3. Por favor observe que la librería smpeg toca el flujo MP3 descodificado directamente sobre la tarjeta de sonido. No tenemos que manejar nada de PCM. También disponible aquí.
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#include < signal.h >
#include < unistd.h >
#include < errno.h >
#include < sys/types.h >
#include < sys/stat.h >
#include < sys/ioctl.h >
#include < sys/time.h >
#include "smpeg.h"
void usage(char *argv0)
{
printf("\n\nHi,\n\n%s \n\nÉste es el uso normal.\n",argv0);
}
int main(int argc, char *argv[])
{
int volume;
SMPEG *mpeg;
SMPEG_Info info;
volume = 100; //Nivel del volumen
/* Si no hubiera argumentos,simplemente sacar el uso por pantalla */
if (argc == 1)
{
usage(argv[0]);
return(0);
}
mpeg = SMPEG_new(argv[1], &info;, 1);
if ( SMPEG_error(mpeg) )
{
fprintf(stderr, "%s: %s\n", argv[1], SMPEG_error(mpeg));
SMPEG_delete(mpeg);
}
SMPEG_enableaudio(mpeg, 1);
SMPEG_setvolume(mpeg, volume);
/* Sacar información sobre el audio */
if ( info.has_audio )
{
printf("%s: Flujo de audio MPEG\n", argv[1]);
if ( info.has_audio )
printf("\tAudio %s\n", info.audio_string);
if ( info.total_size )
printf("\tSize: %d\n", info.total_size);
if ( info.total_time )
printf("\tTotal time: %f\n", info.total_time);
/* Lo toca, y espera a que termine la reproducción */
SMPEG_play(mpeg);
while(SMPEG_status(mpeg)==SMPEG_PLAYING);
SMPEG_delete(mpeg); //entrega la estructura
}
return(0);
}
Necesita compilar este programa después de que instale smpeg, que se pude encontrar aquí. Note por favor que smpeg necesita que esté instalado SDL como prerrequisito. Para compilar, necesita proporcionar las rutas a los archivos include y a los de librería. Para hacer fácil este proceso, el paquete smpeg proporciona una sencilla utilidad llamada smpeg-config. Compile el programa con el siguiente comando:
gcc playmp3.c `smpeg-config --cflags --libs` -I/usr/include/SDL
Observe por favor que no es carácter de comilla simple lo que encierra el comando smpeg-config, es el carácter que se presenta junto con el carácter tilde (~) en los teclados norteamericanos, a veces llamado "backquote". Este carácter le ordena a la shell que inyecte la salida del comando encerrado a la actual línea de comandos. Si ejecuta el comando smpeg-config por separado, sabrá de lo que estoy hablando. Reemplace la opción de comando -I/usr/include/SDL con -I < su ruta a SDL >. Incluso aunque no usemos ninguna de las funciones SDL, tenemos que proporcionar a gcc con la ruta al archivo de cabecera de SDL porque smpeg.h lo usa internamente. Los usuarios de RPM deberían instalar tanto el paquete smpeg como el smpeg-devel para compilar el programa.
La función de librería SMPEG_new reserva un nuevo objeto interno, mientras rellena una estructura SMPEG_info que contiene información sobre el archivo MP3. En el programa esta información se saca y comienza la reproducción llamando a SMPEG_play. La cosa es que esta llamada devuelve inmediatamente, lo cual está realmente bien cuando se trata de MP3 en un programa de aplicación porque le permite hacer otras cosas. Dado que no tenemos nada más que hacer en el programa, esperamos hasta que el archivo ha terminado de reproducirse. Entonces SMPEG_delete entrega la memoria reservada previamente por la librería y ya está.
Espero que este artículo encuentre uso en alguna de sus aventuras de programación. Me gustaría escuchar algo de ustedes, chicos. Estaremos en contacto para cualquier aclaración o crítica. Estoy disponible en shuveb@shuvebhussain.org.
Linux Zindabad
Shuveb está pervertido por la conmoción social de una pequeña pero histórica ciudad en el Sur de la India. Piensa que la vida no es ni un Sueño de Una Noche De Verano ni una Tempestad, es simplemente una Comedia de Errores, para ser vivida Como Usted Quiera. Aparte de ser filósofo a tiempo parcial, es un experimentado programador en C que a menudo está confuso acerca de lo que * le hace a una variable puntero... APR Bristol es la empresa que le paga por aprender Linux.