原文 Audio Queue Services Programming Guide (http://developer.apple.com/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/index.html)
Audio Queue Serviceを使って音声を再生する際、その信号源はどんなものでも有り得ます。ディスク上のファイル、ソフトウェアシンセサイザ、メモリ上のオブジェクトなどです。 本章では最も一般的な方法-ディスク上のファイルの再生方法について説明します。
より単純化するため、これらコード例は強力なエラー処理(robust error handling)を省いています。 Audio Queue Serviceを用いた録音/再生を実装する際は、潜在的なエラーを扱うためのコードを必ず追加するようにして下さい。
アプリケーションに音声再生機能を追加するには、通常、以下の段階を踏むと良いでしょう:
この章の残りで、これら段階をそれぞれ詳細に解説します。
手始めに、音声形式とAudio Queueの状態の管理に使用する、独自構造体を定義します。 リスト3-1はその構造体の例です:
リスト3-1 再生Audio Queue向けの独自構造体
static const int kNumberBuffers = 3; // 1 struct AQPlayerState { AudioStreamBasicDescription mDataFormat; // 2 AudioQueueRef mQueue; // 3 AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4 AudioFileID mAudioFile; // 5 UInt32 bufferByteSize; // 6 SInt64 mCurrentPacket; // 7 UInt32 mNumPacketsToRead; // 8 AudioStreamPacketDescription *mPacketDescs; // 9 bool mIsRunning; // 10 };
この構造体の殆どのメンバ変数は、“音声の記録”の“状態管理用の独自構造体の定義”で解説した、録音用独自構造体のそれと全く一緒(ないしそれに近い)です。
例えば、mDataFormat領域は、ここでは再生するファイルの形式を保持します。
録音の時は、この類似した領域はディスクへ書き出すファイルの形式を保持します。
では、この構造体の変数領域の解説をしましょう:
AudioStreamBasicDescription (CoreAudioTypes.h)構造体です。この形式はmQueue変数のAudio Queueによって使用されます。mDataFormat領域は、“ファイルの音声データ形式の取得”で解説するAudio FileへのkAudioFilePropertyDataFormatプロパティの問い合わせで埋められます。AudioStreamBasicDescription構造体の詳細はCore Audio Data Types Referenceをご覧下さい。DeriveBufferSize関数で計算されます。詳しくは“再生Audio Queue Bufferの大きさを求める関数の記述”をご覧下さい。bufferByteSize変数のように、この値はAudio Queueの生成後、再生開始前までに本資料サンプル中のDeriveBufferSize関数で計算されます。次に、再生Audio Queueのコールバック関数を書きます。 このコールバックは、主に次の3つの事を行います:
本項では、以上3つの項目ごとにわけて解説を行い、コールバックの宣言例を示します。そして最終的に再生コールバック全体を披露します。 再生用コールバックの役割については、以前示した図1-4を参照して下さい。
リスト3-2は、再生用Audio Queueのコールバック関数の宣言例を示しており、これはAudioQueue.hヘッダファイルでAudioQueueOutputCallbackとして宣言されています:
リスト3-2 再生用Audio Queueコールバックの宣言
static void HandleOutputBuffer ( void *aqData, // 1 AudioQueueRef inAQ, // 2 AudioQueueBufferRef inBuffer // 3 )
では、このコードの働きを見ていきましょう:
aqDataは、“状態管理用の独自構造体の定義”で解説した、Audio Queueの状態情報を含む独自構造体です。再生用Audio Queueコールバックの最初の仕事は、音声ファイルからデータを読み込んでAudio Queue Bufferに入れる事です。 リスト3-3はその方法を示しています。
リスト3-3 ファイルからデータを読み込みAudio Queue Bufferに入れる
AudioFileReadPackets ( // 1 pAqData->mAudioFile, // 2 false, // 3 &numBytesReadFromFile, // 4 pAqData->mPacketDescs, // 5 pAqData->mCurrentPacket, // 6 &numPackets, // 7 inBuffer->mAudioData // 8 );
では、このコードの働きを見ていきましょう:
AudioFileReadPackets関数は、AudioFile.hヘッダファイルで宣言されおり、Audio Fileからデータを読み込んでバッファへと入れます。音声ファイルからデータを読み込み、Audio Queue Bufferへの格納が完了したので、リスト3-4で示すようにコールバックはAudio Queue Bufferをエンキューします。 一度Buffer Queueに入れば、バッファ中の音声データはAudio Queueから出力装置に送る事が出来ます。
リスト3-4 ディスクから読み込み後Audio Queue Bufferをエンキューする
AudioQueueEnqueueBuffer ( // 1 pAqData->mQueue, // 2 inBuffer, // 3 (pAqData->mPacketDescs ? numPackets : 0), // 4 pAqData->mPacketDescs // 5 );
では、このコードの働きを見ていきましょう:
AudioQueueEnqueueBuffer関数はAudio Queue BufferをBuffer Queueに追加します。Audio Queue Serviceはアプリケーションに、使用するAudio Queue Bufferの大きさの特定を期待します。 リスト3-7はそれを行うための一例です。 このコードは、与えられる音声データの時間を保持するのに十分に大きいサイズのバッファを求めます。
Audio Queueへのバッファ確保要求の条件として、アプリケーション中では、再生用Audio Queueを生成した後にDeriveBufferSize関数を呼びます。
“再生Audio Queueの大きさの設定”をご覧下さい。
このコードは、“録音Audio Queue Bufferの大きさを求める関数の記述”で見た類似した関数と比べて、2つの事項が追加されています。 再生では以下の事も行います:
AudioFileReadPackets関数を呼び出す度に、読み込むパケット数を求めます。この計算は、ディスクから読み込む音声データ形式を考慮します。 その形式には、音声チャンネル数といったバッファサイズに影響を及ぼす、全ての要因が含まれます。
リスト3-7 再生用Audio Queue Bufferの大きさを求める
void DeriveBufferSize ( AudioStreamBasicDescription &ASBDesc, // 1 UInt32 maxPacketSize, // 2 Float64 seconds, // 3 UInt32 *outBufferSize, // 4 UInt32 *outNumPacketsToRead // 5 ) { static const int maxBufferSize = 0x50000; // 6 static const int minBufferSize = 0x4000; // 7 if (ASBDesc.mFramesPerPacket != 0) { // 8 Float64 numPacketsForTime = ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds; *outBufferSize = numPacketsForTime * maxPacketSize; } else { // 9 *outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize; } if ( // 10 *outBufferSize > maxBufferSize && *outBufferSize > maxPacketSize ) *outBufferSize = maxBufferSize; else { // 11 if (*outBufferSize < minBufferSize) *outBufferSize = minBufferSize; } *outNumPacketsToRead = *outBufferSize / maxPacketSize; // 12 }
では、このコードの働きを見ていきましょう:
AudioStreamBasicDescription構造体です。kAudioFilePropertyPacketSizeUpperBoundを用いてAudioFileGetProperty関数(AudioFile.hヘッダファイル)を呼ぶ事で、この値を決定出来ます。“再生Audio Queueの大きさの設定”をご覧下さい。今度は再生する音声ファイルを開きます。それには次の3つの段階を経ます:
リスト3-8は、再生したい音声ファイルのCFURLオブジェクトの取得方法の例です。 このCFURLオブジェクトは、次の段階で、ファイルを開くのに使用します。
リスト3-8 音声ファイルのCFURLオブジェクトを得る
CFURLRef audioFileURL = CFURLCreateFromFileSystemRepresentation ( // 1 NULL, // 2 (const UInt8 *) filePath, // 3 strlen (filePath), // 4 false // 5 );
ではコードの働きを見て行きましょう:
CFURL.hヘッダファイルで宣言されるCFURLCreateFromFileSystemRepresentation関数はCFURLオブジェクトを生成し、再生するファイルを表します。NULL(またはkCFAllocatorDefault)を与えます。filePathの値はユーザーが指定した値となるでしょう。filePathがファイルを表しており、ディレクトリでは無い事を示します。リスト3-9は再生する音声ファイルの開き方の例です。
リスト3-9 再生する音声ファイルを開く
AQPlayerState aqData; // 1 OSStatus result = AudioFileOpenURL ( // 2 audioFileURL, // 3 fsRdPerm, // 4 0, // 5 &aqData.mAudioFile // 6 ); CFRelease (audioFileURL); // 7
ではコードの働きを見て行きましょう:
AQPlayerState独自構造体(“状態管理用の独自構造体を定義する”をご覧下さい)のインスタンスを生成します。再生する音声ファイルを開く際、音声ファイルを表すAudio Fileオブジェクト(AudioFileID型)を保持する場所として、このインスタンスを使用します。AudioFile.hヘッダファイルで宣言されるAudioFileOpenURLは、再生したいファイルを開きます。CFURLオブジェクトです。mAudioFile変数へのポインタを指定します。リスト3-10はファイルの音声データ形式の取得方法を示しています。
リスト3-10 ファイルの音声データ形式を得る
UInt32 dataFormatSize = sizeof (aqData.mDataFormat); // 1 AudioFileGetProperty ( // 2 aqData.mAudioFile, // 3 kAudioFilePropertyDataFormat, // 4 &dataFormatSize, // 5 &aqData.mDataFormat // 6 );
ではコードの働きを見て行きましょう:
AudioFileGetProperty関数は、AudioFile.hヘッダファイルで宣言され、Audio Fileの特定のプロパティ値を得ることが出来ます。AudioFileID型)は、音声データ形式の取得対象のファイルを表します。AudioStreamBasicDescription構造体の予定サイズを指定します。関数からの出力として、実際の大きさが返ってきます。再生用アプリケーションではこの値を利用する必要はありません。AudioStreamBasicDescriptionの形で返ってきます。この行では、ファイルの音声データ形式を、Audio Queueの独自構造体に格納する事によって、それをAudio Queueに適用します。
リスト3-11は再生Audio Queueの生成方法を示しています。
気をつけるべき事は、AudioQueueNewOutput関数は再生するファイルの音声データ形式に加えて、独自構造体と先の手順で設定したコールバックも使用するという事です。
リスト3-11 再生Audio Queueの生成
AudioQueueNewOutput ( // 1 &aqData.mDataFormat, // 2 HandleOutputBuffer, // 3 &aqData, // 4 CFRunLoopGetCurrent (), // 5 kCFRunLoopCommonModes, // 6 0, // 7 &aqData.mQueue // 8 );
ではコードの働きを見て行きましょう:
AudioQueueNewOutput関数は、新規再生用Audio Queueを生成します。kCFRunLoopCommonModes定数が使用されます。次は再生用Audio Queueの各種サイズを設定します。 これらサイズは、音声ファイルから読み込みを開始する前、Audio Queueのバッファを確保する時に使用します。
本項で揚げるコードは以下のサイズの設定のし方を示します:
リスト3-12は、以前に記述したDeriveBufferSize関数(“再生用Audio Queue Bufferの大きさを求める関数を書く”をご覧下さい)の使い方の実例です。
ここでの目標は、各Audio Queue Bufferごとにバイト単位で容量を設定し、再生用Audio Queueコールバック呼び出しにおける読み込みパケット数を決定することです。
このコードは、最大パケットサイズを控え目に見積もります。そしてこのサイズは、Core AudioがkAudioFilePropertyPacketSizeUpperBoundプロパティで提供するものです。
殆どの場合において、このテクニックを使うことは、音声ファイル全体を検査し実際の最大パケットサイズを得るために時間をかけることよりも、良い選択です(おおよそですが、この方が速いです)。
リスト3-12 再生Audio Queue Bufferの容量と読み込むパケット数を設定する
UInt32 maxPacketSize; UInt32 propertySize = sizeof (maxPacketSize); AudioFileGetProperty ( // 1 aqData.mAudioFile, // 2 kAudioFilePropertyPacketSizeUpperBound, // 3 &propertySize, // 4 &maxPacketSize // 5 ); DeriveBufferSize ( // 6 aqData.mDataFormat, // 7 maxPacketSize, // 8 0.5, // 9 &aqData.bufferByteSize, // 10 &aqData.mNumPacketsToRead // 11 );
ではコードの働きを見ていきましょう:
AudioFileGetProperty関数(AudioFile.hヘッダファイルで宣言)は、音声ファイルの特定のプロパティ値を得る関数です。ここでは、再生するファイルの音声データパケットサイズを、バイト単位で控え目に得るために使用しています。AudioFileID型)です。“音声ファイルを開く”をご覧下さい。kAudioFilePropertyPacketSizeUpperBoundプロパティのバイト単位のサイズです。DeriveBufferSize関数が、バッファ容量と再生用Audio Queueコールバックの呼び出し毎に読み込むパケット数を設定します。MPEG 4 AACといった幾つかの圧縮音声形式は、音声メタデータを格納するための構造体を使用します。 これら構造体はマジッククッキーと呼ばれます。 マジッククッキーを持つファイル形式をAudio Queue Serviceを使って再生する時は、再生を始める前に音声ファイルよりマジッククッキーを取得し、それをAudio Queueに追加します。
リスト3-14では、ファイルからマジッククッキーを取得し、Audio Queueへの適用のし方を示しています。 あなたのプログラムの中で、再生前にこのリストの関数を呼び出せばよいでしょう。
リスト 3-14 再生Audio Queueにマジッククッキーを設定する
UInt32 cookieSize = sizeof (UInt32); // 1 bool couldNotGetProperty = // 2 AudioFileGetPropertyInfo ( // 3 aqData.mAudioFile, // 4 kAudioFilePropertyMagicCookieData, // 5 &cookieSize, // 6 NULL // 7 ); if (!couldNotGetProperty && cookieSize) { // 8 char* magicCookie = (char *) malloc (cookieSize); AudioFileGetProperty ( // 9 aqData.mAudioFile, // 10 kAudioFilePropertyMagicCookieData, // 11 &cookieSize, // 12 magicCookie // 13 ); AudioQueueSetProperty ( // 14 aqData.mQueue, // 15 kAudioQueueProperty_MagicCookie, // 16 magicCookie, // 17 cookieSize // 18 ); free (magicCookie); // 19 }
コードの働きを見て行きましょう:
AudioFile.hヘッダファイルで宣言されているAudioFileGetPropertyInfo関数は、特定のプロパティの値の大きさを得ます。プロパティ値を保持する変数の容量を設定するために、この関数を使用します。 AudioFileID型)です。NULLを指定し、プロパティの読み書き属性に興味がないことを示します (#原文:Uses NULL to indicate that you don’t care about the read/write access for the property.)AudioFile.hヘッダファイルで宣言されているAudioFileGetPropertyは、特定のプロパティの値を得ます。今回の場合、プロパティは音声ファイルのマジッククッキーです。AudioFileID型)です。 magicCookie変数の大きさを与えます。出力として、マジッククッキーの実際の大きさがバイト数単位で、magicCookie変数へ書き込まれます。(“再生Audio Queueの生成”で)生成したAudio Queueに、Audio Queue Buffer集合を設定する準備を行います。 リスト3-15は、その作業の例です。
リスト 3-15 再生用のAudio Queue Bufferの確保と準備
aqData.mCurrentPacket = 0; // 1 for (int i = 0; i < kNumberBuffers; ++i) { // 2 AudioQueueAllocateBuffer ( // 3 aqData.mQueue, // 4 aqData.bufferByteSize, // 5 &aqData.mBuffers[i] // 6 ); HandleOutputBuffer ( // 7 &aqData, // 8 aqData.mQueue, // 9 aqData.mBuffers[i] // 10 ); }
このコードの働きを見て行きましょう:
mBuffers配列に加えます。HandleOutputBuffer関数は、あなたが書く再生Audio Queueコールバックです。“再生Audio Queueコールバックの記述”をご覧下さい。Audio Queueで再生を開始する前に、Audio Queueの利得をAudio Queueパラメータという仕組みにより設定します。 リスト3-16は、この作業のし方を示しています。 パラメータ機構のより詳しい情報は“Audio Queueパラメータ”をご覧下さい。
リスト 3-16 Audio Queueの再生利得の設定
Float32 gain = 1.0; // 1 // 利得設定はここで任意に書き換えることが出来ます AudioQueueSetParameter ( // 2 aqData.mQueue, // 3 kAudioQueueParam_Volume, // 4 gain // 5 );
コードの働きを見て行きましょう:
kAudioQueueParam_Volume定数は、Audio Queueの利得を設定します。 これまでの全てのコードで、ファイル再生の工程が整いました。 この項は、リスト3-17に示すように、Audio Queueの開始とファイル再生中の実行ループの管理についてが含まれます。
リスト 3-17 Audio Queueの開始と実行
aqData.mIsRunning = true; // 1 AudioQueueStart ( // 2 aqData.mQueue, // 3 NULL // 4 ); do { // 5 CFRunLoopRunInMode ( // 6 kCFRunLoopDefaultMode, // 7 0.25, // 8 false // 9 ); } while (aqData.mIsRunning); CFRunLoopRunInMode ( // 10 kCFRunLoopDefaultMode, 1, false );
コードの働きを見て行きましょう:
NULLを指定し、Audio Queueが再生をすぐに開始するようにします。mIsRunningメンバを定期的に監視します(#原文:Polls the custom structure’s mIsRunning field regularly to check if the audio queue has stopped.)0.25秒に設定します。ファイルの再生が終了したら、Audio Queueを破棄し、音声ファイルを閉じ、残りの全ての資源を解放します。 リスト3-18はこれらの手順を示しています。
リスト 3-18 音声ファイル再生後の掃除
AudioQueueDispose ( // 1 aqData.mQueue, // 2 true // 3 ); AudioFileClose (aqData.mAudioFile); // 4 free (aqData.mPacketDescs); // 5
コードの働きを見て行きましょう:
trueを使用します。AudioFile.hヘッダファイルで宣言されています。