月別アーカイブ: 2012年10月

AudioQueueProcessingTap

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