投稿者「yuki」のアーカイブ

Core Audio その2 AudioStreamBasicDescription

Core Audioにおいて、オーディオデータの状態(リニアPCM等のフォーマットとか、ビットやサンプリングレートとか、チャンネル数とか)を表す構造体がAudioStreamBasicDescriptionです。

struct AudioStreamBasicDescription
{
    Float64 mSampleRate;       //サンプリング周波数(1秒間のフレーム数)
    UInt32  mFormatID;         //フォーマットID(リニアPCM、MP3、AACなど)
    UInt32  mFormatFlags;      //フォーマットフラグ(エンディアン、整数or浮動小数点数)
    UInt32  mBytesPerPacket;   //1パケット(データを読み書きする単位)のバイト数
    UInt32  mFramesPerPacket;  //1パケットのフレーム数
    UInt32  mBytesPerFrame;    //1フレームのバイト数
    UInt32  mChannelsPerFrame; //1フレームのチャンネル数
    UInt32  mBitsPerChannel;   //1チャンネルのビット数
    UInt32  mReserved;         //意味なし。アラインメントを揃えるためのもの?
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

フォーマットIDは以下のような定数が定義されています。

enum
{
    kAudioFormatLinearPCM               = 'lpcm',
    kAudioFormatAC3                     = 'ac-3',
    kAudioFormat60958AC3                = 'cac3',
    kAudioFormatAppleIMA4               = 'ima4',
    kAudioFormatMPEG4AAC                = 'aac ',
    kAudioFormatMPEG4CELP               = 'celp',
    kAudioFormatMPEG4HVXC               = 'hvxc',
    kAudioFormatMPEG4TwinVQ             = 'twvq',
    kAudioFormatMACE3                   = 'MAC3',
    kAudioFormatMACE6                   = 'MAC6',
    kAudioFormatULaw                    = 'ulaw',
    kAudioFormatALaw                    = 'alaw',
    kAudioFormatQDesign                 = 'QDMC',
    kAudioFormatQDesign2                = 'QDM2',
    kAudioFormatQUALCOMM                = 'Qclp',
    kAudioFormatMPEGLayer1              = '.mp1',
    kAudioFormatMPEGLayer2              = '.mp2',
    kAudioFormatMPEGLayer3              = '.mp3',
    kAudioFormatTimeCode                = 'time',
    kAudioFormatMIDIStream              = 'midi',
    kAudioFormatParameterValueStream    = 'apvs',
    kAudioFormatAppleLossless           = 'alac',
    kAudioFormatMPEG4AAC_HE		= 'aach',
    kAudioFormatMPEG4AAC_LD		= 'aacl',
    kAudioFormatMPEG4AAC_HE_V2		= 'aacp',
    kAudioFormatMPEG4AAC_Spatial	= 'aacs',
    kAudioFormatAMR			= 'samr'
};

普通にオーディオデータを扱うときは、WAVやAIFFでおなじみの非圧縮フォーマットであるリニアPCM(kAudioFormatLinearPCM)になります。その他は、それぞれの圧縮フォーマットのファイルの読み書きをするときに使用します。オーディオファイルのフォーマット以外にもタイムコードやMIDIやParameterValueStreamなんてのがあるのがちょっと面白そうなところです。

フォーマットフラグは以下の定数が定義されています。

enum
{
    kAudioFormatFlagIsFloat                     = (1L << 0),
    kAudioFormatFlagIsBigEndian                 = (1L << 1),
    kAudioFormatFlagIsSignedInteger             = (1L << 2),
    kAudioFormatFlagIsPacked                    = (1L << 3),
    kAudioFormatFlagIsAlignedHigh               = (1L << 4),
    kAudioFormatFlagIsNonInterleaved            = (1L << 5),
    kAudioFormatFlagIsNonMixable                = (1L << 6),
    kAudioFormatFlagsAreAllClear                = (1L << 31),
    
