002-02  前へ←  ホームへ  →次へ  003-02

No.003 - 01  WAVE再生の前フリ解説

筆者音楽系は苦手なのになぜかまたこんなコーナー。
WAVE再生のちゃんとしたソース置いてるサイトってあんまナイですね。

ページ目次
1.手順
2.サンプリングレートとダブルバッファリング解説
3.関数解説


1.手順

1.WAVEデバイスを開く 2.メモリのスワップアウト不可化 3.音データを送る 4.メモリのスワップアウト不可化の解除 5.WAVEデバイスを閉じる
1.waveOutOpen   WAVEデバイスを開く
WAVEデバイスを開きPCM音源を使えるようにします。
waveOutOpen (HWAVEOUT *p_hwave, UINT device_ID, WAVEFORMATEX *pwfx , DWORD callback_func , DWORD callback_instance , DWORD callback_flag );  
第1引数 HWAVEOUTへのポインタ
第2引数 デバイス指定ID(WAVE_MAPPERでデフォルト・デバイス)
第3引数 WAVEFORMATEXへのポインタ
第4引数 ウィンドウハンドルかコールバック関数
第5引数 コールバック関数へ渡すユーザ定義値
第6引数 第3引数の識別コードCALLBACK_FUNCTION(コールバック関数)、CALLBACK_WINDOW(ウィンドウハンドル)など
  
・関数が成功すると第1引数で渡したハンドル・ポインタにWAVEハンドルが返り、
第4引数で指定したコールバック関数に、MM_WOM_OPENメッセージ (ウィンドウ・プロシージャ) またはWOM_OPENメッセージ (コールバック関数) を送ります。

第3引数ポインタ  WAVEFORMATEX構造体
typedef struct { 
    WORD  wFormatTag;       // 波形フォーマットID PCM音源ならWAVE_FORMAT_PCM
    WORD  nChannels;        // チャンネル数。モノラル==1 、ステレオ==2 
    DWORD nSamplesPerSec;   // サンプリングレート
    DWORD nAvgBytesPerSec;  // 1秒あたりデータ転送量(バイト/秒)。PCMは サンプリングレート * nBlockAlign。
    WORD  nBlockAlign;      // ブロックアラインメント。最小単位のデータサイズ。PCMならnChannels * wBitsPerSample /8。
    WORD  wBitsPerSample;   // サンプルあたりのビット数。PCM なら 8 bitか 16 bit
    WORD  cbSize;           // この構造体の末尾に追加される拡張情報のサイズ(バイト単位)PCM なら 0。
} WAVEFORMATEX;
2.waveOutPrepareHeader  WAVE出力ヘッダ、音データ・バッファをスワップアウト不可状態にする。
WAVEデバイスに渡すためのWAVEHDR構造体とその音データバッファのメモリ移動を回避します。
(音声の再生をスムーズに行うため。)
waveOutPrepareHeader (HWAVEOUT hwave , LPWAVEHDR pwhdr , UINT struct_size);
第1引数 WAVEハンドル
第2引数 WAVEHDR 構造体 へのポインタ
第3引数 第2引数の構造体サイズ
第2引数ポインタ WAVEHDR 構造体
typedef struct { 
    LPSTR  lpData;              // 音データ・バッファへのポインタ
    DWORD  dwBufferLength;      // 音データ・バッファのサイズ(バイト単位)
    DWORD  dwBytesRecorded;     // 録音用フィールド
    DWORD  dwUser;              // ユーザーデータ
    DWORD  dwFlags;             // バッファの追加情報
    DWORD  dwLoops;             // ループ回数
    struct wavehdr_tag *lpNext; // ==0
    DWORD  reserved;            // ==0
} WAVEHDR;
3.waveOutWrite  WAVEデバイスに音データを送る
WAVEデバイスに音データを送り、実際に音を鳴らします。
waveOutWrite (HWAVEOUT hwave , LPWAVEHDR pwhdr , UINT struct_size);
第1引数 WAVEハンドル
第2引数 WAVEHDR 構造体 へのポインタ
第3引数 第2引数の構造体サイズ

関数が成功するとWAVEデバイスに音データが送られ、音が鳴ります。
そしてコールバック関数にMM_WOM_DONEメッセージ(ウィンドウ・プロシージャ)またはWOM_DONEメッセージ(コールバック関数)を送ります。
4.waveOutUnprepareHeader WAVE出力ヘッダ、音データ・バッファのスワップアウト不可状態を解除する。
2.waveOutPrepareHeaderでおこなったスワップアウト不可を解除します。
waveOutUnprepareHeader (HWAVEOUT hwave , LPWAVEHDR pwhdr , UINT struct_size);
引数はwaveOutPrepareHeaderと同じ。
5.waveOutClose WAVEデバイスを閉じる
WAVEデバイスを閉じます。(PCM音源を解放し、その制御をOSに返します。)
waveOutClose (HWAVEOUT hwave);
第1引数 WAVEハンドル

