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そのまま渡すと、ノーマルスピードで進んでいると判断してデータを取ってきてしまいます。