MCIコマンド実践

概要:MCIコマンドを使った実践的な処理

前節では基本的な再生とよく使うコマンドについて解説しました。
しかし、単純にコマンドを実行するだけでは実用的なプログラムは作れません。
MCIにはもっとモット色々な機能があります。
そして、ここからがMCIの凄いところです。

■処理が終了した事を知らせるメッセージ

全てのコマンドで使える共通フラグとして MCI_NOTIFY があります。
これは指定したコマンドの処理が終了した時にメッセージを発行させるフラグです。
MCI_PLAY なら、再生終了時にメッセージが発行されます。
発行されるメッセージは MM_MCINOTIFY です。

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


WPARAM にはメッセージが発行された原因が格納されています。

MCI_NOTIFY_ABORTED コマンド実行中に中断した
MCI_NOTIFY_FAILURE コマンド実行中にエラーが発生した
MCI_NOTIFY_SUCCESSFUL 最後まで正しく処理した
MCI_NOTIFY_SUPERSEDED (わかりません……)

再生を停止した場合には MCI_NOTIFY_ABORTED が発行される事に注意して下さい。

LPARAM にはメッセージを発行したデバイス識別子が格納されています。

また、mciSendCommand 関数そのものが失敗した時にはメッセージは発行されません。

その他にも共通フラグとして MCI_WAIT があります。
これは指定したコマンドの処理が終了するまで制御を返さないようにするフラグです。

■構造体

MCI_NOTIFY フラグを指定すると、
mciSendCommand 関数は渡された構造体に
メッセージを送るウィンドウのハンドルが格納されている事を知ります。
構造体はそれぞれのコマンド専用のものを使うのが普通ですが、
ウィンドウハンドルを格納するだけが目的なら共通構造体を使う事もできます。

MCI_PLAY 専用の構造体 MCI_PLAY_PARMS は次のように定義されています。

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


dwCallback はコールバックウィンドウのハンドルです。
dwFrom は再生開始位置、dwTo は再生終了位置です。
通常はミリ秒単位で指定します( MCI_SET コマンドで単位を変更できる)。

dwFrom , dwTo を使いたい時は MCI_FROM , MCI_TO フラグを指定する必要があります。

共通構造体 MCI_GENERIC_PARMS は次のように定義されています。

typedef struct {
  DWORD_PTR dwCallback;
} MCI_GENERIC_PARMS;


一つしかメンバがないので、直接コールバックウィンドウのハンドルを渡した方が手っ取り早いです。

■ループ

再生を無限ループさせるフラグはありません。
ループさせたい場合は、MCI_PLAY コマンドに MCI_NOTIFY フラグを指定して、
MM_MCINOTIFY メッセージの処理で再び再生させます。

一番最初の再生も MM_MCINOTIFY メッセージで処理させる事ができます。
MCI_OPEN コマンドに MCI_NOTIFY フラグを指定すれば、
オープン処理が終了した時に MM_MCINOTIFY メッセージが発行されます。

static MCI_OPEN_PARMS mop;
static MCI_PLAY_PARMS mpp;

case WM_CREATE:
    //オープン
    mop.lpstrDeviceType="WaveAudio";
    mop.lpstrElementName="Windows XP Startup.wav";
    mop.dwCallback=(DWORD)hWnd;
    mciSendCommand(NULL,MCI_OPEN,
        MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_NOTIFY,(DWORD)&mop);
    //MCI_PLAY_PARMS構造体の設定
    mpp.dwCallback=(DWORD)hWnd;
    return 0;
case MM_MCINOTIFY:    //再生(正常終了した時だけ)
    if(wParam!=MCI_NOTIFY_SUCCESSFUL){
        MessageBox(hWnd,"異常終了しました",NULL,MB_OK);
        return 0;
    }
    mciSendCommand((MCIDEVICEID)lParam,MCI_SEEK,MCI_SEEK_TO_START,0);
    mciSendCommand((MCIDEVICEID)lParam,MCI_PLAY,MCI_NOTIFY,(DWORD)&mpp);
    return 0;

MCI_PLAY_PARMS 構造体の代わりに、MCI_GENERIC_PARMS 構造体、
またはコールバックウィンドウのハンドルを直接渡しても同じです。

//MCI_GENERIC_PARMS構造体を使う
static MCI_GENERIC_PARMS mgp;
mgp.dwCallback=(DWORD)hWnd;
mciSendCommand((MCIDEVICEID)lParam,MCI_PLAY,MCI_NOTIFY,(DWORD)&mgp);

//コールバックウィンドウのハンドルを直接渡す
mciSendCommand((MCIDEVICEID)lParam,MCI_PLAY,MCI_NOTIFY,(DWORD)&hWnd);


MM_MCINOTIFY メッセージ処理では、コマンドが最後まで正しく終了した時だけ
再び最初から再生させるようにしています。
これは、停止させる(再生を中断させる)事でも MM_MCINOTIFY メッセージが発行されてしまう為です。
停止させたという事は、ユーザーはそれ以上のループを望んではいないでしょう。

