Ú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

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