    kLinearPCMFormatFlagIsFloat                 = kAudioFormatFlagIsFloat,
    kLinearPCMFormatFlagIsBigEndian             = kAudioFormatFlagIsBigEndian,
    kLinearPCMFormatFlagIsSignedInteger         = kAudioFormatFlagIsSignedInteger,
    kLinearPCMFormatFlagIsPacked                = kAudioFormatFlagIsPacked,
    kLinearPCMFormatFlagIsAlignedHigh           = kAudioFormatFlagIsAlignedHigh,
    kLinearPCMFormatFlagIsNonInterleaved        = kAudioFormatFlagIsNonInterleaved,
    kLinearPCMFormatFlagIsNonMixable            = kAudioFormatFlagIsNonMixable,
    kLinearPCMFormatFlagsAreAllClear            = kAudioFormatFlagsAreAllClear,
    
    kAppleLosslessFormatFlag_16BitSourceData    = 1,
    kAppleLosslessFormatFlag_20BitSourceData    = 2,
    kAppleLosslessFormatFlag_24BitSourceData    = 3,
    kAppleLosslessFormatFlag_32BitSourceData    = 4
};

enum
{
#if TARGET_RT_BIG_ENDIAN
    kAudioFormatFlagsNativeEndian       = kAudioFormatFlagIsBigEndian,
#else
    kAudioFormatFlagsNativeEndian       = 0,
#endif
    kAudioFormatFlagsCanonical =
        kAudioFormatFlagIsFloat |
        kAudioFormatFlagsNativeEndian |
        kAudioFormatFlagIsPacked,
    kAudioFormatFlagsNativeFloatPacked =
        kAudioFormatFlagIsFloat |
        kAudioFormatFlagsNativeEndian |
        kAudioFormatFlagIsPacked
};

フラグですから、設定したい定数をビット演算で組み合わせて指定します。例としてAIFFの16bit整数のオーディオファイルを作成する場合には、ビッグエンディアンで符号付き整数でPackedになりますから、

AudioStreamBasicDescription desc;
desc.mFormatFlags =
    kAudioFormatFlagIsBigEndian |
    kLinearPCMFormatFlagIsSignedInteger |
    kAudioFormatFlagIsPacked;

といった感じになります。

Core Audioではデフォルトだとオーディオデータは32bitのFloatのPackedのネイティブなエンディアンで扱われますから、それらが既に組み合わせられた、kAudioFormatFlagsNativeFloatPackedなんていう便利な定数も用意されています。

ちなみにPackedとは何かというと、オーディオデータの1サンプルに割り当てられたデータ領域の全てのビットを使った状態です。例えば、オーディオデータの1サンプルに32bitのメモリ領域が割り当てられているときに、ぴったり32bitのデータが入っている状態がPackedという事になります。32bitのメモリ領域に20bitのデータが入っているような場合はPackedはセットせず、その20bitのデータが上位ビットに寄せられていればAlignedHigh、下位ビットに寄せられていればAlignedLow(AlignedHighをセットしない)になります。

Core Audioでオーディオデータを扱うときには基本的にPackedなので、Packedでない状態が実際に使われているところがないかと探したら、自分の使っているMacBook Proのオーディオデバイスがそうでした。デバイスのビットを20bitや24bitに設定したときにはpackedではなく、32bitの領域が割り当てられていてAlignedLowになっています。とはいってもデバイス側のフォーマットなので、CoreAudio経由で使うときは基本的に32bitFloatに変換された状態で渡ってきますから、実際に意識する事はないと思います。

mBytesPerPacketからmBitsPerChannelまでの5つのメンバは、オーディオのデータがどんな状態で並んでいるかが表されます。

リニアPCMでのそれぞれの関係性を見ていくと、mBitsPerChannelで指定されたビット数の1サンプルをチャンネル数分まとめたものがフレームで、そのフレームをまとめて一回分の読み書きの単位としているのがパケットです。

mChannelsPerFrameで1フレーム内のチャンネル数、mBytesPerFrameで1フレームの容量、mFramesPerPacketで1パケット内のフレーム数、mBytesPerPacketで1パケットの容量が表される事になります。

リニアPCMならフレーム単位で一つのデータが成立するのでmFramesPerPacketが1となり、他の全てのメンバにもフォーマットに応じた値が設定されますが、圧縮フォーマットであれば、いくらかのフレームがまとめられて1パケットに圧縮されているので、mFramesPerPacketとmChannelsPerFrameのみが設定され、他の値は0という場合もあります。

リニアPCMでPackedの場合、5つのうち3つ決まれば残り2つは自然と値が計算で求められます。たとえば32bitのステレオのInterleavedだと、

desc.mBitsPerChannel = 32;
desc.mFramesPerPacket = 1;
desc.mChannelsPerFrame = 2;
desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame;
desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;

という感じです。

例として、オーディオファイルでよく使われそうなフォーマットの設定値を載せておきます。

// AIFF 16bit 44.1kHz STEREOの場合

AudioStreamBasicDescription aiffFormat;
aiffFormat.mSampleRate = 44100.0;
aiffFormat.mFormatID = kAudioFormatLinearPCM;
aiffFormat.mFormatFlags = 
    kAudioFormatFlagIsBigEndian |
    kAudioFormatFlagIsSignedInteger |
    kAudioFormatFlagIsPacked;
aiffFormat.mBitsPerChannel = 16;
aiffFormat.mChannelsPerFrame = 2;
aiffFormat.mFramesPerPacket = 1;
aiffFormat.mBytesPerFrame = 4;
aiffFormat.mBytesPerPacket = 4;
aiffFormat.mReserved = 0;


// WAVE 8bit 48kHz MONOの場合

AudioStreamBasicDescription wavFormat;
wavFormat.mSampleRate = 48000.0;
wavFormat.mFormatID = kAudioFormatLinearPCM;
wavFormat.mFormatFlags = kAudioFormatFlagIsPacked; //WAVの8bitはunsigned
wavFormat.mBitsPerChannel = 8;
wavFormat.mChannelsPerFrame = 1;
wavFormat.mFramesPerPacket = 1;
wavFormat.mBytesPerFrame = 1;
wavFormat.mBytesPerPacket = 1;
wavFormat.mReserved = 0;


// AAC 44.1kHz STEREOの場合

AudioStreamBasicDescription m4aFormat;
m4aFormat.mSampleRate = 44100.0;
m4aFormat.mFormatID = kAudioFormatMPEG4AAC;
m4aFormat.mFormatFlags = kAudioFormatFlagIsBigEndian;
m4aFormat.mBytesPerPacket = 0;
m4aFormat.mFramesPerPacket = 1024;
m4aFormat.mBytesPerFrame = 0;
m4aFormat.mChannelsPerFrame = 2;
m4aFormat.mBitsPerChannel = 0;
m4aFormat.mReserved = 0;

前回から見てきたAudioBufferListやAudioStreamBasicDescriptionが扱えれば、オーディオデバイスやオーディオファイル等と、オーディオデータやフォーマット情報のやり取りが出来るようになります。その方法については次回やってみたいと思います。

Core Audio その1 AudioBufferとAudioBufferList

いままでvDSPやら何やらと、オーディオプログラミングでも補助的なものをネタにしていたので、ちょっとここらへんで基本に立ち返ってCore Audioの基本的な部分を書いていこうと思います。自分的にもちゃんと書いておかないと、オーディオ系のプログラミングからしばらく離れたりしたときに忘れてしまいそうになるので。

Core Audioというと、広い意味ではAudioUnitプラグインやCore MIDIまで含まれていると思いますが、ここではCoreAudio Frameworkという、まさにCore Audioな部分を見ていこうと思います。

どこから始めようかとと考えましたが、なにはなくともオーディオのデータが扱えなくては始まりません。Core Audioにはオーディオデータを表す構造体として、AudioBufferとAudioBufferListというものが定義されていますので、とりあえず、そこを見ていきます。

AudioBufferはひとつのオーディオデータを表していて、そのAudioBufferを配列でまとめて持っているのがAudioBufferListになります。

struct AudioBuffer
{
    UInt32  mNumberChannels;
    UInt32  mDataByteSize;
    void*   mData;
};
struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[kVariableLengthArray];
};
typedef struct AudioBufferList  AudioBufferList;

