Správce zvukových souborů - ISoundManager |  |
Ú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 předposlední části vytvoříme správce zvukových souborů CSoundManager, který budeme používat k přehrávání hudby. Obsah: 3.1 Rozhraní ISoundManager 3.2 Třída CSoundManager ## 3.1 Rozhraní ISoundManager Stejně jako třída COggFile je navenek přístupná jako rozhraní IOggFile, tak třída CSoundManager bude přístupná jen jako rozhraní ISoundManager. Metody třídy i rozhraní jsou stejné a jsou tyto:
Metody třídy CSoundManager / rozhraní ISoundManager
LoadOgg | Otevře ogg soubor.
UnloadOgg | Zavře ogg soubor.
PlayOgg | Spustí přehrávání otevřeného ogg souboru.
StopOgg | Zastaví přehrávání otevřeného ogg souboru.
PauseOgg | Pozastaví přehrávání otevřeného ogg souboru.
StopAllOgg | Zastaví přehrávání všech otevřených ogg souborů.
PauseAllOgg | Pozastaví přehrávání všech ogg souborů.
CheckOggIfNeedReFillBuffer | Kontroluje zda otevřený ogg soubor potřebuje obnovit data ve zvukovém bufferu.
CheckAllOggIfNeedReFillBuffer | Kontroluje všechny otevřené ogg soubory zda potřebují obnovit data ve zvukovém bufferu.
SetRepeatsOfOgg | Nastaví počet opakování přehrávání otevřenéhp ogg souboru.
HandleAppActivateStateChanges | Reakce na ztrátu/získání fokusu aplikací která využívá třídu.
GetDirectSound | Vrátí ukazatel na rozhraní IDirectSound.
GetOggFile | Vrátí ukazatel na otevřený ogg soubor, pro přístup k dalším funkcím.
AddRef | Přidání instance rozhraní do paměti.
Release | Uvolnění instance rozhraní z paměti.
Do souboru Interfaces.h přidejte také definici rozhraní ISoundManager: `__interface OGGCODEC_API ISoundManager { public: HRESULT CreateDirectSoundSystem(HWND hWnd,DWORD dwCooperativeLevel = DSSCL_PRIORITY) = 0; //vytvoreni objektu DirectSound HRESULT LoadOgg(DWORD dwId, const char* cFileName) = 0; //nacteni ogg souboru HRESULT UnloadOgg(DWORD dwId) = 0; //uvolneni nacteneho ogg souboru HRESULT PlayOgg(DWORD dwId, DWORD dwPriority = 0) = 0; //spusti prehravani ogg souboru HRESULT StopOgg(DWORD dwId) = 0; //zastaveni prehravani ogg souboru HRESULT PauseOgg(DWORD dwId) = 0; //pozastaveni prehravani ogg souboru HRESULT StopAllOgg() = 0; //zastaveni prehravani vsech ogg souboru HRESULT PauseAllOgg() = 0; //pozastaveni prehravani vsech ogg souboru HRESULT CheckOggIfNeedReFillBuffer(DWORD dwId) = 0; //zjisteni zda ogg soubor potrebuje obnovit data bufferu HRESULT CheckAllOggIfNeedReFillBuffer() = 0; //zjisteni zda nektery ze vsech ogg souboru potrebuje obnovit data v bufferu bool SetRepetsOfOgg(DWORD dwId,int dwRepeats) = 0; //nastavi pocet opakovani urciteho ogg souboru void HandleAppActiveStateChanges(bool bIsAppActive) = 0; //reakce na ztratu a ziskani fokusu ridici aplikace LPDIRECTSOUND8 GetDirectSound() = 0; //vraceni ukazatele na ukazatel na zarizeni DirectSound IOggFile* GetOggFile(DWORD dwID); HRESULT AddRef() = 0; HRESULT Release() = 0; };` Obsah ## 3.2 Třída CSoundManager Třída CsoundManager nedělá nic složitého ani nijak složitá není. Kdo čte seriály o DirectX, tak to pro něj nebude nic nového. Jedná se o analogického správce zdrojů jaký byl v těchto seriálech již několikrát vidět. Jde jen o to vytvořit ždy pro nový ogg soubor nové rozhraní IOggFile, přidat ukazatel na něj do pole a poskytnout přístup k funkcím rozhraní. Dále ještě nějaké funkce pro správu otevřených ogg souborů. Vlastně jen práce s polem a volání funkcí které už máme hotové. Soubor SoundManager.h: `#pragma once struct OggData //struktura pro ulozeni dat o nactenem ogg souboru { IOggFile* oggFile; DWORD dwID; //id pomoci nehoz se bude s ogg pracovat DWORD dwPlayPriority; //priorita prehravani }; //************************************************************************************************************** //Manager zvuku, zatim predevsim ogg souboru //************************************************************************************************************** class CSoundManager: public ISoundManager { private: LPDIRECTSOUND8 m_DSound; //objekt DirectSound std::vector m_arOggFiles; //pole pro ukladani ogg souboru DWORD m_dwNotifyCount; //pocet overovacich pozic WORD m_wBufferLength; //velikost, delka bufferu v sekundach void FindPlaceForOgg(); long GetOggPositionInArray(DWORD dwID,DWORD dwLeft,DWORD dwRight); DWORD m_dwRef; public: virtual HRESULT CreateDirectSoundSystem(HWND hWnd,DWORD dwCooperativeLevel = DSSCL_PRIORITY); //vytvoreni objektu DirectSound virtual HRESULT LoadOgg(DWORD dwId,const char* cFileName); //nacteni ogg souboru virtual HRESULT UnloadOgg(DWORD dwId); //uvolneni nacteneho ogg souboru virtual HRESULT PlayOgg(DWORD dwId, DWORD dwPriority = 0); //spusti prehravani ogg souboru virtual HRESULT StopOgg(DWORD dwId); //zastaveni prehravani ogg souboru virtual HRESULT PauseOgg(DWORD dwId); //pozastaveni prehravani ogg souboru virtual HRESULT StopAllOgg(); //zastaveni prehravani vsech ogg souboru virtual HRESULT PauseAllOgg(); //pozastaveni prehravani vsech ogg souboru virtual HRESULT CheckOggIfNeedReFillBuffer(DWORD dwId); //zjisteni zda ogg soubor potrebuje obnovit data bufferu virtual HRESULT CheckAllOggIfNeedReFillBuffer(); //zjisteni zda nektery ze vsech ogg souboru potrebuje obnovit data v bufferu virtual bool SetRepetsOfOgg(DWORD dwId,int dwRepeats); //nastavi pocet opakovani urciteho ogg souboru virtual void HandleAppActiveStateChanges(bool bIsAppActive); //reakce na ztratu a ziskani fokusu ridici aplikace virtual LPDIRECTSOUND8 GetDirectSound() {return m_DSound;} //vraceni ukazatele na ukazatel na zarizeni DirectSound virtual IOggFile* GetOggFile(DWORD dwID); virtual HRESULT AddRef(); virtual HRESULT Release(); CSoundManager(void); ~CSoundManager(void); };` Definujeme strukturu OggData pro uložení dat o každém ogg souboru. Samotné třída COggFile si neukládá jméno otevřeného souboru, to není potřeba. My v manažeru ale potřebujeme každý soubor nějak odlišit, proto definujeme identifikátor větší než nula (dwID) pro každý soubor. Abychom mohli využít priority přehrávání, máme ještě proměnnou dwPlayPriority pro každý soubor. Ve třídě CSoundManager je pomocí šablony vector definováno dynamické pole ukazatelů na strukturu OggData (`std::vector m_arOggFiles;`). Další proměnné jsou ukazatel na rozhraní IDirectSound, proměnné související s rozdělením zvukového bufferu na menší části které se budou obnovovat (m_dwNotifyCount a m_dwBufferLenght). Protože všechno co je v manažeru ogg souborů bylo již někdy probráno v seriálech o C++ nebo DirectX, budu se věnovat jen nejdůležitějším věcem které je dobré si zopakovat. Například vynechám metodu FindPlaceForOgg která třídí prvky pole podle ID metodou quicksort a funkci GetOggPositionInArray která hledá položky v poli, respektive zjišťuje jestli v poli je prvek s dwID metodou půlení intervalů. Na těla funkcí se podívejte do přiloženého projektu, nebo pokud nemáte Visual C++, prostě otevřete cpp soubor v textovém editoru. Nejdříve tedy opakování zprovoznění systému DirectSound: `//************************************************************************************************************* //Zprovozneni DirectSound //************************************************************************************************************* HRESULT CSoundManager::CreateDirectSoundSystem(HWND hWnd,DWORD dwCooperativeLevel) { HRESULT hRet = DS_OK; if (FAILED(hRet = DirectSoundCreate8(NULL,&m_DSound,NULL))) return hRet; hRet = m_DSound->SetCooperativeLevel(hWnd,dwCooperativeLevel); return hRet; }` Moc toho není. Nejdříve potřebujeme rozhraní pro přístup k metodám DirectSound. To získáme voláním funkce DirectSoundCreate8. První parametr je GUID (global unique identifier) určující pro které audio zařízení chceme rozhraní vytvořit. NULL znamená výchozí zařízení. Do druhého parametru se uloží ukazatel na rozhraní pokud se ho podaří vytvořit. Poslední souvisí s technologií COM a zatím musí být vždy NULL. Druhým krokem je nastavení módu spolupráce aplikace a audio zařízení, (SetCooperativeLevel). Parametry jsou jen dva, handle (držadlo) okna s kterým má být audio zařízení spojeno a druhý je mód spolupráce, který také určuje jaké funkce budete a nebudete moci používat. Možnosti jsou tyto: DSSCL_NORMAL - Běžný mód spolupráce. Neumožňuje měnit formát primárního bufferu a výstup je omezen jen na 8bitový formát. DSSCL_PRIORITY - Prioritní režim. Aplikace s tímto režimem může nastavit formát primárního bufferu. DSSCL_EXCLUSIVE - Pro DirectX 8 a vyšší má stejný účinek jako DSSCL_PRIORITY. Nižší verzi už určitě stejně nemáte :) DSSCL_WRITEPRIMARY - Aplikace může zapisovat přímo do primárního bufferu, sekundární buffery nemohou být přehrávány. Nemůže být nastaven pokud je zařízení emulováno. Žádné jasno na obloze to asi nedělá, je to to málo co jsem vyčetl z dokumentace. Ale jen pro účely fungování tohoto příkladu stčí mód DSSCL_PRIORITY, který je také výchozí hodnotou pro parametr funkce CreateDirectSoundSystem. Až budete dělat vlastní Media Player, určitě si prostudujte celé DirectSound v dokumetaci k DirectX :) A nejen DirectSound. `//************************************************************************************************************* //Nacteni ogg souboru //************************************************************************************************************* HRESULT CSoundManager::LoadOgg(DWORD dwId,const char* cFileName) { HRESULT hRet = S_OK; //kontrola jestli uz ogg se stejnym id neni v seznamu if (GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1) >= 0) return S_FALSE; OggData* TmpPtr = new OggData; if (!TmpPtr) return E_OUTOFMEMORY; CreateOggCodecObject(OGGIID_IOggFile,(void**)&(TmpPtr->oggFile)); if (!TmpPtr->oggFile) { SAFE_DELETE(TmpPtr); return E_OUTOFMEMORY; } if(FAILED(hRet = TmpPtr->oggFile->Open(cFileName,m_DSound,m_dwNotifyCount,m_dwBufferLength))) { SAFE_DELETE(TmpPtr); return hRet; } TmpPtr->dwID = dwId; TmpPtr->dwPlayPriority = 0; m_arOggFiles.push_back(TmpPtr); FindPlaceForOgg(); //setrideni polozek return hRet; }` Prvním krokem je hledání ogg souboru s id dwId v poli otevřených ogg souborů. Pokud uspějeme, znamená to že už je otevřený soubor se stejným id jako by měl mít nově otevřený soubor. V tom případě nemůžeme pokračovat v otvírání ogg souboru, nemůžeme mít dva se stejným id. Nezpůsobilo by to přímo chybu, ale jeden ze souborů by nebyl nikdy vyhledán. Vyhledávací funkce by našla vždy jen jeden. Když soubor s id nenajdeme, pokračujeme dál. Vytvoříme novou strukturu OggData a potřebujeme taky rozhraní IOggFile pro práci s ogg souborem. Rozhraní získámne pomocí funkce CreateOggCodecObject (tu napíšeme ve čtvrté části). Pokud se to povede, zavoláme funkci pro otevření ogg souboru a pokud ani ta neselže, uložíme id souboru, prioritu přehrávání a uložíme ukazatel na novou strukturu OggData do pole (`m_arOggFiles.push_back(TmpPtr)`). Poslední krok je volání funkce pro setřídění pole. `//************************************************************************************************************* //Zavreni ogg souboru a odstraneni zaznamu z pole //************************************************************************************************************* HRESULT CSoundManager::UnloadOgg(DWORD dwId) { long i; UINT j; bool bWasFind = false; OggData* TmpPtr; if ((i = (UINT)GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1)) >= 0) bWasFind = true; if (bWasFind) { TmpPtr = (OggData*)m_arOggFiles[i]; for (j = i; j < m_arOggFiles.size() - 1; j++) { m_arOggFiles[j] = m_arOggFiles[j+1]; } SAFE_RELEASE(TmpPtr->oggFile); SAFE_DELETE(TmpPtr); m_arOggFiles.pop_back(); return 0; } return -1; }` Funkce nedělá nic jiného než že najde soubor s id dwId, jeho pozici v poli. Uloží se ukazatel na strukturu OggData. Pak všechny položky na pravo od této pozice přesune o jedno místo doleva. Tím vlastně dojde k přepsání ukazatele na hledaný soubor, což ale nevadí, protože ho máme uložený v TmpPtr. Zavoláme metodu Release rozhraní IOggFile pomocí makra SAFE_RELEASE a smažeme z paměti strukturu reprezentující soubor. Nakonec zmenšíme pole o jednu položku pomocí metody pop_back. Na posledním místě byl stejně jen ukazatel, který se přesunem o jedno místo doleva dostal i na pozici konec-1, byl tedy v poli dvakrát a nevadí, když zmenšíme pole o 1 zprava. Dál tu máme funkce PlayOgg,StopOgg, PauseOgg, CheckOggIfNeedReFillBuffer a SetRepeatsOfOgg. Všechny pracují stejně, hledají soubor s dwId v poli a pokud ho najdou, volají příslušnou funkci rozhraní IOggFile. `HRESULT CSoundManager::PlayOgg(DWORD dwId,DWORD dwPriority) { OggData* tmpPtr; long index; index = GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1); if (index >= 0) { tmpPtr = (OggData*)m_arOggFiles[index]; return tmpPtr->oggFile->Play(dwPriority); } return -1; } HRESULT CSoundManager::StopOgg(DWORD dwId) { OggData* tmpPtr; long index; index = GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1); if (index >= 0) { tmpPtr = (OggData*)m_arOggFiles[index]; return tmpPtr->oggFile->Stop(); } return -1; } HRESULT CSoundManager::PauseOgg(DWORD dwId) { OggData* tmpPtr; long index; index = GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1); if (index >= 0) { tmpPtr = (OggData*)m_arOggFiles[index]; return tmpPtr->oggFile->Pause(); } return -1; } HRESULT CSoundManager::CheckOggIfNeedReFillBuffer(DWORD dwId) { for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) if (TmpPtr->dwID == dwId) return TmpPtr->oggFile->CheckIfNeedReFillBuffer(); } return -1; } bool CSoundManager::SetRepetsOfOgg(DWORD dwId,int iRepeats) { OggData* tmpPtr; long index; index = GetOggPositionInArray(dwId,0,(DWORD)m_arOggFiles.size()-1); if (index >= 0) { tmpPtr = (OggData*)m_arOggFiles[index]; return tmpPtr->oggFile->SetRepeats(iRepeats); } return false; }` Funkce StopAllOgg, PauseAllOgg a CheckAllOggIfNeedReFillBuffer pracují analogicky, jen volají příslušné funkce rozhraní IOggFile pro všechny soubory v poli. `HRESULT CSoundManager::StopAllOgg() { HRESULT hRet; for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) hRet &= TmpPtr->oggFile->Stop(); } return hRet; } HRESULT CSoundManager::PauseAllOgg() { HRESULT hRet; for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) hRet &= TmpPtr->oggFile->Pause(); } return hRet; } HRESULT CSoundManager::CheckAllOggIfNeedReFillBuffer() { HRESULT hRet; for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) hRet &= TmpPtr->oggFile->CheckIfNeedReFillBuffer(); } return hRet; }` Funkce GetOggFile vrací ukazatel na rozhraní IOggFile pro soubor s dwId. Jistě jste si všimli že ne všechny funkce rozhraní IoggFile jsou zpřístupněny pomocí funkcí rozhraní ISoundManager. Abychom mohli používat i ten zbytek, musíme mít možnost přistoupit přímo k souboru přes rozhraní IOggFile. `IOggFile* CSoundManager::GetOggFile(DWORD dwID) { OggData* tmpPtr; long index; index = GetOggPositionInArray(dwID,0,(DWORD)m_arOggFiles.size() - 1); if (index >= 0) { tmpPtr = (OggData*)m_arOggFiles[index]; return tmpPtr->oggFile; } return NULL; }` Poslední funkce na kterou se podíváme, je HandleAppActiveStateChanges. Tato funkce může být volána aplikací při ztátě fokusu, tj. aplikace není aktivní. Pokud aplikace ztratí fokus, pozastaví funkce přehrávání všech souborů které jsou přehrávány (jsou ve stavu OGGSTATUS_PLAYING). Pokud aplikace fokus zase získá, spustí se přehrávání všech souborů co jsou ve stavu OGGSTATUS_PAUSED. `void CSoundManager::HandleAppActiveStateChanges(bool bIsAppActive) { if (bIsAppActive) //ridici aplikace ma fokus { for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) { if (TmpPtr->oggFile->GetStatus() == OGGSTATUS_PAUSED) TmpPtr->oggFile->Play(TmpPtr->dwPlayPriority); } } } else //ridici aplikace nema fokus { for (UINT i = 0; i < m_arOggFiles.size(); i++) { OggData* TmpPtr = (OggData*)m_arOggFiles[i]; if (TmpPtr) { if (TmpPtr->oggFile->GetStatus() == OGGSTATUS_PLAYING) TmpPtr->oggFile->Pause(); } } } }` To je vše ohledně rozhraní ISoundManager. Na nejasnosti a zbytek kódu se podívejte do přiloženého projektu. Obsah