dla programistów
Zobacz w:
Grajek, czyli dekoder plików
OGG
Marek Sawerwain
pewnością wielu Czytelników jest ciekawych, ( high-level API ) to zaledwie kilka funkcji przeznaczonych
jak przedstawia się kod zródłowy programu do sterowania procesem dekodowania.
XMMS. Zadają sobie również pytanie, jak we Skoro tworzymy program w KDE, to powinniśmy sko-
Zwłasnym programie zrealizować odtwarzanie rzystać z systemu dzwięku aRts, ale wybierzemy system
plików audio. Właściwie można to sprawdzić samemu, ścią- dzwięku ALSA, który stosunkowo niedawno doczekał się
gając odpowiedni pakiet i przeglądając zródła. Wydawać się wersji 1.0.
może, że program XMMS nie jest skomplikowaną aplika-
cją, ale każdy, kto spróbuje przejrzeć zródła tego programu Funkcje do obsługi systemu ALSA
bądz wielu podobnych, będzie zmuszony zmienić zdanie. Pisanie programu odtwarzającego pliki w formacie OGG
Kod zródłowy, choć nie jest specjalnie duży, to rozwiązu- rozpoczniemy od przygotowania kilku funkcji związanych
je wiele problemów, które zaciemniają sam proces dekodo- z obsługą dzwięku. Przeznaczenie tych funkcji jest dość
wania dzwięku. oczywiste: init_alsa oraz init_mixer dokonują inicjaliza-
W tym artykule chciałbym przedstawić przykład nie- cji systemu ALSA oraz miksera. Odwrotne w działaniu są
wielkiej aplikacji dekodującej pliki w formacie OGG, zdo- następujące funkcje: done_alsa oraz done_mixer. Nasz przy-
bywającym coraz większą popularność. Wszystkie progra- szły player pozwala sterować głośnością w mikserze, więc
my przeznaczone do odtwarzania plików audio pozwala- potrzebna jest funkcja do odczytu głośności get_volume
ją na odtwarzanie plików w formacie OGG, więc tworze- i ustalania głośności set_volume. Kod tych funkcji jest
nie własnego programu może wydawać się niepotrzeb- zawarty w pliku alsa_func.c i każdy, kto go przejrzy, zoba-
ne. Nasza aplikacja posiada jednak walory edukacyjne. Ze czy, że znajduje się tam definicja dodatkowych pięciu funk-
względu na jej niewielkie rozmiary, będzie przydatna dla cji. Spis i znaczenie wszystkich funkcji zawiera Tabela 1.
osób chcących poznać główne zasady dekodowania plików Może dziwić brak definicji funkcji do odtwarzania danych.
audio. Specjalna funkcja w istocie nie jest nam potrzebna, gdyż
skorzystamy z funkcji bibliotecznej ALSA snd_pcm_writei.
Założenia początkowe Przykład funkcji dokonującej inicjalizacji systemu
Naszą aplikację napiszemy w oparciu o program KDeve- ALSA przedstawia Listing 1. Właściwą inicjalizację wykona
lop w wersji 3.0 (Rysunek 1). Zastosowanie tego narzędzia wywołanie snd_pcm_open. Dodatkowa funkcja alsa_error_
oznacza, że program będzie przeznaczony dla środowiska fnc obsługuje ewentualne błędy -- polega to tylko na
KDE chociaż KDevelop aspiruje do miana uniwersalnego wyświetlaniu komunikatów tekstowych na konsoli. Następ-
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
dekoder plików ogg
nia funkcji snd_pcm_writei. Przykład jej zastosowania jest
Listing 1. Treść funkcji init_alsa
trywialny:
int init_alsa() err = snd_pcm_writei(handle, ptr, cptr);
{
if(alsa_error_fnc(snd_pcm_open(&handle, pcm_name, Zmienna handle to uchwyt do urządzenia PCM, uzyskany
stream, open_mode))==-1) funkcją snd_pcm_open. Następny parametr ptr to wskaznik
printf( Błąd w snd_pcm_open\ ); na bufor, a cptr to ilość ramek w buforze. Funkcja ta prze-
return -1; syła podane próbki bezpośrednio do karty. Naszym zada-
} niem jest cykliczne dostarczanie danych do karty dzwię-
sample_buffer=malloc(sample_buffer_size); kowej, więc wywołanie snd_pcm_writei następuje w pętli
snd_pcm_hw_params_alloca(&hwparams); while, która w większości programów pisanych w systemie
if(set_hw_params()<0) ALSA (np. wtyczka ALSA dla XMMS) wygląda mniej więcej
{ tak, jak na Listingu 2.
printf( Błąd w set_hw_params\ ); Przekazanie zdekodowanych danych do karty to
return -1; 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 dzwię-
kowa; w naszym przypadku mamy dwa kanały: lewy
ną istotną czynnością jest przydzielenie pamięci dla bufora, i prawy; dla każdego z tych kanałów dzwięk jest opisywa-
w którym będą znajdować się rozkodowane dane przezna- ny za pomocą dwóch bajtów, czyli ramka składa się z czte-
czone do odtworzenia. Wielkość buforu koniecznie musi rech bajtów), natomiast w wyniku działania funkcji ov_read
być podzielna przez cztery. (dekoduje plik w formacie OGG), zmienna tt zawiera ilość
Ostateczne ustalenie parametrów to zadanie dla funk- danych, ale w bajtach. Przed wejściem do pętli przygotowu-
cji set_hw_params. Ustalamy w niej tryb dostępu. W naszej jemy zatem nową wartość, określającą ilość danych. Jest to
aplikacji będzie to przeplatany tryb zapisu i odczytu, okre- zmienna cptr, w której umieszczamy wartość zmiennej tt
ślony przez wartość: SND_PCM_ACCESS_RW_INTERLEAVED. podzieloną przez cztery.
Równie istotne jest określenie ilości kanałów i częstotliwo- Po operacji przygotowania zmiennych cptr i ptr,
ści, z którą będzie próbkowany dzwięk: wchodzimy w pętlę. Jest ona konieczna, gdyż ALSA może
nie przyjąć w całości przekazywanych danych. W takim
err = snd_pcm_hw_params_set_channels(handle, hwparams, przypadku zmienna err będzie zawierać ilość ramek
channels); wysłanych do karty. Sprawdzamy także, czy zmienna err
err = snd_pcm_hw_params_set_rate_near(handle, hwparams, nie zawiera informacji o potencjalnym błędzie. Może ona
&rrate, 0); przyjąć wartość -EAGAIN, co jest sygnałem, aby ponownie
Powyższe dwie linijki kodu ustalają tryb stereo oraz często-
Kompilacja potrzebnych pakietów
tliwość równą 44100hz. Ostatnim bardzo istotnym parame-
Zanim przystąpimy do kompilacji pakietów OGG/Vorbis, warto
trem jest format danych, a będą to liczby szesnastobitowe
sprawdzić, czy używana dystrybucja przypadkiem nie zawie-
ze znakiem. Fragment kodu z pliku alsa_func.c jest nastę-
ra tych bibliotek. W większości przypadków biblioteki powinny
pujący:
być obecne. Gdy jednak chcemy przeprowadzić kompilację,
to w pierwszej kolejności kompilujemy pakiet libogg, a po nim
static snd_pcm_format_t format=SND_PCM_FORMAT_S16;
libvorbis. Pozostałe pakiety nie będą nam potrzebne. Ponie-
...
waż do kompilacji zastosowano system Autoconf i Automa-
err = snd_pcm_hw_params_set_format(handle, hwparams,
ke, to kompilacja nie powinna sprawić kłopotów. Jak zwykle
format); w przypadku tandemu Autoconf i Automake, wydajemy trzy
polecenia:
Ponieważ na łamach Linux+ system ALSA był już omawia-
./configure prefix=/usr
ny, więc pozwolę sobie Czytelnika odesłać do artykułu
make
z numeru 08/2003 (został on zamieszczony na płycie CDA/
make install
DVDA w katalogu ALSA).
W przypadku, gdy nie mamy programu KDevelop w wersji 3.0,
Odtwarzanie próbki dzwiękowej
czeka nasz samodzielna jego kompilacja. KDevelop wymaga,
ALSA oferuje kilka trybów przesyłania danych do karty
aby KDE było przynajmniej w wersji 3.0.2, ale większość dys-
dzwiękowej. Nasza aplikacja wykorzystuje z powodze- trybucji w ostatnich wydaniach zawiera już wersję z serii 3.1.x.
niem najprostszą metodę, opartą o bezpośrednie wywoła-
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-
Plik alsa_func.c
nych danych do karty dzwiękowej. Teraz trzeba poznać
int alsa_error_fnc(int i); wyświetla komunikat
sposób, w jaki dane dekodujemy. Funkcje realizują-
o błędzie
ce proces dekodowania z formatu OGG w przypadku
int set_hw_params(); ustalenie parametrów API wysokiego poziomu nie są trudne do opanowania.
sprzętowych Co więcej, funkcje, które za chwilę przedstawię, są podob-
ne w użyciu do typowych funkcji bibliotecznych, takich
int xrun_recovery(snd_ funkcja po wystąpieniu błędu
jak open, read, close czy seek. Każdy, kto popraw-
pcm_t *h, int err); próbuje doprowadzić do
nie posługuje się funkcjami do obsługi plików, nie
porządku bufor
będzie miał problemów z obsługą plików w formacie
urządzenia PCM
OGG.
void find_obj_pcm(); odszukanie obiektu
Zanim rozpoczniemy dekodowanie pliku, musimy go
miksera odpowiedzialnego
otworzyć. Wykonujemy to funkcją fopen. Uzyskane odnie-
za sterowanie głośnią
sienie do pliku przekazujemy funkcji ov_open w pierw-
urządzenia pcm
szym parametrze, a w drugim podajemy adres zmiennej
void set_volume(double v); ustalenie głośności
reprezentującej właściwy uchwyt. W dalszych wywoła-
void get_volume(double *v); odczytanie aktualnej
niach posługujemy się tym uchwytem (typ OggVorbis_File)
głośności
zamiast zmienną plikową FILE *
. Czynności przygotowaw-
int alsa_reset(); reset urządzenia PCM
cze wyglądają następująco:
int init_alsa(); inicjalizacja systemu
FILE *file_handle;
int done_alsa(); zamknięcie systemu
OggVorbis_File vf;
int init_mixer(); inicjalizacja miksera
file_handle=fopen( ~/file.ogg , r );
int done_mixer(); zamknięcie miksera
ov_open(file_handle, &vf, NULL, 0);
Plik oggplay.c
int ogg_prepare_to_play wykonanie wstępnych
Następnie musimy poznać pewne podstawowe infor-
(const char *fn); czynności
macje, takie jak całkowita długość utworu (ilość próbek
int ogg_do_play _full(); odtworzenie całego pliku
PCM albo całkowity czas trwania). Odczytanie długo-
int ogg_do_play _frag- odtworzenie pojedynczego ści utworu dokonujemy w naszej aplikacji w następują-
ment(); fragmentu pliku cy sposób:
long ogg_tell_pcm_pos(); odczytanie aktualnej pozycji
pcm_length=ov_pcm_total(&vf,-1);
w utworze
total_time=ov_time_total(&vf, -1);
long ogg_full_pcm(); całkowita długość utworu
void ogg_set_new_pos ustawienie nowej pozycji
Istotna informacja, którą wykorzystamy w dalszej części
(int i); w utworze (zero początek,
programu, to aktualna pozycja w odtwarzanym utwo-
sto koniec)
rze. Odczytujemy ją albo w postaci próbek PCM albo
void ogg_set_new_pcm_ ustawienie nowej pozycji
czasu trwania, a służą do tego funkcje: ov_pcm_tell oraz
pos(long i); w utworze
ov_time_tell.
int ogg_close_play(); zamknięcie pliku
Oczywiście można uzyskać jeszcze kilka dodatko-
const char *ogg_get_title(); odczytanie tytułu utworu
wych informacji dzięki strukturze vorbis_info. Dostęp
do tych danych uzyskujemy w następujący sposób:
void ogg_get_time(char pobranie czasu trwania
vorbis_info *vi= ov_info(&vf,-1);. Uzyskanie ilości kana-
*time_str); utworu
łów dzwiękowych, które zawiera określony plik, sprowa-
void ogg_get_remain- pobranie czasu pozostałego
dza się do skorzystania z informacji zawartych w poszcze-
der_time(char *tstr); do końca utworu
gólnych polach, np. channels=vi->channels;.
W każdym odtwarzaczu ważną funkcją jest przeszu-
spróbować wysłać próbki do karty dzwiękowej. W przy- kiwanie utworu. Może się jednak zdarzyć, że określony
padku wartości ujemnej, jak widać na Listingu 2, prze- plik OGG nie pozwala na ustalenie, od którego fragmen-
rywamy działanie pętli, a dodatkowo, jeśli nie udało się tu ma być odtwarzany. Do sprawdzenia, czy plik pozwa-
zastosować funkcji xrun_recovery, przerywamy działanie la na szybkie przeszukiwanie, wykorzystujemy funkcję
programu. Gdy nie wystąpiła ujemna wartość err, to otrzy- ov_seekable:
maliśmy ilość odtworzonych ramek. Wobec tego zwięk-
szamy wskaznik ptr oraz zmniejszamy zmienną cptr o już if(ov_seekable(&vf)){
odtworzone ramki. }
60 kwiecień 2004
dekoder plików ogg
Zajmijmy się parametrami ov_read. Pierwszy para-
Listing 2. Pętla odtwarzająca pojedynczą próbkę dzwięku
metr to uchwyt typu OggVorbis_File. W drugim podaje-
my wskaznik naszego buforu. Po nim określamy w bajtach
int tt=0, err=0, cptr=0, current_section=0; ilość danych, które chcemy zdekodować. Wartość 4096 jest
short int ptr; najlepsza, choć można poeksperymentować z innymi licz-
/* dekodowanie fragmentu pliku */ bami. Pamiętajmy, że muszą to być potęgi liczby dwa. Jest to
tt=ov_read(&vf, sample_buffer, 4096, 0, 2, 1, ¤t_ dość istotne dla wartości wynikowej, ponieważ dzielimy ją
section); przez cztery, a ilość danych musi koniecznie być parzysta.
cptr=tt / 4; Tajemniczo wyglądają trzy następne cyfry: 0, 2, 1.
ptr=(short int*)sample_buffer; Zero oznacza, że zdekodowane dane będą w formacie
while (cptr > 0){ little endian (pierwszy bajt mniej znaczący). Podanie
err = snd_pcm_writei(handle, ptr, cptr); zamiast zera jedynki oznacza ułożenie danych w formacie
if (err == -EAGAIN) continue; big endian (pierwszy bajt bardziej znaczący). Następna
if (err < 0){ liczba (2) oznacza wielkość pojedynczego słowa, a dokład-
if (xrun_recovery(handle, err) < 0){ niej oznacza słowo szesnastobitowe. Ostatnia liczba doty-
printf( Błąd zapisu: %s\ , snd_strerror(err)); czy znaku. Podanie jedynki oznacza, że dekodowane dane
exit(EXIT_FAILURE); będą posiadały znak (zero oznacza wartości bez znaku).
} Ostatni argument to tzw. numer logicznego strumienia,
break; ale nas ta wartość nie interesuje, choć w wywołaniu trzeba
} ją oczywiście podać. Kod zródłowy funkcji przeznaczonych
ptr += err * channels; do odtwarzania plików w formacie ogg zawiera plik ogg-
cptr -= err; play.c ich spis znajduje się w Tabeli 1.
}
Czas aktualny i czas pozostały do
końca
Po sprawdzeniu, czy możemy szybko się przenieść w inną Listing 3 zawiera funkcję realizującą odczyt czasu pod-
część utworu, sam proces przeniesienia jest zaskakująco czas odtwarzania utworu (ogg_get_time) oraz wyznacza-
prosty i dla ramek PCM realizuje to poniższa funkcja: jąca pozostały czas (ogg_get_remainder_time) do końca
utworu.
void ogg_set_new_pos(int i){ Zajmijmy się pierwszą z tych funkcji. Na początku
double new_pos=(i/100.0) * pcm_length; wykonujemy odczytanie czasu trwania funkcją ov_time_
ov_pcm_seek(&vf, (long)new_pos); 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;
Ustalenie pozycji to wywołanie funkcji ov_pcm_seek, jednak dzielimy czas przez wartość 60. Naszym zamiarem jest
wcześniejsze obliczenie nowej pozycji to także proste zada- uzyskanie całkowitej wartości zamiast np. 4,823. O ile cyfra
nie. Zakładamy, że nową pozycją będzie liczba od zera 4 to z pewnością liczba minut, to pozostała część po prze-
do stu. Całkowita długość utworu jest zawarta w zmien- cinku istotnie reprezentuje sekundy i jest to w przybliżeniu
nej pcm_length, więc mnożąc ją przez ułamek i/100.0 prze- 82% minuty. Z tego powodu wykorzystujemy konwersję do
mieszczamy się proporcjonalnie po całym utworze dla
100 powędrujemy na sam koniec, dla 50 mniej więcej na
Listing 3. Funkcje odczytujące czas
środek, a dla i równego zero (0/100) na początek.
Jeśli ktoś przypuszcza, że proces dekodowania dzwięku void ogg_get_time(char *time_str){
z poziomu API jest skomplikowany, to z pewnością ucieszy double curr_time=ov_time_tell(&vf);
się, gdy powiem, że sprowadza się do jednej funkcji -- całą long min = (long) curr_time / (long) 60;
robotę odwala funkcja ov_read. double sec = curr_time - 60.0f * min;
Dekodowanie pojedynczego fragmentu przedstawia sprintf(time_str, %02li:%05.2f , min, sec);
poniższa linia kodu: }
void ogg_get_remainder_time(char *time_str){
tt=ov_read(&vf, sample_buffer, 4096, 0, 2, 1, ¤t_ double curr_time=ov_time_tell(&vf);
section); curr_time=total_time curr_time;
long min = (long) curr_time / (long) 60;
W zmiennej tt otrzymamy ilość bajtów, która została zde- double sec = curr_time - 60.0f * min;
kodowana. Wykorzystujemy ją także do wykrycia końca sprintf(time_str, %02li:%05.2f , min, sec);
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- Następna funkcja, która pozwoli naszej prostej aplika-
stałe sekundy wyznaczamy w następujący sposób: double cji nadać bardziej rasowy wygląd, to get_remainder_time.
sec = curr_time - 60.0f * min; od czasu odejmujemy Wyznacza ona czas, który pozostał do końca utworu. Od
całkowitą ilość minut. Pozostała cześć to właśnie brakujące poprzedniej różni się tylko jedną linią kodu: curr_time-
sekundy. Oznacza to, że funkcja ogg_get_time zwraca war- =total_time curr_time;. Zmienna total_time reprezentu-
tości w sekundach. Ostatnim krokiem do uzyskania czasu je całkowitą długość utworu. Jeśli od tej wielkości odejmie-
w postaci odpowiedniego ciągu znaków jest zastosowanie my aktualny czas, czyli pozycję w odtwarzanym utworze,
funkcji sprintf. to otrzymamy pozostały czas do końca utworu. Z otrzyma-
62 kwiecień 2004
dekoder plików ogg
z projektem, pracujemy poprzez pryzmat systemów Auto-
make i Autoconf.
Po utworzeniu projektu trzeba dodać do niego infor-
macje o dołączanych przez nas bibliotekach. Wpisujemy
je do okna Project Options z menu Project. Nasz program
korzysta z bibliotek Vorbis/OGG oraz ALSA. Jeśli bibliote-
ki związane z formatem Vorbis/OGG zostały zainstalowane
z prefiksem /usr, to nie trzeba podawać ścieżki do plików
nagłówkowych oraz bibliotecznych, gdyż standardowo
zawsze korzystamy z /usr/include czy /usr/lib. W przypad-
ku systemu ALSA korzystamy z programu pkg-config. Efekt
końcowy wygląda tak, jak na Rysunku 4.
Takie pliki, jak oggplay.c czy alsa_func.c, dodajemy
poprzez okno o nazwie Automake manager (lewy przy-
cisk myszy na gałęzi grajek i opcja Add Files...). Ponad-
to, musimy dodać do naszego projektu nowe okno. Aby
to zrobić, wybieramy z menu File opcję New ilustruje to
Rysunek 5.
Nowo utworzony plik to z punktu widzenia KDE
i biblioteki QT pojedynczy widget reprezentujący całe okno
typ pliku trzeba ustalić na widget. Po zatwierdzeniu przy-
Rysunek 3. Tworzenie nowego projektu
ciskiem OK, uruchomi się program QT Designer, w którym
ną wartością postępujemy tak, jak w poprzedniej funkcji, co musimy zaprojektować cały interfejs naszej aplikacji. Pamię-
widać na Listingu 3. tajmy też, aby określić nazwy (własność name) poszczegól-
nych widgetów. Koniecznie trzeba zdefiniować sloty do
Tworzymy aplikację obsługi sygnałów generowanych, np. poprzez wciśnięcie
Po tych przygotowaniach jesteśmy gotowi do pisania przycisku do odtwarzania. Gotowy interfejs wygląda tak,
aplikacji w środowisku KDE przy użycia narzędzia KDe- jak na Rysunku 6.
velop 3.0. QT Designer pozwala na edycję kodu slotów powią-
KDevelop bardzo dobrze współpracuje z programem zanych z sygnałami w naszej aplikacji, ale my będziemy
QT Designer, więc przy jego pomocy stworzymy interfejs używać mechanizmów generowania funkcji wybudowa-
naszego programu. nych do programu KDevelop, a dokładniej metod reprezen-
Schemat z Rysunku 2 przedstawia ogólny projekt apli- tujących poszczególne sloty.
kacji oraz przepływ informacji. W naszym programie mamy Wygenerowanie tzw. podklasy (ang. subclass) zawiera-
oddzielny obiekt reprezentujący wątek. W kodzie będzie jącej metody wywoływane w momencie np. przyciśnięcia
on reprezentowany przez klasę OggPlayerThread. Podstawo- przycisku polega na wybraniu pliku reprezentującego inter-
wym zadaniem tego wątku jest odtwarzanie dzwięku, ale fejs i wciśnięciu lewego przycisku myszki. W przykłado-
odpowiada on także za zmianę aktualnej pozycji w utwo- wym projekcie umieszczonym na płycie CD jest to plik gra-
rze, gdy użytkownik zmieni położenie suwaka. Wątek jekdlg.ui z poziomu okna Automake manager.
sprawdza też, czy ma przerwać swoje działanie. Wybieramy naturalnie opcję Subclass Widget.. , co spo-
.
Po zapoznaniu się ze schematem, pracę nad aplikacją woduje wygenerowanie nowego pliku (wcześniej musimy
rozpoczynamy od utworzenia projektu. W tym celu z menu
Project wybieramy opcję New Project. Pokaże się nam takie
okno, jak na Rysunku 3. Mamy tu szereg pól do uzupeł-
nienia, ale najważniejsze to Application Name, czyli przy-
szła nazwa aplikacji, oraz Location, czyli katalog, w którym
znajdą się wszystkie pliki naszego projektu. Jak widać na
rysunku, nasz projekt to Simple KDE Application z pojedyn-
czym widgetem.
Po utworzeniu projektu, nasza aplikacja jest gotowa
do kompilacji wystarczy wcisnąć klawisz [F8 ]. Program
KDevelop może pokazać okno, w którym poinformu-
je nas, że nie zostały jeszcze utworzone skrypty configu-
re oraz makefile. Oczywiście zgadzamy się, aby KDevelop
sam utworzył te skrypty. Jest to jedna z ważniejszych zalet
tego programu pomimo graficznego środowiska pracy Rysunek 4. Uzupełnienie informacji o bibliotekach
www.linux.com.pl 63
dla programistów
utworu od innego miejsca, a do tego służy nam funkcja
ogg_set_new_pos. Funkcja ta przyjmuje wartości od zera do
stu, więc podczas projektowania interfejsu komponentowi
QSlider należy ustawić właśnie taki zakres, aby wskazywa-
ne przez niego wartości były prawidłowe.
Bez dodatkowych warunków jest wykonywana w pętli
funkcja ogg_do_play_fragment(), ale na działanie pętli ma
wpływ wartość wynikowa tej funkcji. Gdy otrzymamy -1, to
znaczy, że albo wystąpił jakiś błąd lub proces dekodowania
dotarł do końca i odtwarzanie utworu należy zakończyć,
czyli przerwać pętlę, a po jej opuszczeniu wywołać funkcję
Rysunek 5. Tworzenie nowego interfejsu
finalizującą cały proces dekodowania ogg_close_play();.
podać jego nazwę poprzez kolejne okno dialogowe) i dołą-
czenie go do naszego projektu. Jeśli w trakcie pracy nad apli- Wybieranie pliku i obsługa
kacją zmienimy coś w interfejsie, np. dodamy nowy widget, przycisku start
to nie należy generować nowej podklasy ale dokonać edycji Nasza aplikacja nie posiada listy odtwarzania, więc każdy
już istniejącej. Wystarczy wskazać plik reprezentujący pod- plik trzeba wczytywać oddzielnie poprzez okno dialogowe.
klasę (w przykładowym projekcie jest to plik grajek.cpp) Program został napisany dla środowiska KDE, a to oznacza,
i tym razem wybrać opcję Edit ui-Subclass.. . Zobaczymy że będziemy korzystać z okna dialogowego w stylu KDE.
.
okno ze spisem metod. Jeśli pojawią się nowe metody, to W pliku grajek.cpp dołączamy nowy plik nagłówkowy:
zostaną one zaznaczone pogrubioną czcionką. Już istnieją- #include
. Samo wywołanie okna dialogo-
ce są oznaczane szarym kolorem. Dodanie nowych metod wego sprowadza się do skorzystania ze statycznej metody
oczywiście nie zmienia postaci metod istniejących wcze- zdefiniowanej w klasie KFileDialog:
śniej, więc nie trzeba się obawiać o stratę kodu.
file_name = KfileDialog::getOpenFileName(
Wątek odtwarzający dzwięk . ,
Większość istotnych funkcji obsługujących odtwarzanie *.ogg ,
i sterowanie systemem ALSA została już przygotowana. this,
Centralnym elementem w naszym programie jest klasa Ogg- Wybierz plik );
PlayerThread. Klasa ta, jak to widać na Listingu 4 (jest to
pełny kod tej klasy), dziedziczy z klasy QThread, a to ozna- Zmienna file_name jest oczywiście typu QString, co ozna-
cza, że mamy do czynienia z wątkiem. Pozwoli to na nieza- cza, że możemy ją podać jako argument do funkcji ogg_
leżne wykonywanie kodu odgrywającego utwór, podczas prepare_to_play, co pokazuje Listing 4.
gdy główna pętla sterująca elementami GUI naszego pro- Oprogramowanie przycisku Start jest nieco dłuższe.
gramu pozwoli nam regulować np. głośność czy sterować Przede wszystkim musimy rozpatrzyć dwa przypadki.
paskiem postępu, aby wskazać inny fragment utworu. Pierwszy przypadek ma miejsce, gdy w naszej aplikacji
Wątek OggPlayerThread posiada tylko jedną metodę po raz pierwszy zostanie naciśnięty przycisk Start. W takim
run, uruchamianą po wydaniu odpowiedniego polecenia. przypadku nie istnieje jeszcze obiekt wątku. Wygląda to
Oprócz tego, mamy w klasie deklarację kilkunastu zmien- w następujący sposób:
nych publicznych. Zmienne, takie jak slider czy TimeLBL, to
wskazniki na widgety. Z poziomu wątku będziemy zmie-
niać stan niektórych komponentów, aby wyświetlić aktu-
alny czas bądz odczytywać wskazanie suwaka (kompo-
nent Qslider), wykrywając, czy użytkownik zmienił położe-
nie suwaka, co oznacza, że trzeba przenieść się do innego
fragmentu utworu.
Cały proces dekodowania pliku odbywa się w pętli
do ... while, działającej tak długo, jak zmienna stop jest
równa zero. Gdy użytkownik zechce zatrzymać odtwarza-
nie utworu, wystarczy zmienić wartość stop na różną od
zera i wątek zakończy swoje działanie. W pętli sprawdza-
my także stan zmiennej set_new_pos gdy jest ona równo
zero, to uaktualniamy kontrolki wyświetlające aktualny
czas. Gdy set_new_pos zawiera wartość większą od zera,
to oznacza, że nastąpiło przesuniecie suwaka przez użyt-
Rysunek 6. Projektowanie interfejsu w programie QT Designer
kownika w inne miejsce, co musi skutkować odtwarzaniem
64 kwiecień 2004
dekoder plików ogg
Listing 4. Wątek odtwarzający plik ogg Listing 5. Slot OnStop zatrzymujący działanie wątku
OggPlayerThread
class OggPlayerThread : public Qthread{
public: void grajek::OnStop(){
int stop; if(ply!=NULL){
QSlider *slider; if(ply->finished()==true){
QLabel *title, *TimeLBL, *TimeToEndLBL; delete ply;
char time1[25], time2[25]; ply=NULL;
virtual void run(){ }
double p=0; else{
ogg_prepare_to_play((const char *)file_name); ply->stop=1;
title->setText(ogg_get_title()); title->update(); ply->wait();
pcm_length=(double)ogg_full_pcm(); delete ply;
do{ ply=NULL;
if(ogg_do_play_fragment()==-1) break; }
current_pcm=(double)ogg_tell_pcm_pos(); }
if(set_new_pos==0){ }
p=(current_pcm / pcm_length)*100;
slider->setValue((int)p); slider->update();
ogg_get_time(&time1[0]); this->OnStop();
ogg_get_remainder_time(&time2[0]); ply=new OggPlayerThread();
TimeLBL->setText(&time1[0]); TimeLBL- ply->start();
>update();
TimeToEndLBL->setText(&time2[0]); Musimy zatrzymać działanie istniejącego wątku i go
TimeToEndLBL->update(); usunąć. Dokładnie takie zadanie realizuje slot OnStop, który
} jest wywoływany w momencie kliknięcia na przycisk Stop.
if(set_new_pos>0){ Kod metody OnStop zawiera Listing 5.
ogg_set_new_pos(set_new_pos); W metodzie OnStop pierwszą czynnością jest sprawdze-
set_new_pos=0; nie, czy zmienna globalna ply jest różna od NULL. Jeśli tak,
} to sprawdzamy, czy wątek w naturalny dla siebie sposób
} zakończył działanie. Do tego służy metody finished(). Jeśli
while(stop==0); tak się stało, to możemy usunąć obiekt. Gdy metoda fini-
ogg_close_play();
}
Listing 6. Poprawki w funkcji main
};
KApplication app;
ply=new OggPlayerThread(); grajek *mainWin = 0;
ply->title=TitleLBL; /* if (app.isRestored()){
ply->stop=0; RESTORE(grajek);
ply->start(); }
else{
Na początek przydzielamy pamięć operatorem new, // no session.. just start up normally
a następnie do publicznych zmiennych przypisuje- KCmdLineArgs *args = KcmdLineArgs::parsedArgs();
my poszczególne widgety (w powyższym przykładzie /// @todo do something with the command line args
tylko dla etykiety TitleLBL). Nim jednak wątek zacznie here
działać, to zmiennej stop podajemy wartość zero, aby */
być pewnym, że po wykonaniu ply->start();, wątek mainWin = new grajek();
zadziała. app.setMainWidget( mainWin );
Drugi przypadek pojawia się, gdy wątek został mainWin->show();
już utworzony i chcemy go zatrzymać, aby utworzyć /*
nowy wątek dla innego utworu wybranego przez args->clear();
użytkownika. Procedura ta jest blizniaczo podobna }
do poprzedniej z jednym wyjątkiem na samym począt- */
ku:
www.linux.com.pl 65
dla programistów
shed() zwraca wartość false, to znaczy, że wątek jeszcze
działa. Do przerwania wątku wystarczy zmienić wartość
zmiennej stop na różną od zera. Dla bezpieczeństwa cze-
kamy na poprawne zakończenie działania wątku wywoła-
niem ply->wait().
W ten sposób przy okazji omawiania czynności związa-
nych z przyciskiem Start przedstawiłem również, jakie czyn-
ności wykonujemy, aby zatrzymać odtwarzanie utworu.
Poprawki w funkcji main
Ostatnim elementem, którym trzeba się zająć, jest funk-
cja main. KDevelop po utworzeniu projektu wygenerował
kod, który zamiast naszego nowo zaprojektowanego okna
wyświetla widget QLabel. Fragment funkcji main po nanie-
sieniu poprawek zawiera Listing 6. Poprawki są następu-
jącego rodzaju: usuwamy test, czy nastąpiło odtworzenie
aplikacji, czyli konstrukcję if(app.isRestored()) ... else
... obejmujemy komentarzem. W sekcji else znajdował się
kod tworzący zawartość okna aplikacji. Pozostawiamy tylko
ten fragment, który tworzy nowy obiekt grajek. Jeśli Czytel-
nik w swoim projekcie inaczej nazwał widget, który repre-
zentuje nasze okno oraz plik podklasy, to zmiana będzie
polegać na utworzeniu innego obiektu. Pamiętajmy też, aby
dołączyć plik nagłówkowy z definicją własnej klasy. Warto
jeszcze w pierwszych liniach funkcji main zmienić bądz uzu-
pełnić takie dane, jak imię i nazwisko autora oraz numer
wersji programu, w deklaracji obiektu KaboutData. Pozosta-
łą część pliku pozostawiamy bez żadnych zmian.
Zakończenie
Zachęcam wszystkich, aby wprowadzić obsługę nowych
formatów plików, takich jak choćby WAV, wykorzystując
bibliotekę Audiofile. Najłatwiej będzie to zrobić tworząc
nową klasę opartą o typ QThread, dedykowaną konkret-
nym typom plików. Jest to zadanie na przysłowiową minut-
kę. Przydatnym unowocześnieniem będzie dodanie do
aplikacji listy odtwarzanych utworów. Dla tych wszystkich,
którzy chcą zdobyć niezbędne doświadczenie, zachęcam
do modyfikacji projektu, który znajduje się na płycie CD.
W Sieci:
" Strona domowa projektu KDE:
http://www.kde.org/
" Strona dla programistów KDE i QT:
http://developer.kde.org/
" Strona środowiska dla programistów KDevelop:
http://www.kdevelop.org/
" Strona domowa projektu ALSA:
http://www.alsa-project.org/
" Strona domowa fundacji Xiph.org:
http://www.xiph.org/
" Strona standardu OGG/Vorbis:
http://www.vorbis.com/
66 kwiecień 2004
Wyszukiwarka
Podobne podstrony:
2004 07 Konsolowa przeglądarka plików graficznych [Programowanie]
2004 04 Fonty w Linuksie [Administracja]
Matematyka dyskretna 2004 04 Rachunek prawdopodobieństwa
2004 10 Skojarzenia aplikacji z typami plików [Poczatkujacy]
2004 11 Usprawniamy OpenOffice org, czyli makro do tworzenia tabel [Programowanie]
Dobry program lojalnościowy, czyli jaki
2004 09 Rozszerzanie możliwości przeglądarek WWW [Programowanie]
E marketing Ewolucja modeli reklamy w w wyszukiwarkach 04 2004
2006 04 Piszemy widget zegara w GTK [Programowanie]
2004 grudzień arkusz chemia poziom r rok 04 7 MODEL
22 04 2004
Programowanie i jezyk C Lab 04
04 03 2004
Podstawy Programowania 04 Programowanie Obiektowe
01 04 2004
programy 18 04 2013
2004 10?lipse i Java–program do obliczania sum kontrolnych [Programowanie]
więcej podobnych podstron