AudioBufferから見ていくと、mDataはオーディオデータのあるメモリ領域へのポインタです。mDataByteSizeはmDataの領域のサイズで、mNumberChannelsは含まれるチャンネル数になります。

AudioBufferListに移りまして、mBuffers[kVariableLengthArray]がAudioBufferの配列で、mNumberBuffersが配列の要素数になります。

なぜこんな風に2重構造になっているのかというと、オーディオデータが複数チャンネルある場合に、データの状態がインターリーブドになっているかいないかで2通りの扱い方があるからです。オーディオファイルなどではインターリーブドで渡す場合が多いですし、AudioUnitなどではインターリーブドでない形で渡す事になります。

例として、それぞれの場合でAudioBufferListを作成してみます。

長さが256フレームの32bitのステレオのデータをAudioBufferListを作ってみると、Interleavedならこんな感じです。

UInt32 frames = 256;
UInt32 channels = 2;
AudioBufferList list;
list.mNumberBuffers = 1;
list.mBuffers[0].mNumberChannels = channels;
list.mBuffers[0].mDataByteSize = frames * sizeof(float) * channels;
list.mBuffers[0].mData = calloc(1, frames * sizeof(float) * channels);

NonInterleavedだと、ちょっと工夫しなくてはいけません。

AudioBufferListのmBuffersに「kVariableLengthArray」という定数が記述されていますが、コマンド+ダブルクリックでたどってみると、