WAVEデバイスを閉じます。。
そしてコールバック関数にMM_WOM_CLOSEメッセージ(ウィンドウ・プロシージャ)またはWOM_CLOSEメッセージ(コールバック関数)を送ります。


おまけ waveOutReset 音の全停止
WAVEデバイスを閉じます。(PCM音源を解放し、その制御をOSに返します。)
waveOutReset (HWAVEOUT hwo);
第1引数 WAVEハンドル

現在鳴っている音を全て停止します。
そしてコールバック関数にMM_WOM_DONEメッセージ(ウィンドウ・プロシージャ)またはWOM_DONEメッセージ(コールバック関数)を送ります。

参考:

実行 関数名   送られるメッセージ
ウィンドウプロシージャ 専用コールバック関数
WAVEデバイス開く waveOutOpen  → MM_WOM_OPEN WOM_OPEN
WAVEデバイスに音データを送る waveOutWrite;  → MM_WOM_DONE WOM_DONE
音の全停止 waveOutReset;
WAVEデバイス閉じる waveOutClose;  → MM_WOM_CLOSE WOM_CLOSE





2.サンプリングレートとダブルバッファリング

サンプリングレート
音はアナログな波形です。
1つの音は連続して途切れることなく流れていきます。
しかし途切れることなく音を処理させることなんてことはコンピュータにはできません
そこで音の波形を数万分の一秒くらいの一定周期ごとに波形の振幅を数値化し、コンピュータで処理します。
これをサンプリングと呼び
1秒間のサンプリング数をサンプリングレートと呼びます

例えば、1秒間に10000回の割合で、音声を数値化するならば
サンプリングレートは10000Hz、または 10kHz となります。
サンプリングレートが高ければ高いほど高品質な音になりますが、その分コンピュータには負担になります。

1930年代の技術者ナイキストさんによれば
サンプリングレートはサンプリング音の最大周波数の2倍でなければならないと
いうことから
人間の耳が認識できるのは20kHzなので全音域を拾うには 40kHz必要です。
しかし再生ハードウェアのローパス・フィルタ(高音域を排除し波形のスムーズにするフィルタ)で
波形が削られてしまうため10%周波数を上げる必要があり、ここで44kHzとなります。
更に欧米のテレビのフレーム率にあわせ、米30Hz, 欧25Hzの整数倍から
44.1kHzとなります。

CD は 44.1kHz を使ってますが、
44.1kHzはデータサイズとしてはかなり大きなもので音声の録音には不向きです。
サンプリングレートを半分の22.05kHzにすれば高音域1オクターブ分(10KHz)減ります。
さらに半分の11.025kHz、さらに半分の8kHzといったカンジで
一般的なデバイスは 8kHz、11.025kHz、22.05kHz、44.1kHz が標準サポートになってます。

ダブルバッファリング
ダブルバッファリングは音が途切れ途切れになるのを防ぐテクニックです。

WAVEデバイスは送られたデータの再生が終わるとコールバック関数にMM_WOM_DONEメッセージWOM_DONE)を送ります。
したがってMM_WOM_DONEメッセージWOM_DONE)で次に再生するデータを送るわけですが
このメッセージはデータの再生が終わってから送られて来るので次のデータの再送までわずかに無音状態が発生します。

そこで最初に2回waveOutWriteで音データを送ります。
すると最初に送った音と2回目に送ったデータの分で2回 MM_WOM_DONEメッセージ(WOM_DONE)が送られます
1回目のMM_WOM_DONEメッセージ(WOM_DONE)が送られたとき、まだ2回目の分の音データが再生されているので無音状態にならなくて済む仕組みです。
あとは再生の終わった音データが交互に送られてくるのでもう一方のデータの再生が終わらないうちにwaveOutWriteで再送してやるだけです。



3.関数詳細


<mmsystem.h>
typedef UINT MMRESULT; /* error return code, 0 means no error */

waveOutOpen()
MMRESULT waveOutOpen(
        LPHWAVEOUT phwo , UINT uDeviceID,            
        LPWAVEFORMATEX pwfx ,
        DWORD dwCallback , DWORD dwCallbackInstance ,
        DWORD fdwOpen
);
ウェーブ出力デバイスを開きます

phwo - デバイス・ハンドルへのポインタ
uDeviceID - 対象の出力デバイスの指定ID
pwfx - WAVEFORMATEX 構造体へのポインタ
dwCallback - 再生中にウェーブの関連メッセージを受け取る対象を指定します
dwCallbackInstance - コールバック関数に渡すユーザ定義データ
fdwOpen - デバイスを開くためのフラグを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、そうでなければエラー値

fdwOpen には以下のいずれかの値、または組み合わせを指定します
WAVE_ に組みあわせることができるのは 1 つの CALLBACK_ だけです

