>> WINAPI入門トップに戻る

MCIコマンドによる音声ファイル再生

今回はMCIコマンドを使って音声ファイルを再生する方法を説明します。

MCIコマンドというものを用いると、waveファイルだけでなく、
MP3ファイルや動画ファイルの再生なども扱うことができます。
今回はwaveファイルとmp3ファイルを再生する方法について説明します。

まずMCIコマンドを送信するために、mciSendCommand関数を使います。
mmsystem.hをインクルードし、winmm.libをリンクする必要があります。

MCIERROR mciSendCommand(
MCIDEVICEID IDDevice, // デバイス識別子
UINT uMsg, // コマンドメッセージ
DWORD fdwCommand, // フラグ
DWORD dwParam // パラメータを保持している構造体
);

第一引数には再生対象のデバイス識別子を指定します。
第二引数には送信するコマンドを指定します。
以下のものがあります。

MCI_OPEN		デバイスをオープンします。
MCI_CLOSE	デバイスをクローズします
MCI_PLAY		再生します
MCI_STOP		停止します
MCI_PAUSE	一時停止します
MCI_RESUME	一時停止解除します
MCI_SET		MCI_FORMAT_MILLISECONDSを指定し、時間のフォーマットを指定します。
MCI_SEEK		MCI_SEEK_TO_STARTなどを指定し、シーク位置の調整をします。
MCI_STATUS	MCI_STATUS_PARMS.dwItemにMCI_STATUS_LENGTHを指定し、再生時間取得
MCI_STATUS	MCI_STATUS_PARMS.dwItemにMCI_STATUS_POSITIONを指定し、現在位置を取得
MCI_STATUS	 MCI_STATUS_PARMS.dwItemにMCI_STATUS_MODEを指定すると以下のものを返す。
			MCI_MODE_PLAY : 再生中
			MCI_MODE_STOP : 停止中
			MCI_MODE_PAUSE : 一時停止中
			

第三引数には、オプションのコマンドメッセージを指定します。
かなり数がおおいいので、MSDNを参照して下さい。
基本的には、アクションがあった時に親ウィンドウに通知メッセージを送る、MCI_NOTIFY,
デバイスオープン時に使うMCI_OPEN_TYPE,MCI_OPEN_TYPE_ID,MCI_OPEN_ELEMENTぐらいしか使いません。

第四引数にはパラメータをセットして、MCI_OPEN_PARMS構造体のポインタを指定します。

まず関数を実行する前に、
MCI_OPEN_PARMS構造体の値をセットする必要があります。

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

必要なのはlpstrDeviceTypeとlpstrElementNameだけです。
デバイスタイプには、以下のものを指定できます。

MCI_DEVTYPE_WAVEFORM_AUDIO ウェーブフォームオーディオ
MCI_DEVTYPE_CD_AUDIO CD オーディオ
MCI_DEVTYPE_DAT デジタルオーディオデバイス
MCI_DEVTYPE_DIGITAL_VIDEO 非 GDI ベースのウィンドウ内デジタルビデオ
MCI_DEVTYPE_OTHER 未定義
MCI_DEVTYPE_OVERLAY オーバーレイ
MCI_DEVTYPE_SCANNER イメージスキャナ
MCI_DEVTYPE_SEQUENCER MIDI シーケンサ
MCI_DEVTYPE_VCR ビデオカセットレコーダー、またはプレーヤー
MCI_DEVTYPE_VIDEODISC ビデオディスクプレーヤー
MCI_DEVTYPE_ANIMATION アニメーション


waveファイルを再生するには、MCI_DEVTYPE_WAVEFORM_AUDIOを指定します。
メンバの型自体は、LPSTR型なのでキャストします。
MCIではMP3ファイルも再生できます。
MP3ファイルの場合は定数がありませんが、「MPEGVideo」という文字列を直接指定することで、
再生できます。
定数を使わない場合のデバイス名の一覧は、レジストリの、
「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MCI Extensions」
の中に載っていますので、一度見てみるとよいかもしれません。
拡張子ごとのデバイス名が記載されています。
lpstrElementNameにはファイル名を書きます。


これで構造体のセットが終わったので、まずmciSendCommand関数でデバイスを開きます。

