PlaySoundとwaveOutOpenで音声を再生する
Windowsアプリケーションで短い効果音や長めの音声ファイルを扱う場合、目的に応じてAPIを選ぶ必要があります。手軽に再生したいならPlaySound、再生形式やバッファーを細かく管理したいならwaveOutOpenが基本的な選択肢です。どちらもWindows標準のマルチメディア機能に含まれています。
PlaySoundは、WAVファイルを指定するだけで再生できる簡易APIです。通知音、ボタン操作音、エラー音など、再生開始後のストリームを細かく制御しない用途に向いています。呼び出し側のコードが短く、リソース管理も比較的少なく済む点が利点です。
一方、waveOutOpenはWindowsマルチメディアの低レベル寄りの波形出力APIです。WAVEFORMATEXでサンプリング周波数、チャンネル数、量子化ビット数を指定し、WAVEHDRに用意したPCMデータを渡して再生します。複数のバッファーを順番に送る設計にすれば、長時間再生や独自の音声生成にも対応できます。
APIの仕様を確認するときは、関数の引数だけでなく、戻り値、コールバック、デバイスの解放順序まで調べることが重要です。関連情報を整理したWindows API資料も参照しながら、用途に対して必要十分な実装を選ぶと、過剰なコードや再生停止時の不具合を避けやすくなります。
用途に応じた音声出力APIの選び方
PlaySoundの代表的な宣言は次のとおりです。
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,
DWORD fdwSound
);
Unicode環境ではPlaySoundW、マルチバイト文字列を使う場合はPlaySoundAを利用します。通常はPlaySoundW(L"notify.wav", nullptr, SND_FILENAME | SND_ASYNC);のように呼び出します。SND_FILENAMEはファイル名として解釈する指定、SND_ASYNCは非同期再生の指定です。
waveOutOpenでは、最初に出力デバイスを開きます。音声形式を表すWAVEFORMATEXを設定し、waveOutOpenで得たHWAVEOUTハンドルを使って、waveOutPrepareHeader、waveOutWriteを順に呼び出します。再生完了後はヘッダーを解除し、最後にwaveOutCloseでデバイスを閉じます。
短いWAVを一度鳴らすだけならPlaySoundが適しています。音量、再生位置、データ供給のタイミング、デバイス選択などを管理する必要がある場合はwaveOut系APIを選びます。実装量の差が大きいため、最初から低レベルAPIを採用するのではなく、要件を明確にして判断します。
PlaySoundでWAVファイルを再生する
同期再生の最小例は次のようになります。
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
int main()
{
BOOL result = PlaySoundW(
L"C:\\音声\\効果音.wav",
nullptr,
SND_FILENAME | SND_SYNC
);
return result ? 0 : 1;
}
SND_SYNCを指定すると、再生が終わるまでPlaySoundの呼び出しが戻りません。画面操作を止めたくない場合はSND_ASYNCを指定します。非同期再生では、呼び出し直後に次の処理へ進めるため、アプリケーションのユーザーインターフェースをブロックしません。
アプリケーションに埋め込んだWAVリソースを再生することもできます。リソース識別子を指定する場合はSND_RESOURCEとモジュールハンドルを組み合わせます。ファイルパスを外部設定にする場合は、存在確認、アクセス権、相対パスの基準ディレクトリを考慮してください。相対パスは実行場所によって変わるため、設定ファイルや実行ファイルのディレクトリを基準に絶対パスへ変換すると安定します。
再生中の音を止めるには、PlaySoundW(nullptr, nullptr, 0);を使います。ただし、非同期再生を連続して開始したときの上書き動作や、同じファイルを繰り返すSND_LOOPの扱いは、使用するフラグによって変わります。再生状態を独自に管理する場合は、開始と停止を行うスレッドやUIイベントの競合にも注意が必要です。
waveOutOpenでPCMデータを送る
waveOutOpenを使う場合は、まず音声データの形式を定義します。たとえば16ビット、44.1kHz、ステレオのPCMなら、wFormatTagにWAVE_FORMAT_PCM、nChannelsに2、nSamplesPerSecに44100、wBitsPerSampleに16を設定します。nBlockAlignは1サンプルフレームのバイト数、nAvgBytesPerSecは1秒あたりの平均バイト数です。
WAVEFORMATEX format{};
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 2;
format.nSamplesPerSec = 44100;
format.wBitsPerSample = 16;
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
HWAVEOUT device = nullptr;
MMRESULT mmr = waveOutOpen(
&device,
WAVE_MAPPER,
&format,
0,
0,
CALLBACK_NULL
);
この後、PCMデータを格納したメモリをWAVEHDRのlpDataとdwBufferLengthへ設定し、waveOutPrepareHeaderで準備してからwaveOutWriteで送ります。再生が終わる前にデータ領域を解放すると、無効なメモリを参照するため、WHDR_DONEが設定されるまで待機しなければなりません。
同期的に待機する簡単な検証コードでは、waveOutGetPositionやWAVEHDRの状態を確認できます。実用的なストリーミング再生では、CALLBACK_FUNCTIONやイベント通知を使い、再生済みバッファーを再利用します。複数のバッファーを循環させれば、音声データを少しずつ読み込みながら出力できます。
waveOutOpenの戻り値はMMRESULTです。MMSYSERR_NOERROR以外が返った場合はデバイスを利用できない、形式に対応していない、引数が不正といった可能性があります。エラー時には開いたハンドルだけを閉じる、準備済みヘッダーだけを解除するなど、処理済みの段階に応じて後始末を分けます。
安定した再生を実現する実装上の要点
WAVやPCMの再生は単純に見えても、ファイル形式、スレッド、メモリ、デバイス状態が関係します。実装時には次の項目を先に決めておくと、検証しやすくなります。
- 短い効果音にはPlaySound、ストリーミングにはwaveOutOpenを使い分ける
- APIの戻り値を確認し、失敗時に原因をログへ記録する
- WAVEHDRを解除する前に再生完了状態を確認する
- 非同期再生中のバッファーや文字列を早期に解放しない
- 44.1kHzや48kHzなど、対象デバイスが扱える音声形式を検証する
- アプリケーション終了時に再生停止、ヘッダー解除、デバイス解放を行う
音量調整を行う場合、waveOutSetVolumeはデバイス全体や左右チャンネルに影響することがあります。アプリケーションだけの音量を変えたいなら、PCMサンプルを処理してから出力する方法や、より新しいオーディオAPIの採用も検討対象になります。
Windows APIには音声以外にも、GDIオブジェクトなどハンドル管理を伴う機能があります。たとえば標準オブジェクト解説で扱われるようなリソース管理の考え方と同様に、取得したハンドルや準備済み構造体の所有者を明確にしておくことが大切です。
動作確認と保守を進める手順
まずPlaySoundで対象WAVを再生し、ファイルパス、文字コード、リンクライブラリ、音声形式に問題がないかを切り分けます。同期再生で音が出ることを確認してから非同期再生へ変更すると、音声データの問題とスレッド処理の問題を分けて調査できます。
waveOutOpenへ移行する場合は、単一バッファーで再生できることを確認した後、バッファー分割や通知コールバックを追加します。再生終了を待つ処理、停止処理、デバイス切断時のエラー処理を個別に試験し、アプリケーション終了時にプロセスが残らないことも確認します。
最後に、実装対象のWAVファイルを固定し、PlaySoundの同期再生で正常終了する最小プログラムを作成することから始めます。音声が確認できたら非同期フラグへ切り替え、その後に必要な場合だけwaveOutOpenのバッファー処理を追加します。