enum {
  kVariableLengthArray          = 1
};

となっています。AudioBufferListを作成した時点で要素が一つのAudioBufferの配列が確保されているという事ですので、InterleavedならAudioBufferListだけを作っておけば良かったのですが、NonInterleavedで配列の要素が2以上の場合はその分メモリを確保しておかなくてはいけません。mBuffersは構造体の最後のメンバですので、その後ろにそのまま確保します。

アップルのサンプルにあったAudioBufferListの生成と解放をするコードを参考にして、Objective-Cのメソッドにしてみると、

- (AudioBufferList *)allocateAudioBufferList:(UInt32)numChannels size:(UInt32)size
{
    AudioBufferList *list;
    UInt32 i;
	
    list = (AudioBufferList*)calloc(1, sizeof(AudioBufferList)
        + numChannels * sizeof(AudioBuffer));
    if (list == NULL) return NULL;
	
    list->mNumberBuffers = numChannels;
	
    for(i = 0; i < numChannels; ++i) {
        list->mBuffers[i].mNumberChannels = 1;
        list->mBuffers[i].mDataByteSize = size;
        list->mBuffers[i].mData = malloc(size);
        if(list->mBuffers[i].mData == NULL) {
            [self removeAudioBufferList:list];
            return NULL;
        }
    }
	
    return list;
}

- (void)removeAudioBufferList:(AudioBufferList *)list
{
    UInt32 i;
	
    if(list) {
        for(i = 0; i < list->mNumberBuffers; i++) {
            if (list->mBuffers[i].mData) free(list->mBuffers[i].mData);
        }
        free(list);
    }
}

といった感じになります。ちなみに、メモリを確保するところで、

list = (AudioBufferList*)calloc(1, sizeof(AudioBufferList)
    + numChannels * sizeof(AudioBuffer));

となっていて、もともとAudioBufferListでAudioBufferが1チャンネル分確保されているのに、さらにチャンネル数分のAudioBufferを確保しているのが無駄なような気がしますが、余分に確保しておく分には動作に問題はなさそうなのと、MTCoreAudioでも同じなので、とりあえずそのままコピペしてきてます。(ヘッダのコメントを見てみると、以前はkVariableLengthArrayを0にしてたけど、ANSI Cだと駄目だから1にしてるんだ、と書いてあります。)

このメソッドを使ってAudioBufferListを作成してみると、こんな感じになります。

UInt32 frames = 256;
UInt32 channels = 2;
AudioBufferList *list =
    [self allocateAudioBufferList:channels size:frames * sizeof(float)];

と、今回はAudioBufferListを見てきましたが、AudioBufferListの情報だけでは、チャンネル数が分かっても、ビットとかサンプリング周波数とかオーディオデータのフォーマットは分かりません。それに関しては、また次回。

AudioUnitをオフラインで使う

