2004.04_Grajek, czyli dekoder plików OGG_[Programowanie].pdf
(
834 KB
)
Pobierz
439033900 UNPDF
dla programistów
Zobacz w:
Grajek, czyli dekoder plików
OGG
jak przedstawia się kod źródłowy programu
XMMS. Zadają sobie również pytanie, jak we
własnym programie zrealizować odtwarzanie
plików audio. Właściwie można to sprawdzić samemu, ścią-
gając odpowiedni pakiet i przeglądając źródła. Wydawać się
może, że program XMMS nie jest skomplikowaną aplika-
cją, ale każdy, kto spróbuje przejrzeć źródła tego programu
bądź wielu podobnych, będzie zmuszony zmienić zdanie.
Kod źródłowy, choć nie jest specjalnie duży, to rozwiązu-
je wiele problemów, które zaciemniają sam proces dekodo-
wania dźwięku.
W tym artykule chciałbym przedstawić przykład nie-
wielkiej aplikacji dekodującej pliki w formacie OGG, zdo-
bywającym coraz większą popularność. Wszystkie progra-
my przeznaczone do odtwarzania plików audio pozwala-
ją na odtwarzanie plików w formacie OGG, więc tworze-
nie własnego programu może wydawać się niepotrzeb-
ne. Nasza aplikacja posiada jednak walory edukacyjne. Ze
względu na jej niewielkie rozmiary, będzie przydatna dla
osób chcących poznać główne zasady dekodowania plików
audio.
(„high-level API”) to zaledwie kilka funkcji przeznaczonych
do sterowania procesem dekodowania.
Skoro tworzymy program w KDE, to powinniśmy sko-
rzystać z systemu dźwięku
aRts
, ale wybierzemy system
dźwięku
ALSA
, który stosunkowo niedawno doczekał się
wersji 1.0.
Funkcje do obsługi systemu ALSA
Pisanie programu odtwarzającego pliki w formacie OGG
rozpoczniemy od przygotowania kilku funkcji związanych
z obsługą dźwięku. Przeznaczenie tych funkcji jest dość
oczywiste:
init_alsa
oraz
init_mixer
dokonują inicjaliza-
cji systemu ALSA oraz miksera. Odwrotne w działaniu są
następujące funkcje:
done_alsa
oraz
done_mixer
. Nasz przy-
szły „player” pozwala sterować głośnością w mikserze, więc
potrzebna jest funkcja do odczytu głośności
get_volume
i ustalania głośności
set_volume
. Kod tych funkcji jest
zawarty w pliku
alsa_func.c
i każdy, kto go przejrzy, zoba-
czy, że znajduje się tam definicja dodatkowych pięciu funk-
cji. Spis i znaczenie wszystkich funkcji zawiera Tabela 1.
Może dziwić brak definicji funkcji do odtwarzania danych.
Specjalna funkcja w istocie nie jest nam potrzebna, gdyż
skorzystamy z funkcji bibliotecznej ALSA
snd_pcm_writei
.
Przykład funkcji dokonującej inicjalizacji systemu
ALSA przedstawia Listing 1. Właściwą inicjalizację wykona
wywołanie
snd_pcm_open
. Dodatkowa funkcja
alsa_error_
fnc
obsługuje ewentualne błędy -- polega to tylko na
wyświetlaniu komunikatów tekstowych na konsoli. Następ-
Założenia początkowe
Naszą aplikację napiszemy w oparciu o program
KDeve-
lop
w wersji 3.0 (Rysunek 1). Zastosowanie tego narzędzia
oznacza, że program będzie przeznaczony dla środowiska
KDE
– chociaż KDevelop aspiruje do miana uniwersalnego
IDE, np. pozwala pisać programy dla środowiska GNOME,
to jednak najlepiej wspiera system KDE oraz bibliotekę QT.
W przypadku dekodowania plików w formacie OGG,
nie mamy wyboru -- jest tylko jedna implementacja tego
standardu, czyli biblioteka
OGG/Vorbis
. Obiektywnie trzeba
jednak przyznać, że API, które oferuje, jest bardzo wygod-
ne w stosowaniu. Istnieją dwa zestawy funkcji. Pierwszy
to „low-level API”, czyli interfejs niskiego poziomu, gdzie
samodzielnie troszczymy się o odbiór danych, ich deko-
dowanie i zajmujemy się wieloma innymi detalami. Drugi
O autorze:
Autor zajmuje się tworzeniem oprogramowania dla
WIN32 i Linuksa. Zainteresowania: teoria języków
programowania oraz dobra literatura. Kontakt z autorem:
autorzy@linux.com.pl
Rysunek 1.
KDevelop i tworzona przez nas aplikacja
58
kwiecień 2004
Marek Sawerwain
Z
pewnością wielu Czytelników jest ciekawych,
dekoder plików ogg
Listing 1.
Treść funkcji init_alsa
nia funkcji
snd_pcm_writei
. Przykład jej zastosowania jest
trywialny:
int
init_alsa
()
{
if
(
alsa_error_fnc
(
snd_pcm_open
(&
handle, pcm_name,
stream, open_mode
))==-
1
)
printf
(
„Błąd w snd_pcm_open
\
”
);
return
-
1
;
}
sample_buffer
=
malloc
(
sample_buffer_size
);
snd_pcm_hw_params_alloca
(&
hwparams
);
if
(
set_hw_params
()<
0
)
{
printf
(
„Błąd w set_hw_params
\
”
);
return
-
1
;
}
}
err = snd_pcm_writei(handle, ptr, cptr);
Zmienna
handle
to uchwyt do urządzenia PCM, uzyskany
funkcją
snd_pcm_open
. Następny parametr
ptr
to wskaźnik
na bufor, a
cptr
to ilość ramek w buforze. Funkcja ta prze-
syła podane próbki bezpośrednio do karty. Naszym zada-
niem jest cykliczne dostarczanie danych do karty dźwię-
kowej, więc wywołanie
snd_pcm_writei
następuje w pętli
while
, która w większości programów pisanych w systemie
ALSA (np. wtyczka ALSA dla XMMS) wygląda mniej więcej
tak, jak na Listingu 2.
Przekazanie zdekodowanych danych do karty to
zadanie wymienionej wcześniej funkcji
snd_pcm_writei
.
Koniecznie trzeba wprowadzić jedną poprawkę związa-
ną z ilością danych. Funkcja systemu ALSA mierzy ilość
danych w ramkach (ramka to pojedyncza próbka dźwię-
kowa; w naszym przypadku mamy dwa kanały: lewy
i prawy; dla każdego z tych kanałów dźwięk jest opisywa-
ny za pomocą dwóch bajtów, czyli ramka składa się z czte-
rech bajtów), natomiast w wyniku działania funkcji
ov_read
(dekoduje plik w formacie OGG), zmienna
tt
zawiera ilość
danych, ale w bajtach. Przed wejściem do pętli przygotowu-
jemy zatem nową wartość, określającą ilość danych. Jest to
zmienna
cptr
, w której umieszczamy wartość zmiennej
tt
podzieloną przez cztery.
Po operacji przygotowania zmiennych
cptr
i
ptr
,
wchodzimy w pętlę. Jest ona konieczna, gdyż ALSA może
nie przyjąć w całości przekazywanych danych. W takim
przypadku zmienna
err
będzie zawierać ilość ramek
wysłanych do karty. Sprawdzamy także, czy zmienna
err
nie zawiera informacji o potencjalnym błędzie. Może ona
przyjąć wartość
-EAGAIN
, co jest sygnałem, aby ponownie
ną istotną czynnością jest przydzielenie pamięci dla bufora,
w którym będą znajdować się rozkodowane dane przezna-
czone do odtworzenia. Wielkość buforu koniecznie musi
być podzielna przez cztery.
Ostateczne ustalenie parametrów to zadanie dla funk-
cji
set_hw_params
. Ustalamy w niej tryb dostępu. W naszej
aplikacji będzie to przeplatany tryb zapisu i odczytu, okre-
ślony przez wartość:
SND_PCM_ACCESS_RW_INTERLEAVED
.
Równie istotne jest określenie ilości kanałów i częstotliwo-
ści, z którą będzie próbkowany dźwięk:
err = snd_pcm_hw_params_set_channels(handle, hwparams,
channels);
err = snd_pcm_hw_params_set_rate_near(handle, hwparams,
&rrate, 0);
Powyższe dwie linijki kodu ustalają tryb stereo oraz często-
tliwość równą 44100hz. Ostatnim bardzo istotnym parame-
trem jest format danych, a będą to liczby szesnastobitowe
ze znakiem. Fragment kodu z pliku
alsa_func.c
jest nastę-
pujący:
Kompilacja potrzebnych pakietów
Zanim przystąpimy do kompilacji pakietów OGG/Vorbis, warto
sprawdzić, czy używana dystrybucja przypadkiem nie zawie-
ra tych bibliotek. W większości przypadków biblioteki powinny
być obecne. Gdy jednak chcemy przeprowadzić kompilację,
to w pierwszej kolejności kompilujemy pakiet libogg, a po nim
libvorbis. Pozostałe pakiety nie będą nam potrzebne. Ponie-
waż do kompilacji zastosowano system Autoconf i Automa-
ke, to kompilacja nie powinna sprawić kłopotów. Jak zwykle
w przypadku tandemu Autoconf i Automake, wydajemy trzy
polecenia:
static snd_pcm_format_t format=SND_PCM_FORMAT_S16;
...
err = snd_pcm_hw_params_set_format(handle, hwparams,
format);
Ponieważ na łamach Linux+ system ALSA był już omawia-
ny, więc pozwolę sobie Czytelnika odesłać do artykułu
z numeru 08/2003 (został on zamieszczony na płycie CDA/
DVDA w katalogu ALSA).
./configure –prefix=/usr
make
make install
Odtwarzanie próbki dźwiękowej
ALSA oferuje kilka trybów przesyłania danych do karty
dźwiękowej. Nasza aplikacja wykorzystuje z powodze-
niem najprostszą metodę, opartą o bezpośrednie wywoła-
W przypadku, gdy nie mamy programu KDevelop w wersji 3.0,
czeka nasz samodzielna jego kompilacja. KDevelop wymaga,
aby KDE było przynajmniej w wersji 3.0.2, ale większość dys-
trybucji w ostatnich wydaniach zawiera już wersję z serii 3.1.x.
www.linux.com.pl
59
dla programistów
Tabela 1.
Spis funkcji omawianych w artykule
Dekodowanie pliku w formacie ogg
Znamy już dokładną metodę przesyłania zdekodowa-
nych danych do karty dźwiękowej. Teraz trzeba poznać
sposób, w jaki dane dekodujemy. Funkcje realizują-
ce proces dekodowania z formatu OGG w przypadku
API wysokiego poziomu nie są trudne do opanowania.
Co więcej, funkcje, które za chwilę przedstawię, są podob-
ne w użyciu do typowych funkcji bibliotecznych, takich
jak
open
,
read
,
close
czy
seek
. Każdy, kto popraw-
nie posługuje się funkcjami do obsługi plików, nie
będzie miał problemów z obsługą plików w formacie
OGG.
Zanim rozpoczniemy dekodowanie pliku, musimy go
otworzyć. Wykonujemy to funkcją
fopen
. Uzyskane odnie-
sienie do pliku przekazujemy funkcji
ov_open
w pierw-
szym parametrze, a w drugim podajemy adres zmiennej
reprezentującej właściwy uchwyt. W dalszych wywoła-
niach posługujemy się tym uchwytem (typ
OggVorbis_File
)
zamiast zmienną plikową
FILE *
. Czynności przygotowaw-
cze wyglądają następująco:
Plik alsa_func.c
int alsa_error_fnc(int i);
wyświetla komunikat
o błędzie
int set_hw_params();
ustalenie parametrów
sprzętowych
funkcja po wystąpieniu błędu
próbuje doprowadzić do
„porządku” bufor
urządzenia PCM
void ind_obj_pcm();
odszukanie obiektu
miksera odpowiedzialnego
za sterowanie głośnią
urządzenia pcm
void set_volume(double v);
ustalenie głośności
void get_volume(double *v);
odczytanie aktualnej
głośności
int alsa_reset();
reset urządzenia PCM
int init_alsa();
inicjalizacja systemu
int done_alsa();
zamknięcie systemu
FILE *file_handle;
OggVorbis_File vf;
file_handle=fopen(”~/file.ogg”, „r”);
ov_open(file_handle, &vf, NULL, 0);
int init_mixer();
inicjalizacja miksera
int done_mixer();
zamknięcie miksera
Plik oggplay.c
int ogg_prepare_to_play
(const char *fn);
wykonanie wstępnych
czynności
int ogg_do_play_full();
odtworzenie całego pliku
int ogg_do_play_frag-
ment();
Następnie musimy poznać pewne podstawowe infor-
macje, takie jak całkowita długość utworu (ilość próbek
PCM albo całkowity czas trwania). Odczytanie długo-
ści utworu dokonujemy w naszej aplikacji w następują-
cy sposób:
odtworzenie pojedynczego
fragmentu pliku
long ogg_tell_pcm_pos();
odczytanie aktualnej pozycji
w utworze
long ogg_full_pcm();
całkowita długość utworu
void ogg_set_new_pos
(int i);
pcm_length=ov_pcm_total(&vf,-1);
total_time=ov_time_total(&vf, -1);
ustawienie nowej pozycji
w utworze (zero początek,
sto koniec)
Istotna informacja, którą wykorzystamy w dalszej części
programu, to aktualna pozycja w odtwarzanym utwo-
rze. Odczytujemy ją albo w postaci próbek PCM albo
czasu trwania, a służą do tego funkcje:
ov_pcm_tell
oraz
ov_time_tell
.
Oczywiście można uzyskać jeszcze kilka dodatko-
wych informacji dzięki strukturze
vorbis_info
. Dostęp
do tych danych uzyskujemy w następujący sposób:
vorbis_info *vi=ov_info(&vf,-1);
. Uzyskanie ilości kana-
łów dźwiękowych, które zawiera określony plik, sprowa-
dza się do skorzystania z informacji zawartych w poszcze-
gólnych polach, np.
channels=vi->channels;
.
W każdym odtwarzaczu ważną funkcją jest przeszu-
kiwanie utworu. Może się jednak zdarzyć, że określony
plik OGG nie pozwala na ustalenie, od którego fragmen-
tu ma być odtwarzany. Do sprawdzenia, czy plik pozwa-
la na szybkie przeszukiwanie, wykorzystujemy funkcję
ov_seekable
:
ustawienie nowej pozycji
w utworze
int ogg_close_play();
zamknięcie pliku
const char *ogg_get_title();
odczytanie tytułu utworu
void ogg_get_time(char
*time_str);
pobranie czasu trwania
utworu
void ogg_get_remain-
der_time(char *tstr);
pobranie czasu pozostałego
do końca utworu
spróbować wysłać próbki do karty dźwiękowej. W przy-
padku wartości ujemnej, jak widać na Listingu 2, prze-
rywamy działanie pętli, a dodatkowo, jeśli nie udało się
zastosować funkcji
xrun_recovery
, przerywamy działanie
programu. Gdy nie wystąpiła ujemna wartość
err
, to otrzy-
maliśmy ilość odtworzonych ramek. Wobec tego zwięk-
szamy wskaźnik
ptr
oraz zmniejszamy zmienną
cptr
o już
odtworzone ramki.
if(ov_seekable(&vf)){
}
60
kwiecień 2004
int xrun_recovery(snd_
pcm_t *h, int err);
void ogg_set_new_pcm_
pos(long i);
dekoder plików ogg
Listing 2.
Pętla odtwarzająca pojedynczą próbkę dźwięku
Zajmijmy się parametrami
ov_read
. Pierwszy para-
metr to uchwyt typu
OggVorbis_File
. W drugim podaje-
my wskaźnik naszego buforu. Po nim określamy w bajtach
ilość danych, które chcemy zdekodować. Wartość 4096 jest
najlepsza, choć można poeksperymentować z innymi licz-
bami. Pamiętajmy, że muszą to być potęgi liczby dwa. Jest to
dość istotne dla wartości wynikowej, ponieważ dzielimy ją
przez cztery, a ilość danych musi koniecznie być parzysta.
Tajemniczo wyglądają trzy następne cyfry: 0, 2, 1.
Zero oznacza, że zdekodowane dane będą w formacie
„
little endian”
(pierwszy bajt mniej znaczący). Podanie
zamiast zera jedynki oznacza ułożenie danych w formacie
„
big endian”
(pierwszy bajt bardziej znaczący). Następna
liczba (2) oznacza wielkość pojedynczego słowa, a dokład-
niej oznacza słowo szesnastobitowe. Ostatnia liczba doty-
czy znaku. Podanie jedynki oznacza, że dekodowane dane
będą posiadały znak (zero oznacza wartości bez znaku).
Ostatni argument to tzw. numer logicznego strumienia,
ale nas ta wartość nie interesuje, choć w wywołaniu trzeba
ją oczywiście podać. Kod źródłowy funkcji przeznaczonych
do odtwarzania plików w formacie ogg zawiera plik
ogg-
play.c
– ich spis znajduje się w Tabeli 1.
int
tt
=
0
,
err
=
0
,
cptr
=
0
,
current_section
=
0
;
short int
ptr
;
/* dekodowanie fragmentu pliku */
tt
=
ov_read
(&
vf, sample_buffer,
4096, 0, 2, 1,
&
current_
section
);
cptr
=
tt /
4
;
ptr
=(
short int
*)
sample_buffer
;
while
(
cptr
>
0
)
{
err
=
snd_pcm_writei
(
handle, ptr, cptr
);
if
(
err
== -
EAGAIN
)
continue
;
if
(
err
<
0
)
{
if
(
xrun_recovery
(
handle, err
) <
0
)
{
printf
(
„Błąd zapisu: %s
\
”,
snd_strerror
(
err
));
exit
(
EXIT_FAILURE
);
}
break
;
}
ptr
+=
err
*
channels
;
cptr
-=
err
;
}
Czas aktualny i czas pozostały do
końca
Listing 3 zawiera funkcję realizującą odczyt czasu pod-
czas odtwarzania utworu (
ogg_get_time
) oraz wyznacza-
jąca pozostały czas (
ogg_get_remainder_time
) do końca
utworu.
Zajmijmy się pierwszą z tych funkcji. Na początku
wykonujemy odczytanie czasu trwania funkcją
ov_time_
tell
. Otrzymujemy wartość rzeczywistą, z której trzeba „wy-
ciągnąć” minuty oraz sekundy. Wyznaczenie liczby minut
jest następujące:
long min = (long) curr_time / (long) 60;
– dzielimy czas przez wartość 60. Naszym zamiarem jest
uzyskanie całkowitej wartości zamiast np. 4,823. O ile cyfra
4 to z pewnością liczba minut, to pozostała część po prze-
cinku istotnie reprezentuje sekundy i jest to w przybliżeniu
82% minuty. Z tego powodu wykorzystujemy konwersję do
Po sprawdzeniu, czy możemy szybko się przenieść w inną
część utworu, sam proces przeniesienia jest zaskakująco
prosty i dla ramek PCM realizuje to poniższa funkcja:
void ogg_set_new_pos(int i){
double new_pos=(i/100.0) * pcm_length;
ov_pcm_seek(&vf, (long)new_pos);
}
Ustalenie pozycji to wywołanie funkcji
ov_pcm_seek
, jednak
wcześniejsze obliczenie nowej pozycji to także proste zada-
nie. Zakładamy, że nową pozycją będzie liczba od zera
do stu. Całkowita długość utworu jest zawarta w zmien-
nej
pcm_length
, więc mnożąc ją przez ułamek
i/100.0
prze-
mieszczamy się proporcjonalnie po całym utworze – dla
100 powędrujemy na sam koniec, dla 50 mniej więcej na
środek, a dla
i
równego zero (0/100) na początek.
Jeśli ktoś przypuszcza, że proces dekodowania dźwięku
z poziomu API jest skomplikowany, to z pewnością ucieszy
się, gdy powiem, że sprowadza się do jednej funkcji -- całą
robotę „odwala” funkcja
ov_read
.
Dekodowanie pojedynczego fragmentu przedstawia
poniższa linia kodu:
Listing 3.
Funkcje odczytujące czas
void
ogg_get_time
(
char
*
time_str
)
{
double
curr_time
=
ov_time_tell
(&
vf
);
long
min
= (
long
)
curr_time /
(
long
)
60
;
double
sec
=
curr_time
-
60.0f
*
min
;
sprintf
(
time_str,
„%02li:%05.2f”,
min, sec
);
}
void
ogg_get_remainder_time
(
char
*
time_str
)
{
double
curr_time
=
ov_time_tell
(&
vf
);
curr_time
=
total_time – curr_time
;
long
min
= (
long
)
curr_time /
(
long
)
60
;
double
sec
=
curr_time
-
60.0f
*
min
;
sprintf
(
time_str,
„%02li:%05.2f”,
min, sec
);
}
tt=ov_read(&vf, sample_buffer, 4096, 0, 2, 1, ¤t_
section);
W zmiennej
tt
otrzymamy ilość bajtów, która została zde-
kodowana. Wykorzystujemy ją także do wykrycia końca
utworu – w takim przypadku zmienna będzie zawierać
wartość 0.
www.linux.com.pl
61
dla programistów
Rysunek 2.
Schemat budowy odtwarzacza plików ogg
typu całkowitego
long
, otrzymując wartość całkowitą. Pozo-
stałe sekundy wyznaczamy w następujący sposób:
double
sec = curr_time - 60.0f * min;
– od czasu odejmujemy
całkowitą ilość minut. Pozostała cześć to właśnie brakujące
sekundy. Oznacza to, że funkcja
ogg_get_time
zwraca war-
tości w sekundach. Ostatnim krokiem do uzyskania czasu
w postaci odpowiedniego ciągu znaków jest zastosowanie
funkcji
sprintf
.
Następna funkcja, która pozwoli naszej prostej aplika-
cji nadać bardziej „rasowy” wygląd, to
get_remainder_time
.
Wyznacza ona czas, który pozostał do końca utworu. Od
poprzedniej różni się tylko jedną linią kodu:
curr_time-
=total_time – curr_time;
. Zmienna
total_time
reprezentu-
je całkowitą długość utworu. Jeśli od tej wielkości odejmie-
my aktualny czas, czyli pozycję w odtwarzanym utworze,
to otrzymamy pozostały czas do końca utworu. Z otrzyma-
62
kwiecień 2004
Plik z chomika:
SOLARIX33
Inne pliki z tego folderu:
2006.01_Koder plików w formacie OGG_[Programowanie].pdf
(722 KB)
2007.06_Piękno fraktali_[Programowanie].pdf
(1778 KB)
2008.11_GanttProject_[Programowanie].pdf
(1014 KB)
2007.04_USB Device Explorer_[Programowanie].pdf
(1134 KB)
2006.09_QT, PyQT – szybkie tworzenie baz danych_[Programowanie].pdf
(1319 KB)
Inne foldery tego chomika:
Administracja
Aktualnosci
Audio
Bazy Danych
Bezpieczenstwo
Zgłoś jeśli
naruszono regulamin