003-03  前へ←  ホームへ  →次へ  004-01

No.003 - 04 WAVEファイル読込と再生(waveOutProc版)


WAVEファイルを読込再生するプログラムを紹介します。

前回とほとんど同じですが再生プロシージャにwaveOutProcを使用しています。
WAVEデータ構造体を作成し、そのポインタを再生プロシージャに渡しています。

waveOutProcのプロシージャ・メッセージIDはWINDWプロシージャから若干変化しています。
WM_WOM_OPEN → WOM_OPEN
WM_WOM_DONE → WOM_DONE
WM_WOM_CLOSE → WOM_CLOSE

ここで注意すべきなのはWOM_OPENメッセージの時waveOutWrite関数を使ってはいけません。
Windowコールバックでは大丈夫(?)ですがwaveOutProcではうまくいきません。(デバイス側の準備が間に合わない?)

Borland C++ Compiler でコンパイルして下さい

wave_player.c

#include <windows.h>
#include <stdio.h>
#include <math.h>

#define IDC_STATIC      -1
#define IDC_TEXT        1001
#define IDC_ONOFF       1002
#define IDC_FILE_OPEN   1003

#define SAMPLE_RATE         11025

TCHAR app_name [] = TEXT("PlayWave");

typedef struct {
    HWND hwnd;
    HWAVEOUT        hwave;                  // WAVEデバイス・ハンドル
    int shut_off_flag;
    PBYTE       p_buffer1, p_buffer2;   // 音データ格納バッファ(ポインタ)
    PWAVEHDR    p_whdr1, p_whdr2;       // 音データ・ヘッダ(ポインタ)
    char *data_buf;
    int data_buf_len, index;
}WAVE_DATA;

// WAVEファイル読込
read_file(HWND hwnd, char *file_name, WAVEFORMATEX *p_wfe, PBYTE *data_buf, int *data_buf_len){
    FILE *fp;
    char rb[5];
    int ri, byte_len, fmt_len;
    short rs;
    OPENFILENAME ofn;
    memset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter =   TEXT("wave ファイル\0*.wav\0")
                TEXT("すべて表示 \0*.*\0\0");
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = file_name;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_FILEMUSTEXIST;
    if (!GetOpenFileName(&ofn)) return 0;

    rb[4]='\0';
    fp=fopen(file_name, "rb");
    if (!fp) return -1;
    fread( rb, 1, 4, fp);   if (strcmp(rb, "RIFF")) return -1;
    fread(&ri, 1, 4, fp);   byte_len=ri;
    fread( rb, 1, 4, fp);   if (strcmp(rb, "WAVE")) return -2;  // WAVEヘッダ

    // == 波形フォーマット
    fread( rb, 1, 4, fp);   if (strncmp(rb, "fmt", 3)) return -3;   // fmtチャンク
    fread(&ri, 1, 4, fp);   fmt_len=ri;     // fmtチャンクバイト数
    fread(&rs, 1, 2, fp);   p_wfe->wFormatTag=rs;       // fmtフォーマットID
    fread(&rs, 1, 2, fp);   p_wfe->nChannels=rs;            // チャンネル数  ==1 or ==2
    fread(&ri, 1, 4, fp);   p_wfe->nSamplesPerSec=ri;   // サンプリング・レート
    fread(&ri, 1, 4, fp);   p_wfe->nAvgBytesPerSec=ri;  // データ速度 (Byte/sec)
    fread(&rs, 1, 2, fp);   p_wfe->nBlockAlign=rs;      // ブロックサイズ (sample×チャンネル数/Byte)
    fread(&rs, 1, 2, fp);   p_wfe->wBitsPerSample=rs;   // サンプル当たりのビット数
    p_wfe->cbSize = 0;

    fread(rb, 1, 4, fp);    if (strcmp(rb, "data")) return -4;
    fread(&ri, 1, 4, fp);   *data_buf_len=ri;

    //サンプリングデータ部  (サンプルビット数)×(チャンネル数)×(サンプル数)
    (*data_buf) = malloc(*data_buf_len);
    fread(*data_buf, 1, *data_buf_len, fp);

    fclose(fp);
    return 0;
}

