Záznam zvuku pomocí MCI v C++

V předchozích dvou článcích jsme se zabývali přehráváním multimediálních souborů pomocí MCI (Media Control Interface). V tomto článku si ukážeme, jak lze MCI využít pro záznam zvuku do formátu WAV.

Použijeme funkci mciSendCommand.

MCIERROR mciSendCommand(
  MCIDEVICEID IDDevice,
  UINT uMsg,
  DWORD fdwCommand,
  DWORD dwParam
);

Pro použití této funkce v projektu je potřeba přidat (pokud to ještě nemáte) hlavičkový soubor <mmsystem.h> a knihovnu winmm.lib.

S touto funkcí jsme se již setkali, když jsme si ukázali, jak programově ovládat CD mechaniku. Tato funkce je totiž velmi univerzální a vhodným nastavením parametrů lze její pomocí realizovat nejrůznější multimediální funkce.

Jako parametr dwParam v případě záznamu zvuku budeme zadávat adresy některých MCI struktur. Jako první použijeme strukturu MCI_OPEN_PARMS

typedef struct {
    DWORD        dwCallback;
    MCIDEVICEID  wDeviceID;
    LPCSTR      lpstrDeviceType;
    LPCSTR      lpstrElementName;
    LPCSTR      lpstrAlias;
} MCI_OPEN_PARMS;

V nejjednodušším případě stačí jako parametr lpstrDeviceType zadat text „waveaudio“, ostatní parametry „vynulovat“ a zavolat poprvé funkci mciSendCommand:

mciOpen.dwCallback = 0;
mciOpen.lpstrAlias = NULL;
mciOpen.lpstrDeviceType = TEXT("waveaudio");
mciOpen.lpstrElementName = TEXT("");
mciOpen.wDeviceID = 0;
dwResult = mciSendCommand(0, MCI_OPEN,
MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
(DWORD)(LPMCI_OPEN_PARMS)&mciOpen);
if ( dwResult != 0 )
{
ErrorMessage(dwResult);
return;
}

Vlastní chybová funkce, která z kódu chyby vypíše textový popis chyby, může vypadat následovně:

void CMainFrame::ErrorMessage(DWORD dwError)
{
TCHAR chText[1024];
mciGetErrorString(dwError, chText, sizeof(chText) / sizeof(TCHAR));
MessageBox(chText, "Chyba při záznamu", MB_ICONERROR);
}

Možná se divíte, proč při otevírání zařízení nemusíme zadat žádný identifikátor (prvek wDeviceID). Je tomu tak proto, že takto zavolaná funkce nám právě tento identifikátor vrátí, takže dalším krokem bude:

wDeviceID = mciOpen.wDeviceID;

Dalším krokem bude další volání funkce mciSendCommand, tentokrát s použitím struktury

typedef struct {
    DWORD dwCallback;
    DWORD dwFrom;
    DWORD dwTo;
} MCI_RECORD_PARMS;

Prvek dwCallback určuje handle okna, které bude dostávat notifikační zprávy o změně či dokončení příslušné operace. To je výhodné, zejména pokud specifikujeme také parametry dwFrom a dwTo, které, jak již názvy napovídají, určují „pozice“ začátku a konce operace záznamu. V našem případě je zatím necháme nulové, budeme tedy nahrávat zvuk neustále až do „ručního“ zastavení.

wDeviceID = mciOpen.wDeviceID;
mciRecord.dwCallback = (DWORD)m_hWnd;
mciRecord.dwFrom = 0;
mciRecord.dwTo = 0;
mciSendCommand(wDeviceID, MCI_RECORD, MCI_NOTIFY,
(DWORD)(LPMCI_RECORD_PARMS)&mciRecord);
m_RecordingNow = TRUE;

Jak je vidět, opět jde o použití funkce mciSendCommand, tentokrát s parametrem MCI_RECORD.

Zmínil jsem se o „ručním“ zastavení. Když si vytvoříme třeba handler stlačení tlačítka na dialogu, bude funkce zastavení záznamu zvuku vypadat třeba takto:

void CUkazkaDlg::StopRecording()
{
MCI_GENERIC_PARMS mciGeneric;
if ( !m_RecordingNow )
return;
mciGeneric.dwCallback = 0;
mciSendCommand (wDeviceID, MCI_STOP, MCI_WAIT,
(DWORD)(LPMCI_GENERIC_PARMS)&mciGeneric);
mciSave.dwCallback = 0;
mciSave.lpfilename = TEXT(chFileName);
mciSendCommand(wDeviceID, MCI_SAVE, MCI_WAIT | MCI_SAVE_FILE,
(DWORD)(LPMCI_SAVE_PARMS)&mciSave);
mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT,
(DWORD)(LPMCI_GENERIC_PARMS)&mciGeneric);
m_RecordingNow = FALSE;
}