定数 解説
CALLBACK_EVENT dwCallback はイベントハンドルである
CALLBACK_FUNCTION dwCallback はコールバック関数のポインタである
CALLBACK_NULL コールバックは使用しない
CALLBACK_THREAD dwCallback はスレッドハンドルである
CALLBACK_WINDOW dwCallback はウィンドウハンドルである
WAVE_FORMAT_DIRECT このフラグが指定されていれば
ACM ドライバは音声データを転送しない
WAVE_FORMAT_QUERY このフラグが指定されていれば
関数はデバイスに、指定フォーマットをサポートしているか求める
ただし、実際にデバイスはオープンされない
WAVE_MAPPED このフラグが指定されていれば
uDeviceID は、ウェーブマっパによってマップされる
ウェーブ出力デバイスを指定する

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_ALLOCATED リソースは、すでに割り当てられている
MMSYSERR_BADDEVICEID デバイス識別子が無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない
WAVERR_BADFORMAT デバイスドライバは、指定されたフォーマットをサポートしていない
WAVERR_SYNC 同期デバイスが指定されたが
WAVE_ALLOWSYNC フラグが指定されていない



waveOutProc()
void CALLBACK waveOutProc(
        HWAVEOUT hwo , UINT uMsg,         
        DWORD dwInstance,  
        DWORD dwParam1, DWORD dwParam2     
);
waveOutOpen() 関数が呼び出す専用のコールバック関数定義です

hwo - waveOutOpen() で作成された出力デバイスのハンドルを指定します
uMsg - 送信メッセージを指定します
dwInstance - 関数で指定したユーザー定義の追加情報を指定します
dwParam1 - メッセージの追加情報を指定します
dwParam2 - メッセージの追加情報を指定します



waveOutPrepareHeader()
MMRESULT waveOutPrepareHeader(
        HWAVEOUT hwo , LPWAVEHDR pwh , UINT cbwh
);
再生に使う出力バッファをスワップ・アウト(HDDなどへのメモリ退避)させなくする。

hwo - ウェーブ出力デバイスのハンドル
pwh - WAVEHDR 構造体へのポインタを指定します
cbwh - WAVEHDR 構造体のサイズを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、そうでなければエラー値

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_HANDLEBUSY 別のスレッドがハンドルを使用中である
MMSYSERR_INVALHANDLE デバイスハンドルが無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない



waveOutWrite()
MMRESULT waveOutWrite(
        HWAVEOUT hwo , LPWAVEHDR pwh , UINT cbwh
);
データバッファを指定出力デバイスに送信し、音を鳴らします。
そしてコールバック関数にMM_WOM_OPENメッセージ(ウィンドウ・プロシージャ)またはWOM_OPENメッセージ(コールバック関数)を送ります。

hwo - ウェーブ出力デバイスのハンドルを指定します
pwh - WAVEHDR 構造体へのポインタを指定します
cbwh - WAVEHDR 構造体のサイズを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、そうでなければエラー値

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_HANDLEBUSY 別のスレッドがハンドルを使用中である
MMSYSERR_INVALHANDLE デバイスハンドルが無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない
WAVERR_UNPREPARED pwh が指すバッファが準備されていない



waveOutReset()

MMRESULT waveOutReset(HWAVEOUT hwo);

再生を停止し、現在位置をリセットします
そしてコールバック関数にMM_WOM_OPENメッセージ(ウィンドウ・プロシージャ)またはWOM_OPENメッセージ(コールバック関数)を送ります。

hwo - ウェーブ出力デバイスのハンドルを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、それ以外の場合はエラー値

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_HANDLEBUSY 別のスレッドがハンドルを使用中である
MMSYSERR_INVALHANDLE デバイスハンドルが無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない
MMSYSERR_NOTSUPPORTED 関数がサポートされていない



waveOutUnprepareHeader()
MMRESULT waveOutUnprepareHeader(
        HWAVEOUT hwo , LPWAVEHDR pwh , UINT cbwh
);
waveOutPrepareHeaderでおこなったスワップ・アウト不可状態を解除します。

hwo - ウェーブ出力デバイスのハンドルを指定します
pwh - WAVEHDR 構造体へのポインタを指定します
cbwh - WAVEHDR 構造体のサイズを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、そうでなければエラー値

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_HANDLEBUSY 別のスレッドがハンドルを使用中である
MMSYSERR_INVALHANDLE デバイスハンドルが無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない



waveOutClose()
MMRESULT waveOutClose(HWAVEOUT hwo);

ウェーブ出力デバイスを閉じます

hwo - ウェーブフォームオーディを出力デバイスのハンドルを指定します

戻り値 - 成功すれば MMSYSERR_NOERROR、そうでなければエラー値

この関数が返すエラー値は、以下の定数のいずれかになります

定数 解説
MMSYSERR_HANDLEBUSY 別のスレッドがハンドルを使用中である
MMSYSERR_INVALHANDLE デバイスハンドルが無効である
MMSYSERR_NODRIVER デバイスドライバがない
MMSYSERR_NOMEM メモリを割り当てられない、またはロックできない




 002-02  前へ←  ホームへ  →次へ  003-02