// データをバッファに書きこむ
VOID set_buf(PWAVEHDR p_whdr, char *data_buf, int *index, int data_buf_len){
     int i, len=min(p_whdr->dwBufferLength, data_buf_len- *index);
     PBYTE p_buffer=p_whdr->lpData;
     int i_index= *index, rem_len;

    // 音データを出力バッファに入れる
    for(i=0; i<len; i++)     {
        p_buffer [i] = data_buf[i_index];
        i_index++;
    }
    // 音データ末尾のとき出力バッファ残りを0で埋める
    rem_len=p_whdr->dwBufferLength-len;
    for(i=len; i<rem_len; i++) p_buffer [i] = 0;
    *index=i_index;
}
// プロシージャ
void CALLBACK waveOutProc( HWAVEOUT hwave, UINT msg, DWORD instance, DWORD param1, DWORD param2 ){
    WAVE_DATA *p_wave_data=(WAVE_DATA*)instance;
    switch(msg){
    case WOM_OPEN : break;  // WAVEデバイス開いた
    case WOM_DONE : // WAVEデバイス出力分の再生終了
        if(p_wave_data->shut_off_flag || p_wave_data->index>=p_wave_data->data_buf_len) {
            waveOutClose(hwave);    // MM_WOM_CLOSEメッセージ送信
            break;
        }

        set_buf((PWAVEHDR)param1, p_wave_data->data_buf, &p_wave_data->index, p_wave_data->data_buf_len);
        waveOutWrite(hwave,(PWAVEHDR)param1, sizeof(WAVEHDR));  // 再生後、MM_WOM_DONEメッセージ送信
        break;
    case WOM_CLOSE:     // WAVEデバイス閉じる
        // p_whdr1、p_whdr2 のスワップアウト禁止状態を解除
        waveOutUnprepareHeader(hwave, p_wave_data->p_whdr1, sizeof(WAVEHDR));
        waveOutUnprepareHeader(hwave, p_wave_data->p_whdr2, sizeof(WAVEHDR));

        free(p_wave_data->p_whdr1);
        free(p_wave_data->p_whdr2);
        free(p_wave_data->p_buffer1);
        free(p_wave_data->p_buffer2);

        p_wave_data->hwave = NULL;
        SetDlgItemText(p_wave_data->hwnd, IDC_ONOFF, TEXT("再生"));
        break;
    }
    return;
};

