SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

WAVEファイルにリアルタイムで加工を施し再生する

ステレオデータのモノラル化と簡易ボーカルキャンセル機能

  • X ポスト
  • このエントリーをはてなブックマークに追加

ダウンロード ソースファイル (3.2 KB)

WAVEデータの読み込み

 WAVEデータは、大抵RIFFという形式でファイルに収まっています。RIFFでは「チャンク」と呼ばれるまとまりでデータを管理し、各チャンクは「ASCIIコード4文字」「チャンクのサイズ」「データ」の並びで構成されています。

 WAVEデータでは「fmt 」と「data」というチャンクがあります(fmtは四文字にするために後ろにスペースが入っています)。fmt(フォーマットチャンク)にはWAVEの再生に必要な情報が入っています。data(データチャンク)には音のデータが入っています。

 要するに、これを解析してデータを取り出せばいいわけです。ただのファイル入力なので、C言語標準のFILEで読み込んでも構いませんが、面倒なのでAPIに頼りましょう。

 読み込みの流れは次のようになります。

  1. ファイルを開く
  2. WAVEかどうかチェック
  3. 「fmt 」チャンクに入る
  4. フォーマットデータを取得
  5. 「fmt 」チャンクから出る
  6. 「data」チャンクに入る
  7. データを取り出す
  8. 再生中は7.を繰り返す
  9. 「data」チャンクから出る
  10. データを読み込む
  11. ファイルを閉じる

 具体的には次のようなコードになります。

MMIOを使ったWAVEの読み込み
//ファイルネーム
static LPCTSTR openFileName = TEXT("i:\\temp\\test.wav");

//WAVEファイルを読み込むための構造体
static HMMIO hmmio;

WAVEFORMATEX wfe;

//WAVEデータ読み込みのための設定

//WAVEファイルのオープン
hmmio = mmioOpen((LPSTR)openFileName,NULL,MMIO_READ);
if(!hmmio){
    MessageBox(0, "WAVEファイルのオープンに失敗","MyApp", MB_OK);
    return FALSE;
}
//WAVEファイルかのチェック
if(mmioDescend(hmmio,&ckRiff, NULL, 0) != MMSYSERR_NOERROR
    || ckRiff.ckid != FOURCC_RIFF
    || ckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E'))
{
    MessageBox(0, "WAVEファイルではありません","MyApp", MB_OK);
    mmioClose(hmmio, 0);
    return FALSE;
}

/**********************************
 フォーマットチャンクの読み込み
**********************************/
ckRiff.ckid = mmioFOURCC('f', 'm', 't', ' ');
//フォーマットチャンクに侵入
if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) !=
    MMSYSERR_NOERROR){
    MessageBox(0, "フォーマットチャンクの読み込みに失敗",
        "MyApp", MB_OK);
    mmioClose(hmmio, 0);
    return FALSE;
}

//フォーマットチャンクを読み込む
ZeroMemory(&wfe, sizeof(wfe));
if(mmioRead(hmmio, (HPSTR)&wfe, (long)(ckRiff.cksize)) !=
    (long)(ckRiff.cksize))
{
    MessageBox(0, "フォーマットチャンクの読み込みに失敗",
        "MyApp", MB_OK);
    mmioClose(hmmio, 0);
    return FALSE;
}

//フォーマットチャンクから抜け出す
if(mmioAscend(hmmio, &ckRiff, 0) != MMSYSERR_NOERROR){
    MessageBox(0, "フォーマットチャンクの読み込みに失敗",
        "MyApp", MB_OK);
    mmioClose(hmmio, 0);
    return FALSE;
}

/**********************************
 データチャンクの読み込み
**********************************/
ckRiff.ckid = mmioFOURCC('d', 'a', 't', 'a');
//データチャンクに侵入
if(mmioDescend(hmmio, &ckRiff, NULL, MMIO_FINDCHUNK) !=
        MMSYSERR_NOERROR){
    MessageBox(0, "データチャンクの侵入に失敗","MyApp", MB_OK);
    mmioClose(hmmio, 0);
    return FALSE;
}

//16Bitステレオかをチェック
if(wfe.nChannels !=2 || wfe.wBitsPerSample !=16){
    MessageBox(0, "16Bitまたはステレオでないので再生できません",
        "MyApp", MB_OK);
}

//読み込み
mmioRead(hmmio,(HPSTR)buf,bufferSize);

//データチャンクから抜け出す
mmioAscend(hmmio, &ckRiff, 0);
mmioClose(hmmio,0);

 いろいろと面倒なことをしていますが、すべて必要な処理です。