オーディオファイルにエフェクトをバッチ処理でかけるような時に、AudioUnitを使って出来ないかと思ったのですが、そういう事ができそうなkAudioUnitType_OfflineEffectを取得しても、VariSpeedやTimePitchしか出てきません。しかし、Logicとかで出来てる事が出来ないはずはないと思い、普通のkAudioUnitType_Effectをオフラインで使える方法はないかと調べてみました。

リアルタイムでの処理をシミュレートするという形で調べた結果、ポイントのみをとりあえず書いておきます。

・AudioUnitRender関数を実行すると別のスレッドでエフェクト処理をしてしまうので、AudioUnitAddRenderNotifyで、エフェクト処理した後のタイミングでデータを受け取る関数を設定する。
・その関数内ではActionFlagsがkAudioUnitRenderAction_PostRenderの時が処理後のデータとなる。
・AudioUnitRender関数に渡すAudioBufferListを自前で用意する。NotInterleaved。1024フレーム分くらいがベスト(小さいと処理が遅く、大きいと受け付けてくれない)。
・AudioUnitRender関数に渡すAudioUnitRenderActionFlagsは0。
・AudioUnitRender関数に渡すAudioTimeStampはmSampleTimeだけ必要だが、同じ数字を連続で渡してはいけない(一つでもずらしていればOK)。

ここらへんを守って、一回の処理が終わるごとに次のAudioUnitRenderを呼び出す、といったところでしょうか。ちなみにこの方法だと再生しながらバックグラウンドで処理させるみたいな事は出来ないと思います。

なんか、オフラインモードに切り替えてAudioUnitRender呼ぶだけでいいなんていうスマートな方法ってないですかねぇ。

高速フーリエ変換

vDSPで高速フーリエ変換を行う関数の使い方です。

vDSP One-Dimensional Fast Fourier Transforms Referenceというリファレンスを見ると、一次元のフーリエ変換だけで30個ほどの関数が用意されています。

大きく分けてRealとComplexがあり、そのそれぞれがさらに、In-Place(変換前のバッファにそのまま上書き)かOut-of-Place(変換前と後で別々のバッファ)かで分かれています。さらに細かく見るとfloat用とdouble用とか、一度にたくさん処理するものとか、いろいろあります。

今回はIn-PlaceでComplexのfft_zip()を使ってみます。

ところで、このfft_zip()関数などは、リファレンスには関数名にvDSP_がついていますが、コード補完では引数付きの状態では出てこなくて、vDSP_がつかないfft_zipで出てきたりします。

//fft_zip()関数

void vDSP_fft_zip (FFTSetup setup,	//FFTセットアップ
   DSPSplitComplex * ioData,		//複素数配列の構造体
   vDSP_Stride stride,			//ストライド
   vDSP_Length log2n,			//FFTサイズ
   FFTDirection direction);		//正変換か逆変換か

だいたいのvDSPの関数は、その関数のみで完結しますが、FFTを使うにはvDSP Libraryというリファレンスでもわざわざ使い方が解説されているように、ちょっとした準備と後始末が必要です。

まず、Accelerateフレームワークをインポートしておきます。

#import <Accelerate/Accelerate.h>

create_fftsetup()という関数で、FFTSetupというインスタンスのようなものを作成します。第一引数で指定するFFTのサイズは2の何乗か、になります。9なら512サンプル、10なら1024サンプルといった感じです。使う関数によって指定できるサイズの範囲が決まっています。

第二引数ではradixを指定します。今回はFFT_RADIX2を指定します。ほかにFFT_RADIX3とFFT_RADIX5もあり、FFTするサンプル数が合えば(2の累乗の3倍とか5倍とか)、こちらを使う事もできるようです。

//FFTするサイズを2の何乗かで指定する。この場合1024サンプル。
vDSP_Length log2n = 10;

//FFTセットアップを作成する
FFTSetup fftSetup = create_fftsetup(log2n, FFT_RADIX2);

複素数を実数と虚数を別々の配列でメンバに持つDSPSplitComplexという構造体を宣言し、realp(実数配列)とimagp(虚数配列)に、それぞれ変換するデータを入れたメモリ領域を割り当てます。