// プロシージャ
BOOL CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam){
    static WAVE_DATA    wave_data;
    static WAVEFORMATEX waveformat;             // WAVEデバイス初期化構造体(WAVEデバイスを開くため専用)
    static BOOL         app_closing_flag;   // 音の再生停止フラグ、プログラム終了フラグ
    static char file_name[MAX_PATH];

    switch(msg)  {
    case WM_CREATE: // プログラム起動時
        wave_data.hwnd=hwnd;
        wave_data.data_buf=NULL;
        wave_data.data_buf_len=0;
        wave_data.index=0;
        wave_data.p_whdr1=wave_data.p_whdr2=NULL;       // 音データ・ヘッダ(ポインタ)
        wave_data.p_buffer1=wave_data.p_buffer2=NULL;   // 音データ格納バッファ(ポインタ)
        wave_data.hwave=NULL;                   // WAVEデバイス・ハンドル
        wave_data.shut_off_flag=0;

        file_name[0]='\0';
        CreateWindow( TEXT("STATIC"), TEXT("ファイル選択して下さい"), WS_CHILD | WS_VISIBLE | SS_LEFT, 10,10,320,18,
                        hwnd, (HMENU)IDC_TEXT, (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), NULL
        );
        CreateWindow(TEXT("BUTTON"), TEXT("再生"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 80,36,50,28,
                        hwnd, (HMENU)IDC_ONOFF, (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), NULL
        );
        CreateWindow(TEXT("BUTTON"), TEXT("参照"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 20,36,50,28,
                        hwnd, (HMENU)IDC_FILE_OPEN, (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), NULL
        );
        return TRUE;
    case WM_COMMAND:    // コマンド・メッセージ(このプログラムでは IDC_FILE_OPEN, IDC_ONOFF のみ)
        switch(LOWORD(wparam)) {
        case IDC_FILE_OPEN:
            if (read_file(hwnd, file_name, &waveformat, &wave_data.data_buf, &wave_data.data_buf_len)<0)
               MessageBox(hwnd, TEXT("ファイルを開けませんでした!"), app_name, MB_ICONEXCLAMATION | MB_OK);
            if (file_name[0]==0) break;
            SetDlgItemText(hwnd, IDC_TEXT, file_name);  // 表示ファイル名の更新
            InvalidateRect(hwnd, NULL, 1);
            break;
        case IDC_ONOFF:
            if(wave_data.data_buf == NULL) {
                MessageBox(hwnd, TEXT("先にファイルを選んで下さい"), app_name, MB_ICONEXCLAMATION | MB_OK);
                break;
            }

            // hwave が NULLなら(まだWAVEデバイスを開いてまいなら) WAVEデバイスを開く
            if(wave_data.hwave == NULL) {    // 2 ヘッダ(WAVEHDR) と 2 バッファの メモリ確保 
                // 音データ・バッファ確保 
                wave_data.p_whdr1 = malloc(sizeof(WAVEHDR));
                wave_data.p_whdr2 = malloc(sizeof(WAVEHDR));
                wave_data.p_buffer1 = malloc(waveformat.nAvgBytesPerSec);
                wave_data.p_buffer2 = malloc(waveformat.nAvgBytesPerSec);

                wave_data.shut_off_flag = FALSE;    // 音を出力してるかどうかのフラグ
                wave_data.index = 0;                // 再生位置リセット

                // WAVEデバイスを開く
                if(waveOutOpen(&wave_data.hwave, WAVE_MAPPER, &waveformat,(DWORD)waveOutProc, (DWORD)&wave_data, CALLBACK_FUNCTION)
                          != MMSYSERR_NOERROR){ // WAVEデバイスを開くのに失敗したとき
                     free(wave_data.p_whdr1);
                     free(wave_data.p_whdr2);
                     free(wave_data.p_buffer1);
                     free(wave_data.p_buffer2);

                     wave_data.hwave = NULL;
                     MessageBeep(MB_ICONEXCLAMATION);
                     MessageBox(hwnd, TEXT("WAVEデバイスのオープンに失敗しました!"),
                                                    app_name, MB_ICONEXCLAMATION | MB_OK);
                     return TRUE;
                }

                // ヘッダの準備とセットアップ
                wave_data.p_whdr1->lpData       = wave_data.p_buffer1;
                wave_data.p_whdr1->dwBufferLength  = waveformat.nAvgBytesPerSec;
                wave_data.p_whdr1->dwBytesRecorded = 0;
                wave_data.p_whdr1->dwUser       = 0;
                wave_data.p_whdr1->dwFlags  = 0;
                wave_data.p_whdr1->dwLoops  = 1;
                wave_data.p_whdr1->lpNext       = NULL;
                wave_data.p_whdr1->reserved     = 0;

                waveOutPrepareHeader(wave_data.hwave, wave_data.p_whdr1, sizeof(WAVEHDR));  // スワップアウト不可にする

                wave_data.p_whdr2->lpData       = wave_data.p_buffer2;
                wave_data.p_whdr2->dwBufferLength  = waveformat.nAvgBytesPerSec;
                wave_data.p_whdr2->dwBytesRecorded = 0;
                wave_data.p_whdr2->dwUser       = 0;
                wave_data.p_whdr2->dwFlags  = 0;
                wave_data.p_whdr2->dwLoops  = 1;
                wave_data.p_whdr2->lpNext       = NULL;
                wave_data.p_whdr2->reserved     = 0;

                waveOutPrepareHeader(wave_data.hwave, wave_data.p_whdr2, sizeof(WAVEHDR));  // スワップアウト不可にする

                SetDlgItemText(hwnd, IDC_ONOFF, TEXT("停止"));

                // WAVEデバイスに2バッファ 送信
                set_buf(wave_data.p_whdr1, wave_data.data_buf, &wave_data.index, wave_data.data_buf_len);
                waveOutWrite(wave_data.hwave, wave_data.p_whdr1, sizeof(WAVEHDR));
                set_buf(wave_data.p_whdr2, wave_data.data_buf, &wave_data.index, wave_data.data_buf_len);
                waveOutWrite(wave_data.hwave, wave_data.p_whdr2, sizeof(WAVEHDR));
            }
            else {  // 音を止める
                wave_data.shut_off_flag = TRUE;
                waveOutReset(wave_data.hwave);
            }
            return TRUE;
        }
        break;
    case WM_CLOSE:
        if(wave_data.hwave != NULL) {   // WAVEデバイスを開いてるなら
            wave_data.shut_off_flag = TRUE;
            app_closing_flag = TRUE;
            waveOutReset(wave_data.hwave);  // 再生停止後、MM_WOM_DONEメッセージ送信( → MM_WOM_CLOSE → 終了)
        }
        else DestroyWindow(hwnd); 
        break;
    case WM_DESTROY:
        free(wave_data.data_buf);
        PostQuitMessage(0);
        break;
    }
    return DefWindowProc(hwnd, msg, wparam, lparam);
    return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
    HWND hwnd;
    MSG msg;
    WNDCLASS winc;

    winc.style      = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc    = DlgProc;
    winc.cbClsExtra     = winc.cbWndExtra = 0;
    winc.hInstance      = hInstance;
    winc.hIcon      = LoadIcon(NULL, IDI_APPLICATION);
    winc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    winc.hbrBackground  = (HBRUSH)GetSysColorBrush(COLOR_BTNFACE );
    winc.lpszMenuName   = NULL;
    winc.lpszClassName  = TEXT("play_wave_winc");

    if (!RegisterClass(&winc)) return 1;

    hwnd = CreateWindow(
        TEXT("play_wave_winc"), app_name,   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        100, 100, 350, 100, NULL, NULL, hInstance, NULL
    );

    if (hwnd == NULL) return 1;

    while (GetMessage(&msg, NULL, 0, 0 )) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


WAVEファイルフォーマットについては以下のサイトを参考にさせていただきました。
参考: WAVEファイルフォーマット (近藤正芳のウェブページ)


上記ソースは全て自由に改編して使ってかまいませんが必ず自己責任でね。 

2004.5.2 G丸


 003-03  前へ←  ホームへ  →次へ  004-01