現在の再生位置の取得

概要:現在再生している位置を取得する

現在の再生位置の取得は関数を使えば一発ですが、機能が豊富なのでちょっと難しいです。
でも音楽プレイヤーには必須ですし、処理の幅も広がる重要な機能です。

■waveOutGetPosition関数

現在の再生位置を取得するには waveOutGetPosition 関数を使います。

MMRESULT waveOutGetPosition(
  HWAVEOUT hwo,
  LPMMTIME pmmt,
  UINT cbmmt
);


hwo にはウェーブフォームオーディオ出力デバイスのハンドルを指定します。
pmmt にはMMTIME構造体のアドレスを指定します。
cbmmt にはMMTIME構造体のサイズをバイト単位で指定します。

この関数を呼び出す前にMMTIME構造体のwTypeメンバを設定して希望の時間形式を示す必要があります。
ただし、希望が叶うとは限りません。

関数が成功すると pmmt に現在の再生位置が格納されます。
もしサポートしていない時間形式を指定していた場合は、
代替形式を wType メンバに設定して、代替形式の時間を格納します。
サポートされていれば希望形式で時間が格納されます。

typedef struct mmtime_tag {
  UINT wType;
  union {
    DWORD ms;
    DWORD sample;
    DWORD cb;
    DWORD ticks;
    struct {
      BYTE hour;
      BYTE min;
      BYTE sec;
      BYTE frame;
      BYTE fps;
      BYTE dummy;
      BYTE pad[2]
    } smpte;
    struct {
      DWORD songptrpos;
    } midi;
  } u;
} MMTIME;

UINT wType union u 説明
TIME_BYTES cb ファイルの先頭からのバイト数
TIME_MIDI midi MIDI時間
TIME_MS ms ミリ秒
TIME_SAMPLES sample WAVEのサンプル数(チャンネル数に非依存)
TIME_SMPTE smpte SMPTE規格フォーマットの時間
TIME_TICKS ticks MIDIストリームのチック数

WAVEの場合は TIME_BYTES , TIME_MS , TIME_SAMPLES が使えそうです。
TIME_BYTES , TIME_SAMPLES から TIME_MS への変換式は以下の通りです。

MMTIME mmt;
static int ms;

○TIME_BYTES → TIME_MS

ms=1000 * mmt.u.cb / wfe.nAvgBytesPerSec;

○TIME_SAMPLES → TIME_MS

ms=1000 * mmt.u.sample / wfe.nSamplesPerSec;

TIME_SAMPLES はチャンネル数に非依存です。

それでは、ステレオWAVEデータにおける現在の再生位置をミリ秒単位で取得するプログラムを作ってみましょう。
時間の更新はタイマーを利用します。

#define SRATE    8000    //標本化周波数(1秒間のサンプル数)
#define F        400     //周波数(1秒間の波形数)

static WAVEFORMATEX wfe;
static HWAVEOUT hWaveOut;
static WAVEHDR whdr;
static LPBYTE lpWave;
int i,len;

case WM_CREATE:
    wfe.wFormatTag=WAVE_FORMAT_PCM;
    wfe.nChannels=2;    //ステレオ
    wfe.wBitsPerSample=8;    //量子化ビット数
    wfe.nBlockAlign=wfe.nChannels * wfe.wBitsPerSample/8;
    wfe.nSamplesPerSec=SRATE;    //標本化周波数
    wfe.nAvgBytesPerSec=wfe.nSamplesPerSec * wfe.nBlockAlign;

    waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfe,(DWORD)hWnd,0,CALLBACK_WINDOW);

    lpWave=(LPBYTE)calloc(wfe.nAvgBytesPerSec,2);    //2秒分

   len=SRATE/F;    //波長
    for(i=0;i<SRATE*2;i++){    //波形データ作成
        if(i%len < len/2){
            lpWave[2*i  ]=128+64;    //左
            lpWave[2*i+1]=128+32;    //右
        }else{
            lpWave[2*i  ]=128-64;    //左
            lpWave[2*i+1]=128-32;    //右
        }
    }

    whdr.lpData=(LPSTR)lpWave;
    whdr.dwBufferLength=wfe.nAvgBytesPerSec * 2;
    whdr.dwFlags=WHDR_BEGINLOOP | WHDR_ENDLOOP;
    whdr.dwLoops=1;

    waveOutPrepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
    waveOutWrite(hWaveOut,&whdr,sizeof(WAVEHDR));

    SetTimer(hWnd,1,50,NULL);
    return 0;