//FFTを行うデータを作成する
vDSP_Length fftSize = 1 << log2n;
DSPSplitComplex splitComplex;
splitComplex.realp = calloc(fftSize, sizeof(float));
splitComplex.imagp = calloc(fftSize, sizeof(float));
	
/*
ここでsplitComplex.realpとimagpに、変換するデータをコピーする
 */

変換する方向を指定します。正変換ならFFT_FORWARD、逆変換ならFFT_INVERSEです。ストライドも指定します。ここでは連続したデータと想定して1にしておきます。

//FFTか逆FFTを指定する。逆ならFFT_INVERSE
FFTDirection direction = FFT_FORWARD;
//ストライドを指定する
vDSP_Stride signalStride = 1;

fft_zip()関数で変換を行います。

//FFTを行う
fft_zip(fftSetup, &splitComplex, signalStride, log2n, direction);

なお、fft_zip()関数において逆変換をした場合には、FFTサイズ倍の大きさのレベルそのままでデータが返ってきていますので、FFTサイズで割ります。

//逆FFTの場合FFTサイズでシグナルを割る
if (direction == FFT_INVERSE) {
    float scale = 1.0 / fftSize;
    vsmul(splitComplex.realp, 1, &scale, splitComplex.realp, 1, fftSize);
    vsmul(splitComplex.imagp, 1, &scale, splitComplex.imagp, 1, fftSize);
}
	
/*
ここでsplitComplexからデータを取り出す
 */

このvsmul()という関数は、float配列の全要素に対して、同じ数をかけ算する関数です。このようにIn-Placeでも使えますし、アウト側に別の配列を渡してOut-of-Placeで使う事もできます。

最後にdestroy_fftsetup()でFFTセットアップを解放します。

//FFTセットアップを解放する
destroy_fftsetup(fftSetup);

free(splitComplex.realp);
free(splitComplex.imagp);

続けて変換を行うなら、fft_zipを繰り返して終わった後で解放すれば良いと思います。FFTをクラスとして作ってしまうなら、initでcreateして、deallocでdestroyという感じでしょうか。

と、単純にフーリエ変換はこんなところですが、フーリエ変換時によく使われる窓関数をかける関数もvDSPに用意されています。

vDSP_blkman_window (ブラックマン窓)
vDSP_hamm_window (ハミング窓)
vDSP_hann_window (ハニング窓)

という3種類(double用も含めると6種類)です。

dBとリニア値を変換する

0.0 = -inf dB、1.0 = 0dBとした場合。

リニア値からdBへ変換するには、

dBVolume = 20.0*log10(linearVolume);

dBからリニア値へ変換するには、

linearVolume = pow(10.0, dBVolume/20.0);

で、できる

ちなみにvDSPにはvDSP_vdbconという関数があり、リニア値からdBへの変換はできる。

void  vDSP_vdbcon (
   float * A,
   vDSP_Stride I,
   float * B,
   float * C,
   vDSP_Stride K,
   vDSP_Length N,
   unsigned int F);

Aはインプットする配列、Cはアウトプットされる配列(Aと同じでも可)、IとKにはそれぞれのストライド、Nには変換する配列の長さ、Bはzero referenceなので1.0を、Fには元のデータがpowerなら0、amplitudeなら1を指定となっているので1を指定する。

biquad filter

Mac OS XのAccelerateフレームワークには、連続したデータの配列にデジタル信号処理をするときなどに便利な関数がいろいろ用意されています。Accelerateフレームワークを使えば1つの記述をするだけで、内部で勝手に判断してAltiVecやSSE等で素早く処理を行ってくれるようです。場合によってはアクセラレートしない事もありますが、いちいち自分でfor文を回したりしなくても良くて、処理が速くなる可能性があるのですから、使わない手はありません。

では、Accelerateフレームワークの中にあるvDSP_deq22()という関数でbiquadのフィルタをかけるクラスを作ってみます。vDSP_deq22()関数は以下のように宣言されています。

