Přehrávání Wave souborů (MFC a C++)

Jistě jste už ve svých programech někdy potřebovali přehrát nějaký ten zvuk či melodii. První způsob spočívá v použití funkce PlaySound(), která je k přehrávání wave souborů přímo dělaná. Ovšem pokud bychom chtěli vytvořit například editor zvukových nahrávek, tak zde nastane problém, protože funkce PlaySound neobsahuje žádný parametr, kterým by se dala určit pozice od které se bude přehrávat.

Řešení spočívá v použití funkcí waveOut , které nejsou zase tak složité, ale umožňují velice dobrou kontrolu nad přehrávanými daty. Pokud však s těmito funkcemi chcete pracovat, musíte vložit hlavičkový soubor mmsystem.h a dále přilinkovat knihovnu winmm.lib .

V projektu používám třídu CWaveFile , kterou jsem sám vytvořil a která slouží k načítání a ukládání wave souborů. Zde ji blíže popisovat nebudu, pokud byste si ji chtěli stáhnout samostatně, tak ji najdete v sekci Ke stažení.

Načtení souboru

Nejprve si celý soubor nahrajeme do paměti. K tomu slouží funkce Load třídy CWaveFile. Potom si od třídy vyžádáme pomocí funkce GetBuffer buffer, ve kterém jsou uložena samotná zvuková data. O vymazání bufferu se starat nemusíme, protože třída to dělá automaticky ve svém destruktoru. Potom si načteme formát zvukových dat pomocí funkce GetFormat a nakonec zjistíme délku zvukových dat pomocí GetLength .

CWaveFile file;
file.Load("C:\\zvuk.wav");
BYTE* buffer=file.GetBuffer();

WAVEFORMATEX wfe=file.GetFormat();
int delka=file.GetLength();

Inicializace zvukového výstupu

Před tím, než začneme posílat data na výstup, si musíme výstupní zařízení řádně otevřít. K tomu slouží funkce waveOutOpen . Potom je potřeba určit formát a délku výstupních dat nastavením struktury WAVEHDR a předáním do funkce waveOutPrepareHeader . Tyto dvě funkce si tedy blíže popíšeme:

MMRESULT waveOutOpen(
  LPHWAVEOUT phwo,            //Handle pro výstupní zařízení
  UINT uDeviceID,             //Identifikátor zařízení (WAVE_MAPPER)
  LPWAVEFORMATEX pwfx,        //Formát dat, které budeme přehrávat
  DWORD dwCallback,           //Adresa funkce pro zpětné volání
  DWORD dwCallbackInstance,   //Nepoužívám
  DWORD fdwOpen               //Určuje, co je v parametru dwCallback
);

//příklad na funkci waveOutOpen
waveOutOpen(&m_hWaveOut,WAVE_MAPPER,&m_WaveFormat,NULL,NULL,NULL);
MMRESULT waveOutPrepareHeader(
  HWAVEOUT hwo,                  //Handle výstupního zař.
  LPWAVEHDR pwh,                 //Adresa hlavičky, ve které jsou data
  UINT cbwh                      //velikost hlavičky pwh
);

//příklad na funkci waveOutPrepareHeader
waveOutPrepareHeader(m_hWaveOut,&m_WaveHdr,sizeof(m_hWaveHdr));

Čísla, které vrací tyto funkce a která určují jestli funkce proběhla úspěšně, zde nebudu používat, můžete si je vyhledat v dokumentaci.

Zapsání dat na výstup

Vlastní zapsání dat se provádí funkcí waveOutWrite , po jejímž volání se začne přehrávat zvuk. A jak zjistíme, že všechna data už byla přehrána? Jednoduše, když je dohráno, tak tato funkce nastaví ve struktuře WAVEHDR, která byla předána funkci waveOutPrepareHeader, člen dwFlags .

MMRESULT waveOutWrite(
  HWAVEOUT hwo,         //Handle výst. zař.
  LPWAVEHDR pwh,        //Adresa struktury WAVEHDR
  UINT cbwh             //Velikost struktury WAVEHDR
);

//příklad na volání
waveOutWrite(m_hWaveOut,&m_WaveHdr,sizeof(m_WaveHdr));

Deinicializace zvukového výstupu

Použijeme funkce waveOutUnprepareHeader , které předáme adresu struktury WAVEHDR a funkci waveOutClose , které předáme handle výstupního zařízení.

Vše v jednom

Tento zdrojový kód, který je spojením všech předcházejících akcí, dělá to, že po spuštění funkce zobrazí standardní dialog pro výběr souboru a potom ho přehraje. Nakonec zobrazí hlášení o tom, že soubor byl přehrán.

void CMujDialog::OnPlayFile()
{
  CFileDialog dlg(TRUE);
  if(dlg.DoModal()==IDCANCEL)
    return;

  CWaveFile file;
  file.Load(dlg.GetPathName());
  BYTE* buffer=(BYTE*)file.GetBuffer();
  int delka=file.GetLength();
  WAVEFORMATEX wex=file.GetFormat();
  HWAVEOUT wo;
  WAVEHDR wh;

  ::waveOutOpen(&wo,WAVE_MAPPER,&wex,NULL,NULL,NULL);
  ::ZeroMemory(&wh,sizeof(wh));
  wh.dwBufferLength=delka;
  wh.lpData=(LPSTR)buffer;
  ::waveOutPrepareHeader(wo,&wh,sizeof(wh));

  ::waveOutWrite(wo,&wh,sizeof(wh));
  MSG message;
  while(!(wh.dwFlags & WHDR_DONE))
  {
    while (::PeekMessage(&message,NULL,0,0,PM_REMOVE)) //čekací smyčka
    {
      ::TranslateMessage(&message);
      ::DispatchMessage(&message);
    }
  }

  ::waveOutUnprepareHeader(wo,&wh,sizeof(wh));
  ::waveOutClose(wo);

  MessageBox("Hotovo!");
}

Lepší postup?

Tento příklad je sice pro ilustraci, jak tyto funkce fungují dobrý, ale představte si situaci, že takto chcete přehrávat zvukový soubor o velikosti 100MB. Potom se v paměti vyhradí prostor 100MB, což určitě není příliš ekonomické. Proto se v praxi spíše používají dva buffery o specifické velikosti a ty se vždy naplní částí souboru a pošlou na výstup. Když se potom jeden vyprázdní, tak druhý mezitím hraje a my můžeme ten první zase naplnit a poslat na výstup. Ale to už přesahuje rozsah tohoto článku.

Stáhnout zdrojový kód (27kb)

UPOZORNĚNÍ: Jedná se o archiv článků z let 2003 - 2005, uvedené technologie či postupy již mohou být neaktuální.