今回からは、楽器アプリを取り上げてみる。iPhoneをギターやピアノといった楽器にするアプリケーションは、App Storeの売り上げでも人気を博しており、iPhoneアプリの代表格とも言えるだろう。この連載としては、楽器アプリの作り方を通して、iPhoneのオーディオテクノロジーを概観する事が目的となる。
iPhoneのオーディオフレームワーク
アプリの制作にとりかかる前に、iPhoneが持つオーディオフレームワークを紹介しよう。図示すると、次のようになると思う。
iPhoneのオーディオテクノロジーは、Core Audioと総称される。その中でも重要なのは、MacOS Xに由来するオーディオフレームワークであるAudio ToolboxとAudio Unitだ。この2つのフレームワークに、多くの機能が備わっている。その一部を書き出すと、
オーディオファイルの再生、一時停止、ループ
オーディオの録音
様々なオーディオフォーマットの読み込み
オーディオのエフェクト処理
警告音やバイブレーションの再生(バイブレーションはiPod touchでは不可)
などがある。オーディオ関連の処理で必要なものは、ここにそろっている。
OpenALは、標準化されたオーディオAPIである。OpenGLのオーディオ版と考えてもいい。OpenALは様々なプラットフォームで実装されているので、その使い方に慣れている方は、ここから始めるといいだろう。その成り立ちから、ゲームアプリケーションでよく使われているようだ。3次元空間での音源位置指定や、ドップラー効果といった、ゲームで便利そうな機能を備えているのも特徴だ。
AVFoundationは、オーディオプレイヤーを簡単に作るために用意されたフレームワークだ。オーディオファイルの再生、一時停止、シーク、ボリューム設定、再生時間の取得、などを簡単に行える。これにユーザインタフェースを被せれば、自分のアプリケーションで簡単にオーディオプレイヤーを実現できる。他のフレームワークと比べると、高レベルに位置するものになる。
楽器アプリのためのライブラリ
このようなフレームワークがあることをふまえて、楽器アプリを作成する事を考えてみよう。
楽器アプリに求められる要件は、次のようなものである。
オーディオファイルの再生。ユーザが画面上に表示された楽器の一部をタップすると、用意されたオーディオファイルを再生する。
複数オーディオの同時再生。ユーザが同時に二カ所以上タップした場合は、その数だけオーディオを再生する。
オーディオのループ再生。管楽器のように任意の長さの音を演奏できる楽器では、ループ再生する事が必要だろう。
このような条件を考えると、Core Audioの中から楽器アプリ実現に適したライブラリとして、次のものが考えられる。
System Sound Service
Audio Toolboxに含まれるサービスである。30秒以下のシステム警告音を鳴らすのに使われる。打楽器のようなものに対しては、十分な機能と言えるだろう。オーディオライブラリの中でも、最も少ない手順でオーディオファイルを再生する事ができるのも魅力である。
Audio Queue Service
こちらもAudio Toolboxに含まれるサービスである。オーディオデータのためのバッファを作成して、そこにデータを追加していく事で再生を行う。再生データを任意に変更する事ができるので、好きな箇所でループを再生を行うなど、自由度の高いオーディオ処理が可能となる。また、自分で波形を作成して鳴らすという、シンセサイザーのようなこともできる。機能が高い反面、取り扱い方は複雑になる。
OpenAL
OpenALはもともとゲームのために用意されたAPIなので、自由度の高い再生ができる。OpenGLとの相性もいいので、画面の表示と同期させたいときには適切だろう。また、パフォーマンスの点でも有利な事が多い。
これらのライブラリを使って、楽器アプリを作成してみよう。
最後に、一つ重要な点を注意しておく。iPhoneは様々なオーディオフォーマットに対応しているが、ハードウェアの制限としてMP3とAACは、一度に一つのファイルしか再生する事ができない。おそらく、フォーマットのデコードをハードウェアで処理しているためであろう。楽器アプリのように複数オーディオ再生が必要な場合は、AIFFやWAVといったフォーマットで音源を用意しておこう。
では実際に楽器アプリを作り始めてみる。楽器アプリ作成で大事なのは、オーディオフレームワークの選定だ。前回解説したように、iPhoneは様々な種類のオーディオ技術を提供している。そこで、いくつかの技術を取り上げて、個別の楽器アプリを作ってみよう。
まず使ってみるのは、System Sound Serviceだ。おそらく、これが最も簡単な楽器アプリになるだろう。実現する楽器は、ベルにしてみる。つまり、鐘の音だ。この楽器を選んだ理由は、後で説明しよう。
System Sound Serviceの使い方
はじめに、System Sound Serviceについて説明しよう。これは、Audio Toolboxフレームワークが提供するサービスだ。警告音のような、短いサウンドを鳴らすために使われるものだ。目安としては、30秒以下のサウンドだ。iPhoneのバイブレーション機能も、このサービスからアクセスする。
System Sound Serviceの使い方は、非常に簡単だ。まず、システムサウンドIDを作成する。これには、AudioServicesCreateSystemSoundIDという関数を使う。引数として、.aifや.wavといったオーディオファイルを渡してやればいい。取得したシステムサウンドIDを鳴らすには、AudioServicesPlaySystemSoundという関数を使う。
たとえば、次のようなソースコードで音を鳴らす事ができるだろう。
List 1.
// オーディオファイルのパスを取得する NSURL* url; url = [NSURL fileURLWithPath: [[NSBundle mainBundle]
pathForResource:@"C4" ofType:@"aif"]]; // システムサウンドを作成する AudioServicesCreateSystemSoundID((CFURLRef)url, &_soundC4); // サウンドを鳴らす AudioServicesPlayAlertSound(sound);
これだけだ。これで、プロジェクトに追加されているC4.aifというオーディオファイルを鳴らす事ができる。
このサービスを使って、楽器アプリを作る事を考えてみよう。まず、必要なオーディオファイル(おそらく12音以上)を読み込み、システムサウンドIDを取得する。そして、画面上のボタンなどが押されたら、それを鳴らす。これだけで楽器アプリとして動作するはずだ。
鍵盤のレイアウトと横回転
早速、アプリを作ってみよう。Xcodeを立ち上げて、新規プロジェクトを作成する。そして、ユーザインタフェースのレイアウトを行おう。
今回は、ピアノの鍵盤のようにボタンを配置しておこう。iPhoneを横に持って使うと想定しておく。
それぞれのボタンには、インスペクタウインドウを使って、tagの値を設定しておく。この値を使って、どのボタンが押されたか判別する事にしよう。この値は、UIViewクラスのtagプロパティから取得できる。
ボタンからは、アクションを接続しておこう。今回は、playSound:という、送り手を引き数に取るアクションを用意した。すべてのボタンからこのアクションを呼び出す事にする。レイアウトとアクションの接続が終わったら、Xcodeに戻ろう。
今回は横画面専用のインタフェースにしたので、アプリケーションの起動時に強制的に横画面にしておくことにする。applicationDidFinishLaunching:メソッドの中で、この処理を行おう。
List 2.
- (void)applicationDidFinishLaunching:(UIApplication
*)application { // ステータスバーを上にする [UIApplication sharedApplication].statusBarOrientation =
UIInterfaceOrientationLandscapeRight; // アプリケーションの表示フレームを取得する CGRect frame; frame = [UIScreen mainScreen].applicationFrame; // 中心位置を作成 CGPoint center; center.x =
CGRectGetWidth(frame) * 0.5f; center.y =
CGRectGetHeight(frame) * 0.5f; // バウンズを作成 CGRect bounds; bounds.origin = CGPointZero;
bounds.size.width = CGRectGetHeight(frame);
bounds.size.height = CGRectGetWidth(frame); // アフィン変換を作成 CGAffineTransform transform; transform
= CGAffineTransformMakeRotation(M_PI_2); // ビューを回転させる viewController.view.center = center; viewController.view.bounds = bounds;
viewController.view.transform = transform; // ウィンドウ
を表示する [window addSubview:viewController.view];
[window makeKeyAndVisible]; }
まず、ステータスバーの位置を変更しておく。そして回転を行うのだが、これはビューコントローラのビューに対して、transformプロパティを設定してやる事で実現できる。このプロパティはアフィン変換を表すものなので、90度回転する変換を指定してやればいい。
ただし、このとき注意しておきたい事がある。UIViewの大きさや位置を表すプロパティにframeがあるが、transformのプロパティとして単位行列以外を設定すると、この値は不定となる。代わりに、ビューの中心位置を表すcenterと、ビューの大きさを表すboundsといったプロパティを使用する必要が出てくるのだ。それらもあわせて設定行っている。
音を鳴らす
続いて、音を鳴らすための設定だ。まず、システムサウンドIDを作成する。これは、ビューコントローラクラスの、viewDidLoadで行っている。
List 3.
- (void)viewDidLoad { // サウンドを読み込む NSURL* url; url = [NSURL fileURLWithPath:[[NSBundle
mainBundle] pathForResource:@"C4" ofType:@"aif"]];
AudioServicesCreateSystemSoundID((CFURLRef)url,
&_soundC4); url = [NSURL fileURLWithPath:[[NSBundle
mainBundle] pathForResource:@"C#" ofType:@"aif"]];
AudioServicesCreateSystemSoundID((CFURLRef)url,
&_soundCS); ……
このようにして13個のオーディオファイルを読み込んでいる。
音を鳴らすのは、playSound:アクションメソッドだ。
List 4.
- (IBAction)playSound:(id)sender { // タグを取得して、サウンドを決定する SystemSoundID sound =
(SystemSoundID)NULL; switch ([sender tag]) { case
100: sound = _soundC4; break; case 101: sound =
_soundCS; break; case 102: sound = _soundD; break;
case 103: sound = _soundDS; break; case 104: sound =
_soundE; break; case 105: sound = _soundF; break;
case 106: sound = _soundFS; break; case 107: sound =
_soundG; break; case 108: sound = _soundGS; break;
case 109: sound = _soundA; break; case 110: sound =
_soundAS; break; case 111: sound = _soundB; break;
case 112: sound = _soundC5; break; } // サウンドを鳴らす if (sound) {
AudioServicesPlayAlertSound(sound); } }
このメソッドの引き数であるsenderとしてボタンが送られてくるので、そのタグの値からサウンドを決定している。そして、AudioServicesPlayAlertSoundで音を鳴らせばいい。
これで、十分な機能を持つ楽器アプリの完成だ。だが実際に使ってみると、いくつか不満を感じるだろう。まず、音量の調整ができていない。また、鳴らした音を途中で止める事ができない。System Sound Serviceは、これらの機能は提供していないのだ。
今回の説明で楽器としてベルを取り上げたのは、このようなAPIの制約があるためだ。ベルならば、音量や持続時間を変化できなくとも、それらしく聞こえると考えたのだ。
次回は、別のフレームワークを使って楽器アプリを作成して見よう。
ここまでのソースコード: ChurchBell-1.zip
今回は、Audio Queueを使ってみよう。Audio Queueは、Audio Toolboxフレームワークの中において、主役と呼べるライブラリだ。柔軟なオーディオの再生が可能になる。
楽器アプリを実現するためのライブラリとして、前回はSystem Sound Serviceを使った。今回のAudio Queueがこれと大きく異なるのは、再生音の繰り返しの有無である。Audio Queueを使えば、任意のポイントで繰り返し再生を行う事ができるようになるのだ。これにより、楽器アプリの表現力は大きく広がる事になる。
Audio Queueとバッファ
Audio Queueの特徴は、名前の通り、音データをキューイングすることだ。音データを格納するためのバッファを複数用意して、そこにデータを格納してキューを作成する。バッファへのデータの格納は、アプリケーションへのコールバックで行われる。
Audio Queueの動作を理解するには、Appleが公開しているドキュメントを見るのが分かりやすい。次の図が、Audio Queueを使って音を再生するときのプロセスだ。
Audio Queueの動作図(Apple Developer Connectionより)
最初に、アプリケーションはキューとバッファを作成し、初期データをバッファに格納する(1)。バッファにデータが入ると、それはキューへ移動する。その状態で再生を行うと(2)、キューにあるバッファを読み込みながら再生が始まる(3)。バッファのデータを読み込み終わると次のバッファへと移動し(4)、空になったバッファがコールバックを使ってアプリケーションに通知される(5)。アプリケーションはバッファに必要なデータを満たして、再びキューに追加するのだ(6)。
これがAudio Queueの再生プロセスだ。バッファへのデータ格納が、キューにあるデータを再生しきる前に完了する事ができれば、スムーズに音を再生する事ができる。
キューとコールバックによる方式は、多くの利点がある。まず、事前にすべてのオーディオデータを用意する必要がない。再生する直前に、必要な分だけデータを用意すればいいのだ。これにより、使用メモリは少なくなる。また、バッファに任意のデータを格納できるとこも注目だ。この仕組みのため、単純にオーディオファイルの先頭から最後まで再生するといったものだけでなく、任意のポイントでループさせたり、別のオーディオファイルのデータを組み合わせたり、といったことが可能になる。または、プログラム中で波形を作成して再生するといった、シンセサイズ機能も実現できる。
欠点をあげるならば、データを格納してから実際に再生されるまでの時間がアプリケーションから決定できない、ということがある。つまり、レイテンシが長いという事だ。レイテンシを短くするには、バッファの数を少なくしたり、サイズを小さくすればいい。だがそうすると、バッファへのデータ格納が再生時間に間に合わないという自体が発生する。これは、音飛びという現象になって現れてしまう。
以上のことから、Audio Queueは長時間の音楽再生には向くが、ゲームのようにユーザの反応にリアルタイムに即応するタイプのものには不向きと言えるだろう。
データフォーマットの決定とAudio Queueの作成
では、Audio Queueの使い方を説明しよう。先に言っておくが、なかなか面倒なところがある。
Audio Queueとバッファを作成して、音を再生する準備を整えるには、以下のような手順が必要になる。
1. 再生するオーディオのデータフォーマットを決定する2. データフォーマット、コールバック関数などを指定して、Audio Queueを作成する
3. バッファの数と大きさを決定する4. バッファを確保する
まず、Audio Queueに渡すデータのフォーマットを決定する必要がある。もしオーディオファイルを再生するのであれば、そのオーディオフォーマットを取得する事になる。データフォーマットは、AudioStreamBasicDescription構造体で指定する。
List 1.
struct AudioStreamBasicDescription { Float64
mSampleRate; UInt32 mFormatID; UInt32
mFormatFlags; UInt32 mBytesPerPacket; UInt32
mFramesPerPacket; UInt32 mBytesPerFrame; UInt32
mChannelsPerFrame; UInt32 mBitsPerChannel;
UInt32 mReserved; };
この構造体で注目してほしいのは、mFormatIDフィールドだ。ここにオーディオフォーマットを指定する事ができる。実はAudio Queueでは、MP3やAACの再生を行う事ができる。それらのフォーマットはここに指定するのだ。
もしオーディオファイルからデータを読み込むのであれば、そのフォーマットは、Audio FileライブラリのAPIである、AudioFileGetPropertyを使って取得する事ができる。次のようなソースコードになるだろう。
List 2.
OSStatus status; UInt32 size; // オーディオファイルを開く AudioFileID audioFileId; status =AudioFileOpenURL( (CFURLRef)[NSURL
fileURLWithPath:path],
kAudioFileReadPermission, 0, &audioFileId); // オーディオデータフォーマットを取得する AudioStreamBasicDescriptionaudioBasicDesc; size = sizeof(audioBasicDesc);
status = AudioFileGetProperty( audioFileId,
kAudioFilePropertyDataFormat, &size, &audioBasicDesc);
まず、AudioFileOpenURLを使ってオーディオファイルをオープンする。そして、AudioFileGetPropertyを、kAudioFilePropertyDataFormatを指定して呼び出す。これで、データフォーマットが取得できるのだ。
続いて、Audio Queueを作成しよう。Audio Queueには、再生用と録音用のものとがある。今回使うのは、再生用だ。AudioQueueNewOutputを使って作成する。
List 3.
// オーディオキューを作成する AudioQueueRef audioQueue; status = AudioQueueNewOutput(
&audioBasicDesc, audioQueueOutputCallback, self,
CFRunLoopGetCurrent(), kCFRunLoopCommonModes,
0, audioQueue);
第一引き数に先ほど取得したデータフォーマット、第二引き数にデータを核のするためのコールバック関数を指定する。コールバック関数は、次のような型のものになる。
List 4.
typedef void (*AudioQueueOutputCallback) (
void *inUserData, AudioQueueRef
inAQ, AudioQueueBufferRef inBuffer );
第二引き数がAudio Queue、第三引き数がこの後に説明するバッファ型になっている。
バッファサイズの決定とバッファの作成
続いて、バッファを作成しよう。まず、バッファの数と大きさを決定する必要がある。
バッファの数は任意だ。多くすればその分再生が安定する。その代わりに、レイテンシが大きくなる。ここは、アプリケーションの動作を見ながら調整する事になる。
問題は、バッファの大きさだ。詳しくは後述するが、実はバッファにオーディオデータを書き込む際は、それが何パケット分のデータに相当するかを指定する必要がある。適当にデータを書き込むだけではだめなのだ。従って、バッファに何パケットのデータを書き込み、そのサイズがいくつなのか、を知る必要がある。
これは、非圧縮データであれば簡単だ。パケット毎のデータサイズは固定になる。問題はMP3やAACといった、圧縮データの場合だ。パケットサイズが可変になる。こういったときには、そのオーディオファイルにある最大パケットサイズを求めて、それをサポートできるようにしよう。そして、任意の時間のデータを格納できるようにバッファサイズを決定する。
List 5.
// 最大パケットサイズを取得する UInt32 maxPacketSize;
size = sizeof(maxPacketSize); status =
AudioFileGetProperty( &audioFileId,
kAudioFilePropertyPacketSizeUpperBound, &size,
&maxPacketSize); // 単位時間あたりのパケット数を計算する Float64 numPacketsPerTime; numPacketsPerTime =
audioBasicDesc.mSampleRate /
audioBasicDesc.mFramesPerPacket; // 0.5秒分のパケットデータサイズを計算する UInt32 bufferSize; bufferSize =numPacketsPerTime * maxPacketSize * 0.5;
バッファサイズは決定できた。あとは、バッファを作成すれば、完了である。
List 6.
// オーディオキューバッファを作成する AudioQueueBufferRef audioBuffer; status =
AudioQueueAllocateBuffer(audioQueues, bufferSize,
&audioBuffers);
これでキューとバッファの準備は整った。次回は、バッファにデータを格納するコールバックについて説明しよう。
前回はAudio Queueとバッファを作成するところまで説明を行った。今回は、バッファにデータを格納して、音を再生する手順を説明しよう。
オーディオキューからのコールバックAudio Queueにオーディオデータを渡すには、作成時に登録したコールバック関数を使う事になる。コールバックの仕組みに関する大枠は、前回も紹介したAppleのドキュメントを参考にしてほしい。
コールバック関数自体は、C関数として登録する事になる。だが、この関数の中で様々な処理を行うのは都合が悪い。なぜなら、Audio Queueの管理はObjective-Cのコントロールクラスで行っているからだ。そこで、コールバックであるC関数からは、すぐにObjective-Cメソッドを呼び出してしまうようにしよう。
List 1.
static void audioQueueOutputCallback(
void* userData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer)
{
[(OrganViewController*)userData
_audioQueueOutputWithQueue:audioQueue queueBuffer:audioQueueBuffer];
}
第一引き数はアプリケーションが任意に指定できる値なので、ここにObjective-Cのインスタンスのポインタを入れておく。これを使えば、適当なメソッドに処理をパススルーすることができるだろう。
パケットデータの読み込みコールバックから続く処理では、まずオーディオデータを読み込みを行う。これには、Audio FileライブラリにあるAudioFileReadPacketsを使う事ができる。これを使えば、指定したパケットの大きさだけデータを読み込む事ができる。さらに、読み込んだパケットデータの詳細情報を知るために、AudioStreamPacketDescriptionという構造体が用意されている。この構造体の定義は、次のようになっている。
List 2.
struct AudioStreamPacketDescription {
SInt64 mStartOffset;
UInt32 mVariableFramesInPacket;
UInt32 mDataByteSize;
};
実はAudio Queueにパケットデータを設定するときは、この情報もあわせて指定してやる必要がある。あらかじめインスタンス変数などにバッファを確保しておこう。
パケットデータを読み込むための呼び出しは、次のようになるだろう。
List 3.
// パケットデータを読み込む UInt32 numBytes;
UInt32 numPackets = _packetSizeToRead;
status = AudioFileReadPackets(
_audioFileId, NO, &numBytes, &_audioPacketDesc,
_startPacketNumber, &numPackets, audioQueueBuffer->mAudioData);
AudioFileReadPacketsには、7つの引き数がある。これらを説明しよう。まず第一引き数は、オープンしているオーディオファイルを表すAudioFile ID。第に引き数は、データをキャッシュするかどうか指定するもの。第三引き数は、読み込んだバイト数を出力するためのもの。第四引き数は、パケットの詳細情報を表すAudioStreamPacketDescriptionを出力するためのもの。第五引き数は、読み込むパケットの開始位置を表すもの。先頭から読み込む場合は0になり、続きから読み込みたい場合は順次インクリメントさせていく。第六引き数は、読み込むパケット数。このパケット数は、前回単位時間あたりのパケット数を計算したので、そこから求める事ができるだろう。そして第七引き数は、読み込んだパケットデータを格納するバッファになる。これには、AudioQueueBufferを直接している。
これでオーディオファイルからパケットデータを読み込む事ができる。かなり複雑だ。これは、オーディオデータを単なるバイト列として扱うのではなく、パケットごとに区切られたデータとして見る必要があるた
めだ。
データの読み込みに成功したら、これをAudio Queueに追加しよう。
List 4.
// 読み込みに成功した場合 if (numPackets > 0) {
// バッファの大きさを、読み込んだパケットデータのサイズに設定する audioQueueBuffer->mAudioDataByteSize = numBytes;
// バッファをキューに追加する status = AudioQueueEnqueueBuffer(
audioQueue, audioQueueBuffer, numPackets, _audioPacketDescs[index]);
再生音のループこれでデータの読み込みは行えたのだが、もう1つ考慮しなくてはいけないものがある。それは、再生音のループだ。ここでは楽器アプリを作っているのだが、ドラムやピアノのように、単純に用意した音を一度だけ再生すれば十分なものもあるが、オルガンや管楽器のように、ボタンを押している間は継続して音を鳴らす必要があるものもある。この場合、音のループ再生を行わなくてはいけない。
しかも、楽器のシミュレートの場合は、単純なループではだめである。なぜなら、楽器音は始まりの音(アタック音)と、伸ばしているときの音(経過音)とで構成されているからだ。単にループするとアタック音が何度も再生されてしまうので、適切な経過音のポイントに戻ってループを行う必要がある。
Audio Queueでは、バッファに書き込むデータのパケット位置を管理する事で、これらを実現する事ができる。具体的には、読み込んだパケット位置を覚えておき、ループ終了点を超えたら、ループ開始点まで戻してやる事になる。次のようなソースコードで対応できるだろう。
List 5.
// パケット位置を動かす _startPacketNumber += numPackets;
// ループの処理を行う if (_startPacketNumber > 40) {
_startPacketNumber = 8;
}
ここでは、40パケット目がループの終了地点。8パケット目がループの開始地点となっている。
気をつけなくてはいけないのは、一度音が止まったら、再び音を出すときは0パケット目から再生する必要がある事だ。8パケット目はループの戻り地点なので、ここからスタートすることはない。
これらを組み込んだ、アプリケーションのソースコードを公開しよう。楽器としては、オルガンをシミュレートしている。ボタンを押している間、音が鳴り続けているのが分かると思う。ただし、ループの継ぎ目があまりきれいではない。違和感のないループ音を作成するには、そのための技術が必要だ。今回はAudio Queueの説明に重きを置いたため、そこは省略させていただいた。
次回は、OpenALを使った再生について議論しよう。
ここまでのソースコード: RSS-3.zip
今回は、楽器アプリを作るための、3つめのフレームワークを紹介しよう。OpenALだ。
OpenALとは
OpenALは、オープンな標準に基づくオーディオライブラリだ。名前から想像できると思うが、3Dグラフィックの分野で標準の地位を築いたOpenGLのようなものを、オーディオの分野でも作ろうとしているものだ。そのような動機であるため、単なるオーディオの再生だけではなく、ドップラー効果や3D音響などゲームの役に立つAPIが色々と定義されている。
OpenALプログラミングは、3つの要素から構成される。Buffer、Source、Listenerだ。Bufferは、オーディオデータを管理するものだ。Sourceは、Bufferのデータを使い、音を再生するものになる。OpenALの面白いところは、このSourceを3D空間中に自由に配置できる事だ。たとえば、右前方、左後方などにSourceを置く事ができる。これにより、3D音響の様々な効果を期待できる。最後に、それらの音を聞くものがListenerとなる。
OpenALはオーディオの再生に特化したライブラリであり、汎用的なオーディオライブラリとしてみると機能は少ない。たとえば、mp3やaacといったオーディオフォーマットのデコードには対応していない。OpenALで再生を行うときは、データをリニアPCMの形で渡してやる必要がある。
となると、アプリケーション側ではなんらかの対応が必要になる。対応の1つは、再生するオーディオをすべてリニアPCMの形で持っておく事だ。だが、これではアプリケーションのファイルサイズが非常に大きいものになってしまう。そこで考えられるもう1つの対応は、OpenALを使う前に、オーディオファイルのデコードをプログラム中で行う事だ。この手順について説明しよう。
オーディオファイルのデコード
iPhoneでは様々な種類のオーディオフレームワークを使う事ができるが、最も機能の種類が豊富なのは、Audio Toolboxフレームワークだ。前回紹介した、Audio QueueやAudio Fileなどもこれに含まれる。
このフレームワークを使えば、mp3やaacといったオーディオファイルを、リニアPCMの形に変換する事ができる。オーディオフォーマットの変換にはAudio Converterというライブラリを使う事ができる。ここでは、この変換を直接オーディオファイルから行う事ができる、Extended Audio Fileライブラリを使う事にしよう。
このライブラリの使い方は、iPhone SDKに付属するサンプルである、oalTouchを参考にするのがいい。このサンプルには、MyGetOpenALAudioDataという関数が実装されている。この関数でオーディオファイルを読み込み、リニアPCMに変換しているのだ。
少し長くなるが、この関数を見やすくしたものをここに掲載しておこう。
List 1.
void* GetOpenALAudioData( CFURLRef fileURL,
ALsizei* dataSize, ALenum* dataFormat, ALsizei
*sampleRate) { OSStatus err; UInt32 size;
// オーディオファイルを開く ExtAudioFileRef audioFile; err = ExtAudioFileOpenURL(fileURL, &audioFile); if
(err) { goto Exit; } // オーディオデータフォーマットを取得する AudioStreamBasicDescription fileFormat; size = sizeof(fileFormat); err =
ExtAudioFileGetProperty( audioFile,
kExtAudioFileProperty_FileDataFormat, &size, &fileFormat);
if (err) { goto Exit; } // アウトプットフォーマットを設定する AudioStreamBasicDescription outputFormat; outputFormat.mSampleRate =
fileFormat.mSampleRate; outputFormat.mChannelsPerFrame
= fileFormat.mChannelsPerFrame; outputFormat.mFormatID
= kAudioFormatLinearPCM; outputFormat.mBytesPerPacket
= 2 * outputFormat.mChannelsPerFrame;
outputFormat.mFramesPerPacket = 1;
outputFormat.mBytesPerFrame = 2 *
outputFormat.mChannelsPerFrame;
outputFormat.mBitsPerChannel = 16;
outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked |
kAudioFormatFlagIsSignedInteger; err =
ExtAudioFileSetProperty( audioFile,
kExtAudioFileProperty_ClientDataFormat,
sizeof(outputFormat), &outputFormat); if (err) {
goto Exit; } // フレーム数を取得する SInt64 fileLengthFrames = 0; size = sizeof(fileLengthFrames);
err = ExtAudioFileGetProperty( audioFile,
kExtAudioFileProperty_FileLengthFrames, &size,
&fileLengthFrames); if (err) { goto Exit;
} // バッファを用意する UInt32 bufferSize; void* data; AudioBufferList dataBuffer;
bufferSize = fileLengthFrames *
outputFormat.mBytesPerFrame;; data =
malloc(bufferSize); dataBuffer.mNumberBuffers = 1;
dataBuffer.mBuffers[0].mDataByteSize = bufferSize;
dataBuffer.mBuffers[0].mNumberChannels =
outputFormat.mChannelsPerFrame;
dataBuffer.mBuffers[0].mData = data; // バッファにデータを読み込む err = ExtAudioFileRead(audioFile,
(UInt32*)&fileLengthFrames, &dataB uffer); if (err)
{ free(data); goto Exit; } // 出力値を設定する *dataSize = (ALsizei)bufferSize; *dataFormat = (outputFormat.mChannelsPerFrame > 1) ?
AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; *sampleRate =
(ALsizei)outputFormat.mSampleRate; Exit: // オーディオファイルを破棄する if (audioFile) { ExtAudioFileDispose(audioFile); } return data; }
基本的な流れを紹介しておく。まず、オーディオファイルをオープンする。これには、ExtAudioFileOpenURLを使う。そして、オーディオファイルに対して、アウトプットフォーマットを指定する事になる。このフォーマットに応じた形に変換が行われる事になる。ここでは、リニアPCMを表すkAudioFormatLinearPCMを指定している。そして変換したデータを格納するためのバッファを用意して、ExtAudioFileReadを使ってデータを読み込めばいい。
これで、mp3やaacといったオーディオフォーマットを、プログラム中で変換する事ができるようになる。この方式の利点は、オーディオリソースのファイルサイズを抑えられる事。それに対して欠点は、変換のための時間がかかる事になる。変換しなくてはいけないデータが多いときは、できるだけ分散して変換を行い、ユーザを待たせないように気をつける必要があるだろう。
OpenALの使い方
では、実際にOpenALを使って楽器アプリを作ってみよう。今回作成するのは、ドラムだ。とりあえず、このような画面を用意した。
OpenALを使うには、まず初期化を行う必要がある。これは、まずOpenALデバイスを開き、そこにOpenALコンテキストを作成する、という手順で行う。
List 2.
// OpneALデバイスを開く ALCdevice* device; device = alcOpenDevice(NULL); // OpenALコンテキスを作成して、カレントにする ALCcontext* alContext; alContext = alcCreateContext(device, NULL);
alcMakeContextCurrent(alContext);
次に、Bufferを作成する。これには、alGenBuffersという関数を使う。そして、これにオーディオデータを設定する。
List 3.
// バッファを作成する alGenBuffers(1, _buffers);
// サウンドファイルパスを取得する NSString* fileName = @"BD"; path = [[NSBundle mainBundle]
pathForResource:fileName ofType:@"m4a"]; // オーディオデータを取得する void* audioData; ALsizei dataSize; ALenum dataFormat; ALsizei sampleRate; audioData
= GetOpenALAudioData( (CFURLRef)[NSURL
fileURLWithPath:path], &dataSize, &dataFormat,
&sampleRate); // データをバッファに設定する alBufferData(_buffers[0], dataFormat, audioData, dataSize,
sampleRate);
ここでは、先ほど紹介したGetOpenALAudioData関数を使って、オーディオファイルをリニアPCMに変換している。そして、alBufferData関数を使ってデータをBufferに設定しているのだ。
そして、Sourceを作成する。作成したら、Bufferを関連付けてやる。
List 4.
// ソースを作成する alGenSources(1, _sources); // バッファをソースに設定する alSourcei(_sources[0], AL_BUFFER, _buffers[0]);
これで準備完了だ。あとは、オーディオを再生してやればいい。これには、alSourcePlayを使う。
List 5.
// オーディオを再生する alSourcePlay(_sources[0]);
これがOpenALを使ったオーディオの再生だ。あとは、画面上のそれぞれのボタンで、バスドラムやスネアドラムの音を鳴らすようにしてやれば、楽器アプリの完成だ。
ここまでのソースコード: Drums-1.zip
Top Related