void vDSP_deq22 (
   float * A,		//インプットする配列
   vDSP_Stride I,	//インプットのストライド
   float * B,		//係数の配列
   float * C,		//アウトプットされる配列
   vDSP_Stride K,	//アウトプットのストライド
   vDSP_Length N	//処理するサイズ
);

これはfloat配列用ですが、double配列用にvDSP_deq22D()という関数も用意されています。基本的にvDSPの関数はDがついているのがdouble用で、ついてないのがfloat用という名前のつけ方になっているようです。

まず、Accelerateフレームワークを追加しインポートします。

#import <Accelerate/Accelerate.h>

オーディオデータを細切れに分けて処理をすることを考え、使い回すデータのインスタンス変数を用意します。

@interface IIRUnit : NSObject {
    float *gCoefBuffer;	//係数の配列
    float *gInputKeepBuffer;	//インプット2サンプル分のバッファ
    float *gOutputKeepBuffer;	//アウトプット2サンプル分のバッファ
}
- (id)init
{
    self = [super init];
    if (self != nil) {
        gInputKeepBuffer = calloc(2, sizeof(float));
        gOutputKeepBuffer = calloc(2, sizeof(float));
        gCoefBuffer = calloc(5, sizeof(float));
    }
    return self;
}

- (void)dealloc
{
    free(gInputKeepBuffer);
    free(gOutputKeepBuffer);
    free(gCoefBuffer);
    [super dealloc];
}

biquadフィルタでは処理するデータに対して2サンプル前と1サンプル前のデータが必要なので、その配列を用意します。インプット用2サンプル分とアウトプット用2サンプル分です。
次に5個の係数の配列を用意します。係数の順番はB0、B1、B2、A1、A2です。6個の係数を使用する場合は先に5つの係数をA0で割っておきます。フィルタやEQの係数の求め方はAudio-EQ-Cookbook等を参考にしてください。

http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt

それでは、処理を行うメソッドです。

- (void)processWithIoData:(float *)ioData frames:(NSUInteger)frames
{
    //処理用のバッファを用意する
    float *tInputBuffer = malloc((frames + 2) * sizeof(float));
    float *tOutputBuffer = malloc((frames + 2) * sizeof(float));
    
    //処理用のバッファにデータをコピー
    memcpy(tInputBuffer, gInputKeepBuffer, 2 * sizeof(float));
    memcpy(tOutputBuffer, gOutputKeepBuffer, 2 * sizeof(float));
    memcpy(&(tInputBuffer[2]), ioData, frames * sizeof(float));
    
    //処理を行う
    vDSP_deq22(tInputBuffer, 1, gCoefBuffer, tOutputBuffer, 1, frames);
    
    //処理後のデータをコピー
    memcpy(ioData, tOutputBuffer, frames * sizeof(float));
    memcpy(gInputKeepBuffer, &(tInputBuffer[frames]), 2 * sizeof(float));
    memcpy(gOutputKeepBuffer, &(tOutputBuffer[frames]), 2 * sizeof(float));
    
    free(tInputBuffer);
    free(tOutputBuffer);
}

処理したいシグナルデータの配列のサイズ+2サンプル分のサイズの配列をインプットA用とアウトプットC用に用意します。それぞれ頭の2サンプルには、前回処理した最後の2サンプルをコピーしておきます。3サンプル目以降に元のデータをコピーします。

ストライドI、Kはデータをいくつ飛ばしで読み込みor書き込みをするかを指定します。シグナルデータがNotInterleavedなモノラルの連続したデータであれば、ストライドは1です。もし、StereoのInterleavedなデータ(LRLRLR…の順番)の片方のチャンネルを処理するのであれば、ストライドは2です。StereoのRchとかならストライドを2にしつつ、vDSP_deq22関数に渡す配列のポインタをRchの先頭位置に持っていかなければいけません。

処理サイズNは、2サンプル足さない元データのサイズ(バイト数ではなくサンプル数)を指定します。

vDSP_deq22()関数で処理したら、処理前のデータA、処理後のデータCのそれぞれ最後の2サンプルを次の処理用にインスタンス変数にコピーしておきます。

処理後のデータCの3サンプル目から必要に応じてコピーします。今回はもとのデータを上書きしています。