iPhone+OpenAL入門

最近作ったアプリで、独自ピッカーを動かす際に効果音を鳴らそうと思って、AVFoundationで実装したところ、ピッカーをゆっくり動かす分には問題ないのだが、少しでも速く動かすと、再生遅延が起こって音飛びが発生し、最悪の場合再生されなくなってしまった。

そこで、遅延が少ないらしいOpenALで実装してみたので、そのときの手順をまとめてみる。

フレームワーク追加

  • AudioToolbox.framework
  • OpenAL.framework

インポート

#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioToolbox.h>

デバイス取得・コンテキストの生成と登録

ALCdevice *device = alcOpenDevice(NULL);
if (device)
{
    ALCcontext *context = alcCreateContext(device, NULL);
    alcMakeContextCurrent(context);
}

alcOpenDeviceでデバイスを取得する。引数をNULLにしておくと最適なデバイスを返してくれる(iPhoneの場合、デバイスはひとつしかないのでNULL以外の選択肢はない)。
次にalcCreateContextでコンテキストの生成を行い(戻り値NULLで失敗)、それをalcMakeContextCurrentで登録する。
コンテキストとは、内部状態のことであり、alcMakeContextCurrentで登録を行うと、以後OpenALで行うすべての操作は、そのコンテキストに適用されることになる(複数の設定を使い分けたいときに利用する。通常はひとつで十分)。

音声ファイルのデータを取得する

iPhoneでは容量の関係から、音声ファイルはcaf形式で使用するのが好ましいらしい。変換はターミナル上で行う。変換のコマンドは、検索するとたくさん出てくるが、一応mp3からcafへの変換コマンドを書いておく。音声ファイルがあるところまでターミナルで移動してから、以下のコマンドを実装。

mp3→caf

/usr/bin/afconvert -f caff -d LEI16@44100 -c 1 変換前.mp3 変換後.caf

Bufferの用意

ALuint buffer;
alGenBuffers(1, &buffer);

再生したい曲を格納するメモリ(バッファ)を用意する(曲とバッファは1対1の関係)。
alGenBuffersは、一度に複数のBufferを作ることができる。
第一引数は、生成するBufferの数。

Sourceの用意

ALuint source;
alGenSources(1, &sources);

alSourcei(source, AL_LOOPING, AL_TRUE);
alSourcei(source, AL_PITCH, 1.0f);
alSourcei(source, AL_GAIN, 0.45f);

再生ソースを用意する。
再生位置、ループ、ピッチやゲインなどを変更したい場合は、ソースに適用する。
alGenBufferと使い方は同じ。

サウンドファイル(cafファイル)の読み込み

void *data;
ALenum format;						// フォーマット
ALsizei size;							// ファイルサイズ
ALsizei freq;							// 周波数

NSBundle *bundle = [NSBundle mainBundle];
CFURLRef fileURL = (__bridge CFURLRef)[NSURL fileURLWithPath:[bundle pathForResource:fileName ofType:type]];
data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);

alBufferDataStaticProc(buffer, format, data, size, freq);

サウンドファイル(cafファイル)を読み込み、バッファを作成する。
まず、サウンドの各オーディオデータを取得する。
オーディオデータの取得には、OpenALサンプルoalTouchで使用されているMyGetOpenALAudioData関数を利用すると簡単。
(MyOpenALSupport.hとMyOpenALSupport.mをプロジェクトにコピーし、インポートしておく)
次に、そのオーディオデータを利用してバッファを作成する。
バッファ作成にも、oalTouchで使用されているalBufferDataStaticProc関数を利用すると簡単。

SourceにBufferを適用する

alSourcei(source, AL_BUFFER, buffer);

バッファを再生ソースに割り当てる。

再生

alSourcePlay(source);