>> 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入門トップに戻る
●更新履歴
2016/08/16 Java入門ページにページを幾つか追加
2016/04/08 Java入門ページ作成
2016/03/09 メニューレイアウト変更。ブラウザキャッシュのクリアをお願い致します。
2016/03/09 PDOトランザクション、自動コミットモードをオフ追加
2016/03/09 PDO 例外処理 try catch追加
2016/03/09 PDO update文実行追加
2016/03/09 PDO delete文実行追加
2016/03/09 PDO insert文実行追加
2016/03/09 PDO selectでデータを取得、fetchAll、queryメソッド追加
2016/03/09 PDO bindValueとbindParamの違い追加
2016/03/09 PDO prepare プリペアドステートメントの使い方追加
2016/03/04 ソースコードをクリップボードにコピーする機能を追加
2016/03/04 C言語、C++のページのソースコードを一部修正
2014/01/31 C言語関数一覧ページに11ページほど追加
2014/01/31 C言語関数一覧ページに30ページほど追加
2014/01/30 C言語関数一覧ページ作成中
2013/07/01 レイアウト変更に伴いブラウザキャッシュのクリアをお願いします。
2013/07/01 MySQL入門ページ作成
2013/07/01 PHP入門ページにSQLite学習項目追加
2013/06/25 ドメイン変更、レイアウトを一部変更
2013/03/14 レイアウトを一部変更
2012/08/13 C言語よくある課題・宿題ページ開設!
2012/08/13 シューティングゲーム作成第33章追加!
2012/08/11 ドメイン変更&サーバ移設完了
2012/04/21 シューティングゲームプログラミング第2,3章の内容を修正
2012/04/19 シューティングゲームプログラミング第2章の内容を修正
2012/04/03 Googleカスタム検索を設置!
2012/04/03 シューティングゲームプログラミング第32章追加!
2012/04/03 シューティングゲームプログラミング第31章追加!
2012/03/31 サイトをリニューアルしました!
2012/03/25 シューティングゲームプログラミング第30章追加!
2012/03/19 シューティングゲームプログラミング第29章追加!
2012/03/16 シューティングゲームプログラミング第28章追加!
2012/02/27 シューティングゲームプログラミング第27章追加!
2012/02/03 シューティングゲームプログラミング第26章追加!
2012/01/31 シューティングゲームプログラミング第25章追加!
2012/01/20 シューティングゲームプログラミング第23,24章追加!
2012/01/11 シューティングゲームプログラミング第22章追加!
2012/01/05 トップページ、ゲームプログラミング関連のトップページのデザインを変更
2012/01/04 シューティングゲームプログラミング第21章追加!
2012/01/01 シューティングゲームプログラミング第20章追加!
2011/12/25 シューティングゲームプログラミング第19章追加!
2011/12/22 シューティングゲームプログラミング第18章追加!
2011/12/18 シューティングゲームプログラミング第17章追加!
2011/12/17 シューティングゲームプログラミングページOPEN!
2011/11/21 ゲームプログラミングページOPEN!
2011/11/21 サイトデザインを大幅に変更
2011/11/17 TOPページのデザインを変更。相互リンクページに、複数サイト追加。
2011/11/06 WINAPI学習ページ(33~36章)追加
2011/11/05 WINAPI学習ページ(20~32章)追加
2011/10/27 WINAPI学習ページ(14~19章)追加
2011/10/21 WINAPI学習ページ(13章)追加
2011/10/21 サイトマップ、連絡ページ追加
2011/10/17 WINAPI学習ページ(6~11章)追加
2011/10/16 WINAPI学習ページ(1~5章)追加
2011/10/13 全体のレイアウト変更
2011/10/07 PHP学習ページ(8~11章)追加
2011/10/06 PHP学習ページ(1~7章)作成
2011/10/06 JavaScriptリファレンスページ作成
2011/10/05 C言語学習ページ発展編(10~14章)追加
2011/10/04 C言語学習ページ発展編(1~9章)追加。
2011/10/03 HTML/CSSリファレンスのページ追加。(個々の詳細ページは作成中)
2011/09/30 HTML学習ページ(8章)追加
2011/09/29 JavaScript学習ページ(12~17章)追加
2011/09/28 JavaScript学習ページ(1~11章)追加
2011/09/27 HTML学習ページ(4~7章)追加
2011/09/26 C言語学習ページ(27章)追加、C++学習ページ(17章)、HTML学習ページ(1~3章)追加
2011/09/25 C言語学習ページ(23~26章)を追加
2011/09/24 C++学習ページ(9~16章)追加
2011/09/23 C++学習ページ(3~8章)追加
2011/09/22 C言語の学習ページ(22章)とC++学習ページ(1~2章)追加
2011/09/21 C言語の学習ページ(15章~21章)を追加
2011/09/20 C言語の学習ページ(10章~14章)を追加
2011/09/19 サイト作成(随時更新予定)