さぁ、オーディオデータをキャプチャーするぞ!
データをどこに入れようか? サウンドバッファ(= sf::SoundBuffer)だ!
つまり、サウンドバッファの使い道は2つあるのですね。再生、と、ファイルへの保存。
目的はとてもシンプルなインターフェイスで達成できるぞ。
さぁ、sf::SoundBufferRecorder クラスのお出ましだ:
// まずはオーディオ入力デバイスが使用可能かどうかを確認
if (!SoundBufferRecorder::isAvailable())
{
// エラー:このマシンでは録音できない。
...
}
// レコーダーを作成
SoundBufferRecorder recorder;
// キャプチャー開始
recorder.start();
// 録音中……
// キャプチャー終了
recorder.stop();
// キャプチャーしたオーディオデータが入っているバッファを取り出す
const sf::SoundBuffer& buffer = recorder.getBuffer();
スタティック関数 SoundBufferRecorder::isAvailable は、
お使いのマシンで音声のキャプチャーができるかどうかを調べます。
false と言われたら、sf::SoundBufferRecorder クラスは使えません。
start() と stop() は、わかりますよね?
キャプチャー処理は裏側で独自のスレッドが走って、そこで行われます。
なので、start() と stop() の間で、他の処理を好きなように書くことができます。
キャプチャーが終了すると、サウンドバッファを取り出せるようになります。
中に音声データが入っているぞ。getBuffer() 関数を使うのだ。
さぁ、音声データが手に入った。
今こそ、キミの夢を叶えるのだ↓
- ファイルに保存
buffer.saveToFile("my_record.ogg");
- その場で再生
sf::Sound sound(buffer);
sound.play();
- 生のサンプリングデータを取得して分析したり変換したり、あんなことこんなこと
const sf::Int16* samples = buffer.getSamples();
std::size_t count = buffer.getSampleCount();
doSomething(samples, count);
サウンドバッファをコピーしておくのを忘れるな!
録音した音声データは、レコーダーオブジェクトを破棄したり再利用したりすると消えてしまうぞ!
サウンドバッファに溜め込む形式での録音がキミの望みではないならば、独自のレコーダークラスを作ることもできるぞ。
すると、音声を録音しながら処理することができるようになるのだ。
たとえばネットワークにストリーミングしたり、リアルタイムでの音声解析などが可能になるぞ。
独自のレコーダークラスを作るには、抽象クラス sf::SoundRecorder を継承するのだ。
何を隠そう、sf::SoundBufferRecorder は このクラスの使用例に過ぎないのだ。
このクラスを継承して、オーバーライドする必要がある仮想関数は1つだけ。onProcessSamples() だ。
この関数は音声サンプルのチャンクがキャプチャーされるたびに呼び出される。
つまり、この関数の内部こそ、キミの独自の処理を記述する場所なのだ。
音声サンプルは 100ms ごとに、onProcessSamples() 関数に送られます。
この仕様は現在のところ SFML にハードコーディングされているので、変更できません
(キミが SFML 自体を改造しない限り)。
この仕様は将来変更されるかもしれません。
あと2つ、必須ではないけれどオーバーライドできる仮想関数があるぞ。
onStart() と onStop() だ。
それぞれ、キャプチャーの開始時と終了時に呼ばれる。
初期化と後処理を記述しておくのに便利なのだ。
class MyRecorder : public sf::SoundRecorder
{
virtual bool onStart() // オーバーライドは必須ではありません。
{
// キャプチャー開始前に必要な前処理を記述
...
// true を返すとキャプチャー開始。false を返すとキャンセルされる。
return true;
}
virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount)
{
// 送られてきた音声サンプルのチャンクを、好きなように処理
// ...
// true を返すとキャプチャーを続行。false を返すと停止します。
return true;
}
virtual void onStop() // オーバーライドは必須ではありません。
{
// キャプチャー終了後に必要な後処理を記述
// ...
}
}
isAvailable()、start()、stop() は ベースクラス sf::SoundRecorder に定義されてます。
なので、子クラスにも継承されます。
つまり、sf::SoundRecorder を継承しているならば、使い方は上で説明した sf::SoundBufferRecorder と同じなのだ。
if (!MyRecorder::isAvailable())
{
// error...
}
MyRecorder recorder;
recorder.start();
...
recorder.stop();
録音は別スレッドで処理されます。
実際のところ、どこでどんな処理が行われているのかな?
onStart()関数は start() 関数から直接呼ばれます。
つまり、この関数は start()を呼んだのと同じスレッド内で実行されます。
一方、onProcessSample() と onStop() は SFML が内部的に作るスレッドから呼ばれます。
ということは……キミが作ったレコーダークラスが start()を呼んだスレッドからも、録音スレッドからも、
同時にアクセスされる可能性のあるデータを使っている場合は、
アクセスが衝突しないようにプロテクトしておく必要があります(ミューテックスか何かで)。
さもないと、おかしなことが起きてしまうかもしれません。データが壊れるとか、クラッシュするとか。
スレッドのことをよく知らないってお友達は、
こちらのチュートリアルを読んでね。