Siempre he tenido la curiosidad de como funciona algo tan simple que hacemos a menudo , como escuchar una canción en mp3, pero para empezar por lo fácil , he decidido investigar con el fichero .wav que no tiene compresión y por lo tanto es sólo leer en el.
En esta entrada quiero enseñarte como reproducir un fichero wav a código en Ubuntu , eso sí , tendrás que usar una versión de Ubuntu un poco antigua ... la 7.10 (virtualizala con VirtualBox ) concretamente he usado yo para poder ejecutar el programa, porque las nuevas versiones de Ubuntu el sistema de sonido ha cambiado y no es igual , tengo que investigarlo ( lo dejo para otra entrada ;D )
¿Por qué Ubuntu y por qué un .wav?
Pues te contesto: Ubuntu porque Linux permite usar todos los dispositivos del sistema como si fueran ficheros y eso es una gran facilidad, en windows tendrías que usar sus APIS. Y .wav porque es un archivo crudo , ¿Qué es eso? , pues un fichero pesado sin compresión como podría ser un .mp3 y para reproducirlo tendríamos que entrar en los algoritmos de compresión/descompresión MPEG-1 Layer 3 y como que es entrar en más lios para lo que queremos hacer aquí, y quiero empezar por lo básico como un .wav.
Estructura del fichero .wav.
El fichero wav tiene la siguiente estructura en su cabecera que nos indica todo lo que queremos saber, si es en Stereo , Mono ... y demás datos:
Definición de cada parte:
FIELD NAME | SIZE AND FORMAT | MEANING |
---|---|---|
File description header | 4 bytes (DWord) | Texto ascii RIFF |
Size of file | 4 bytes (DWord) | Tamaño del fichero sin incluir la cabecera RIFF. |
WAV description header | 4 bytes (DWord) | Text ascii que indica el tipo de fichero: ASCII "WAVE" |
Format description header | 4 bytes (DWord) | Texto ascii "fmt " con caracter espacio incluido. |
Size of WAVE section chunck | 4 bytes (DWord) | Tamaño de una sección wav que viene de calcular : Wave type format (2 bytes) + mono/stereo flag (2 bytes) + sample rate (4 bytes) + bytes per sec (4 bytes) + block alignment (2 bytes) + bits per sample (2 bytes). Normalmente da de resultado 16. |
WAVE type format | 2 bytes (Word) | Tipo de formato wav.Esta es la cabecera PCM, valor 01 . Otro valor indica que tiene algun tipo de compresión el archivo. |
Number of channels | 2 bytes (Word) | Indica si el fichero esta en MONO o STEREO VALOR: 01 MONO 02 STEREO. |
Samples per second | 4 bytes (DWord) | Frecuencia de cuantificación normalmente 44100 Hz. |
Bytes per second | 4 bytes (DWord) | Velocidad de stream de lectura viene del calculo de Number_of_channels * Samples_per_second * Bits_per_Sample / 8. |
Block alignment | 2 bytes (Word) | Número de bytes por cuantificación, viene del calculo : Number_of_channels*Bits_per_Sample/8 |
Bits per sample | 2 bytes (Word) | Digitos de cuantificación (normalmente 32, 24, 16, 8). |
Data description header | 4 bytes (DWord) | Texto ASCII "data". |
Size of data | 4 bytes (DWord) | Número de datos que hay en la sección de datos del fichero. |
Data | -?- | Datos del fichero en si para su sonido , pero hay que fijarse si es en STEREO o MONO porque cada sample es para canal izquierdo y otro sample para derecho. |
¿Cómo funciona esto en Linux para reproducir audio?
Una vez leida la estructura del fichero wav , no es tan fácil como abrir el fichero de la tarjeta de sonido y empezar a escribir bytes en ella para que se escuche algo , si hacemos esto así ,lo que escucharemos será un pitido.
Hay que configurar la tarjeta de sonido para decirle cuantos BITS por sample tendrá , si va a emitir en STERO o MONO y cuanto ocupará su SAMPLE RATE que será cada porción de sonido que va a reproducir.
Para ello está la función: ioctl , que lleva tres parámetros de entrada. El primer parámetro pues es el manejador que tenemos para la tarjeta de sonido, el segundo parámetro es para configurar a que propiedad de la tarjeta de sonido queremos poder un valor específico , y el tercer y último es para enviar el valor para este parámetro de la tarjeta de sonido. La función devolverá -1 en caso de error .
Aquí te dejo el código fuente para que lo compiles en tu GCC de Ubuntu. Recuerda que probablemente no encuentre las librerías de los include, y en la imágen iso están e instalalos con el gestor de paquetes Synaptic.
// PLAY EDITOR por Gabriel Gonzalez
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <linux/soundcard.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#define MONO 1
#define STEREO 2
unsigned char chunkID[4];
unsigned char format[4];
unsigned char subChunk1ID[4];
unsigned char subChunk2ID[4];
unsigned int chunkSize;
unsigned int subChunk1IDSize;
unsigned int audioFormat;
unsigned int numChannels;
unsigned int sampleRate;
unsigned int byteRate;
unsigned int blockAlign;
unsigned int bitPerSample;
unsigned int wavFileSize;
int main(int argc, char *argv[]){
FILE *fileSound;
int soundCard;
printf("Reproductor de sonido por Gabriel González\n\n");
soundCard = open("/dev/dsp", O_WRONLY);
if( soundCard < 0){
printf("ERROR AL ABRIR TARJETA DE SONIDO \n");
}else{
printf("TARJETA DE SONIDO ABIERTA\n");
printf("VALOR DE SoundCard %d\n",soundCard);
}
if( fileSound = fopen(argv[1],"r")){
printf("Archivo de sonido abierto ! \n");
}
else{
printf("ERROR AL ABRIR ARCHIVO WAV \n");
}
printf("Descripción del wav \n");
fseek(fileSound , 0L, SEEK_SET);
fread(&chunkID,4 , 1 , fileSound);
printf("RIFF: %s \n", chunkID) ;
fseek(fileSound , 4L, SEEK_SET);
fread(&chunkSize , 4, 1 , fileSound);
printf("CHUNCK SIZE: %d\n", ( chunkSize / 1024 ) / 1024 );
fseek(fileSound , 8L , SEEK_SET);
fread(&format, 4, 1, fileSound);
printf("FORMATO DE ARCHIVO: %s\n",format);
fseek(fileSound , 12L , SEEK_SET);
fread(&subChunk1ID , 4, 1 , fileSound);
printf("FMT ID %s\n",subChunk1ID);
fseek(fileSound , 16L , SEEK_SET);
fread(&subChunk1IDSize , 4, 1 , fileSound);
printf("FMT Size: %d\n",subChunk1IDSize);
fseek(fileSound , 20L , SEEK_SET);
fread(&audioFormat , 2, 1 , fileSound);
if(audioFormat == 1)
printf("NO HAY COMPRESION\n");
else
printf("HAY ALGUN TIPO DE COMPRESION\n");
fseek(fileSound , 22L , SEEK_SET);
fread(&numChannels , 2 , 1 , fileSound);
if(numChannels == 1)
printf("MONO: Valor devuelto : %d\n",numChannels);
else if(numChannels == 2)
printf("STEREO: Valor devuelto : %d\n",numChannels);
fseek(fileSound , 24L , SEEK_SET);
fread(&sampleRate , 4, 1 , fileSound);
printf("SampleRate %d\n", sampleRate);
fseek(fileSound , 28L , SEEK_SET);
fread(&byteRate , 4 , 1, fileSound);
printf("SAMPLE RATE: %d\n",byteRate);
fseek(fileSound , 32L , SEEK_SET);
fread(&blockAlign , 2 , 1, fileSound);
printf("BLOCK ALIGN %d\n", blockAlign);
fseek(fileSound , 34L , SEEK_SET);
fread(&bitPerSample , 2 , 1, fileSound);
printf("BITS POR SAMPLE %d\n",bitPerSample);
fseek(fileSound , 36L , SEEK_SET);
fread(&subChunk2ID , 4 , 1 , fileSound);
printf("DATA : %s\n",subChunk2ID);
fseek(fileSound , 40L , SEEK_SET);
fread(&wavFileSize , 4 , 1 , fileSound);
printf("Tamaño del archivo wav %d\n",wavFileSize);
fseek(fileSound , 44L , SEEK_SET);
printf("Configurando tarjeta de sonido ...\n");
if (numChannels == 1){
if (ioctl(soundCard, SOUND_PCM_WRITE_CHANNELS, MONO) == -1){
printf("No se puede configurar los canales para MONO\n");
}else{
printf("Reproduciendo en MONO\n");
}
}
if (numChannels == 2){
if (ioctl(soundCard, SOUND_PCM_WRITE_CHANNELS, STEREO) == -1) printf("No se puede configurar los canales para STEREO\n");
else{
printf("Reproduciendo en STEREO\n");
}
}
// sample rate
if (ioctl(soundCard, SOUND_PCM_WRITE_RATE, &sampleRate) == -1){
printf("No se puede configurar el sample rate\n");
}
if( ioctl(soundCard , SOUND_PCM_WRITE_BITS , &bitPerSample ) == -1){
printf("ERROR AL ENVIAR BITS POR SAMPLE\n");
}else{
printf("BITS POR SAMPLE CONFIGURADOS\n");
}
int sample=0;
//vamos leyendo y reproduciendo byte a byte
while(feof(fileSound) == 0)
{
fread(&sample, 1, 1, fileSound);
write(soundCard, &sample, 1);
}
fclose(fileSound);
close(soundCard);
return EXIT_SUCCESS;
}
Como compilarlo:
Para compilarlo he usado el compilador GCC , y lo he compilado con la orden:
gcc playeditor.c -o play
y para probarlo , donde lo hayas compilado pon:
./play fichero.wav
y pulsa enter, deberías de escuchar la "música" ;).
Espero que te haya gustado esta entrada.