OpenALとCでマイクを扱う
前回までは音を鳴らす(出力)だけのプログラムだったので次は録音(入力)するプログラムを作ってみましょう。
今回は録音した音をそのまま再生するプログラムです。
重要キーワード「ストリーミング再生」
ストリーミングとは!(恒例)
「インターネット上で動画や音声などのコンテンツをダウンロードしながら逐次再生すること」
まあ聞いたことがある人も多いでしょう。最近の動画配信サービスに使われているアレです。
例えば20分以上の長い動画を視聴するとき、全部ダウンロードして再生したら時間がかかりますよね?
そこで見たい部分(シークバーでいうツマミの前後)を少しずつダウンロードしていく手法のことです。
対義語的に表現すると ストリーミング再生↔ダウンロード再生 ってな感じです。
さて、これっぽい技術をプログラムにも応用していきます。「ぽい」なので本来の意味とは違いますが、逐次処理していくっていう点でこのブログでは呼んでいきます。下にイメージ画像を載せときます。
今回のプログラムはなんと!環境音を録音&再生を行います!
前のはsin波を鳴らすだけだったのであっふーん…てな感じでしたが身近なものを扱えばモチベとかも上がるんじゃないでしょうか?
早速ソースコードを載せときます。
#include <al.h> #include <alc.h> #include <iostream> #define SAMPLINGRATE 44100 #define BUFFER_SIZE 4410 #pragma comment(lib, "OpenAL32.lib") using namespace std; int main(void) { //マイクセットアップ ALCdevice* mic = alcCaptureOpenDevice(NULL, SAMPLINGRATE, AL_FORMAT_MONO16, BUFFER_SIZE); //スピーカーセットアップ ALCdevice* speaker = alcOpenDevice(NULL); //いつもの ALCcontext *context = alcCreateContext(speaker, NULL); alcMakeContextCurrent(context); //バッファ(保存領域)とソース(音源)を宣言 //ストリーミング用にバッファを二つ ALuint buffer[2]; ALuint source; //それを生成 alGenBuffers(2, &buffer[0]); alGenSources(1, &source); //マイクから録音した音を一旦入れておく ALshort* store = new ALshort[BUFFER_SIZE]; //再生状態を監視するための準備 alBufferData(buffer[0], AL_FORMAT_MONO16, &store[0], 0, SAMPLINGRATE); alSourceQueueBuffers(source, 1, &buffer[0]); alSourcePlay(source); //録音開始 alcCaptureStart(mic); cout << "録音&再生中・・・" << endl; //録音と再生を制御 int a = 0, count = 100; ALint sample, state; while (1) { //sourceが再生中か確認 alGetSourcei(source, AL_BUFFERS_PROCESSED, &state); //録音可能なバッファ長を取得 alcGetIntegerv(mic, ALC_CAPTURE_SAMPLES, sizeof(sample), &sample); //再生が終わり、録音が可能なとき if (sample > 0 && state == 1) { //録音してstoreに格納 alcCaptureSamples(mic, (ALCvoid*)&store[0], sample); //再生バッファをソースから外す alSourceUnqueueBuffers(source, 1, &buffer[a]); //待機バッファをソースに適用 alSourceQueueBuffers(source, 1, &buffer[a ^ 1]); //再生 alSourcePlay(source); //待機バッファに録音した音を入れる alBufferData(buffer[a], AL_FORMAT_MONO16, &store[0], sample * sizeof(unsigned short), SAMPLINGRATE); //ここでバッファの切り替え a = a ^ 1; count--; } if (count <= 0) { break; } } //マイク停止 alcCaptureStop(mic); //バッファ・ソースの後始末 alDeleteBuffers(2, &buffer[0]); alDeleteSources(1, &source); //OpenALの後始末 alcMakeContextCurrent(NULL); alcDestroyContext(context); //マイクを閉じる alcCloseDevice(mic); //スピーカを閉じる alcCloseDevice(speaker); }
それでは主要な部分を解説していきましょう
ALCdevice* mic = alcCaptureOpenDevice(NULL, SAMPLINGRATE, AL_FORMAT_MONO16, BUFFER_SIZE);
マイクのセットアップの行です。引数に関してはみたまんまです。またBUFFER_SIZEですが、今回は4410としました。参考にした本は1024でしたが、エラー吐いたりしたので環境によって変わるかもしれません。
//再生状態を監視するための準備
alBufferData(buffer[0], AL_FORMAT_MONO16, &store[0], 0, SAMPLINGRATE);
alSourceQueueBuffers(source, 1, &buffer[0]);
alSourcePlay(source);
以降に再生中かどうかの関数を呼び出しますが、ソースが空だと状態を見てくれないのでダミーを入れて再生終了に見せかけるためのコードを入れます。
//sourceが再生中か確認
alGetSourcei(source, AL_BUFFERS_PROCESSED, &state);
//録音可能なバッファ長を取得
alcGetIntegerv(mic, ALC_CAPTURE_SAMPLES, sizeof(sample), &sample);
ここではソースの状態を監視します。
再生中なら0を、終わっていたら1を返してくれます。
次の行でマイクの状態を監視します。
sampleの中に録音可能な長さを入れて、それに応じた録音をやっていきます。
//録音してstoreに格納
alcCaptureSamples(mic, (ALCvoid*)&store[0], sample);
//再生バッファをソースから外す
alSourceUnqueueBuffers(source, 1, &buffer[a]);
//待機バッファをソースに適用
alSourceQueueBuffers(source, 1, &buffer[a ^ 1]);
//再生
alSourcePlay(source);
//待機バッファに録音した音を入れる
alBufferData(buffer[a], AL_FORMAT_MONO16, &store[0], sample * sizeof(unsigned short), SAMPLINGRATE);
//ここでバッファの切り替え
a = a ^ 1;
上でマイクとソースの状態を調べ、どちらもスタンバイOKなら録音&バッファ切り替え&再生をしていきます。
ハイ!ここで今までノータッチだったストリーミングが出てきます。4行目と6行目にQueueとUnqueueという関数がありますね?ここでバッファの切り替えを行っていました。
具体的にはQueueがバッファ→ソース、Unqueueがソース→バッファという動作をします。それぞれ格納と抜き出しって感じですね。
あとは残ったバッファに今録音したデータを入れてループに戻ります。
お気づきの通り1ループ前の音を再生しているのでちょっと遅れて再生されます。中の順序を変えればリアルタイムもできるんじゃないかな・・・?(丸投げ)
コンパイルが通れば真っ黒い画面とともに環境音が遅れてスピーカから再生されるハズです。countで制御しているので数秒経ったら消えてしまいますが・・・
あと、マイクとスピーカが近い人は気を付けてください。校長先生がよくやらかす「ハウリング」が起こりやすいです。気になる人はヘッドフォンを使うか、音量自体を下げてみてください。
ここまでで入力と出力をやってきましたが、OpenALにはまだまだ機能がたくさんあります。これからはその機能の紹介や、録音した音を変化させてみたりとかを紹介していこうと思います。
それではまた次回の置手紙でお会いしましょう・・・