FC2ブログ


VS2017-はじめの1/10歩(4):音声データを再生する

   プログラミング [2019/02/24]
やっとこさ「プログラミング」らしい内容になるのかな。

まずは、機械に喋らせた発話で.WAVファイルを作ります。
前回(3)で説明した「◆PowerPoint読み上げ&録音」⑦の手順(audacity)で、
「Export」形式を「WAV(Microsoft) 16bit PCM 符号あり」を指定してファイルに書き込みます。
audacityはでの、音自体の編集は、説明してくれてるところが沢山あるので、必要ならそちらを参考にされてください。


.WAVを再生するなんて、しかもVS6とか使うんじゃもはや古典ですな。
でも、構わず書きます。
さる的にも随分昔にコーディングしたので、
どこのサイトを参考(半分コピペ?)して作ったものなのかは、既に記憶にありません。
ちょっと、参考させていただいたところには申し訳ないです。

使用するのは、WaveOutXxxx APIです。
使うためには以下のヘッダが必要だそうです。

//----------------------------------------------------------
//****<<音声(.wav)再生API(waveOutXxxx)に必要
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
//----------------------------------------------------------


で、参考にしたサンプルが、イッキに.WAVデータをメモリにロードして使っていたので、そこはそのままにしています。
1)ロードの処理と、2)解放、3)ロードされたデータを再生させて、4)再生が終了したら終了処理をする
をそれぞれ関数化。 サンプルもそうなってたかなー。

各関数のパラメタの数を減らすためにテーブルを定義しました。
//----------------------------------------------------------
//****<<Waveファイル再生制御用のテーブル定義
typedef struct _waveinfo {
PBYTE WaveBuff; //WAVEファイルデータバッファ
HWAVEOUT hWaveOut; //WAVEハンドル
WAVEFORMATEX Wfe; //WAVEフォーマットデータ
WAVEHDR Whdr; //WAVEヘッダ
} WAVEINFO, *PWAVEINFO;
//----------------------------------------------------------


では、実際の処理を載せます。










//----------------------------------------------------------
//name :LoadWaveFile
//function :PCM形式のWAVEファイルを読み込む
//parameter :lpFileName -ファイルの名前を指すポインタ
// lpWinf -Wave情報テーブルのポインタ(テーブルに以下を含む)
// (lpWfe -WAVEFORMATEX構造体領域を指すポインタ)
// (lpWhdr -WAVEHDR構造体領域を指すポインタ)
//return :(0)-成功、(0以外)-失敗

