Adrianistán

PortAudio + libsndfile. Reproducir Ogg Vorbis, FLAC y WAV en C++

04/09/2013
Escribo este post ya que cuando quise buscar información sobre el tema no encontré nada en castellano y en inglés poca información. Así pues escribo el que yo considero el mejor método para reproducir audio Ogg Vorbis, FLAC y WAV en C++.
Lo primero es descargar e instalar las librerías, ambas son multiplataforma. En Windows irás a la página oficial y seguirás las instrucciones:

A su vez si tenemos que compilar libsndfile también necesitaremos libogg y libvorbis para activar la reproducción de Ogg Vorbis o las librerías de FLAC para activar FLAC en la librería. El soporte para WAV es nativo y no hace falta nada.
En Linux podemos ahorrarnos tiempo instalando los paquetes binarios. En Debian/Ubuntu:
sudo apt-get install portaudio19-dev libsndfile1-dev

Ahora creamos un fichero de C. Atención, aunque C++ sea compatible con el código de C, este código de C tiene problemas en compiladores de C++. Así pues el código de aquí abajo da error con g++ pero funciona correctamente y sin advertencias en gcc. Si quieres insertarlo en un programa de C++, sigue creando este fichero como fichero de C y en el header encapsula la función definida en un "extern C":
extern "C" {   
miFuncionAudio();
}

Ahora en el ejemplo voy a usar una función main para no liarnos y usar solo C, pero eso serán los pasos a seguir si tienes C++.  Creamos el fichero con extensión .c y escribimos la función main(). Ahora abriremos el archivo de audio con la librería encargada de decodificar el audio, libsndfile. Después inicializamos PortAudio y obtenemos el dispositivo de audio por defecto (en el caso de mi Ubuntu, es ALSA). Configuramos el dispositivo con los datos que tenemos del fichero. Abrimos un stream de PortAudio que leerá el archivo. Esta función necesita dos funciones de callbak que se crean más arriba. La principal tiene como objetivo leer el fichero más y la otra hacer algo cuando termine el fichero (se puede dejar vacía). En la de leer el fichero tenemos que tener en cuenta si el equipo es Mono o Estéreo. Yo no he llegado a saber como detectarlo, así que hago la multiplicación por 2 para el estéreo. Luego en el flujo principal iniciamos el stream, sonará, esperamos 10 segundos, paramos y cerramos el stream. Finalmente cerramos el fichero de audio y deshabilitamos PortAudio. Fácil y sencillo. Ahora el código:
#include "portaudio.h"
#include "sndfile.h"

SF_INFO sfinfo;
PaStreamParameters out_param;
PaStream * stream;
PaError err;
SNDFILE * file;
static int
output_cb(const void * input, void * output, unsigned long frames_per_buffer,
const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags flags, void * data)
{
SNDFILE * filex = data;
/* Here you need to multiply per 2 for stereo and per 1 for mono*/
sf_read_short(filex, output, frames_per_buffer*2);
return paContinue;
}
static void
end_cb(void * data)
{
Pa_StopStream(stream);
Pa_CloseStream(stream);
sf_close(file);
Pa_Terminate();
}
#define error_check(err) \ do {\ if (err) { \ fprintf(stderr, "line %d ", __LINE__); \ fprintf(stderr, "error number: %d\n", err); \ fprintf(stderr, "\n\t%s\n\n", Pa_GetErrorText(err)); \ return err; \ } \ } while (0)
int main(int argc, char ** argv)
{
file = sf_open(argv[1], SFM_READ, &sfinfo);
printf("%d frames %d samplerate %d channels\n", (int)sfinfo.frames,
sfinfo.samplerate, sfinfo.channels);
/* init portaudio */
err = Pa_Initialize();
error_check(err);
/* we are using the default device */
out_param.device = Pa_GetDefaultOutputDevice();
if (out_param.device == paNoDevice)
{
fprintf(stderr, "Haven't found an audio device!\n");
return -1;
}
/* stero or mono */
out_param.channelCount = sfinfo.channels;
out_param.sampleFormat = paInt16;
out_param.suggestedLatency = Pa_GetDeviceInfo(out_param.device)->defaultLowOutputLatency;
out_param.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(&stream, NULL, &out_param, sfinfo.samplerate,
paFramesPerBufferUnspecified, paClipOff,output_cb, file);
error_check(err);
err = Pa_SetStreamFinishedCallback(stream, &end_cb);
error_check(err);
err = Pa_StartStream(stream);
error_check(err);
printf("Play for 10 seconds.\n");
Pa_Sleep(10000);
err = Pa_StopStream(stream);
error_check(err);
err = Pa_CloseStream(stream);
error_check(err);
sf_close(file);
Pa_Terminate();
return 0;
}

Y para terminar un diagrama de salidas de audio nativas soportadas por PortAudio:
Tags: vorbis programacion audio portaudio linux tutorial cpp flac