Ir al contenido principal

Reproducir fichero wav en Ubuntu

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 NAMESIZE AND FORMATMEANING
File description header4 bytes (DWord)Texto ascii RIFF
Size of file4 bytes (DWord)Tamaño del fichero sin incluir la cabecera RIFF.
WAV description header4 bytes (DWord)Text ascii que indica el tipo de fichero: ASCII "WAVE"
Format description header4 bytes (DWord)Texto ascii "fmt " con caracter espacio incluido.
Size of WAVE section chunck4 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 format2 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 channels2 bytes (Word)Indica si el fichero esta en MONO o STEREO VALOR:  01 MONO 02 STEREO.
Samples per second4 bytes (DWord)Frecuencia de cuantificación normalmente 44100 Hz.
Bytes per second4 bytes (DWord)Velocidad de stream de lectura viene del calculo de Number_of_channels * Samples_per_second * Bits_per_Sample / 8.
Block alignment2 bytes (Word)Número de bytes por cuantificación, viene del calculo : Number_of_channels*Bits_per_Sample/8
Bits per sample2 bytes (Word)Digitos de cuantificación (normalmente 32, 24, 16, 8).
Data description header4 bytes (DWord)Texto ASCII "data".
Size of data4 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.


Entradas populares de este blog

LPIC-1 101 y 102

Certificación LPIC-1 101-102. Linux Certification Institute. Esta entrada es especial para mi porque he aprobado el certificado LPIC-1 de Linux. Al buscar información sobre las certificaciones , pues encontré información confusa y sé de algunas personas que quieren prepararse el exámen para un futuro , y si las bases de los exámenes no cambian quería aclararle las dudas. Quien no sepa lo que es LPIC-1 , es una certificación del Linux Professional Institute  la cual avala que tienes los conocimientos mínimos para gestionar un sistema operativo Linux independientemente de su distribución, como siempre te pongo la información más extendida   aquí .  El LPIC-1 es el primer exámen de todos y te digo desde ya que tiene su dificultad. Esto lo digo porque yo mismo al llevar un par de años con Linux , creía que mis conocimientos en Linux eran suficientes y solo había visto la punta del iceberg. Y en el primer nivel tocas muchas cosas. Lo que te quiero decir con esto es que e

Juego de la Serpiente con Java Swing

En esta entrada les voy a mostrar como hice el juego de la serpiente. Cuando tenía 15 años lo intente hacer y en ese momento lo ví como algo extremadamente complejo.  En su momento no vi mucha información de como hacer el juego ( o no supe encontrar ) , y por si alguien está en esta situación , aquí va mi aportación para intentar aclarar sus dudas.    He visto muchos códigos que lo han hecho mucho mejor, dibujando en el Frame y mucho más trabajado, pero yo personalmente soy partidiario de que siempre es mejor ver las cosas desde su nivel más simple, y una vez entendida la idea, ya luego pasarla a otra cosa, darle mejor aspecto y adornos son valores añadidos. Para el que no sepa como funciona el juego de la serpiente ( cosa que dudo , todos tuvimos un Nokia ) , es una serpientita a la que le van apareciendo frutas , ella va comiendo y se va haciendo grande, hasta que llega un momento que se hace muy difícil controlarla porque es demasiado grande y chocamos con nosotros mismos.

Añadiendo autocompletado a un JTextField de Java Swing

Esta vez para un trabajo de clase en  Java Swing  me vi con la necesidad de tener un JTextField con autocompletado ( como la caja de texto de Google cuando buscas algo ) y como no viene por defecto el JTextField con esta característica , pues tuve que hacerme uno. Pero en este caso no me hacía falta consultar una base de datos para rellenarlo según vas escribiendo en él, sino que a este ya le doy los datos a filtrar de antemano. Tú dirás ... pero si hay miles en internet ya hechos , por qué reinventar la rueda ...,  Ya , ya lo sé , pero en algunos de los que ví , no me gustaba la manera de como se implementaban en el código para poder usarlo , otros no me gustaban como se veían visualmente , y lo más importante ... yo no me podía quedar con la duda de como se hacía uno ( esta última era la que más me podía jeje , yo y mi curiosidad ). ¿Cómo lo uso ? Te explico como funciona el componente GTextField , tan simple como instanciarlo y en su constructor pasarle t