INT LoadWaveFile(
PCHAR lpFileName,
PWAVEINFO lpWinf)
{
PWAVEFORMATEX lpWfe = &(lpWinf->Wfe);
PWAVEHDR lpWhdr = &(lpWinf->Whdr);

typedef struct _wavefile_topfmt {
CHAR riff[4]; //"RIFF"
DWORD size; //この後のデータサイズ
CHAR wave[4]; //"WAVE"
} WAVEFILE_TOPFMT, *PWAVEFILE_TOPFMT;

INT l;
PWAVEFILE_TOPFMT pWavTop;
PMMCKINFO pChunk;
HANDLE hfile;
DWORD dwFileSize;
DWORD dwReadSize;

//WAVファイルをオープンする
l = strlen(g_CurPath);
wsprintf(g_CurPath+l, "\\%s", lpFileName);
hfile = CreateFile(g_CurPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
g_CurPath[l] = 0x00;
if (hfile == INVALID_HANDLE_VALUE) {
//メッセージ出力とかのエラー処理
//例えば。(バッファの準備とか必要です。)
//wsprintf(g_MsgBuf, "Waveファイル<%s>を開けません。rc=%d", lpFileName, GetLastError());
//MessageBox(NULL, g_MsgBuf, "WAV再生開始", MB_ICONERROR|MB_OK);
return -1;
}

//いったん全部メモリにリードする
dwFileSize = GetFileSize(hfile, NULL);
lpWinf->WaveBuff = (PBYTE) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileSize);
ReadFile(hfile, lpWinf->WaveBuff, dwFileSize, &dwReadSize, NULL);
CloseHandle(hfile);

//内容をチェックし、必要なフォーマット情報をテーブルにコピーする。
pWavTop = (PWAVEFILE_TOPFMT) lpWinf->WaveBuff;
if (strncmp((PCHAR)(pWavTop->wave), "WAVE", 4)) {
HeapFree(GetProcessHeap(), 0, lpWinf->WaveBuff);
//メッセージ出力とかのエラー処理
return -2;
}

pChunk = (PMMCKINFO) (lpWinf->WaveBuff + sizeof(WAVEFILE_TOPFMT));
if (strncmp((PCHAR) &(pChunk->ckid), "fmt ", 4) != 0) {
HeapFree(GetProcessHeap(), 0, lpWinf->WaveBuff);
//メッセージ出力とかのエラー処理
return -2;
}
CopyMemory((PVOID) lpWfe, (PVOID) &(pChunk->fccType), sizeof(PCMWAVEFORMAT));

if (lpWfe->wFormatTag != WAVE_FORMAT_PCM) {
HeapFree(GetProcessHeap(), 0, lpWinf->WaveBuff);
//メッセージ出力とかのエラー処理
return -3;
}

//PCMデータの先頭を検索する
while ((PCHAR) pChunk < (PCHAR) &(pWavTop->wave) + pWavTop->size -8) { //-8:最低限参照するIDとsize分
pChunk = (PMMCKINFO) ((PCHAR) pChunk + pChunk->cksize +8); //+8:IDとsize分
if (strncmp((PCHAR) &(pChunk->ckid), "data", 4) == 0) {
//**** 発見:正常時 ****
lpWhdr->lpData = (PCHAR) &(pChunk->fccType);
lpWhdr->dwBufferLength = pChunk->cksize;
lpWhdr->dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
lpWhdr->dwLoops = 1;
return 0;
}
}

//PCMデータが見つからなかった場合
HeapFree(GetProcessHeap(), 0, lpWinf->WaveBuff);
//メッセージ出力とかのエラー処理
return -2;
}
//----------------------------------------------------------


ちょっと見ていただいて分かったかと思いますが、結構面倒な感じですよね。
これが完全なのかどうかは分かりません。
現にこの間、Windows10にバンドルされているWAVを再生しようとしたらできなかった。

いずれ、データファイルの中身をある程度理解していないと、上記が何をしているのかは分からないかと思います。
なぜ、こんなんなってるのか? もっと簡単な方法もあるのでは?
とは思うものの・・・面倒なのでこのままにしてます。

データのヘッダ情報をチェックして、データ形式をチェックして、
実際の「PCMデータはこの辺にあるよ」と指定しないといけなくなってます。

ファイルのデータ形式とかは、以下を参照されたい。(2019/02現在)
http://sky.geocities.jp/kmaedam/directx9/waveform.html

ロードしたらアンロードしなくちゃね。

//----------------------------------------------------------
//name :UnloadWaveFile
//function :開いていたWAVEファイルを閉じる
//parameter :lpWinf -Waveデータ関連情報ポインタ
//return :なし

VOID UnloadWaveFile(
PWAVEINFO lpWinf)
{
StopWave(lpWinf);
if (lpWinf->WaveBuff) {
HeapFree(GetProcessHeap(), 0, lpWinf->WaveBuff);
lpWinf->WaveBuff = NULL;
}
return;
}
//----------------------------------------------------------


再生開始の処理は、こんな。
//----------------------------------------------------------
//name :StartWave
//function :WAVEの再生開始
//parameter :hWnd - ウィンドウハンドル
// lpWinf - Waveデータ関連情報ポインタ
//return :なし

