GNU/Linux rendszeren sokféle módja van annak, hogy hangot csiholjunk ki a gépből. Itt is megfigyelhető az a tendencia, hogy az eszközök egymásra épülnek és mindegyik célja az, hogy elfedjék szegény programozók elől a problémás részeket.
Ha viszont a lehető leghordozhatóbb megoldásra van szükségünk, akkor célszerű átvágni magunkat az API-k dzsungelén, hogy megleljük a tiszta forrást. Ez nem más, mint az ALSA. Az ALSA segítségével előállíthatunk bármilyen hangot. Szerény véleményem szerint nem olyan bonyolult a használata. Ez azonban nem jelenti azt, hogy nem érhetnek minket meglepetések. Erről majd egy következő bejegyzésben részletesen is írok.
A zenét mi Ogg/Vorbisban tároljuk, ezért a példaprogramban a vorbis könyvtárat is használni fogjuk.
Inicializálás
Első lépés, hogy beállítsuk a kívánt paramétereket. Milyen eszközön, milyen típusú puffert használunk és milyen minőségű hangot kívánunk megszólaltatni.
snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
Minden parancs snd_pcm-el kezdődik. Ezt könnyű lesz megjegyezni. Először meg kell nyitni az eszközt. Az eszköz nevét egy sztring határozza meg. Ha több hangkártya is van a gépbe, akkor explicite megadhatjuk, hogy melyik eszközt használjuk. Ha hordozható megoldásra vágyunk, akkor legyen "default".
snd_pcm_hw_params_malloc(&hw_params);
snd_pcm_hw_params_any(playback_handle, hw_params);
snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE);
A második sorban lefoglaljuk a memóriát a paramétereknek, majd beállítjuk azokat. A dokumentáció részletesen leírja, hogy melyik mit jelent. Ezek a beállítások függetlenek a lejátszani kívánt zenétől.
music_file = fopen(argv[i+1], "r");
ov_open(music_file, &music, NULL, 0);
music_info = ov_info(&music, -1);
A zenefájl megnyitása ennyi. A zene jellemzői a music_info struktúrába kerülnek. Ezt fogjuk felhasználni, hogy beállítsuk az ALSA-nak a hangjellemzőket.
snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &music_info->rate, 0);
Ez már kellően elrettentő. A lényege röviden, hogy mi nem adhatjuk meg pontosan a paramétereket, mert az egyes hangkártyák esetleg nem ismerik azt a beállítást. Ezért mi csak annyit mondunk, hogy körül-belül ilyen beállításokat akarunk (snd_pcm_hw_params_set_period_size_near), majd a meghajtóprogram kiválasztja azt, ami a legközelebb esik hozzá, amivel mi már beállíthatjuk a kívánt paramétert (snd_pcm_hw_params_set_period_size). Beállítjuk továbbá a hangpuffer jellemzőit is.
snd_pcm_hw_params_set_channels(playback_handle, hw_params, music_info->channels);
snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &frames, &dir);
snd_pcm_hw_params(playback_handle, hw_params);
snd_pcm_hw_params_get_period_size(hw_params, &frames, &dir);
size = frames * 4;
buffer = (char *)malloc(size);
snd_pcm_hw_params_get_period_time(hw_params, &music_info->rate, &dir);
snd_pcm_hw_params_free(hw_params);
snd_pcm_prepare(playback_handle);
A size = frames * 4 sor egy kis magyarázatot igényel. Az ALSA a pufferben két adatot vár, mivel sztereóban játszunk le. De a másik kettő honnan jön? Azt nem tudom. Bármilyen más értékre átírom, a mintavételezés olyan rossz lesz, hogy élvezhetetlenné válik a zene.
sample_length = ov_read(&music, buffer + pcm_index, bytes_needed, 0, 2, 1, &bitstream);
ret = snd_pcm_writei(playback_handle, buffer, frames);
Gyakorlatilag ennyi a további kód. Az ov_read segítségével kiolvassuk a következő puffernyi adatot, majd az snd_pcm_writei-vel lejátszuk.
A teljes kód:
#include <vorbis/vorbisfile.h>
#include <vorbis/codec.h>
#include <alsa/asoundlib.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/*
This program is try to play an ogg-vorbis file. This version
is working with only in 1 bitstream files.
*/
int main(int argc, char *argv[]){
int i;
FILE *music_file;
OggVorbis_File music;
vorbis_info *music_info;
snd_pcm_uframes_t frames = 128;
snd_pcm_sframes_t ret;
long sample_length;
char *buffer;
int size;
int bitstream;
int playing = 1;
int bytes_needed;
int pcm_index;
int dir;
snd_pcm_hw_params_t *hw_params;
snd_pcm_t *playback_handle;
for(i = 1; i < argc; i++){
if(!strcmp(argv[i], "-f")) music_file = fopen(argv[i+1], "r");
}
/* Alsa initialization */
snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_hw_params_malloc(&hw_params);
snd_pcm_hw_params_any(playback_handle, hw_params);
snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE);
/* Open the music*/
ov_open(music_file, &music, NULL, 0);
/* Get the sample rate information from vorbis file */
music_info = ov_info(&music, -1);
/* Set the alsa channels and rate */
snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &music_info->rate, 0);
snd_pcm_hw_params_set_channels(playback_handle, hw_params, music_info->channels);
snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &frames, &dir);
snd_pcm_hw_params(playback_handle, hw_params);
snd_pcm_hw_params_get_period_size(hw_params, &frames, &dir);
size = frames * 2;
buffer = (char *)malloc(size);
snd_pcm_hw_params_get_period_time(hw_params, &music_info->rate, &dir);
snd_pcm_hw_params_free(hw_params);
snd_pcm_prepare(playback_handle);
while( playing ){
bytes_needed = size;
pcm_index = 0;
while(bytes_needed > 0){
sample_length = ov_read(&music, buffer + pcm_index, bytes_needed, 0, 2, 1, &bitstream);
switch(sample_length){
case 0:
playing = 0;
bytes_needed = 0;
break;
default:
pcm_index += sample_length;
bytes_needed -= sample_length;
}
}
ret = snd_pcm_writei(playback_handle, buffer, frames);
if(ret < 0){
ret = snd_pcm_recover(playback_handle, ret, 0);
printf("Recover\n");
}
if(ret < 0){
printf("Underrun: %s\n", snd_strerror(ret));
}
}
snd_pcm_close(playback_handle);
ov_clear(&music);
return(EXIT_SUCCESS);
}