Zvukový buffer |  |
Ú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 vytvoříme projekty se kterými budeme dále pracovat a budeme se též věnovat implementaci zvukového bufferu. Obsah: 1.1 Nové řešení 1.2 Nastavení projektu 1.3 Třída CSoundBuffer ## 1.1 Nové řešení Vytvoříme si dva nové projekty. Oba budou typu Win32. Bude se jednat o jednu dynamickou knihovnu (dll) a spustitelný soubor (exe). V knihovně bude implementována samotná třída a spustitelná aplikace bude sloužit jako testovací program. Já jsem si pro knihovnu zvolil název OggCodec a pro aplikaci Tester. Otevřete vývojové prostředí a vytvořte nový projekt typu Win32 Project, zadejte název projektu jaký je vám nejbližší a zaškrtněte volbu Create directory for Solution (zobrazí se po stisku tlačítka More). Klepněte na Ok. Zobrazí se průvodce. Na kartě Application Settings vyberte přepínač Dll a vygenerujte základní soubory kliknutím na Finish. Teď přidáme druhý projekt přes menu File->Add Project->New Project.... Dále postupujeme stejně jako při vytváření prvního projektu, jen vybereme přepínač Windows Application. Obsah ## 1.2 Nastavení projektů Dále je třeba provést ještě nějaká nastavení než budeme moci začít. Klepněte na první projekt pravým myšítkem a z kontextového menu vyberte Properties. Ve stromové struktuře v levé části okna vyberte Configuration Properties/General a upravte vlastnost Output Directory na ..\Debug u Debug konfigurace projektu, respektive na ..\Release u Release konfigurace projektu. Takto učiňte i pro druhý projekt. Dále u projektu OggCodec (dynamická knihovna) přejděte do záložky Linker a do řádku Input vložte, zapište následující knihovny: vorbisfile_static.lib ogg.lib vorbis.lib dxerr9.lib dsound.lib winmm.lib strmiids.lib dxguid.lib. Zbývá už jen nastavit závislost projektů, respektive aplikace Tester na knihovně OggCodec. Stačí navštívit dialog Dependencies pomocí nabídky Project->Project Dependencies.... V menu vybereme projekt Tester a zaškrtneme políčko u OggCodec v seznamu. Pokud nyní řešení přeložíte (Build->Build Solution), dostane se vám chybového hlášení při sestavování ve smyslu že nelze nálezt soubor OggCodec.lib. To je způsobeno tím, že jste z projektu ještě neexportovali žádnou funkci, třídu ani rozhraní. Obsah ## 1.3 Třída CSoundBuffer Jelikož od tohoto odstavce už vlastně začínáme s celým projektem, doporučil bych vám hned na začátku vytvořit si v projektu OggCodec nový hlavičkový soubor Common.h, který bude sloužit definicím které budou společné pro více souborů. Navíc se tak vyhnete problémům s vícenásobným vložením hlavičkového souboru a chybovým hlášením a v neposlední řadě je to víc přejhlednější, aspoň podle mého názoru, než vkládat takovou definici do každého cpp souboru nebo hlavičkového souboru. Do souboru vložte, napište tyto řádky: Soubor Common.h: `#include <dmusici.h> #include <vector> #include <dsound.h> #include <dxerr9.h> #include "vorbisfile.h" #include "codec.h" #include <math.h> #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } #define SAFE_DELETE_ARRAY(p) { if(p) {delete [] (p); (p) = NULL; } } #define OGGCODEC_API __declspec(dllexport)` Do souboru jsou vloženy hlavičkové soubory nezbytně nutné pro běh našeho "kodeku", jako je dsound.h. Hlavičkové soubory z Ogg SDK: vorbisfile.h, codec.h. Makra SAFE_RELEASE, SAFE_DELETE a SAFE_DELETE_ARRAY která budeme často používat. První makro slouží k uvolnění rozhraní z paměti, druhé ke smazání dynamicky vytvořeného objektu a třetí ke smazání dynamicky vytvořeného pole. OGGCODEC_API použijeme u definic funkcí, tříd nebo rozhraní které budeme chtít z knihovny exportovat abychom je mohli použít v dalších programech. Nyní už se můžeme pustit do třídy CSoundBuffer :)
Metody třídy CSoundBuffer
Create | Vytvoří rozhraní reprezentující zvukový buffer.
RestoreBuffer | Obnoví buffer při jeho ztrátě.
Reset | Nastaví pozici hracího kurzoru v bufferu na 0.
Play | Spustí přehrávání bufferu
Stop | Zastaví přehrávání bufferu.
SetFrequency | Nastaví frekvenci přehrávaných zvukových dat.
SetPan | Nastavení vyváženosti levého a pravého kanálu.
SetVolume | Nastaví hlasitost bufferu.
IsSoundPlaying | Zjistí zda buffer data přehrává nebo ne.
Implementace bufferu je velice podobná té kterou je možné najít v příkladech SDK k DirectX, konkrétně u DirectSound, ale tato třída je tak jednoduchá, že se u ní snad ani mnoho nového vymyslet nedá. Jedná se vlastně jen o volání metod rozhraní IDirectSoundBuffer pomocí našich vlastních metod, které navíc jen ošetřují možné chybové stavy. Vložte do projektu OggCodec novou třídu CSoundBuffer a zapište implementaci třídy. Podívejme se na soubor SoundBuffer.h: `#pragma once //******************************************************************************************** //Trida zapouzdrujici fce pro praci se zvukovymi buffery //******************************************************************************************** class CSoundBuffer { protected: LPDIRECTSOUNDBUFFER m_DSSoundBuffer; //ukazatel na rozhrani IDirectSoundBuffer DWORD m_dwBufferSize; //velikost bufferu v bajtech DWORD m_dwCreationFlags; //volby ktere jsem pouzili pri vytvareni bufferu public: virtual HRESULT Create(LPDIRECTSOUND lpDSound,DSBUFFERDESC* dsBufferDesc); HRESULT RestoreBuffer(bool* bWasRestored); HRESULT Play(DWORD dwPriority = 0,DWORD dwFlags = 0); HRESULT Stop(); HRESULT Reset(); HRESULT SetFrequency(long lFrequency); HRESULT SetPan(long lPan); HRESULT SetVolume(long lVolume); bool IsSoundPlaying(); LPDIRECTSOUNDBUFFER GetBuffer() {return m_DSSoundBuffer;} CSoundBuffer(void); ~CSoundBuffer(void); };` Nyní se můžeme podbobněji podívat na jednotlivé funkce. První bude konstruktor a destruktor, kde se provádí jen inicializace a úklid a myslím že to není třeba podbrobněji rozebírat. Soubor SoundBuffer.cpp: `#include "StdAfx.h" #include "Common.h" #include "soundbuffer.h" //******************************************************************************************** //Konstruktor //******************************************************************************************** CSoundBuffer::CSoundBuffer(void) { m_DSSoundBuffer = NULL; //neplatny ukazatel na rozhrani, neni jeste vytvoreno m_dwBufferSize = 0; //vychozi hodnoty promennych m_dwCreationFlags = 0; } //******************************************************************************************** //Destruktor //******************************************************************************************** CSoundBuffer::~CSoundBuffer(void) { SAFE_RELEASE(m_DSSoundBuffer); //uvolneni rozhrani z pameti }` Další si pobereme funkci Create `//************************************************************************************************************* //Fce pro vytvoreni rozhrani pro zvukovy buffer //************************************************************************************************************* HRESULT CSoundBuffer::Create(LPDIRECTSOUND lpDSound,DSBUFFERDESC* dsBufferDesc) { HRESULT hRet = DS_OK; if (FAILED(hRet = lpDSound->CreateSoundBuffer(dsBufferDesc,&m_DSSoundBuffer,NULL))) //pokud vytvareni rozhrani selze return hRet; //vratime chybu dal m_dwBufferSize = dsBufferDesc->dwBufferBytes; //ulozeni velikosti bufferu m_dwCreationFlags = dsBufferDesc->dwFlags; //ulozeni příznaků return hRet; //vraceni vysledku, sem uz by se melo dostat jen S_OK }` Funkce očekává že jí předáme ukazatel na rozhraní IDirectSound a strukturu obsahující popis bufferu. Voláním funkce CreateSoundBuffer zmíněného rozhraní vytvoříme rozhraní které bude reprezentovat zvukový buffer. Předáme jí popis bufferu, do druhého argumentu se uloží ukazatel na nově vytvořené rozhraní a poslední argument souvisí s technologií COM a je vždy NULL. Pokud funkce vrátí chybnou hodnotu, nepokračuje se dál. V opačném případě si jen uložíme velikost bufferu a příznaky. `//************************************************************************************************************* //Fce pro obnoveni bufferu //************************************************************************************************************* HRESULT CSoundBuffer::RestoreBuffer(bool *bWasRestored) { HRESULT hRet; if (!m_DSSoundBuffer) //kontrola jestli je rozhrani vubec vytvorene return CO_E_NOTINITIALIZED; if (bWasRestored) //kontrola ukazatele *bWasRestored = false; DWORD dwStatus; if (FAILED(hRet = m_DSSoundBuffer->GetStatus(&dwStatus))) //pokud se nam nepodari ziskat stav bufferu return hRet; //tak rovnou koncime vracenim chyby if (dwStatus & DSBSTATUS_BUFFERLOST) //pokud byl buffer ztracen { do //budeme se v cyklu snazit ho obnovit { //dokud se to nepovede hRet = m_DSSoundBuffer->Restore(); if (hRet == DSERR_BUFFERLOST) Sleep(10); } while ((hRet = m_DSSoundBuffer->Restore()) == DSERR_BUFFERLOST); if (bWasRestored) //ulozime do uzivatelske promenne ze buffer *bWasRestored = true; //byl obnoven return S_OK; } return S_FALSE; }` Funkce nejdřív kontroluje ukazatel na rozhraní bufferu a posléze ukazatel který byl zadán jako parametr. Na místo kam ukazuje bWasRestored se v případě že je ukazatel platný uloží zda byl buffer obnoven nebo ne. Bude-li tato hodnota po vykonání funkce nabývat false, nemusí to znamenat že fuknce selhala, ale také že nebylo třeba buffer obnovit. Pokud je potřeba buffer obnovit, provádí se to v cyklu, který trvá tak dlouho, dokud funkce Restore rozhraní IDirectSoundBuffer nevrátí jinou hodnotu než DSBSTATUS_BUFFERLOST. Pokud je buffer ztracen, je možné že ho nebudeme moci obnovit právě v době volání této funkce, ale až později. Třeba proto že aplikace nemá fokus. `//************************************************************************************************************* //Fce spusti prehravani bufferu //************************************************************************************************************* HRESULT CSoundBuffer::Play(DWORD dwPriority,DWORD dwFlags) { if (!m_DSSoundBuffer) //kontrola platnosti ukazatele na rozhrani return CO_E_NOTINITIALIZED; return m_DSSoundBuffer->Play(0,dwPriority,dwFlags); }` Funkce Play je jednoduchá. Kontroluje jen platnost ukazatele na rozhraní, abychom se neodkazovali na neplatné místo v paměti a nezpůsobili tak krach programu a pak už jen volá metodu Play rozhraní IDirectSoundBuffer a předává jí argumenty které dostala. Jak si můžete všimnout v hlavičkovém souboru třídy CSoundBuffer, jsou pro oba parametry zadány výchozí hodnoty, funkci proto můžete volat bez, s jedním nebo s oběma parametry. První parametr je priorita a pokud je nula a budete mít takové buffery třeba dva, výsledný efekt bude ten, že budou hrát oba najednou. Pokud bude u jednoho priorita 1, počká se až dohraje buffer s prioritou 0 a pak se spustí buffer s prioritou 1. `//************************************************************************************************************* //Fce nastavi frekvenci //************************************************************************************************************* HRESULT CSoundBuffer::SetFrequency(long lFrequency) { if (!m_DSSoundBuffer) return CO_E_NOTINITIALIZED; if (lFrequency != -1 && m_dwCreationFlags & DSBCAPS_CTRLFREQUENCY) return m_DSSoundBuffer->SetFrequency(lFrequency); return S_FALSE; } //************************************************************************************************************* //Fce nastavi hlasitost //************************************************************************************************************* HRESULT CSoundBuffer::SetVolume(long lVolume) { if (!m_DSSoundBuffer) return CO_E_NOTINITIALIZED; if (m_dwCreationFlags & DSBCAPS_CTRLVOLUME) return m_DSSoundBuffer->SetVolume(lVolume); return S_FALSE; } //************************************************************************************************************* //Fce pro nastaveni vyvazenosti leveho a preveho kanalu //************************************************************************************************************* HRESULT CSoundBuffer::SetPan(long lPan) { if (!m_DSSoundBuffer) return CO_E_NOTINITIALIZED; if (m_dwCreationFlags & DSBCAPS_CTRLPAN) return m_DSSoundBuffer->SetPan(lPan); return S_FALSE; } //************************************************************************************************************* //Fce zastavi prehravani bufferu //************************************************************************************************************* HRESULT CSoundBuffer::Stop() { if (!m_DSSoundBuffer) return CO_E_NOTINITIALIZED; return m_DSSoundBuffer->Stop(); } //************************************************************************************************************* //Fce nastavi pozici hraciho kurzoru v bufferu na nula //************************************************************************************************************* HRESULT CSoundBuffer::Reset() { if (!m_DSSoundBuffer) return CO_E_NOTINITIALIZED; return m_DSSoundBuffer->SetCurrentPosition(0); }` První tři funkce manipulují s různými parametry které jsou zvukovým datům vlastní :) Jde o nastavení frekvence (SetFrequency), vyváženosti levého a pravého kanálu (SetPan) a hlasitosti (SetVolume. Všechny tyto funkce kontrolují ukazatel na rozhraní a pak také příznaky s jakými byl buffer vytvořen. Pokud není příznak pro danou vlastnost nastaven, znamená to že ji u befferu nemůžeme řídit, měnit, jen ji číst. Pokud ano, zavolá se jen příslušná funkce rozhraní IDirectSoundBuffer a předá se jí zadaný parametr. Další dvě funkce jsou Stop a Reset. Zase se jen kontroluje ukazatel na rozhraní. Funkce Reset funguje tak, že nastaví pozici hracího kurzoru na 0, dalo by se jí také říkat Rewind. Poslední funkcí třídy CSoundBuffer je IsSoundPlaying: `//************************************************************************************************************* //Fce rekne jestli je buffer prehravan ci ne //************************************************************************************************************* bool CSoundBuffer::IsSoundPlaying() { if (!m_DSSoundBuffer) return false; DWORD dwStatus; m_DSSoundBuffer->GetStatus(&dwStatus); return (dwStatus & DSBSTATUS_PLAYING) ? true : false; }` Funkce je opět jednoduchá. Zda je buffer přehráván zjistíme jednoduše tak, že se ho zeptáme na stav pomocí fuknce GetStatus. Pokud je buffer přehráván vrátí se nám DSBSTATUS_PLAYING a podle toho vrátíme buď true nebo false To je ke třídě CSoundBuffer vše. Pokud řešení přeložíte (Build->Build Solution), oběví se vám opět chyba o nenalezení lib souboru knihovny OggCodec. Otevřete soubor SoundBuffer.h a definici třídy upravte takto: `class OGGCODEC_API CSoundBuffer` Přidáním konstanty ze souboru Common.h jste vývojovému prostředí řekli, že chcete třídu exportovat a soubor lib bude automaticky vytvořen. Pokud nyní řešení zkusíte přeložit znovu, neměla by se objevit již žádná chyba. Upravte definici zpět na původní, protože tuto třídu exportovat nebudeme. Ale pokud chcete, můžete ji samozřejmě takto ponechat. Na další dění to nemá vliv :) Můžete se pustit do další části :) Obsah