VOID StartWave(
HWND hWnd,
PWAVEINFO lpWinf)
{
MMRESULT mresult;

if (!lpWinf->hWaveOut) {
mresult = waveOutOpen(&(lpWinf->hWaveOut), WAVE_MAPPER, &(lpWinf->Wfe), (DWORD)hWnd, 0, CALLBACK_WINDOW);
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
mresult = waveOutPrepareHeader(lpWinf->hWaveOut, &(lpWinf->Whdr), sizeof(WAVEHDR));
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
//再生開始
mresult = waveOutWrite(lpWinf->hWaveOut, &(lpWinf->Whdr), sizeof(WAVEHDR));
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
}
return;
}
//----------------------------------------------------------


再生が終わったときの処理は、
//----------------------------------------------------------
//name :StopWave
//function :WAVEの再生停止
//parameter :lpWinf -Waveデータ関連情報ポインタ
//return :なし

VOID StopWave(
PWAVEINFO lpWinf)
{
MMRESULT mresult;
if (lpWinf->hWaveOut) {
//停止巻き戻し(本来不要:waveOutWriteの反対操作の意味)
mresult = waveOutReset(lpWinf->hWaveOut);
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
mresult = waveOutUnprepareHeader(lpWinf->hWaveOut, &(lpWinf->Whdr), sizeof(WAVEHDR));
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
mresult = waveOutClose(lpWinf->hWaveOut);
if (mresult != MMSYSERR_NOERROR) {
//メッセージ出力とかのエラー処理
}
}
lpWinf->hWaveOut = NULL;
return;
}
//----------------------------------------------------------


で、上記してきた関数をどお使うかが、以下になります。

//----------------------------------------------------------
//****<<外部変数にしておいて楽をします>>
//外部変数
TCHAR g_CurPath[MAX_PATH]; //どっかで.WAVのあるフォルダ名に初期化する。
WAVEINFO g_WaveA = NULL; //NULLで初期化
//----------------------------------------------------------


ロード/アンロードは、まあWinMain()とかでやっときます。
//----------------------------------------------------------
//****<<準備(音源データをメモリにロード>>
//.WAVのロード
if (!g_WaveA
&& LoadWaveFile("WAVEファイル.WAV", &g_WaveA) != 0) {
//メッセージ出力とかのエラー処理
}

//この期間は再生可能です。

//****<<後始末(音源データメモリを解放>>
if (g_WaveA) UnloadWaveFile(&g_WaveA);
//----------------------------------------------------------


でもって、普通にはウィンドウプロジシャ中に、何かのタイミングで、
//----------------------------------------------------------
//****<<鳴らしたいタイミングのところに入れる>>
StartWave(hDlg, &g_WaveA);
//----------------------------------------------------------

再生開始されます。非同期再生です。
なので、再生中も他のイベントを処理できます。

WaveOutXxxxは、再生開始した後に、再生にかかわるイベント(WM_WOMXXXX)が通知されます。
その中のWM_WOMDONEが「再生終わり」なので、あえて停止時の処理(StopWave)を実施します。
//----------------------------------------------------------
//****<<ウィンドウプロシジャ内に以下追加>>
case MM_WOM_OPEN:
break;
case MM_WOM_DONE:
if ((HWAVEOUT) wParam == g_WaveA.hWaveOut) {
if (g_WaveA) StopWave(&g_WaveA);
}
return TRUE;
case MM_WOM_CLOSE:
break;
//----------------------------------------------------------


これだけです。
長かったかな。

前にも書いたけど、これが最善でイマドキな処理だとはとても言えません。
現に再生できない .wav もOS提供のデータにもあるわけで。
(audacityで開いて、別名の.WAVにセーブしなおしたら、そっちは再生できた。)

もっと簡単で、新しいデータ形式に対応するためのやりかたもあるかとは思うけど、
これで困ってないので・・・

では。m(__)m

スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
11 | 2019/12 | 01
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 - - - -
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-








本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



ブログランキング・にほんブログ村へ にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR