2006.01_Koder plików w formacie OGG_[Programowanie].pdf
(
722 KB
)
Pobierz
439161176 UNPDF
dla programistów
Koder plików
w formacie OGG
Marek Sawerwain
już wiele razy udowod-
niliśmy, że niewielkim
nakładem pracy można
napisać interesujący program, który będzie
przydatny w codziennej pracy. Zrobimy to
jeszcze raz, pisząc program, który będzie
kompresował pliki audio
wav
do formatu
ogg
. Będzie on rozmiarów nie większych
niż 8 kB tekstu źródłowego, ale pomimo
tego będzie bardzo skuteczny – sam go
używam do tworzenia oggów z nieskom-
presowanych wavów (działa wyśmieni-
cie).
przypadku ABR określamy wartość bitra-
te'u: w przedziale od wartości 64 do 320
kbit/s.
Obsługa naszego programu będzie
dość prosta, aby nie powiedzieć, trywial-
na. Pozwolimy na podanie nazwy pliku
źródłowego, a program samodzielnie za-
proponuje nazwą wynikową, co będzie
polegać na dopisaniu do nazwy dodat-
kowego rozszerzenia
.ogg
. Następnie
możemy rozpocząć proces kompresji.
Postęp w tym procesie będzie wyświe-
tlany za pomocą paska postępu – widget
o typie
GtkProgressBar
. Oczywiście,
przed przystąpieniem do kompresji mo-
żemy zmienić parametry związane z tym
zadaniem.
Centralnym elementem w naszym
programie, jak łatwo zgadnąć, będzie pro-
cedura kodowania, czyli kompresja da-
nych audio do formatu
ogg
. Ogólnie, pro-
ces ten sprowadza się do trzech kroków.
W pierwszej kolejności odczytujemy dane
z pliku, np. w formacie
wav
. Po odczy-
taniu fragmentu danych z pliku audio
dokonujemy ich konwersji na typ
loat
,
gdyż biblioteka Vorbis posługuje się licz-
bami
loat
do reprezentacji danych audio.
Gdy dokonamy konwersji, przystępuje-
my do kompresji i zapisu danych do pliku
docelowego.
Najważniejsze zdarzenia w progra-
mie, które przed chwilą wymieniłem,
warto przedstawić w postaci schematu
– Rysunek 1.
Nasz program będzie pracował w śro-
dowisku GNOME, co oznacza, że napi-
szemy go przy wykorzystaniu progra-
mu GLADE oraz biblioteki GTK+ (bę-
dziemy korzystać z ostatnich wersji sta-
bilnych, czyli GTK+ w wersji 2.8.X bądź
2.6.X). Będziemy jeszcze potrzebowali
odpowiednich narzędzi do obsługi plików
audio. Do realizacji odczytu plików znako-
micie nadaje się biblioteka
Audioile
, nato-
miast do samego kodowania niezbędny
jest zestaw bibliotek
Vorbis
.
Założenia do programu
Zakładamy, że będziemy kompresować
pliki zawierające dwa kanały audio (stereo)
o próbkach 16-bitowych. Niewątpliwie,
jest to najbardziej rozpowszechniony stan-
dard zapisu sygnału audio. Ponadto, użyt-
kownik będzie miał do wyboru dwa pod-
stawowe tryby kompresji:
VBR
(zmien-
ny bitrate) oraz
ABR
(uśredniony bitra-
te). Oprócz tych dwóch podstawowych
rodzajów pracy, w przypadku trybu VBR
możemy określić jakość kompresji: od zera
(najgorsza) do dziewięciu (najlepsza), a w
Na płycie CD/DVD
Na płycie CD/DVD znajdują się
wykorzystywane biblioteki oraz
kod źródłowy programu.
Pierwsze czynności
w programie
Proces projektowania interfejsu odbywa
się za pomocą myszki, więc nie warto go
66
styczeń 2006
W
dziale
Dla Programistów
C/C++/Vorbis
dla programistów
Dodatkowe biblioteki
i kompilacja programu
Ponieważ środowisko GNOME, podob-
nie jak KDE, stało się standardowym ele-
mentem wielu dystrybucji Linuksa, więc
z pewnością w systemie używanym przez
Czytelniczkę/Czytelnika, GNOME jest
dostępne. Podobnie jest w przypadku pa-
kietu Vorbis. Biblioteka ta jest dostęp-
na w większości dystrybucji, przy czym
pakiet Vorbis to dwie zasadnicze biblio-
teki:
libogg
(zarządzanie strumieniem)
oraz
libvorbis
(właściwa biblioteka zaj-
mująca się kompresją). Z tego powodu
wystarczy upewnić się, czy odpowiednie
pakiety binarne oraz pakiety dla dewelo-
perów zostały zainstalowane w systemie
– nie musimy instalować oraz kompilować
samodzielnie dodatkowych bibliotek.
Proces kompilacji naszego progra-
mu nie jest skomplikowany, gdyż wystar-
czy utworzyć prosty plik
makeile
o nastę-
pującej postaci:
S
src `pkg-conig --clags gtk+-2.0
S
S
src `pkg-conig --clags gtk+-2.0
libgnomeui-2.0` -I./include
S
src `pkg-conig --clags
S
gtk+-2.0 libgnomeui-2.0
S
audioile vorbis vorbisenc`
S
S
conig --libs gthread-2.0
S
gtk+-2.0 libgnomeui-2.0
S
audioile vorbis vorbisenc`
Rysunek 1.
Schemat najważniejszych zdarzeń występujących w programie
dokładnie omawiać. Utworzone okno dia-
logowe możemy załadować do progra-
mu na dwa sposoby. Pierwszy polega na
tym, iż interfejs jest zapisywany do pliku
glade
, a do programu jest on wczytywa-
ny dynamicznie. Drugi sposób to wyge-
nerowanie plików źródłowych, co spowo-
duje, że interfejs jest tworzony statycznie
(każda jego zmiana wymaga rekompilacji
programu), ale z drugiej strony, program
nie potrzebuje dodatkowych plików do
poprawnego funkcjonowania. My wybie-
rzemy tę drugą metodę.
Wygenerowanie kodu nastąpi, gdy
w GLADE wybierzemy przycisk
Zbuduj
(
Build
w wersji angielskiej), przy czym
nas interesują tylko pliki
interface.c
i
inter-
face.h
(w nich znajduje się kod tworzą-
cy interfejs) oraz pliki
support.c
i
support.h
(zawierają one pomocnicze funkcje, takie
jak np. bardzo przydatną
lookup_widget
).
Pozostałe pliki możemy zignorować.
Chociaż mogą przydać się pliki
callback.h
i
callback.c
, to obsługę poszczególnych
funkcji odpowiadających na sygnały umie-
ścimy bezpośrednio w pliku, w którym
znajduje się funkcja
main
.
Pierwsza czynność w funkcji
main
to
inicjalizacja biblioteki GTK+:
gtk_init
(&argc, &argv);
, natomiast bezpośrednio
po tym następuje utworzenie okna głów-
nego:
MainWin=create_MainWin();
.
Funkcja tworząca okno została wyge-
nerowana przez GLADE, a jej implemen-
Pierwsze dwie linie są odpowiedzialne
za kompilację interfejsu i dodatkowych
funkcji pomocniczych. Właściwy program
jest kompilowany za pomocą trzeciej
linii. Budowa pliku binarnego odbywa się
w ostatniej linii skryptu.
tacja (osobom, które zaczynają poznawać
GTK+, warto polecić analizę tej funkcji,
aby zobaczyli, w jaki sposób jest budo-
wany interfejs użytkownika) znajduje się
w pliku
interface.c
. Ponieważ w kodzie
programu będziemy odwoływać się bez-
pośrednio do niektórych kontrolek, to
za pomocą funkcji
lookup_widget
uzy-
skamy odniesienia do potrzebnych nam
widgetów:
www.lpmagazine.org
67
all:
gcc -c src/interface.c -I./
libgnomeui-2.0` -I./include
gcc -c src/support.c -I./
S
gcc -c ogg_encoder.c -I./
-I./include
gcc -o ogg_encoder ogg_encoder.o
interface.o support.o `pkg-
dla programistów
Listing 1.
Wybór pliku, który ma zostać poddany kompresji
krótko zaprezentować najważniejsze zda-
rzenia, które pojawiają się w kontekście
obsługi interfejsu. Na Rysunku 2 widzi-
my nasz program. Znajduje się tam przy-
cisk z trzema kropkami. To za jego pomocą
wybieramy plik, który ma zostać poddany
kompresji. Gdy użytkownik kliknie na
ten przycisk, zostanie wywołana funkcja
o nazwie
on_FileSelBTN_clicked
. Jej pełna
treść została przedstawiona na Listingu 1.
Wykonujemy w niej dwie czynności – two-
rzymy okno dialogowe za pomocą funk-
cji
gtk_ile_chooser_dialog_new
, a następ-
nie nakazujemy wyświetlenie tego okna
funkcją
gtk_dialog_run
. Jeśli użytkownik
wybrał plik i potwierdził jego wybór, np.
klikając przycisk
OK
, to wynikiem działa-
nia funkcji
gtk_dialog_run
będzie wartość
GTK_RESPONSE_ACCEPT
, czyli zostanie wyko-
nana treść instrukcji warunkowej. Nazwę
pliku, który wybrał użytkownik, odczy-
tujemy za pomocą funkcji
gtk_ile_cho-
oser_get_ilename
. Wpisujemy ją do pola
tekstowego o nazwie
FileToEncodeEntry
.
Do drugiego pola, o nieco długiej nazwie
FinalNameEncodedFileEntry
, wpisuje-
my nazwę pliku wynikowego. Wcześniej,
za pomocą kilku instrukcji
strcpy
oraz
strcat
, dopisujemy do oryginalnej nazwy
rozszerzenie
.ogg
. W ten sposób w obydwu
polach tekstowych mamy poprawnie wpi-
sane nazwy plików.
To nie wszystkie czynności związane
z poprawną obsługą interfejsu. Gdy użyt-
kownik naciśnie przycisk
OK
, to zamknięta
void
on_FileSelBTN_clicked
(
GtkButton
*
button
,
gpointer
user_data
)
{
GtkWidget
*
dialog
;
dialog
=
gtk_ile_chooser_dialog_new
(
"Open File"
,
MainWin
,
GTK_FILE_CHOOSER_ACTION_OPEN
,
GTK_STOCK_CANCEL
,
GTK_RESPONSE_CANCEL
,
GTK_STOCK_OPEN
,
GTK_RESPONSE_ACCEPT
,
NULL
);
if
(
gtk_dialog_run
(
GTK_DIALOG
(
dialog
))
==
GTK_RESPONSE_ACCEPT
)
{
char
*
ilename
,
newile
[
1024
];
ilename
=
gtk_ile_chooser_get_ilename
(
GTK_FILE_CHOOSER
(
dialog
));
gtk_entry_set_text
(
GTK_ENTRY
(
FileToEncodeEntry
)
,
ilename
);
strcpy
(&
ile_in
[
0
]
,
ilename
);
strcpy
(&
newile
[
0
]
,
ilename
);
strcat
(&
newile
[
0
]
,
".ogg"
);
strcpy
(&
ile_out
[
0
]
,
newile
);
gtk_entry_set_text
(
GTK_ENTRY
(
FinalNameEncodedFileEntry
)
,
&
newile
[
0
]);
g_free
(
ilename
);
}
gtk_widget_destroy
(
dialog
);
}
S
(MainWin, "EndWorkBTN");
S
"delete_event", G_CALLBACK
S
(gtk_main_quit), NULL);
Po odczytaniu widgetów, wystarczy tylko
wykonać trzy linijki kodu, który prezentu-
ją się następująco:
Obsługa interfejsu
Zanim przejdziemy do opisu, w jaki
sposób kompresujemy dane, warto jeszcze
S
(GTK_PROGRESS_BAR(ProgressBar),
S
".: OGG Mini Encoder V1.0 :.");
gtk_widget_show_all(MainWin);
gtk_main();
W pierwszej linii kodu w pasku postę-
pu umieszczamy komunikat. Druga li-
nia kodu spowoduje, że w oknie zostaną
wyświetlone wszystkie widgety. W ostat-
niej linii uruchamiamy główną pętlę
biblioteki GTK+, co powoduje, że nasza
aplikacja zacznie działać.
Na końcu warto jeszcze wspomnieć
o podłączeniu obsługi sygnału
delete_
event
. Jest on generowany podczas klik-
nięcia na przycisk zamykający okno, przy
czym nie jest to przycisk
OK
, ale przycisk
menedżera okien. W takim przypad-
ku generowane jest wspomniane zda-
rzenie. Najłatwiej jest podłączyć wy-
wołanie funkcji
gtk_main_quit
, która
spowoduje natychmiastowe zamknięcie
okna:
Rysunek 2.
Program OGG Encoder V1.0
68
styczeń 2006
EndWorkBTN=lookup_widget
g_signal_connect (MainWin,
gtk_progress_bar_set_text
C/C++/Vorbis
dla programistów
zostanie cała aplikacja. W tej chwili inte-
resuje nas obsługa czterech widgetów
związanych z parametrami kompresji.
Pierwszym ważnym elementem jest
wybór trybu kompresji: ABR bądź VBR.
Odpowiedzialne są za to dwa przełączni-
ki. Wybranie opcji ABR powoduje wywo-
łanie funkcji
on_ABR_RadioBTN_toggled
,
która wykonuje dwa trywialne przypi-
sania:
vbr_sel=0, abr_sel=1;
. Podobne
przypisania są wykonywane, gdy użyt-
kownik wybierze tryb VBR:
vbr_sel=1,
abr_sel=0;
. Za pomocą następnego wid-
getu,
BitrateComboBox
, wybieramy szero-
kość strumienia dla trybu ABR. Jak widać
na Rysunku 2, wartością domyślną jest
wartość 192 kbit/s. W przypadku, gdy
zdecydujemy się na tryb VBR, możemy
za pomocą suwaka, który reprezentuje
widget
QualityWidget
, ustalić jakość kom-
presji. Wartość zero wskazuje najniższą
możliwą jakość, natomiast wartość dzie-
więć – najwyższą. W obydwu przypad-
kach nie wykorzystujemy zdarzeń, gdyż
po wybraniu przycisku
Encode
, funk-
cja, którą za chwilę szerzej przedstawię,
odczytuje potrzebne dane bezpośrednio ze
wspomnianych widgetów.
Listing 2.
Sprawdzanie, czy plik ma odpowiedni format
if
(
channelCount
!=
2
&&
sampleWidth
!=
16
&&
sampleFormat
!=
AF_SAMPFMT_TWOSCOMP
)
{
afCloseFile
(
ile
);
dialog
=
gtk_message_dialog_new
(
NULL
,
GTK_DIALOG_MODAL
,
GTK_MESSAGE_ERROR
,
GTK_BUTTONS_CLOSE
,
"Sorry, the selected ile hasn't proper format! Only two channels and
16 bits sample iles are allowed."
);
gtk_dialog_run
(
GTK_DIALOG
(
dialog
));
gtk_widget_destroy
(
dialog
);
return
;
}
unt
) czy częstotliwość próbkowania (
rate
).
Wymienione dwie przykładowe wielkości
otrzymamy w następujący sposób:
16-bitowej rozdzielczości. Listing 2 przed-
stawia fragment programu, w którym za
pomocą instrukcji warunkowej spraw-
dzamy, czy otwarty plik spełnia nasze
wcześniejsze założenia. Jeśli tak nie jest,
to zamykamy go funkcją
afCloseFile
.
Następnie wyświetlamy okno dialogo-
we z odpowiednim komunikatem, infor-
mując użytkownika, że plik zawiera dane
w nieobsługiwanym formacie. Zwróćmy
też uwagę na to, iż wykorzystując biblio-
tekę
Audioile
, nie musimy nawet wiedzieć,
czy ten plik był w formacie wav, czy w do-
wolnym innym – wystarczy, aby był
to plik, w którym dane audio zapisa-
no w obsługiwanym przez nas formacie
danych.
Ostatnią ważną czynnością jest utwo-
rzenie pliku oraz przydzielenie pamięci
dla bufora:
S
Channels(ile, AF_DEFAULT_TRACK);
rate=afGetRate(ile, AF_DEFAULT_TRACK);
Otrzymane w ten sposób informacje są
dla nas bardzo ważne. Ponieważ biblio-
teka OGG wymaga dostarczenia danych
w postaci liczb zmiennoprzecinkowych
typu
loat
, to musimy uzyskać informacje,
z jakim typem danych mamy do czynie-
nia, aby poprawnie dokonać odpowiedniej
konwersji danych.
W naszej dość elementarnej aplika-
cji dla uproszczenia będziemy zajmo-
wać się tylko plikami o dwóch kanałach,
w których znajdują się liczby całkowite o
Przygotowania
do kompresji
Przed właściwą kompresją danych należy
otworzyć plik, z którego będziemy odczy-
tywać dane. Ponieważ stosujemy biblio-
tekę
Audioile
, to operacja otworzenia pli-
ku jest wykonywana za pomocą funkcji
afOpenFile
:
ile = afOpenFile(&ile_in[0],
"r", NULL);
Listing 3.
Utworzenie nagłówków pliku OGG
W programie sprawdzamy również, czy
istotnie udało się otworzyć plik, spraw-
dzając, czy zmienna
ile
posiada wartość
równą
AF_NULL_FILEHANDLE
. Następnie,
jeśli bez kłopotów otworzyliśmy plik,
odczytujemy podstawowe informacje,
które są niezbędne w trakcie kompresji.
Istotną informacją dla nas jest ilość
ramek w pliku:
ogg_packet
header
;
ogg_packet
header_comm
;
ogg_packet
header_code
;
srand
(
time
(
NULL
));
ogg_stream_init
(&
os
,
rand
());
vorbis_analysis_headerout
(&
vd
,
&
vc
,
&
header
,
&
header_comm
,
&
header_code
);
ogg_stream_packetin
(&
os
,
&
header
);
ogg_stream_packetin
(&
os
,
&
header_comm
);
ogg_stream_packetin
(&
os
,
&
header_code
);
frameCount = afGetFrameCount
S
(ile, AF_DEFAULT_TRACK);
while
(!
eos
)
{
result
=
ogg_stream_lush
(&
os
,
&
og
);
if
(
result
==
0
)
break
;
fwrite
(
og
.
header
,
1
,
og
.
header_len
,
f_out
);
fwrite
(
og
.
body
,
1
,
og
.
body_len
,
f_out
);
}
Jest nam ona potrzebna, aby podczas wła-
ściwej kompresji pasek statusu wskazywał
poprawny stan zaawansowania tego pro-
cesu. Potrzebne są nam także inne informa-
cje, takie jak np. ilość kanałów (
channelCo-
www.lpmagazine.org
69
channelCount = afGetVirtual
dla programistów
Listing 4.
Pętla, która dokonuje kompresji danych audio do formatu OGG
S
(&vi, 2, 44100, -1, 128000, -1);
while
(!
eos
)
{
framesRead
=
afReadFrames
(
ile
,
AF_DEFAULT_TRACK
,
buffer
,
BUFFER_FRAMES
);
frames
+=
framesRead
;
if
(
framesRead
==
0
)
{
vorbis_analysis_wrote
(&
vd
,
0
);
}
else
{
vorbis_buffer
=
vorbis_analysis_buffer
(&
vd
,
BUFFER_FRAMES
);
for
(
i
=
0
;
i
<
framesRead
;
i
++)
{
vorbis_buffer
[
0
][
i
]=((*(
buffer
+
i
*
4
+
1
)<<
8
)
|
Dla trybu VBR stosujemy inną funkcję:
S
vbr(&vi, 2, 44100, 0.5);
S
w której ostatni parametr o wartości
0.5
oznacza jakość kompresji. Oczywiście,
tak jak wcześniej wspomniałem, wielkość
bitrate'u i jakość kompresji odczytujemy
z odpowiednich kontrolek.
Warto do pliku inalnego dodać ko-
mentarz, w którym umieścimy nazwę na-
szego programu, odpowiedzialnego za
utworzenie pliku OGG:
(
0x00ff
&(
int
)*(
buffer
+
i
*
4
)))
/
32768.f
;
vorbis_buffer
[
1
][
i
]=((*(
buffer
+
i
*
4
+
3
)<<
8
)
|
S
(
0x00ff
&(
int
)*(
buffer
+
i
*
4
+
2
)))
/
32768.f
;
}
vorbis_analysis_wrote
(&
vd
,
framesRead
);
}
while
(
vorbis_analysis_blockout
(&
vd
,
&
vb
)==
1
)
{
vorbis_analysis
(&
vb
,
NULL
);
vorbis_bitrate_addblock
(&
vb
);
while
(
vorbis_bitrate_lushpacket
(&
vd
,
&
op
))
{
ogg_stream_packetin
(&
os
,
&
op
);
while
(!
eos
)
{
int
result
=
ogg_stream_pageout
(&
os
,
&
og
);
if
(
result
==
0
)
break
;
fwrite
(
og
.
header
,
1
,
og
.
header_len
,
f_out
);
fwrite
(
og
.
body
,
1
,
og
.
body_len
,
f_out
);
if
(
ogg_page_eos
(&
og
))
eos
=
1
;
}
}
}
S
"ENCODER","Mini-encoder V1.0");
Następne elementy, które musimy opro-
gramować, stanowią bezpośredni wstęp
do właściwej kompresji. Tworzymy zmien-
ne, które są odpowiedzialne za analizę
danych oraz za bufor danych:
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
Po poprawnej inicjalizacji tych zmien-
nych wykonywany jest kod przedstawio-
ny na Listing 3. Jego zadaniem jest umie-
szczenie w pliku wyjściowym
f_out
trzech nagłówków. W pierwszym (zmien-
na
header)
znajdują się podstawowe infor-
macje o parametrach kompresji. Drugi
(zmienna
header_comm
) zawiera dodatkowe
informacje tekstowe, takie jak np. komen-
tarz, a trzeci poprzedza właściwy strumień
danych poddanych kompresji.
Dane do pliku są zapisywane za po-
mocą funkcji
fwrite
, ale właściwe dane
są przygotowywane przed pętlą
while
.
W samej pętli za pomocą funkcji
ogg_
stream_lush
staramy się przepisywać dane
do zmiennej
og
. Pętla
while
, jak widać
na Listingu 3, będzie działać tak długo,
aż funkcja
ogg_stream_lush
zwróci war-
tość zero.
gtk_progress_bar_update
(
GTK_PROGRESS_BAR
(
ProgressBar
)
,
S
(
loat
)
frames
/
frameCount
);
while
(
gtk_events_pending
())
gtk_main_iteration
();
}
S
(BUFFER_FRAMES * frameSize);
padek kliknąć na któryś z przycisków,
a proces kompresji byłby aktywny.
Krok pierwszy
Przed wejściem do głównej pętli, w której
odbywa się kompresja, należy wykonać
kilka czynności wstępnych. Przede wszys-
tkim, należy zainicjalizować podstawową
zmienną o krótkiej nazwie
vi
w nastę-
pujący sposób:
vorbis_info_init(&vi);
.
Pozostałe zmienne zawsze wykorzystu-
ją zmienną
vi
, bezpośrednio bądź też
w pośredni sposób.
Drugim krokiem jest określenie para-
metrów kompresji, tzn. czy będziemy kodo-
wać plik OGG w trybie VBR, czy też w try-
bie ABR. Jeśli użytkownik wskazał ten dru-
gi tryb, to przykładowe wywołanie funkcji
narzucającej ten tryb kompresji dla bitrate'u
równego 128 kbit/s jest następujące:
Zanim ostatecznie zajmiemy się kom-
presją, to przyciski
Encode
i
OK
oraz
przycisk, którym wybieramy plik, war-
to uczynić nieaktywnymi. Zrobimy to
za pomocą trzech następujących linii
kodu:
S
(GTK_WIDGET(EndWorkBTN), FALSE);
gtk_widget_set_sensitive
S
(GTK_WIDGET(EncodeBTN), FALSE);
gtk_widget_set_sensitive
Pętla główna kompresji
Dotarliśmy do momentu, w którym jeste-
śmy gotowi do przeprowadzenia procesu
kompresji. Za to zadanie jest odpowiedzial-
na pętla z Listingu 4. W kodzie możemy
wyróżnić cztery podstawowe kroki: wczy-
tanie do zmiennej
buffer
odpowiedniej ilo-
S
(GTK_WIDGET(FileSelBTN), FALSE);
W ten sposób eliminujemy sytuację,
w której użytkownik mógłby przez przy-
70
styczeń 2006
ret = vorbis_encode_init
ret = vorbis_encode_init_
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc,
f_out=fopen(&ile_out[0],"w");
buffer = (signed char*)malloc
gtk_widget_set_sensitive
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