Nejprve jsme tedy použili (jak jinak) opět funkci mciSendCommand s parametrem MCI_STOP a dále jsem uložili zaznamenaný zvuk do souboru (typu .wav) na disk, zavoláním funkce mciSendCommand s parametrem MCI_SAVE a vyplněnou strukturou

typedef struct {
    DWORD  dwCallback;
    LPCSTR lpfilename;
} MCI_SAVE_PARMS;

kde lpfilename je samozřejmě jméno požadovaného souboru a dwCallback může být opět handle okna, které obdrží zprávu o dokončené operaci. V našem případě jej zatím nepoužíváme.

Jak jistě víte, v systému Windows existuje většinou několik cest k témuž cíli a ani při programování MCI zařízení tomu není jinak. Ukažme si ještě trochu jiný způsob přehrávání záznamů, než jsme používali v předchozích dvou dílech tohoto seriálu. Nyní, když jsme si ukázali „univerzální“ funkci mciSendCommand, nebude asi žádným překvapením, že touto funkcí můžeme záznam také přehrávat (a samozřejmě zastavit přehrávání).

Pro spuštění přehrávání použijeme opět dvojí volání této funkce, přičemž tím prvním již známým způsobem otevřeme zařízení a poté použijeme strukturu MCI_PLAY_PARMS:

typedef struct {
    DWORD dwCallback;
    DWORD dwFrom;
    DWORD dwTo;
} MCI_PLAY_PARMS;

význam jejichž parametrů je jistě již zřejmý, a parametr MCI_PLAY:

void CUkazkaDlg::OnPlay()
{
mciOpen.dwCallback = 0;
mciOpen.wDeviceID = 0;
mciOpen.lpstrDeviceType = NULL;
mciOpen.lpstrElementName = chFileName;
mciOpen.lpstrAlias = NULL;
dwResult = mciSendCommand(0, MCI_OPEN,
MCI_WAIT | MCI_OPEN_ELEMENT,
(DWORD)(LPMCI_OPEN_PARMS)&mciOpen);
if ( dwResult != 0 )
ErrorMessage(dwResult);
wDeviceID = mciOpen.wDeviceID;
mciPlay.dwCallback = (DWORD)m_hWnd;
mciPlay.dwFrom = 0;
mciPlay.dwTo = 0;
mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY,
(DWORD)(LPMCI_PLAY_PARMS)&mciPlay);
m_PlayingNow = TRUE;
}

Nakonec obdobným způsobem můžeme „ručně“ zastavit přehrávání souboru

void CUkazkaDlg::StopPlaying()
{
mciGeneric.dwCallback = 0;
mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT,
(DWORD)(LPMCI_GENERIC_PARMS)&mciGeneric);
mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT,
(DWORD)(LPMCI_GENERIC_PARMS)&mciGeneric);
m_PlayingNow = FALSE;
}

Nyní si ukažme, jak lze v proceduře okna zachytávat události o změně stavu přehrávání. Okno, jehož handle jsme určili při spuštění příslušné akce, dostane zprávu MM_MCINOTIFY

MM_MCINOTIFY
wParam = (WPARAM) wFlags
lParam = (LONG) lDevID

ve které wParam určuje, z jakého důvodu došlo k příslušné události. Může nabývat následujících hodnot:

  • MCI_NOTIFY_ABORTED – zařízení obdrželo příkaz, který přerušil právě probíhající a ještě nedokončenou operaci.
  • MCI_NOTIFY_FAILURE – během provádění příkazu nastala na zařízení chyba
  • MCI_NOTIFY_SUCCESSFULL – operace byla úspěšně dokončena
  • MCI_NOTIFY_SUPERSEDED – zařízení obdrželo jiný příkaz s nastaveným příznakem notifikace, který změnil aktuální podmínky notifikace.
V našem případě budeme zachytávat zprávu MM_MCINOTOFY a testovat pouze hodnotu MCI_NOTIFY_SUCCESSFUL. Ukažme si, jak zachytit ukončení (a to jak „ruční“ nebo ukončení dosažením konce souboru) přehrávání a záznamu:

LRESULT CUkazkaDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message )
{
case MM_MCINOTIFY:
switch (wParam)
{
case MCI_NOTIFY_SUCCESSFUL:
if ( m_PlayingNow )
MessageBox("Přehrávání zastaveno");
if ( m_RecordingNow )
MessageBox("Záznam zastaven");
break;
}
break;
}
return CDialog::WindowProc(message, wParam, lParam);
}

Zde si můžete stáhnout ukázkový projekt: mci_recorder.zip (33 kB)

Určitě si přečtěte

Články odjinud