まず第一引数のデバイスIDですが最初はわからないのでNULLにします。
次に第二引数ですが、最初はまずデバイスをOPENする必要があるので、
[MCI_OPEN]を指定します。
第三引数はwaveファイルの場合は、
MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENTを指定し、
MP3ファイルの場合は、
MCI_OPEN_TYPE | MCI_OPEN_ELEMENT
を指定します。論理和演算で複数指定可能です。
WAVEみたいにあらかじめ定数が用意されているものはMCI_OPEN_TYPE_IDを指定する必要があるわけです。
第四引数は先程値をセットしたMCI_OPEN_PARMS構造体のポインタを指定します。
型自体は(DWORD_PTR)型なのでキャストして下さい。
成功すると0が返り、第四引数のポインタにデバイスの情報が格納されます。
これによってデバイスIDを取得できるわけです。
失敗すると0以外の値が返ります。

エラーが発生した場合は、mciGetErrorString関数で、
詳細なエラー情報を取得できます。

BOOL mciGetErrorString(
DWORD fdwError, // エラーコード
LPTSTR lpszErrorText, // バッファへのポインタ
UINT cchErrorText // バッファのサイズ
);

第一引数にはmciSendCommand関数の戻り値を指定します。
第二引数はエラー内容を格納するためのバッファのポインタ。
第三引数はそのサイズを指定します。

これで再生の準備が整ったので、後は再生するだけです。
今度はmciSendCommand関数の第二引数を、「MCI_PLAY」にします。
また第三引数にMCI_NOTIFYを指定すると、再生終了時に、
MM_MCINOTIFYというウィンドウメッセージを親ウィンドウに送信してくれます。
その際のWPARAMにはそのときの詳細なメッセージが格納されています。
MCI_NOTIFY_SUCCESSFULであれば正常に再生が終了したという意味です。
他にもいくつかありますが、特に使わないのでMSDNを参照して下さい。
LPARAMには再生が終了したデバイスのデバイスIDが入っています。

第四引数にはMCI_PLAY_PARMS型構造体のポインタを指定します。

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

第三引数にMCI_FROMやMCI_FROMを使った場合、dwFromメンバやdwToメンバに値を代入すると、
それぞれ、そこを開始位置として再生、そこを終了位置として再生することができます。
第一引数にはMCI_NOTIFYを第三引数で設定していた場合は、
親ウィンドウハンドルを代入しておくことで、
再生終了時にそのウィンドウハンドルにメッセージを飛ばすことができます。

最後にデバイスをクローズするために、mciSendCommand関数の、
第二引数をMCI_CLOSEに設定、第三、第四引数を0にしてデバイスをクローズします。

これが一連の流れです。
これらの関数を使ってWAVファイルとMP3ファイルを再生したコードのサンプルが以下になります。

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")

#define MSG(m) {\
	MessageBoxA(NULL,m,NULL,MB_OK);}

//ウィンドウハンドル
HWND hwnd;
//インスタンスハンドル
HINSTANCE hinst;

//ウィンドウ横幅
#define WIDTH 500
#define HEIGHT 300