タイマーメッセージ処理では現在の再生時間を取得して再描画メッセージを発行します。
希望の時間形式には TIME_MS を指定しますが、
代替形式として TIME_SAMPLES , TIME_BYTES が使われた場合にも対応できるようにしています。
MMTIME構造体には、希望の形式か代替形式に対応した値が格納されているので、
希望が叶ったかどうかを調べる必要はありません。

MMTIME mmt;
static int ms;

case WM_TIMER:
    mmt.wType=TIME_MS;
    waveOutGetPosition(hWaveOut,&mmt,sizeof(MMTIME));
    switch(mmt.wType){
        case TIME_MS:
            ms=mmt.u.ms;
            break;
        case TIME_SAMPLES:
            ms=1000 * mmt.u.sample / wfe.nSamplesPerSec;
            break;
        case TIME_BYTES:
            ms=1000 * mmt.u.cb / wfe.nAvgBytesPerSec;
            break;
        default:    //非対応
            ms=-1;
            break;
    }
    InvalidateRect(hWnd,NULL,TRUE);
    return 0;

表示する時は、分:秒:ミリ秒 の形式に変換して表示します。
普通に計算するだけです。

HDC hdc;
PAINTSTRUCT ps;
char str[32];

case WM_PAINT:
    hdc=BeginPaint(hWnd,&ps);
    wsprintf(str,"再生時間 %02d:%02d:%03d",(ms/1000)/60,(ms/1000)%60,ms%1000);
    TextOut(hdc,0,0,str,lstrlen(str));
    EndPaint(hWnd,&ps);
    return 0;

連続再生した場合はどうでしょうか。
waveOutGetPosition関数の謎の仕様として、取得した時間は
「ウェーブフォームオーディオ出力デバイスで再生した合計時間である」という問題があります。
つまり、2回以上再生した場合は意図した値が得られません。

これを回避する為にはwaveOutReset関数を呼び出して、現在の再生位置を 0 にリセットする必要があります。

case MM_WOM_DONE:
    waveOutReset((HWAVEOUT)wParam);
    waveOutWrite((HWAVEOUT)wParam,(LPWAVEHDR)lParam,sizeof(WAVEHDR));
    return 0;

ところで、MM_WON_DONE メッセージは
waveOutWrite関数がWAVEHDR構造体のdwLoopsメンバの値だけ
再生を繰り返して、終了した時
に発行されます。
dwLoops に2以上の値を指定した場合も最後の1回しかメッセージは発行されません。
従って、dwLoops に1以上の値を指定した場合は上記の解決策が機能しません。
あまり dwLoops に1以上の値を指定するのは好ましくないって事ですね。

一時停止の動作も確認してみましょう。

case WM_LBUTTONDOWN:
    waveOutPause(hWaveOut);
    return 0;
case WM_RBUTTONDOWN:
    waveOutRestart(hWaveOut);
    return 0;
case WM_DESTROY:
    waveOutReset(hWaveOut);
    waveOutUnprepareHeader(hWaveOut,&whdr,sizeof(WAVEHDR));
    waveOutClose(hWaveOut);
    free(lpWave);
    PostQuitMessage(0);
    return 0;

以上で現在の再生位置の取得は完了です。

さて、取得ができたら設定もやってみたくなるでしょうが、現在の再生位置を設定する関数はありません。
なぜ存在しないのかまではわかりませんが、憤りを感じずにはおれませんね。
というわけで、この関数の作成を後の節で行います。

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


戻る / ホーム