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丸 |