LRESULT CALLBACK WinProc(HWND hwnd,UINT msg,WPARAM wp,LPARAM lp)
{
	static MCI_OPEN_PARMS open,open2,open3;
	static MCI_PLAY_PARMS play,play2,play3;

	int result;

	char buf[1000];

	switch(msg){
		case WM_DESTROY:
			mciSendCommand(open.wDeviceID,MCI_CLOSE,0,0);
			mciSendCommand(open2.wDeviceID,MCI_CLOSE,0,0);
			mciSendCommand(open3.wDeviceID,MCI_CLOSE,0,0);

			PostQuitMessage(0);
			return 0;

		case WM_CREATE:

			open.lpstrDeviceType=(LPSTR)MCI_DEVTYPE_WAVEFORM_AUDIO;
			open.lpstrElementName="test.wav";

			open3.lpstrDeviceType=(LPSTR)MCI_DEVTYPE_WAVEFORM_AUDIO;
			open3.lpstrElementName="test2.wav";

			result=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT,
				(DWORD_PTR)&open);

			result=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT,
				(DWORD_PTR)&open3);

			//エラーなら0以外が返る
			if(result){
				//エラー取得
				mciGetErrorString(result,buf,sizeof(buf));

				MSG(buf);

				PostQuitMessage(0);

				return -1;
			}

			//mp3再生の場合
			open2.lpstrDeviceType="MPEGVideo";
			open2.lpstrElementName="test.mp3";
			result=mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
				(DWORD_PTR)&open2);

			//エラーなら0以外が返る
			if(result){
				//エラー取得
				mciGetErrorString(result,buf,sizeof(buf));

				MSG(buf);

				PostQuitMessage(0);

				return -1;
			}

			play.dwCallback=(DWORD)hwnd;
			play2.dwCallback=(DWORD)hwnd;
			play3.dwCallback=(DWORD)hwnd;


			return 0;
		case WM_LBUTTONDOWN:

			mciSendCommand(open.wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&play);

			return 0;

		case WM_KEYDOWN:

			mciSendCommand(open2.wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&play2);

			return 0;

		case WM_RBUTTONDOWN:

			mciSendCommand(open3.wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD_PTR)&play3);
			
			return 0;

		case MM_MCINOTIFY:
			if(lp==open.wDeviceID){

						if(wp==MCI_NOTIFY_SUCCESSFUL){
							MSG("再生完了");
							//シークバーを先頭に戻す
							mciSendCommand(open.wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,0);
						}
						return 0;
			}else if(lp==open2.wDeviceID){

						if(wp==MCI_NOTIFY_SUCCESSFUL){
							MSG("再生完了");
							//シークバーを先頭に戻す
							mciSendCommand(open2.wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,0);
						}
						return 0;
			}else if(lp==open3.wDeviceID){
						
						if(wp==MCI_NOTIFY_SUCCESSFUL){
							MSG("再生完了");
							//シークバーを先頭に戻す
							mciSendCommand(open3.wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,0);
						}
						return 0;
			}
		
	}
	return DefWindowProc(hwnd,msg,wp,lp);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
	MSG msg;
	WNDCLASS wc;

	wc.style=CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc=WinProc;
	wc.cbClsExtra=wc.cbWndExtra=0;
	wc.hInstance=hInstance;
	wc.hCursor=wc.hIcon=NULL;
	wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszClassName="test";
	wc.lpszMenuName=NULL;
	
	if(!RegisterClass(&wc)){
		MSG("クラスの登録失敗");
		return -1;
	}

	
	//インスタンスハンドル
	hinst=hInstance;

	hwnd=CreateWindowA("test","テストウィンドウ",WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		0,0,400,400,NULL,NULL,hInstance,NULL);


	if(hwnd==NULL){
		MSG("ウィンドウ作成失敗");
		return -1;
	}


	//エラーチェック用変数
	int check;

	while(check=GetMessage(&msg,NULL,0,0)){
		if(check==-1){
			break;
		}
		DispatchMessage(&msg);
	}

	//クラス解放
	UnregisterClass("test",hinst);

	return 0;

}

プロジェクトファイルがあるフォルダに、
test.wav,test2.wav,test.mp3ファイルを置いて実行すると、
左クリック時にtest.wavファイルの再生、
右クリック時にtest2.wavファイルの再生、
キーボード押下時にtest.mp3ファイルが再生されます。

上記コードでは再生が終了した際に、
完了メッセージを出して、再生位置を最初に戻しています。
再生位置を変更する場合は、mciSendCommand関数の第二引数を、
「MCI_SEEK」に設定し、第三引数にその位置を指定します。
先頭に戻す場合はMCI_SEEK_TO_STARTを指定すればよいです。
他にも最初に説明したとおり、
MCI_STOPやMCI_PAUSEなどを使って停止や一時停止をすることもできます。
また音声ファイルだけでなく、デバイスタイプを動画再生用のデバイスに変更すれば、
動画の再生も可能です。

なお、この再生方法だと違うファイルの同時再生が可能です。

今回の説明は以上です。
次回は動画の再生(MCIウィンドウ)について説明します。

>> 【動画の再生(MCIウィンドウ)】に進む
>> WINAPI入門トップに戻る