Rozhraní IOggFile |  |
Úvod Instalace Ogg SDK Torie k DirectSound Část 1 Část 2 Část 3 Část 4 Funkce knihovny vorbisfile.dll Odkazy Ke stažení R.Henys@seznam.cz ICQ: 159629842 |  | V této části napíšeme rozhraní IOggFile. Toto rozhraní nám bude sloužit pro přístup k metodám třídy COggFile (kterou taky napíšeme) a pomocí které budeme schopni přehrát jeden soubor ogg. Obsah: 2.1 Rozhraní 2.2 Rozhraní IOggFile 2.3 Třída COggFile ## 2.1 Rozhraní V seriálech které se v Chipu objevují již sice bylo řečeno co to rozhraní je, ale myslím že nebude na škodu se tu o tom také zmínit. Rozhraní můžeme brát jako objekt, který nám umožňuje používat jiný objekt, komunikovat s jiným objektem. No, nezní to moc jako definice, snažím se to říct tak jak to chápu :) Třeba rozhraním pro komunikaci s počítačem může být klávesnice. Rozhraní v C++ je třída, která má specifické vlastnosti. Taková třída neobsahuje žádné členské proměnné, ale jen metody, které navíc musejí být všechny virtuální a všechny musejí mít přístup public. Třída je tedy abstraktní. Pokud nadefinujete třídu a budete se držet těchto pravidel, bude se jednat o rozhraní. Další vlastností rozhraní je, že z něj nemůžete vytvořit instanci. V případě rozhraní IOggFile pro třídu COggFile nemůžete v programovém kódu napsat: `IOggFile* pInterface = new IOggFile` To nelze a překladač vás na to v případě že na to zapomenete upozorní :) Dalším způsobem jak v C++ (Visual C++) vytvořit rozhraní je použití klíčového slova __interface namísto class. Záměrně píši Visual C++, protože toto klíčové slovo patří do skupiny "microsoftích" příkazů a nemusí s ním pracovat ostatní překladače. Rozdíl je v tom, že pokud použijete toto klíčové slovo, jste nuceni dodržet pravidla která jsou s rozhraním svázána. Na deklarovanou členksou proměnnou budete překladačem upozorněni stejně jako na metodu s přístupem private. Též při použití tohoto klíčového slova nemusíte před deklarace metod psát slovo virtual. To je doplněno automaticky, bere se to jako samosřejmost, ať to tam napíšete nebo ne. Je jedno jak budete deklarovat rozhraní, zda použítím slova class nebo __interface. V tomto kurzu použijeme __interface, jednak proto že nám to nedovolí udělat chybu a také proto že ve Visual C++ má rozhraní jinou ikonku než třída a je hned na první pohled vidět co je co :) Pro zdárné pochopení rozhraní je nutné znát dědičnost. Probírala se v kurzu C++ od 13. dílu. Obsah ## 2.2 Rozhraní IOggFile Jak už jsem napsal v úvodu, pomocí rozhraní IOggFile budeme přistupovat k metodám třídy COggFile. Abychom se vyhnuli zbytečným úpravám které bychom museli udělat kdybychom nejdříve napsali třídu COggFile a pak rozhraní, napíšeme rozhraní nyní. Ono sice v tomto případě by se jednalo jen o malé úpravy, respektive dopisování kódu, ale někomu by v tom stejně mohl vzniknout menší zmatek. Následuje tabulka s metodami které bude rozhraní obsahovat a které bude obsahovat i třída COggFile
Metody třídy COggFile / rozhraní IOggFile
Open | Otevře soubor.
Close | Zavře soubor.
CheckIfNeedReFillBuffer | Zjistí zda je třeba zapsat nová data do streamovaného bufferu a v případě potřeby je zapíše.
Play | Spustí přehrávání.
Stop | Zastaví přehrávání.
Pause | Pozastaví přehrávání.
GetOggFilePlayingTime | Vrátí délku souboru v milisekundách.
GetOggFilePCMSize | Vrátí počet bajtů které by byly zapotřebí k dekódování celého souboru.
GetOggFileVersion | Vrátí verzi ogg souboru.
GetOggFileVendor | Vrátí jméno identifikující verzi kodeku ogg kterým byl soubor vytvořen.
GetCountOfOggFileComments | Vrátí počet komentářů v ogg souboru.
GetOggFileComment | Vrátí komentář z ogg souboru.
GetStreamedBuffer | Vrátí ukazatel na zvukový buffer (členská proměnná typu ukazatel na CSoundBuffer).
GetRepeats | Vrátí počet opakování přehrávání souboru.
SetRepeats | Nastaví počet opakování přehrávání ogg souboru.
GetStatus | Získání stavu v kterém se zrovna nachází třída COggFile.
ConvertOggToWav | Dekódování ogg souboru na klasický wav.
AddRef | Inicialiazace nové instance třídy.
Release | Uvolnění instance třídy z paměti.
Nyní se podíváme na soubor Interfaces.h, v kterém je rozhraní definováno. Přidejte do projektu OggCodec nový hlavičkový soubor a pojmenujte jej stejně. Postarejte se aby soubor nabyl následujícího obsahu. `#pragma once class CSoundBuffer; __interface IOggFile { public: HRESULT Open(const char* cFileName,LPDIRECTSOUND8 lpDSound,DWORD dwNotifyCount,WORD wBufferLength = 3) = 0; HRESULT Close() = 0; HRESULT CheckIfNeedReFillBuffer() = 0; HRESULT Play(DWORD dwPriority = 0) = 0; HRESULT Stop() = 0; HRESULT Pause() = 0; //ruzne vlastnosti ogg souboru double GetOggFilePlayingTime() = 0; DWORD GetOggFilePCMSize() = 0; int GetOggFileVersion() = 0; char* GetOggFileVendor() = 0; int GetCountOfOggFileComments() = 0; char* GetOggFileComment(int nComment) = 0; CSoundBuffer* GetStreamedBuffer() = 0; int GetRepeats() = 0; bool SetRepeats(int iRepeatCount) = 0; BYTE GetStatus() = 0; HRESULT ConvertOggToWav(const char* cOggFileName,const char* cWavFileName) = 0; HRESULT AddRef() = 0; HRESULT Release() = 0; };` Do souboru budeme ještě přidávat jedno rozhraní v třetí části kurzu, zatím nám ale stačí toto. Myslím že to je vše co jsem měl na srdci ohledně rozhraní, jdeme na třídu COggFile. Obsah ## 2.3 Třída COggFile Nemusíme si už uvádět tabulku metod, byla by stejná jako pro rozhraní IOggFile, ukážeme si rovnou soubor OggFile.h, kde uvidíte i vazbu mezi rozhraním a třídou. `#pragma once int const IWAVEHEADERSIZE = 38; int const IWAVEDATASIZE = 8; int const IQUIET = 0; int const IBITS = 16; int const IENDBITS = 0; int const ISIGNED = 1; //nejake konstanty pro prehravani, stavy souboru #define OGGPLAY_REPEATINFINITE -1 //opakovat prehravani do nekoncecna #define OGGSTATUS_PLAYING 1 //ogg soubor je prave prehravan #define OGGSTATUS_STOPPED 2 //prehravani je zastaveno #define OGGSTATUS_PAUSED 3 //prehravani je pozastaveno //****************************************************************************************** //Trida pro praci se soubory ogg //****************************************************************************************** class COggFile : public IOggFile { private: CSoundBuffer* m_StreamedBuffer; //zvukovy buffer pro ulozeni zvukovych dat OggVorbis_File m_OggVorbisFile; //struktura ogg vorbis file FILE* m_OggFilePointer; //ukazatel na otevreny ogg soubor DWORD m_dwPCMSize; //velikost dekodovaneho ogg souboru v bajtech //promenne pro funkcnost streamovaneho bufferu DWORD m_dwLastPlayPos; //posledni pozice hraciho kurzoru v bufferu DWORD m_dwNextWriteOffset; //dalsi offset v bufferu odkud se bude zapisovat DWORD m_dwNotifySize; //po jak velkych usecich se ma zapisovat DWORD m_dwPlayProgress; //pocet prehranych bajtu souboru DWORD m_dwBufferSize; //celkova velikost bufferuv bajtech int m_iRepeatCount; //pocet opakovani BYTE m_bStatus; //stav tridy, prehravani, zastaveno, pozastaveno DWORD m_dwRef; //fce pro vytvoreni bufferu a nahrani dat HRESULT InitBufferAndLoadFirstData(LPDIRECTSOUND8 lpDSound,DWORD dwNotifyCount,WORD wBufferLength); //fce pro nahravani dat o velikosti dwBytesRead do bufferu na offset dwOffset bool WriteDataToBuffer(DWORD dwOffset,DWORD dwBytes,DWORD* dwBytesRead); public: virtual HRESULT Open(const char* FileName,LPDIRECTSOUND8 lpDSound,DWORD dwNotifyCount,WORD wBufferLength = 3); //otevre ogg z disku virtual HRESULT Close(); //zavreni ogg souboru virtual HRESULT CheckIfNeedReFillBuffer(); //fce testuje jestli je cas naplnit cast bufferu novymi daty a pokud ano tak tak ucini virtual HRESULT Play(DWORD dwPriority = 0); //prehravani ogg souboru s nastavenim priority virtual HRESULT Stop(); //zastaveni prehravani, ukazatel v souboru bude presunut na zacatek a pri spusteni se bude hrat od zacatku virtual HRESULT Pause(); //pozastaveni prehravani //ruzne vlastnosti ogg souboru virtual double GetOggFilePlayingTime() {return ov_time_total(&m_OggVorbisFile,-1);} //celkovy cas souboru virtual DWORD GetOggFilePCMSize() {return m_dwPCMSize;} //vrati velikost ogg souboru v bytech virtual int GetOggFileVersion() {return m_OggVorbisFile.vi->version;} virtual char* GetOggFileVendor() {return m_OggVorbisFile.vc->vendor;} virtual int GetCountOfOggFileComments() {return m_OggVorbisFile.vc->comments;} virtual char* GetOggFileComment(int nComment); virtual CSoundBuffer* GetStreamedBuffer() {return m_StreamedBuffer;} virtual int GetRepeats() {return m_iRepeatCount;} virtual bool SetRepeats(int iRepeatCount); virtual BYTE GetStatus() {return m_bStatus;} virtual HRESULT ConvertOggToWav(const char* cOggFileName,const char* cWavFileName); virtual HRESULT AddRef(); virtual HRESULT Release(); COggFile(void); ~COggFile(void); };` Jak je vidět třída je potomkem rozhraní IOggFile. V tom je celý fígl rozhraní. Třída COggFile může jako potomek zastoupit svého rodiče. Budeme tedy používat ukazatele typu IOggFile, které budou ukazovat na stejnou nebo různé instance třídy COggFile a přes rozhraní volat metody třídy COggFile. V třetí části ještě napíšeme funkci která bude vracet ukazatele na rozhraní. Jak si můžete všimnout je definováno několk konstant OGGSTATUS_..., které snad sami jistě říkají jaký stav třídy zastupují. Můžete vidět také několik členských proměnných, kterým se budu blíže věnovat až u funkcí kde budou použity. Můžeme poppjít. Konstruktor. Destruktor a metody související s vytvořením a uvolněním instance. `//******************************************************************************************************* //Konstruktor //******************************************************************************************************* COggFile::COggFile(void) { m_dwLastPlayPos = 0; //nulovani posledni pozice hraciho kurzoru m_dwPlayProgress = 0; //nulovani prubehu prehravani m_dwNextWriteOffset = 0; //nulovani pozice zapisovaciho kurzoru m_iRepeatCount = 0; //nastaveni poctu opakovani na nula, zvuk se prehraje jen jednou m_bStatus = OGGSTATUS_STOPPED; //stav nastavime na zastaveny m_StreamedBuffer = NULL; } //******************************************************************************************************* //Destruktor //******************************************************************************************************* COggFile::~COggFile(void) { Close(); //zavreni ogg souboru a uvolneni zdroju ktere k jeho otevreni a prehrani byly zabrany } //******************************************************************************************************* //Fce zvýší počet instancí na rozhraní IOggFile //******************************************************************************************************* HRESULT COggFile::AddRef() { return m_dwRef++; } //******************************************************************************************************* //Fce uvolní rozhraní //******************************************************************************************************* HRESULT COggFile::Release() { if (m_dwRef > 0) m_dwRef--; if (m_dwRef == 0) { delete this; return 0; } else return m_dwRef; }` Konstruktor a destruktor snad není nutné nějak popisovat. Dělají jen to co se od nich očekává. Inicializace a úklid. Metoda AddRef je také velmi jednoduchá, zvýší počet instancí rozhraní které ukazují na třídu COggFile (tak to budeme později používat, samo o sobě to jen inkrementuje hodnotu členské proměnné m_dwRef (References, odkazy)) a vrátí počet instancí. Pokud vytvoříme jednu třídu COggFile, můžeme na ní vytvořit rozhraní pomocí funkce kterou napíšeme ve třetí části. Pokud čtete seriál o DirectX jistě jste se s tím setkali, rozhraní jsem se naučil právě z nich :) Vždy předáte funkci jen proměnnou typu ukazatel na ukazatel na IOggFile do které chcete ukazatel uložit. Po uložení ukazatele se zavolá automaticky tato metoda, aby třída věděla že na ní existuje nový odkaz. Když nebudete v programu funkce třídy více potřebovat, zavoláte metodu Release přes rozhraní. Ta sníží počet instancí, odkazů na třídu o jeden který právě rušíte. Pokud zrušíte všechny odkazy, bude z paměti smazána samotná třída (delete this). Stejně vlastně pracuje i automatická správa paměti ve Visual Studiu NET, tzv. Garbage Collection. Také pokud zrušíte všechny ukazatele na objekt, usoudí se že ho už nepoužíváte a je automaticky uvolněn. Náš projekt ale Garbage Collection nepoužívá a proto musíme úklid zajistit sami. Při používání rozhraní je proto nutné nezapomínat volat metodu Release a to přesně tolikrát kolik jsme vytvořili odkazů, rozhraní, jiank nám i po skončení projektu zůstane třída viset v paměti. To se ale dá také programově ohlídat :) Nyní už se můžeme pustit do metody Open `//******************************************************************************************************* //OggOpen: Fce pro otevreni ogg soubor //******************************************************************************************************* HRESULT COggFile::Open(const char *FileName,LPDIRECTSOUND8 lpDSound,DWORD dwNotifyCount,WORD wBufferLength) { HRESULT hRet; if ((m_OggFilePointer = fopen(FileName,"rb")) == NULL) //otevreni zvukoveho souboru { return -1; //vraceni chyby } //volani modifikovane fce pro otevreni ogg souboru, inicializaci dekoderu ogg //posledni parametr je velikost souboru, -1 znamena ze fce si velikost souboru, respektive jeho konec zjisti tim ze se presune na jeho konec if ((hRet = ov_open(m_OggFilePointer,&m_OggVorbisFile,NULL,0,-1)) < 0) { return -2; } //vypocet velikosti dekodovaneho ogg souboru v bajtech m_dwPCMSize = (DWORD)ceil(m_OggVorbisFile.vi->channels * m_OggVorbisFile.vi->rate * ov_time_total(&m_OggVorbisFile, -1) * IBITS / 8); //vypiseme do logu zakladni informace o ogg soubru ktery nacitame //ErrorMsg.Format("Opening Ogg Vorbis File: <b>%s</b>, Channels: <b>%d</b>, Rate: <b>%ldHz</b>, Samples: <b>%I64d</b>, Serial Number: <b>%ld</b>, Bitrate: <b>%ld</b>bits",cFileName->GetBuffer(),m_OggVorbisFile.vi->channels,m_OggVorbisFile.vi->rate,ov_pcm_total(&m_OggVorbisFile,-1),ov_serialnumber(&m_OggVorbisFile,-1),ov_bitrate(&m_OggVorbisFile,-1)); //TRACE(ErrorMsg.GetBuffer()); //vytvoreni zvukoveho bufferu a nahrani prvnich dat hRet = InitBufferAndLoadFirstData(lpDSound,dwNotifyCount,wBufferLength); //vraceni vysledku otevirani return hRet; }` Metoda očekává několik parametrů. První je jméno souboru, druhý je ukazatel na rozhraní IDirectSound. Další parametr udává kolikrát chceme do bufferu nahrát data při jednom jeho přehrání (dwNotifyCount) a poslední je velikost bufferu v bajtech (dwBufferLength). Pomocí těchto hodnot později vypočítáme po jak velkých blocích budeme data v bufferu obnovovat. Funkce nejprve otevře soubor pomocí standartní io funkce fopen a uloží si ukazatel na otevřený soubor. Tento ukazatel pak předá funkci ov_open, která neobsahuje parametr jako jméno souboru, sama si tedy soubor neotevře. Má to i svoje plus, můžete mít ogg soubor zapsaný v nějakým velkém souboru, něco na způsob archivu zip (ale nesmí být nijak komprimovaný ani zašifrovaný), otevřít ho a přesunout se na pozici, kde ogg soubor začíná. Ukazatel pak předáte zmíněné funkci ov_open, která ukazatel nepřesune na začátek souboru, ale začne číst data z aktuální pozice v souboru. Bohužel funkce ov_open neobsahuje žádný parametr jako počet bajtů souboru, což v reálu znemožňuje přehrávat soubor přímo z archivu. Funkce ov_open nejdřív načte potřebná data ze začátku souboru a pak se přesune na konec a začne číst odzadu. Tak zjistí jak je soubor dlouhý a spočte jeho délku. Když budeme mít ogg soubor v archivu, je velmi pravděpodobné že data na konci archivu už nebudou patřit otevíranému ogg souboru a funkce ov_open tak selže. Jediný případ kdy to bude fungovat je tedy případ kdy bude ogg soubor posledním souborem v archivu. Jelikož jsou ale zdrojové kódy knihoven kodeku ogg dostupné volně ke stažení, není ani toto nepřekonatelný problém. Dalším parametrem který funkce ov_open žádá je struktura OggVorbis_File, která obsahuje další struktury a proměnné potřebné jednak pro dekódování souboru, jednak jsou v těchto strukturách všechny důležité informace o souboru. Všechny další funkce z knihovny vorbisfile.dll vyžadují tuto strukturu jako parametr. Pokud funkce ov_open neselže, bude struktura inicializována a bude možno ji používat při volání dalších funkcí. Není důležité znát členské proměnné struktury OggVorbis_File, můžete ji používat stejně jako strukturu FILE při práci s "obyčejnými" soubory, ale přesto se tu o některých proměnných zmíním: vorbis_info vi; - dílčí struktura obsahující informace jako verze kodéru použitého pro vytvoření souboru, počet kanálů, počet vzorků, minimální, průměrný a maximální bitrate (počet bitů použitých k uchování jedné sekundy záznamu). vorbis_comment vc; - dílčí struktura obsahující pole komentářů z ogg souboru a související proměnné. ov_callbacks callbacks; - struktura uchovávající ukazatele na funkce pro práci se soubory (čtení, psaní, přesun v souboru a zavření souboru). Standartně jsou tyto ukazatele nastaveny na funkce fread, fwrite, fseek a fclose. Je možné je ale nastavit na jiné, uživatelem definované funkce. Pokud budete chtít použít jiné funkce než zmíněné standartní, použijte k otevření souboru funkci ov_open_callbacks, jejíž poslední parametr je ukazatel na zmíněnou strukturu ov_callbacks. Poslední dva parametry funkce ov_open se používají v případě že již byla nějaká data ze zdrojového souboru načtena. Většinou jsou ale NULL a 0 a my nebudeme v tomto kurzu dělat žádné vyjímky :) Pokud se podaří otevřít soubor standartní funkcí fopen a následně neselže ani funkce ov_open, můžeme se pustit do dekódování. Ale nejdřív :) Nejdřív je třeba zajistit ještě několik maličkostí. m_dwPCMSize je velikost čistých audio dat dekódovaného souboru v bajtech, nepočítají se do toho hlavičky ogg souboru. Tuto proměnnou využijeme při zjišťování počtu přehraných bajtů a rozhodování o konci přehrávání. Funkce ov_time_total vrací délku souboru v sekundách. Parametry jsou struktura OggVorbis_File a číslo (ID) fyzického bitového proudu (bitstreamu). Když zadáte -1, znamená to aktuální bitstream. Způsob výpočtu se shoduje s tím co jsem uvedl v úvodu. Dále si můžete všimnout že je zakomentována část s výpisem informací o souboru do debug souboru. Je to jen proto že v projektu funkci nemám definovanou. Doporučuji vám ale si podobnou funkci napsat a průběžně si vypisovat do souboru co se v programu děje. Hlavně vypisovat chybové hlášky, pomáhá to při hledání problémů. Na konci se už volá jen funkce která vytvoří zvukový buffer pro nově otevřený soubor a naplní ho prvními daty. A tady je: `//******************************************************************************************************* //Fce pro vytvoreni zvukoveho bufferu a nacteni prvnich dat do bufferu //******************************************************************************************************* HRESULT COggFile::InitBufferAndLoadFirstData(LPDIRECTSOUND8 lpDSound,DWORD dwNotifyCount,WORD wBufferLength) { HRESULT hRet; DWORD dwBufferSize, dwNotifySize; //vypocet velikosti bufferu pro uchovani wBufferLength sekund zvuku //dalsi je vypocet velikosti bloku dat po kterych se bude buffer obnovovat, po kolika bajtech budeme do buffer //nahravat nova data //posledni je prepocet velikosti bufferu, pokud bude zadan nesikovne pocet obnoveni bufferu, muze nam dojit pri deleni k zaokrouhleni //protoze se pracuje s celymi cisly dwBufferSize = (DWORD)ceil(float(m_OggVorbisFile.vi->channels * m_OggVorbisFile.vi->rate * wBufferLength * IBITS / 8)); dwNotifySize = (DWORD)dwBufferSize / dwNotifyCount; m_dwBufferSize = dwBufferSize = dwNotifyCount * dwNotifySize; m_dwNotifySize = dwNotifySize; //ulozeni poctu obnoveni bufferu za jedno jeho prehrani WAVEFORMATEX wfx; //struktura pro definovani formatu zvuku ZeroMemory(&wfx,sizeof(wfx)); //nulovani pameti wfx.cbSize = sizeof(wfx); //dulezite, nataveni velikosti struktury wfx.wFormatTag = WAVE_FORMAT_PCM; //format dat - klasicke nekomprimovane data, PCM jako u wave wfx.nChannels = m_OggVorbisFile.vi->channels; //pocet kanalu wfx.nSamplesPerSec = m_OggVorbisFile.vi->rate; //pocet vzorku za sekundu wfx.nAvgBytesPerSec = m_OggVorbisFile.vi->channels * m_OggVorbisFile.vi->rate * IBITS / 8; wfx.nBlockAlign = m_OggVorbisFile.vi->channels * IBITS / 8; wfx.wBitsPerSample = IBITS; //bitu na vzorek DSBUFFERDESC dsBufferDesc; //struktura obsahujici popis zvukove bufferu - Buffer Description ZeroMemory(&dsBufferDesc,sizeof(dsBufferDesc)); dsBufferDesc.dwSize = sizeof(DSBUFFERDESC); dsBufferDesc.dwBufferBytes = dwBufferSize; //velikost zvukoveho bufferu //v priznacich nastavime co vsechno chceme aby buffer umoznoval, umel //DSBCAPS_CTRLPAN - chci mit moznost ridit vyvazeni leveho a praveho kanalu //DSBCAPS_CTRLVOLUME - chci mit moznost ridit hlasitost //DSBCAPS_CTRLFREQUENCY - chci mit moznst zmenit frekvenci, kmitocet //DSBCAPS_GETCURENTPOSITION2 - chci mit moznost ptat se na pozici hraciho kurzoru, tato volba je nezbytne nutna pro //implementaci streamovaneho bufferu, bez tech predchozich se obejdeme dsBufferDesc.dwFlags = DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GETCURRENTPOSITION2; dsBufferDesc.lpwfxFormat = &wfx; //format audio dat, viz struktura inicilizovana vyse m_StreamedBuffer = new CSoundBuffer; //vytvoreni noveho objektu zvukoveho bufferu if (!m_StreamedBuffer) //kontrolaukazatele aneb byl objekt vytvoren { //TRACE("Error: Cannot create Sound Buffer!"); return -3; } //zavolani metody create tridy CSoundBuffer pro vytvoreni bufferu, vsechno co chce je vyse inicilizovana struktura //uchovavajici popis bufferu if(FAILED(hRet = m_StreamedBuffer->Create(lpDSound,&dsBufferDesc))) return hRet; //ted naplneni bufferu daty z ogg souboru //nejdriv se radsi presuneme na zacatek souboru, pro jistotu hRet = ov_raw_seek(&m_OggVorbisFile,1); //zapsani dat do bufferu WriteDataToBuffer(0,m_dwBufferSize,NULL); return S_OK; }` Na první pohled možná mnoho kódu, ale přesto není zas tak moc složitý. Myslím že první řádky jsou dostatečně okomentovány v kódu. Dále je inicializována strukura WAVEFORMATEX, která popisuje formát dat wave. Zvuk nemůžeme přehrát komprimovaný, musíme ho vždy dekomprimovat a přehrát v dekomprimované formě. Nejedná se vlastně o nic jiného než přiřazování příslušejících si hodnot struktury WAVEFORMATEX a OggVorbis_File->vi. Samotná tato struktura nám ale nestačí. K vytvoření zvukového bufferu potřebujeme víc informací, jako jeho velikost a nějaké požadavky, co všechno by měl umět. K tomu slouží struktura DSBBUFFERDESC, která je součástí SDK DirectX. V komentáři jsou vysvětleny příznaky které nastavujeme. Pokud chcete znát všechny možnosti, zabrouzdejte do dokumentace k DirectSound kde najdete kapitolu o zvukových bufferech. Jde například o možnosti rozšíření na 3D buffer, kontrola pozice zdroje zvuku v prostoru a přehrávání 3D zvuku. Samozřejmě ne vše jde zkombinovat se vším a ne vše musí kdejaké železo podporovat. Následuje vytvoření zvukového bufferu a přesunutí se na začátek ogg souboru pomocí funkce ov_raw_seek. Jak je vidět, opět žádá strukturu OggVorbis_File. Druhý parametr je offset v komprimovaných bajtech (komprimovanými bajty se myslí že zadáváte offset v ogg souboru, pohybujete se tedy ještě v komprimovaných datech, to je něco jiného než zadat offset v dekódovaném souboru). Další funkce pro změnu pozice v souboru jsou ov_pcm_seek a ov_time_seek. Obě mají také dva parametry, u první funkce se offset zadává jako číslo vzorku, v druhém případě je to čas. Poslední je volání funkce která dekóduje ogg soubor a dekódovaná data zapíše do zvukového bufferu: `//*************************************************************************************************************** //Fce pro zapsani dat do zvukoveho bufferu //*************************************************************************************************************** bool COggFile::WriteDataToBuffer(DWORD dwOffset,DWORD dwBytes,DWORD* dwBytesRead) { LPVOID lpvPtr1 = NULL; //ukazatel na prvni cast pameti pro zapis do bufferu DWORD DataSize1 = 0; //velikost dat ktera se zapisi do prvni casti LPVOID lpvPtr2 = NULL; //ukazatel na druhou cast pameti pro zapis do bufferu DWORD DataSize2 = 0; //velikost dat ktera se zapisi do druhe casti long lBytesRead = 0; //pocet prectenzch bajtu souboru DWORD lSize = 0; int iCurrStream = 0; HRESULT hRet; //nejdriv dekodujeme potrebnou cast ogg souboru char* DataBuffer = new char[dwBytes]; while ((lBytesRead = ov_read(&m_OggVorbisFile,DataBuffer + lSize,dwBytes - lSize,IENDBITS,IBITS / 8,ISIGNED,&iCurrStream)) != 0) { if (lBytesRead < 0 && !IQUIET) { if ((lBytesRead != OV_HOLE) && (lBytesRead != OV_EBADLINK)) break; continue; } lSize += lBytesRead; } if (dwBytesRead) *dwBytesRead = dwBytes - lSize; //ted zapsani dat do sound bufferu hRet = m_StreamedBuffer->GetBuffer()->Lock(dwOffset,lSize,&lpvPtr1,&DataSize1,&lpvPtr2,&DataSize2,0L); //if (DataSize1 != dwBytes) // WriteToDebugFile(__FILE__,__LINE__,"Cannot copy all data!",true); if (hRet == DSERR_BUFFERLOST) { m_StreamedBuffer->RestoreBuffer(NULL); hRet = m_StreamedBuffer->GetBuffer()->Lock(dwOffset,dwBytes,&lpvPtr1,&DataSize1,&lpvPtr2,&DataSize2,0L); } if (SUCCEEDED(hRet)) { CopyMemory(lpvPtr1,DataBuffer,DataSize1); if (lpvPtr2 != NULL) { CopyMemory(lpvPtr2,DataBuffer + DataSize1,DataSize2); } hRet = m_StreamedBuffer->GetBuffer()->Unlock(lpvPtr1,DataSize1,lpvPtr2,DataSize2); if (SUCCEEDED(hRet)) { SAFE_DELETE_ARRAY(DataBuffer); return true; } } SAFE_DELETE_ARRAY(DataBuffer); return false; }` Další ze stěžejních funkcí třídy. Pro její pochopení je nutné pochopit způsob práce streamovaného bufferu, který je popisován v úvodu. Její úkol je docela jednoduchý, a to zapsat do zvukového bufferu dwBytes bajtů dekódovaných zvukových dat ze souboru ogg a zápis začít na pozici dwOffset počítané od nuly (začátek bufferu, první bajt bufferu). Nelekejte se množství proměnných hned na začátku funkce :) První co ve funkci uděláme je alokace pole bajtů do kterého budeme ukládat dekódovaná zvuková data. Následuje cyklus v kterém si určitě všimnete funkce ov_read. Funkce je definována v SDK Ogg a slouží právě k dekomprimaci/dekódování ogg souboru do formátu raw wav (čístý wav, žádné hlavičky a doplňující informace ke zvuku, prostě jen hudební vzorky které pokud se dostanou až do reproduktoru, měly by připomínat něco jako hudbu, řeč atd.). Funkce má trochu víc parametrů než ty se kterými jsme se doposud setkali. První je jako vždy struktura OggVorbis_File. Druhý je ukazatel na buffer typu char do kterého chceme uložit dekódovaná data. Tj. `DataBuffer + lSize`. Při následujícím vysvětlení funkce některých proměnných se budu zmiňovat o dekódovaných bajtech, tím mám na mysli velikost audio dat, které funkce ov_read dekódovala, vrátila. Může přečíst z ogg souboru n bajtů, ale dekódovaných bajtů bude m (m > n). Dekódoavné bajty tedy znamenají m. Doufám že to chápete, těžko se to vysvětluje :( DataBuffer je ukazatel a k němu přičteme hodnotu lSize, která říká kolik bajtů už jsme dekódovali. Funkce ov_read neumí vrátit dwBytes bajtů, typicky dekóduje 4096 bajtů. Počet bajtů které funkce dekódovala a uložila do bufferu (začátek je na DataBuffer + lSize) je též návratová hodnota funkce pokud nedojde k nějaké chybě. Proto si tuto hodnotu ukládáme do proměnné lBytesRead a tuto hodnotu na konci cyklu přičítáme k proměnné lSize, čímž se posunujeme v bufferu DataBuffer stále dopředu a nepřepisujeme si data dekódovaná dříve. Třetí parametr funkce ov_read je naopak počet bajtů k dekódování: `dwBytes - lSize`. Tj zadáváme počet bajtů který byl zadán funkci mínus to co už máme dekódované v bufferu. Další parametry se týkají výstupního formátu audio vzorků. Třetí parametr udávává formát bajtů, big nebo little endian (jedná se o způsob uložení bajtů v paměti). Typická hodnota je 0, tj. little endian. To znamená že bajt s největší váhou je vlevo a bajt s nejmnenší váhou je vpravo (Bajty jsou číslovány z prava do leva od nuly). U big endian je to obráceně, ostatně jak také vyplývá z názvu. Tento parametr má význam pokud budete chtít programovat přehrávač ogg i pod jinou platformu než pro procesory INTEL, která používá big endian. Pátý parametr je velikost slova, přesně řečeno udává kolika bitové chceme vzorky, 1 pro 8bitový zvuk, 2 pro 16bitový. Šestý parametr říká jestli mají být vzorky bez znaménka nebo se znaménkem (zda jsou `signed` nebo `unsigned`). Poslední je ukazatel na číslo aktuálního bitstreamu. Jak je vidět, použil jsem 0. Tenhle parametr bude mít zase význam při dekódování prokládaného ogg souboru, címž se tady nebudeme zabývat. Jak jsem říkal již dříve, funkce vrací počet bajtů dekódovaných audio dat. Pokud je návratová hodnota rovna 0, znamená to EOF, konec souboru. Hodnoty menší než nula jsou chyby, na které se podívejte do dokumentace. To by bylo dekóvání části ogg souboru která zabere v paměti dwBytes bajtů. Do proměnné na kterou ukazuje dwBytesRead se uloží počet bajtů které byly skutečne dekódovány. Nemusí se vždy podařit dekódovat dwBytes bajtů které chceme. Význam této proměnné pochopíte v další funkci. Teď ještě překopírovat obsah bufferu DataBuffer do zvukového bufferu. Abychom to provedli co nejrychleji, poskytuje zvukový bufferu metodu Lock, která nám zpřístupní paměť zvukového bufferu pomocí jednoho nebo dvou ukazatelů. Budeme mít přímý přístup do paměti bufferu. První její paremtr je offset na kterém chceme do paměti zvukového bufferu začít zapisovat (dwOffset), druhý je počet bajtů kolik chceme zapsat (dwBytes). Zbylé parametry jsou výstupní. Postupně se nám do lpvPtr1 uloží ukazatel na první část paměti zvukového bufferu kam můžeme zapisovat, do DataSize1 počet bajtů které můžeme zapsat do paměti na kterou ukazuje lpvPtr1. Poslední dva parametry mají význam úplně stejný, jen je to ukazatel na druhou část paměti a druhý počet bajtů na zapsání. K čemu dva ukazatele a dvě proměnné s počtem bajtů které můžeme do paměti zapsat? Již dříve jsem se zmiňoval že se můžeme nacházet u konce zvukového bufferu a že tam nebude dost místa pro zapsání dwBytes bytů. Nemůžeme zapat data i dál, to bychom přepsali pamět která nám nepatří. Naštěstí funkce Lock počítá s tímto problémem a proto když nemůže zajistit zapsání dwBytes bytů na adrese lpvPtr1, zajistí zapsání m bytů na adrese lpvPtr1 a n bytů na adrese lpvPtr2 (n+m = dwBytes). Jelikož máme zajištěno že tato funkce se bude volat vždy jen když bude vzdálenost mezi zapisovacím kurzorem a hracím kurzorem větší než dwBytes, může nastat popisovaná situace jen u konce bufferu. V ostatních případech bude vždy lpvPtr2 = NULL a DataSize2 = 0, tedy vše se zapíše na lpvPtr1. Pokud situace nastane, zapíšeme data do konce bufferu, lpvPtr2 bude ukazovat na zčátek bufferu a to co se nevešlo na konec zapíšeme zase na začátek. Pokud jste četli úvod, neměli by jte mít nejasnosti ohledně, proč to? Přesun obsahu bufferu DataBuffer do paměti zvukového bufferu pomocí funkce CopyMemory a ošetření NULL ukazatele lpvPtr2 snad už není třeba podrobně vysvětlovat. Ano, máte pravdu, proces by se dal ještě urychlit zápisem dat přímo do paměti zvukového bufferu. Ušetřili bychom si paměťové přesuny které jsou dost časově náročné. Můžete to zkusit. Myslím že pro lepší pochopení dekódování a zápisu dat do zvukového bufferu vůbec, je tento způsob lepší. V praxi bych radši viděl zápis přímo do zvukového bufferu :) `//********************************************************************************************************** //Fce pro kontrolu nutnosti zapisu novych dat do bufferu //********************************************************************************************************** HRESULT COggFile::CheckIfNeedReFillBuffer() { HRESULT hRet = S_FALSE; bool bRet = false; DWORD dwCurrPlayPos = 0; DWORD dwDataDelta = 0; DWORD dwCursorsDistance = 0; if (m_StreamedBuffer->IsSoundPlaying() || m_bStatus != OGGSTATUS_PAUSED) { //ziskani aktualni pozice hraciho kurzoru if(FAILED(hRet = m_StreamedBuffer->GetBuffer()->GetCurrentPosition(&dwCurrPlayPos,NULL))) return hRet; if ((m_dwPlayProgress + dwCurrPlayPos) >= m_dwPCMSize) //tady to se zpracovava pokud dojde k prehrani celeho souboru { hRet = m_StreamedBuffer->Stop(); //pokud dojde k prehrani celeho souboru, zastavime prehravani bufferu hRet = m_StreamedBuffer->Reset(); //a resetujeme buffer a prislusne promenne m_dwNextWriteOffset = 0; //zapis bude zacinat opet na nule m_dwPlayProgress = 0; //stejne tak pocet prehranzch bytu je nula m_dwLastPlayPos = 0; //posledni pozice hraciho kurzoru taktez dwCursorsDistance = 0; //vzdalenost hraciho a zapisovaciho kurzoru taktez nulova ov_raw_seek(&m_OggVorbisFile,0); //presun na zacatek v ogg souboru WriteDataToBuffer(0,m_dwBufferSize,NULL); //naplneni bufferu daty z ogg souboru od zacatku, kdyby byla pozdeji spustena fce play //tak at to tam je if (!m_iRepeatCount) //pokud je pocet opakovani 0 { //nastavime stav zastaveno m_bStatus = OGGSTATUS_STOPPED; return S_FALSE; //vratime false jako indikaci ze nedoslo k obnoveni dat v bufferu } if (m_iRepeatCount != OGGPLAY_REPEATINFINITE) //pokud pocet opakovani neni nula a neni ani roven konstante pro m_iRepeatCount--; //nekonecno opakovani, snizime pocet opakovani hRet = m_StreamedBuffer->Play(0,DSBPLAY_LOOPING); //zacneme znovu prehravat buffer, pac se este neprehral tolikrat jak je nastaveno return hRet; } //pokud je posledni ulozena pozice hraciho kurzoru vetsi nez pozice kurzoru aktualni, znamena to ze //buffer dosahl konce a preskocil zpet na zacatek, podle toho musime pocitat vzdalenost kurzoru rozdilne if (m_dwLastPlayPos > dwCurrPlayPos) //hraci kurzor presel zpet na zacatek, musime vypocitat kolik bytu zbylo na konci bufferu neaktualizovanzch od posledni aktualizace //a pricist k nim pocet bytu co uz byly prehrany znova od zacatku a tak ziskame vzdalenost kurzoru //vzdalenost mezi hracim a zapisovacim kurzorem dwCursorsDistance = (m_dwBufferSize - m_dwNextWriteOffset) + dwCurrPlayPos; else //hraci kurzor je pred koncem, zapisovaci za nim, vzdalenost je rozdil jejich pozic //abychom nepocitali i bajt na kterem hraci kurzor zrovna je dwCursorsDistance = dwCurrPlayPos - m_dwNextWriteOffset; //ted se mrknem jestli vzdalenost kurzoru presahla pocet bajtu po kterych se ma buffer obnovovat if (dwCursorsDistance >= m_dwNotifySize) //a pokud ano, zapiseme do bufferu tolik bytu jako je vzdalenost kurzoru { //cili data co tam zapiseme budou koncit na poslednim bajtu pred pozici bRet = WriteDataToBuffer(m_dwNextWriteOffset,dwCursorsDistance - 1,&dwDataDelta); //hraciho kurzoru, tedy mela by, muze se stat //vzdalenost mezi kurzory bude licha a v tom pripade nam fce ov_read ktera dekoduje ogg soubor m_dwNextWriteOffset += dwCursorsDistance - 1 - dwDataDelta; //vrati o nekolik bytu min, dekoduje totiz vzdy sudy pocet a ten pocet bajtu //musime snizit pozici dalsiho zapisovani, aby nevznikla jakasi dira, viz dwDataDelta if (m_dwNextWriteOffset >= m_dwBufferSize) //tady se hlida situace kdy je psaci kurzor skoro na konci a jeho aktualizaci o pocet zapanzch bajtu by doslo k prekroceni bufferu m_dwNextWriteOffset -= m_dwBufferSize; //odecteme proto velikost bufferu a psaci kurzor se dostane na spravnou pozici od zacatku bufferu if (m_dwLastPlayPos > dwCurrPlayPos) m_dwPlayProgress += m_dwBufferSize; m_dwLastPlayPos = dwCurrPlayPos; //aktualizace posledni pozice hraciho kurzoru } } return hRet; }` Funkce se stará o hlídání aktuálnosti dat v bufferu. Hlídá vzdálenost mezi zapisovacím a hracím kurzorem a v případě že překročí m_dwNotifySize tak do bufferu nahraje nová data. Funkce je myslím dostatečně okomentovaná, proto se jí nebudu tak podrobně zabývat. Úplně první rozhodovací blok hlídá jestli je přehrávání vůbec spuštěné, pokud není, nemá cenu provádět nějaké další testy. V opačném případě následuje kontrola dokončení přehrávání a počítání počtu opakování. Pokud byl zvukový soubor přehrán m_iRepeatCount krát, tak se přehrávání zastaví. Po ošetření těchto stavů následuje výpočet vzdálenosti zapisovacího a hracího kurzoru, který je různý pro dvě možné situace. Jednak může pozice hracího kurzoru být menší než je poslední známá pozice hracího kurzoru (tj. uložená pozice z minulého volání funkce), pak je vzdálenost kurzorů `dwCursorsDistance = dwCurrPlayPos - m_dwNextWriteOffset`. Ale situace může být i opačná, tj. poslední známá pozice je větší než aktuální pozice hracího kurzoru, to znamená že se hrací kurzor od posledního volání funkce vrátil zpět na začátek bufferu. Buffer byl přehrán a přehrávání jde od začátku. Výpočet vzdálenosti je `dwCursorsDistance = (m_dwBufferSize - m_dwNextWriteOffset) + dwCurrPlayPos`. Doufám že názvy proměnných mluví za sebe a že nebude problém pochopit proč je výpočet právě takový. Když je potřeba je buffer naplněn daty voláním funkce WriteDataToBuffer. Poslední parametr je dwDataDelta, do kterého se uloží počet bajtů které nebylo možno dekódovat do paměti zvukového bufferu. Jak jsem říkal u implementace funkce WriteDataToBuffer, funkce ov_read nemusí být vždy schopná dekódovat tolik bajtů kolik bychom chtěli. Funkce WriteDataToBuffer to kontroluje a vždy nám do propměnné dwDataDelta uloží počet bajtů které nebyly do zvukového bufferu zapsány. O tuto hodnotu snížíme pozici dalšího místa zápisu dat, abychom kompenzovali mezeru. Může se zdát, že taková 3bajtová mezera nemůže být vůbec znát, ale věřte mi, je to slyšet! Na konci funkce se aktualizuje průbeh prehrávani který se počítá v bajtech dekódovaného souboru. Už toho moc nezbývá, takže honem na další funkce :) `//********************************************************************************************************** //Funkce pro zavreni ogg souboru //********************************************************************************************************** HRESULT COggFile::Close() { if (m_OggFilePointer) fclose(m_OggFilePointer); ov_clear(&m_OggVorbisFile); return 0; } //********************************************************************************************************** //Funkce pro prevod ogg-wav //********************************************************************************************************** HRESULT COggFile::ConvertOggToWav(const char* cOggFileName,const char* cWavFileName) { char AudioData[4096]; //buffer pro nacitani dat, 4096 je maximalni pocet bytu kolik nacte fce ov_read pri jednom volani //zaroven takove cislo, mocnina dvou je nejvodnejsi pro prenost pres sbernici do souboru na disku OggVorbis_File OggVorbisFile; //struktura OggVorbisFiloe ktera obsahuje informace o ogg souboru FILE* FilePointer; //ukazatel na soubor FILE* OutputFilePointer; //ukazatel na vystupni soubor if ((FilePointer = fopen(cOggFileName,"rb")) == NULL) //ogg soubor musime nejdriv otvrit pres normalni fopen a pak zavolat return -1; //ov_open, ktera naplni strukturu OggVorbisFile if ((OutputFilePointer = fopen(cWavFileName,"wb")) == NULL) //otevreni vystupniho souboru return -2; if ((ov_open(FilePointer,&OggVorbisFile,NULL,0,-1) < 0)) //naplneni struktury OggVorbisFile, precteni hlavicky ogg souboru return -3; //spocitani velikosti dekodovaneho zvuku, to musiem ulozit do wav hlavicky long iDataSize = (long)ceil(OggVorbisFile.vi->channels * OggVorbisFile.vi->rate * ov_time_total(&OggVorbisFile, -1) * IBITS/8); //velikost wav souboru,audio data + hlavicka int riffSize = iDataSize + IWAVEHEADERSIZE; int ckSize = sizeof(WAVEFORMATEX); //velikost struktury WAVEFORMATEX WAVEFORMATEX WaveFormat; WaveFormat.cbSize = ckSize; //nastaveni velikosti WaveFormat.wFormatTag = WAVE_FORMAT_PCM; //format nastavime na normalni nekomprimovany wav WaveFormat.nChannels = OggVorbisFile.vi->channels; //nastaveni poctu kanalu WaveFormat.nSamplesPerSec = OggVorbisFile.vi->rate; //pocet vzorku za sekundu WaveFormat.nAvgBytesPerSec = OggVorbisFile.vi->rate * OggVorbisFile.vi->channels * IBITS / 8; //prumerne bytu za sekundu WaveFormat.nBlockAlign = OggVorbisFile.vi->channels * IBITS / 8; WaveFormat.wBitsPerSample = IBITS; //pocet bitu na vzorek //ukladani dat z hlavicky do vystupniho souboru fprintf(OutputFilePointer,"RIFF"); fwrite(&riffSize,1,sizeof(riffSize),OutputFilePointer); fprintf(OutputFilePointer,"WAVE"); fprintf(OutputFilePointer,"fmt "); fwrite(&ckSize,1,sizeof(ckSize),OutputFilePointer); fwrite(&WaveFormat,1,sizeof(WaveFormat),OutputFilePointer); fprintf(OutputFilePointer,"data"); fwrite(&iDataSize,1,sizeof(iDataSize),OutputFilePointer); long iBytesRead; //sem se bude ukladat kolik bytu bylo precteno z ogg souboru long iSize = 0; //pocet celkem nactenych bytu int iCurrSection; //aktualni cislo logickeho proudu v ogg souboru //cyklus pro nacitani a dekodovani ogg audio dat, o dekodovani se stara fce ov_read, cyklus pokracuje tak dlouho dokud fce vraci ze precetla vic jak 0 bytu while ((iBytesRead = ov_read(&OggVorbisFile, AudioData, iDataSize - iSize,IENDBITS,IBITS / 8,ISIGNED,&iCurrSection)) != 0) { if (iBytesRead < 0 && !IQUIET) continue; iSize += iBytesRead; //aktualizace poctu nactenych bytu //ulozeni dekodovanych dat z bufferu, pameti do souboru fwrite(AudioData,1,iBytesRead,OutputFilePointer); } //dekodovani je hotovo, zavreni ogg souboru fclose(FilePointer); //zavreni vystupniho wav souboru fclose(OutputFilePointer); //klizeni struktury OggVorbisFile ov_clear(&OggVorbisFile); //vraceni 0, dekodovani probehlo uspesne return 0; } //********************************************************************************************************** //Vrati komentar z ogg souboru //********************************************************************************************************** char* COggFile::GetOggFileComment(int nComment) { return m_OggVorbisFile.vc->user_comments[nComment]; } //********************************************************************************************************** //Nastavi pocet opakovani prehravani //********************************************************************************************************** bool COggFile::SetRepeats(int iRepeatCount) { if (iRepeatCount < OGGPLAY_REPEATINFINITE) return false; m_iRepeatCount = iRepeatCount; return true; } //********************************************************************************************************** //Spusti prehravani //********************************************************************************************************** HRESULT COggFile::Play(DWORD dwPriority) { m_bStatus = OGGSTATUS_PLAYING; return m_StreamedBuffer->Play(dwPriority,DSBPLAY_LOOPING); } //********************************************************************************************************** //Zastavi prehravani //********************************************************************************************************** HRESULT COggFile::Stop() { m_bStatus = OGGSTATUS_STOPPED; return m_StreamedBuffer->Stop(); } //********************************************************************************************************** //Pozastavi prehravani //********************************************************************************************************** HRESULT COggFile::Pause() { m_bStatus = OGGSTATUS_PAUSED; return m_StreamedBuffer->Stop(); }` To je zbytek souboru OggFile.cpp. Jde už jen o jednoduché funkce. Ve funkci Close je zavřen zdrojový soubor pomocí funkce fclose (je volána vnitřně metodou ov_clear). Poté je volána funkce ov_clear která uvolní paměť související se strukturou OggVorbis_File a související zdroje použíté pro dekódování. Tuto funkci je nutné volat při ukončení práce s ogg soubory, aby po nás nezůstávalo smetí. Nestačí jen aplikovat na strukturu OggVorbis_File funkci jako ClearMmeory nebo memset, to by bylo jen nulování struktury a nebyly by uvolněny zdroje které si pro dekódování a práci alokoval samotný kodek ogg, respektive knihovna vorbisfile.dll. Další funkce ConvertOggToWav je vlastně jen spojením celého dekódovacího procesu do jedné funkce se zápisem do souboru. Myslím že její funkce by měla být jasná. Zbylé funkce jen nastavují členské proměnné třídy COggFile, hlavně tedy stav na příslušné hodnoty a vracejí hodnotu voláním funkcí zvukového bufferu (CSoundBuffer). Máme za sebou třídu COggFile. Chce to zaexperimentovat s metodami, nejlépe si je také krokovat a podívat se na obsahy různých proměnných a vůbec jak to funguje v praxi :) Obsah