また、LPARAM にはコマンドを発行したデバイス識別子が格納されているので、
この値を使う事で、再生が終了したデバイスに対して処理する事ができます。
複数のWAVEのどれかをループさせるような場面では、
変数によって制御するよりもスマートなプログラムを書く事ができます。

ただし、MM_MCINOTIFY メッセージから発行したコマンドを区別する方法はありません。
必要なら、変数を使って制御して下さい。

case WM_LBUTTONDOWN:    //停止
    mciSendCommand(mop.wDeviceID,MCI_STOP,0,0);
    mciSendCommand(mop.wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,0);
    return 0;
case WM_RBUTTONDOWN:    //再生
    mciSendCommand(mop.wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)&mpp);
    return 0;

★☆ ソースファイル表示 ☆★

■シーク

再生開始場所を任意に設定したい場合は MCI_SEEK コマンドを使います。
MCI_SEEK_TO_START , MCI_SEEK_TO_END はそれぞれ最初と最後に移動する、構造体を必要としないフラグですが、
MCI_TO フラグを指定すると MCI_SEEK_PARMS 構造体に設定した任意の場所に移動する事ができます。

typedef struct {
  DWORD_PTR dwCallback;
  DWORD dwTo;
} MCI_SEEK_PARMS;


dwCallback はコールバックウィンドウのハンドルです。
dwTo に現在の時間単位で移動場所を指定します。
通常はミリ秒単位です( MCI_SET コマンドで単位を変更できる)。

範囲を超えた時間を指定すると mciSendCommand 関数が失敗します。

再生中にシークした場合は、シークによって変更した再生開始場所より
前を再生している場合は変更が直ぐに適応されますが、
既に通過している場合は次から適応され、現在の再生には影響ありません。
再生開始場所なのですから、既に通過していれば意味がありませんものね。

■エラー文字列

mciSendCommand 関数は失敗するとエラーコード(DWORD型)を返しますが、
このまま数値を表示しても意味不明なので、
これを文字列に変換する為に mciGetErrorString 関数を使います。

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


fdwError には mciSendCommand 関数の戻り値を渡します。

この関数が返す文字列は最大で128文字になる可能性があります。

mciSendCommand 関数が成功したか、失敗したかはその戻り値で判断できます。
成功すると 0 、失敗すると 0 以外が返るので、
0 以外が返された時に mciGetErrorString 関数に渡しましょう。

// winmm.lib をリンクする
#pragma comment(lib,"winmm")

#define BUFFERSIZE    256

LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    static MCI_OPEN_PARMS mop;
    MCI_SEEK_PARMS seek;
    static DWORD ms=0;
    MCIERROR mciErr;
    TCHAR str[BUFFERSIZE];

    switch(uMsg){
        case WM_CREATE:    //オープン
            mop.dwCallback=(DWORD)hWnd;
            mop.lpstrDeviceType="WaveAudio";
            mop.lpstrElementName="Windows XP Startup.wav";
            mciSendCommand(NULL,MCI_OPEN,
                MCI_NOTIFY | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,(DWORD)&mop);
            return 0;
        case MM_MCINOTIFY:    //再生
            mciErr=mciSendCommand((MCIDEVICEID)lParam,MCI_PLAY,0,0);
            if(mciErr){
                mciGetErrorString(mciErr,str,BUFFERSIZE);
                MessageBox(hWnd,str,"PLAY",MB_OK);
            }
            return 0;
        case WM_LBUTTONDOWN:    //シーク(後退)
            seek.dwCallback=(DWORD)hWnd;
            seek.dwTo=(ms-=500);    //ミリ秒単位
            mciErr=mciSendCommand(mop.wDeviceID,MCI_SEEK,MCI_NOTIFY | MCI_TO,(DWORD)&seek);
            if(mciErr){
                mciGetErrorString(mciErr,str,BUFFERSIZE);
                MessageBox(hWnd,str,"後退",MB_OK);
            }
            return 0;
        case WM_RBUTTONDOWN:    //シーク(前進)
            seek.dwCallback=(DWORD)hWnd;
            seek.dwTo=(ms+=500);    //ミリ秒単位
            mciErr=mciSendCommand(mop.wDeviceID,MCI_SEEK,MCI_NOTIFY | MCI_TO,(DWORD)&seek);
            if(mciErr){
                mciGetErrorString(mciErr,str,BUFFERSIZE);
                MessageBox(hWnd,str,"前進",MB_OK);
            }
            return 0;
        case WM_DESTROY:    //クローズ
            mciSendCommand(mop.wDeviceID,MCI_CLOSE,0,0);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

MCI_NOTIFY フラグを指定しても、mciSendCommand 関数そのものが失敗すればメッセージは発行されません。
サンプルでは MCI_PLAY でもエラーチェックを行っていますが、
不正な再生開始場所が指定された時はメッセージが送られて来ないので、
MCI_PLAY コマンドは実行されず、従ってエラーも発生しません。

MCI_SET コマンドについては後で解説します。

★☆ ソースファイル表示 ☆★


戻る / ホーム