関数説明
mmioOpenファイルを開いてhmmioを返します。
mmioFOURCCFOURCC、つまりASCII4文字のチャンク名を作ります。
mmioDescendチャンクに入ります。
mmioReadデータを読み込みます。
mmioSeek読み位置を変えます(後で使います)。
mmioAscendチャンクから抜け出します。
mmioClosehmmioOpenで開いたファイルを閉じます。

 関数について詳しく説明すると大変なことになるので、これらの詳細は参考資料を確認してください。とりあえず、開き終わった後も使うことになる「mmioRead」と「mmioSeek」は覚えておいてください。

mmioRead
LONG mmioRead(
  HMMIO hmmio,//ハンドル
  HPSTR pch,//データを入れるバッファ
  LONG cch//読み込むサイズ
);
mmioSeek
LONG mmioSeek(
  HMMIO hmmio, //ハンドル
  LONG lOffset,//オフセット
  int iOrigin  //どの位置から「lOffset」分ずらすかを決める。
);
//SEEK_CUR : 現在のファイル位置から lOffset バイトの位置を探します。
//SEEK_END : ファイルの終わりから lOffset バイトの位置を探します。
//SEEK_SET : ファイルの先頭から lOffset バイトの位置を探します。

再生の準備

 再生は「waveOut~」という関数を使います。

関数説明
waveOutOpenデバイスを開く。
waveOutPrepareHeaderデータブロックを初期化する。
waveOutWriteデバイスにデータブロックを転送する。
waveOutUnprepareHeaderwaveOutPrepareHeaderで行われた初期化をクリーンアップする。
waveOutReset再生を停止する。
waveOutCloseデバイスを閉じる。

 流れは次のようになります。

  1. デバイスを開く
  2. データを入れるバッファを準備
  3. データブロックを初期化
  4. データを書き込む
  5. 再生中は4.を繰り返す
  6. 再生を停止
  7. データブロックをクリーンアップ
  8. バッファを解放
  9. デバイスを閉じる

 ソースコードは次のようになります。

waveOutを使った再生
//バッファ情報
static WAVEHDR whdr;

//バッファ
static short *wWave;

//デバイスを開くための構造体
WAVEFORMATEX wfe;

//デバイスへのハンドル
static HWAVEOUT hWave;

//再生周波数を設定する。WAVEファイルから取得することもできる
int srate =48000; //周波数
const int channel =2; //チャンネル数

int bufferLenght = srate * channel;
int bufferSize =bufferLenght * sizeof(short);

//バッファのための領域確保
wWave = (short *)malloc(bufferSize);

//出力デバイスの設定
wfe.nChannels = channel;//今回は2固定
wfe.wBitsPerSample = 16;//今回は16固定

//waveOutデバイスのための情報をセットする
wfe.wFormatTag = WAVE_FORMAT_PCM;
wfe.nSamplesPerSec = srate;
wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8;
wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign;

//出力デバイスを開く
waveOutOpen(&hWave , WAVE_MAPPER , &wfe ,
    (DWORD)hWndMain , 0 , CALLBACK_WINDOW);

//再生バッファにデータを送るための設定
whdr.lpData = (LPSTR)wWave;
whdr.dwBufferLength = bufferSize;
whdr.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
whdr.dwLoops = 1;

//ロック
waveOutPrepareHeader(hWave , &(whdr) , sizeof(WAVEHDR));

//再生バッファに書き込み
whdr[bufferSelect].dwBufferLength =bufferSize;
waveOutWrite((HWAVEOUT)wParam , &whdr , bufferSize);

//再生をストップ
waveOutReset((HWAVEOUT)wParam);
//クリーンアップ
waveOutUnprepareHeader((HWAVEOUT)wParam , &whdr , sizeof(WAVEHDR));
free(wWave);

//クローズ
waveOutClose(hWave);

 「srate」はサンプリング周波数のことです。

 上記のコードは、あくまでイメージです。ですが、実際のソースコードから取り出して再構成しているので、流れの説明にはなっていると思います。

次のページ
再生

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

利都(りと)

陸奥でプログラムをしている学生。LOGOからVB6.0へ。現在は主にC系言語を利用。Atelier BlueにてManaged DirectXについてのTipsを書いている。http://www.atelier-blue.com/IL(CIL,MSIL)もしているのでそちらに興味がある方もどうぞ。Manag...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/368 2006/05/15 12:58

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング