iOS6からAudioQueueServiceにProcessingTapという機能が追加されました。
これを利用すると、Queueにバッファされた後のデータにエフェクトなど処理を挟み込む事ができるようになります。AudioQueueのピッチ処理はiOSでは機能していないと思いますが、AudioUnitを挟み込む事もできるので、VarispeedとかNewTimePitchとか使えば実現する事ができます。
AudioQueueProcessingTapNew
ProcessingTapをAudioQueueで使えるようにするのが以下の関数です。
extern OSStatus AudioQueueProcessingTapNew( AudioQueueRef inAQ, AudioQueueProcessingTapCallback inCallback, void *inClientData, UInt32 inFlags, UInt32 *outMaxFrames, AudioStreamBasicDescription * outProcessingFormat, AudioQueueProcessingTapRef * outAQTap)
AudioQueueProcessingTapNewでAudioQueueProcessingTapを作って、既に作ってあるAudioQueueに追加します。なお、ひとつのAudioQueueにつき、ひとつのProcessingTapしか割り当てられないようです。
inAQにはProcessingTapを入れたいAudioQueueを指定します。
inCallbackには処理をするコールバックの関数を指定します。引数の構成はAudioUnitのRenderCallbackと似たような感じです。
inFlagsではCallbackの動作の仕方を指定する事ができます。使えるのは以下の3つです。
kAudioQueueProcessingTap_PreEffects kAudioQueueProcessingTap_PostEffects kAudioQueueProcessingTap_Siphon
PreEffectsとPostEffectsは必ずどちらかを指定しないといけません。ここでのEffectというのはMacでのみ使えるAudioQueueのピッチ処理の事のようで、その前後のどちらかを選択できるようです。iOSだと、今の所どちらを選択しても動作的にはおそらく変わりません。
Siphonをフラグに足しておくと、CallbackのioDataにバッファからのデータが入った状態で来ます。フラグを入れなければ、AudioQueueProcessingTapGetSourceAudioという関数を使ってバッファからデータを読み込まなければいけません。ProcessingTapのコールバックの中だけでゴリゴリ自分で処理をするならSiphoneを入れる、AudioUnitを使って入力側のコールバックにデータが欲しい場合やスピードを変更したい場合はSiphonを入れないでAudioQueueProcessingTapGetSourceAudioを呼ぶ、という事になると思います。
outMaxFramesやoutProcessingFormatには、ProcessingTapコールバックでの最大のフレーム数や、フォーマットが返ってきます。たぶん自分でフォーマットを指定したりとかはできません。オーディオファイルをリニアPCMに解凍した状態のフォーマットが返ってくると思います。AudioUnitのエフェクトを使いたい場合は、AUConverterなどを使ってoutProcessingFormatに変換したり、outMaxFramesのフレーム数をmaximumPerFramesに設定する必要があると思います。
AudioQueueにProcessingTapを追加する簡単なコードは以下のような感じです。
// // インスタンス変数とか(AudioQueueは既にセットアップ済みを想定) // AudioQueueRef _queue; // AudioQueueProcessingTapRef _processingTap; // static void Callback( void *inClientData, AudioQueueProcessingTapRef inAQTap, UInt32 inNumberFrames, AudioTimeStamp *ioTimeStamp, UInt32 *ioFlags, UInt32 *outNumberFrames, AudioBufferList *ioData) { AudioQueueProcessingTapGetSourceAudio( inAQTap, inNumberFrames, ioTimeStamp, ioFlags, outNumberFrames, ioData); // ここで何か処理をする } - (void)setupAudioQueueProcessingTap { UInt32 processingMaxFrames; AudioStreamBasicDescription processingFormat; BOOL isPost = YES; BOOL isSiphon = NO; UInt32 flags = isPost ? kAudioQueueProcessingTap_PostEffects : kAudioQueueProcessingTap_PreEffects; if (isSiphon) flags |= kAudioQueueProcessingTap_Siphon; AudioQueueProcessingTapNew( _queue, Callback, NULL, flags, &processingMaxFrames, &processingFormat, &_processingTap); }
このコードではエフェクトが何もかからないスルー状態です。Siphonをフラグに入れていないのでAudioQueueProcessingTapGetSourceAudioを呼んでいます。何か処理をしたい場合は、GetSourceAudioの後にioDataの中身をいじってください。もしSiphonをいれたらAudioQueueProcessingTapGetSourceAudioの行は全くなしでも音が鳴ります。
AudioUnitのエフェクトを使う場合には、AudioUnitの入力側のコールバックでGetSourceAudioを呼ぶ事になります。
あと、スピードを変更する場合、AudioUnitのVariSpeedを使うなら気にしなくてもいいのですが、自分でやる場合にはGetSourceAudioに渡すTimeStampのsampleTimeをちゃんと使ったフレーム分だけ毎コールバック進めないといけません。ProcessingTapに来ているTimeStampそのまま渡すと、ノーマルスピードで進んでいると判断してデータを取ってきてしまいます。