Core Audio – iOS」カテゴリーアーカイブ

iPhone OS 3.0のオーディオ周りの変更点

OS3.0がでてからもう結構時間が経ってますが、オーディオ周りの挙動に変更があったところをまとめておきます。

ロック時に音が途切れない

AudioUnitのRemoteIOを使っている場合、AudioSessionのPreferredHardwareIOBufferDurationを何も設定せずデフォルト状態のままだと、以前と変わらずロックしていないときは1024フレーム、ロック中は4096フレームと切り替わってしまいますが、一度値を設定しておくと、ロックしても設定した値そのままで切り替わらなくなったようです。

RemoteIOのデフォルトフォーマットが変更

OS2.2.1以前のRemoteIOのデフォルトのフォーマットは8.24の固定小数点でしたが、OS3.0からは16bitの整数に変わっているようです。ただし、MultiChannelMixerなどはあいかわらず8.24の様ですので、組み合わせて使う場合は自分で設定して合わせる必要があり、注意が必要です。

Objective-CでAudioSession

いままでのC言語でのAPIだけでなく、AVFoundationにAVAudioSessionというObjective-CのAPIも加わり、より簡単にAudioSessionが使えるようになったようです。でもなぜかIOBufferDurationが設定できないんですよねぇ。使い方が間違ってるんでしょうか。

ExtAudioFileでMP3やAACなどの圧縮ファイルが使える

AVAudioPlayerでは以前から読み込めていましたが(AudioQueueでもでしたっけ?)、OS3.0からはExtAudioFileでも読み込めるようになったようです。個人的に録音に興味が無いので試していませんが、AACとかは書き込みも出来るかも。

Bluetooth経由で音の再生が可能に

BluetoothのA2DPに対応し、ワイヤレスで音の再生が可能になりました。が、手元にあったBluetoothレシーバ(以前使っていたTOSHIBAの携帯用のやつ)で試しに聴いてみたところ、音質は別にいいのですが、レイテンシーがひどすぎて、Touch the Waveとか楽器アプリで使うのはありえないという印象です。

ミュージックライブラリにアクセスが可能に

と聞いて期待したんですが、一瞬で打ち砕かれました。あくまでメタ情報の取得と、用意されているプレイヤーでの再生が出来るだけで、オーディオファイルを直接読み込む事は出来ません。なんかよく、ミュージックライブラリに直接アクセスなんて書いてありますが、「間接的」って表現のほうが合ってると思います。あ、でも、僕の認識が間違っていて、実はオーディオファイルの読み込みができるなんて情報があればぜひとも教えていただきたいです。

AudioUnit MultiChannelMixerを使う

いままでiPhoneのAudioUnitはRemoteIOしか使っていなくて、新規アプリ開発に向けてMixerのUnitを使ってみようと思ったら、なかなかうまくいかなくてハマってしまったのでメモしておきます。

以下が、MultiChannelMixerの初期化のコードになります。

- (void)setupMixerUnit:(AudioUnit *)fMixerUnit busCount:(NSUInteger)fBusCount
{
    AudioComponent component;
    AudioComponentDescription description;
    description.componentType = kAudioUnitType_Mixer;
    description.componentSubType = kAudioUnitSubType_MultiChannelMixer;
    description.componentManufacturer = kAudioUnitManufacturer_Apple;
    description.componentFlags = 0;
    description.componentFlagsMask = 0;
    AURenderCallbackStruct callback;
    callback.inputProc = MixerCallback;
    callback.inputProcRefCon = self;
    
    AudioStreamBasicDescription format;
    format.mSampleRate = 44100;
    format.mFormatID = kAudioFormatLinearPCM;
    format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
    format.mBitsPerChannel = 16;
    format.mChannelsPerFrame = 2;
    format.mFramesPerPacket = 1;
    format.mBytesPerFrame = format.mBitsPerChannel / 8 * format.mChannelsPerFrame;
    format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
    
    component = AudioComponentFindNext(NULL, &description);
    AudioComponentInstanceNew(component, fMixerUnit);
    
    UInt32 elementCount = fBusCount;
    AudioUnitSetProperty(*fMixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &elementCount, sizeof(UInt32));
    
    for (AudioUnitElement i = 0; i < elementCount; i++) {
        AudioUnitSetProperty(*fMixerUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, i, &callback, sizeof(AURenderCallbackStruct));
        AudioUnitSetProperty(*fMixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, i, &format, sizeof(AudioStreamBasicDescription));
    }
    
    AudioUnitInitialize(*fMixerUnit);
    
    //ボリュームを変更してみる
    for (AudioUnitElement i = 0; i < elementCount; i++) {
        AudioUnitParameterValue volume = 0.8;
        AudioUnitSetParameter(*fMixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, i, volume, 0);
    }
}

設定の順番が重要です。
1 - MultiChannelMixerのAudioUnitをインスタンス化
2 - インプットバスを追加
3 - インプットごとにコールバックやフォーマットをセット
4 - AudioUnitを初期化
5 - 初期化後に、プロパティの取得やパラメータの取得・設定

あとは、AUGraphでつなぐなり、RemoteIOのコールバック内でAudioUnitRenderするなりって感じですね。

ミキサーユニットをMacで使う場合のコードが、アップルのQ&Aのページにのっていたりするんですが、そこにはインプットバスを設定するプロパティがElementCountじゃなくてBusCountになっていて、iPhoneでは使えないのがハマった原因でした。よくヘッダを見ると、MacではkAudioUnitProperty_BusCount = kAudioUnitProperty_ElementCountって定義されてます。

ミキサーユニットにはオーディオフォーマットのコンバーターが組み込まれているので便利ですね。今回のコードだと、インプットに16bit・44.1kHz・ステレオのデータをそのまま渡せば8.24の固定小数点に勝手に変換してくれます。ただOS3.0だとRemoteIOのデフォルトのフォーマットが8.24ではなくなっているので、アウト側のフォーマットを設定して合わせる必要があります。

ちなみに、オーディオのデータをコールバックで渡すときは、for文で回してコピーするのではなく、出来るだけmemcpyとかで丸ごとコピーできるように、うまくフォーマットを指定した方が良いです。全然パフォーマンスが違います。

AudioSession その3 IOバッファサイズ

今回は、iPhoneオーディオのIOバッファサイズについてです。とりあえず、前回までのコードに、以下のようなsetIOBufferSizeメソッドを追加して、initにその呼び出しを加えてください。

- (void)setIOBufferSize
{
    Float64 sampleRate;
    UInt32 size = sizeof(sampleRate);
    AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, &sampleRate);
    
    Float32 bufferSize = 256./sampleRate;
    size = sizeof(bufferSize);
    AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, size, &bufferSize);
}
- (id) init
{
    self = [super init];
    if (self != nil) {
        [self setupAudioSession];
        [self setIOBufferSize];
        [self setupOutputUnit];
    }
    return self;
}

前回までのコードのコールバック内は、フレーム数に応じてサイン波の周波数がかわるようになっていましたので、変更を加えないときより高い音が再生されると思います。bufferSizeの256の所がバッファサイズのフレーム数ですので、そこを変更すれば任意のフレーム数に設定できます。コールバック内にprintfとかでinNumberFramesをログに出すようにすれば、ちゃんと変わっているのが分かると思います。

IOバッファサイズは秒数で指定しますので、今回のコードではハードウェアのサンプリング周波数を取得して、目的のフレーム数を割って値を求めています。プロパティの名前にPreferredってついているので指定した秒数の近いところに設定されるという感じでしょうか。Appleのサンプルでは、256に変更するときにちょっと少なめの0.005秒を指定していました。

ちなみにAudioSessionのプロパティのゲットは、AudioSessionがアクティブになっていないと出来ないようです。セットはいつでも大丈夫みたいです。Appleのサンプルをみると、プロパティをセットして、AudioSessionをアクティブにして、プロパティをゲットするというのがセオリーっぽいです。

IOバッファサイズはデフォルトの状態では1024フレームです。ロックしてスリープすると強制的に4096フレームになります。ただしOS 3.0では、今回のサンプルのようにAudioSessionSetPropertyでIOバッファサイズを一度設定すると、ロックしてもIOバッファサイズが変わらなくなったようです。また、カテゴリをPlayAndRecordなどにするだけでも変わりません。

AudioSession その2 AudioCategory

iPhoneのアプリでオーディオを再生・録音する場合に、iPodの音楽を鳴らしたままミックスして出したり、ロックした時にも音を出せるようにするには、AudioSessionSetProperty関数を使ってAudioCategoryというのを設定します。

AudioSessionSetPropertyはこんな感じで宣言されています。

extern OSStatus
AudioSessionSetProperty(AudioSessionPropertyID inID,
                        UInt32                 inDataSize,
                        const void             *inData)

AudioSessionSetPropertyの第一引数にはAudioSessionPropertyID、第二引数はプロパティのサイズ、第三引数には設定する値をポインタで渡します。CoreAudioではおなじみの構成です。

AudioSessionSetPropertyは色々とオーディオ関係の設定できますが、とりあえず今回はAudioCategoryの設定だけをやってみます。前回のコードのsetupAudioSessionメソッドを以下のように書き換えてください。

- (void)setupAudioSession
{
    AudioSessionInitialize(NULL, NULL, InterruptionListener, &outputUnit);
    
    UInt32 category = kAudioSessionCategory_AmbientSound;
    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
    
    AudioSessionSetActive(true);
}

AudioCategoryのプロパティの型はUInt32と決まっていますので、その変数を作ってポインタで渡すという感じになります。プロパティの型の種類はものによってバラバラですが、ヘッダを辿るとコメントに書いてあります。

これで実行すると、iPodの音楽を鳴らした状態でアプリを起動しても音楽が止まらず、サイン波がミックスして再生されると思います。

プロパティのkAudioSessionCategory_AmbientSoundの部分をkAudioSessionCategory_MediaPlaybackに変えてみてアプリを実行すると、iPodの音楽はフェードアウトしてしまいますが、ロックしてスリープするときにサイン波は鳴りっぱなしになります(でも、このコードではピッチは下がってしまいますが)。

カテゴリの動作一覧はAudioSession Programing Guide内にありまして、日本語に置き換えてみるとこんな感じでしょうか。

PropertyID サイレントスイッチの効果(およびスクリーンロック時の消音) 他アプリのオーディオ再生 使用例
UserInterfaceSoundEffects ある する ユーザーインターフェースなど
AmbientSound ある する ゲーム効果音やバーチャル楽器など。iPodの音楽を再生しながら鳴らすもの
SoloAmbientSound ある しない デフォルト。ゲームサウンドなど。他アプリの音を再生しないもの
MediaPlayback ない しない オーディオファイル再生
LiveAudio ない しない ライブパフォーマンス系。バーチャル楽器など
RecordAudio ない しない 録音のみ
PlayAndRecord ない しない 再生と録音

何もカテゴリーを設定しないとSoloAmbientSoundになってしまいます。それと残念ながら、iPodの音楽を鳴らしながらスクリーンロック中に音を出すってことは出来ないみたいです。

※2009/10/7追記
OS3.0以降、MediaPlaybackなどのカテゴリでも、kAudioSessionProperty_OverrideCategoryMixWithOthersを使う事でiPodの音楽を流しながら音を出す事が出来るようになっています。また、LiveAudioとUserInterfaceSoundEffectsはDeprecatedになってます。

また、ちょっと分からない部分なんですが、RecordAudioにしていたからといって再生が出来なくなるという訳ではなかったりするので、結局のところAudioCategoryの設定は3パターンしかないようです。もし今の段階で違いが無かったとしても、今後のOSのバージョンアップで何か変化があるかもしれませんけど。

※追記 2009/1/25
AudioCategoryの種類によってルートチェンジのときに違いがあるみたいですね。それはまたちゃんと調べたらルートチェンジで1つエントリを書きます。

といったところでAudioCategoryはこの辺で。次回は、ずっと悩まされ続けてきたIOバッファあたりについて書いてみようかと思います。

AudioSession その1 Interruption

以前、RemoteIOでのオーディオ再生というエントリでRemoteIOでオーディオ再生をする方法を書きましたが、それだけだとiPhoneのアプリとしては設定が不足です。オーディオを扱うアプリでは、AudioSessionというAPIを使って、オーディオに関する情報の取得や設定を行います。SystemSoundServiceなんかのお手軽再生系では無用の様ですが、AudioUnitを使う場合には必須といえるでしょう。

ちなみに、AudioSession系の関数はシミュレータではエラーが出て一切受け付けてくれず、実機にしか効果が及びません。また、デフォルトのオーディオIOのバッファサイズは実機では1024サンプルですが、シミュレータではMacのオーディオインターフェースの設定そのままですので、オーディオの動作の検証は必ず実機で行いましょう。

※OS 3.0にはAVFoundationにAVAudioSessionというObjective-CでAudioSessionが扱えるフレームワークが追加されましたが、動作に?な部分がありますので、あえてC言語のフレームワークの使い方そのままで記述しておきます。

AudioSessionで出来る事はいろいろありますが、いくつかあげてみると

・iPodのミュージックの音を鳴らしたままにするか、止めてしまうかの設定
・スリープしたときにアプリの音声を停止するかの設定
・通話などが割り込んできて自分のアプリの音声が停止する時の通知を受ける
・ヘッドホンがささったりなど、オーディオのルーティングが変わったときの通知を受ける
・オーディオハードウェアの設定の変更や、設定されている情報の取得

といったところでしょうか。必ずやっておかなければいけないのは、外部から割り込みが入ってオーディオが停止する時と、その後復活する時の処理です。これを適切にやっておかないと、アプリに戻ってきたときに音声が再生できなくなります。

では、以前のエントリRemoteIOでのオーディオ再生のYKAudioOutput.mのコードに以下の関数とメソッドを追加してください。initは修正になります。

void InterruptionListener(void *inUserData,
                          UInt32 inInterruption)
{
    AudioUnit *remoteIO = (AudioUnit*)inUserData;
    if (inInterruption == kAudioSessionEndInterruption) {
        AudioSessionSetActive(true);
        AudioOutputUnitStart(*remoteIO);
        printf("EndInterruption\n");
    }
    
    if (inInterruption == kAudioSessionBeginInterruption) {
        AudioOutputUnitStop(*remoteIO);
        printf("BeginInterruption\n");
    }
}
- (void)setupAudioSession
{
    AudioSessionInitialize(NULL, NULL, InterruptionListener, &outputUnit);
    AudioSessionSetActive(true);
}
- (id) init
{
    self = [super init];
    if (self != nil) {
        [self setupAudioSession];
        [self setupOutputUnit];
    }
    return self;
}

setupAudioSessionメソッドでAudioSessionの設定を行っています。AudioSessionInitialize関数ではInterruptionListnerという関数を登録して初期化しています。以下がAudioToolbox/AudioServices.hの中にある宣言です。

extern OSStatus
AudioSessionInitialize(CFRunLoopRef                      inRunLoop,
                       CFStringRef                       inRunLoopMode,
                       AudioSessionInterruptionListener  inInterruptionListener,
                       void                              *inClientData)

1つ目と2つ目のRunLoopのところはそれぞれNULLを入れておけば、デフォルトで登録されます。4つ目のinClientDataは自由に使えるところなので、今回のコードではAudioUnitのポインタを渡してAudioUnitのスタートとストップを出来るようにしています。

AudioSessionInitializeで初期化が出来たら、AudioSessionSetActive(true)を呼ぶとAudioSessionが開始されます。

AudioSessionが開始されるとInterruptionListener関数がオーディオの割り込み時に呼ばれまして、inInterruptionにkAudioSessionBeginInterruptionがくれば割り込み開始(アプリのオーディオが停止)、kAudioSessionEndInterruptionなら割り込み終了(アプリのオーディオが復活)となりますので、それぞれに応じて処理するようにします。

割り込みが開始された場合にはAudioUnitをストップするだけで良いのですが、終了のときにはAudioSessionSetActiveを先に読んでおかないと、たぶんAudioUnitStartでアプリが落ちます。

※OS 3.0ではアプリが落ちたりする事は無いようですが、AudioSessionSetActiveを呼ばないとAudioUnitが再開しないのは変わらないようです。

最低限これだけやっておけば良いと思いますが、今回のコードだと、アプリ起動時にiPodのミュージックが停止したり、ロック時に音が止まったりとデフォルトの状態のままですので、その設定の仕方は次回やります。

iPod touchのInterruption Callback

iPhoneでアラームとか通話とかが割り込んできてアプリのオーディオが停止してしまうときの通知を受け取るにはAudioSessionでInterruption Callbackを登録するって感じなのですが、サンプルソースなどは以下のようなコードになってまして…

void rioInterruptionListener(void *inUserData, UInt32 inInterruption)
{
    AudioUnit *remoteIO = (AudioUnit*)inUserData;

    if (inInterruption == kAudioSessionEndInterruption) {
        AudioSessionSetActive(true);
        AudioOutputUnitStart(*remoteIO);
    }

    if (inInterruption == kAudioSessionBeginInterruption)
        AudioOutputUnitStop(*remoteIO);		
}

自分のアプリに戻ってきたときにはこのコールバックが呼ばれて、kAudioSessionEndInterruptionがinInterruptionに来る事になっていますが、1つ例外があるようです。

iPod touchでホームボタンをダブルタップすると、ミュージックをコントロールできる小さいウィンドウが現れます。そこで再生を押すとオーディオの割り込み開始のkAudioSessionBeginInterruptionは来ますが、そのあと停止してウィンドウを閉じてもkAudioSessionEndInterruptionは来ません。

とりあえず、戻ってくるところはUIApplicationのapplicationDidBecomeActive:で処理しないといけなさそうです。

OpenGLとRemoteIOとiPod 2Gでのノイズの件

※このエントリの内容を修正しました。

放置するといっていたiPod touch第2世代のノイズですが、どうやらOpenGLのViewをWindow全体に貼付けるようにしておけば入らないようです。

と思って一度書いたのですが、関係ありませんでした。AudioSessionのkAudioSessionProperty_PreferredHardwareIOBufferDurationの値を小さくする事で解決できそうな感じです。たぶんビューあたりをいじってノイズが消えたと思っていたのはたまたまだったのだと思います。どちらにしろ、OpenGL使わなければノイズは入らないんですけどねぇ。

それはそれとして、ここ数ヶ月はずっと開発モードだったので、勉強モードに切り替えてOpenGLを中心にちゃんと習得しようかなぁと思います。今月あたまに頼んでた「MOBILE 3D GRAPHICS」って本もちょうど届いたところなんで。

ちなみに、Touch the Waveのv0.5.2をApp Storeに提出してあります。前回のエントリの修正に加えて、波形表示が60fpsのなめらかでスムーズなスクロールになってます。最初に実機で動かしていた頃は何の変化も無かった「Compile for Thumb」外しが、ここに来てかなり効果を発揮して、60fpsでもタッチを邪魔しなくなってきました。そのかわり、電池の消耗はより激しくなっているかもしれません…。

修正とiPod 2Gでの不具合

Touch the Waveの波形表示が再生位置とちょっとずれていたので、修正したものをApp Storeに提出してあります。何も無ければ2〜3日後くらいには公開されるんじゃないでしょうか。

それはそれとして、音質向上のための修正をしていたらv0.5をiPod touch第2世代で使っているとノイズがプチプチと入っている事が判明してしまいました。v0.4ではCore Animationを使っている事で数秒間の動作停止が起きることがあって、それを修正するためにv0.5ではOpenGLに変更したんですけど、どうやらそっちも相性が悪かったようです。

別に特別な処理をしているからという訳ではなく、何も描画していないOpenGLのビューを一枚表示しておいて、RemoteIOでシンプルにサイン波だけを鳴らしてみると、それだけでプチプチいっちゃいます。なのでTouch the Waveではプレイリストとダウンロードの画面では大丈夫なんですけど、肝心のプレイヤーでノイズが入ってしまうという困った感じです。

まさか僕のデバイスだけの故障とかじゃないですよねぇ。第1世代では何事も無く再生できちゃうんですけど。

まあ、iPod touch第2世代はもともとロスレスの再生がプツプツとぎれるって不具合がありますから。全体的にパフォーマンスはアップしているけどオーディオは逆に弱くなっているんでしょうか。

回避策としてあと残されているのは、Quartzですかね。試してみてパフォーマンス的に問題無さそうならそっちに切り替えます。せっかくのiPhoneでのプログラミングなんだからCore AnimationとかOpenGLとか使ってみたかったのに、結局Quartzに落ち着くとしたらもったいないなぁと思いますけど。それでも駄目ならOS側の修正アップデートを待つしかないって感じです。

さっそくAVAudioPlayerを使ってみた

iPhone OSが2.2にアップデートされまして、Frameworkに新しくAVFoundationなんてものが追加されていました。今のところAVAudioPlayerというAPIしか無いようですが、AVと名がついているだけに今後映像がらみでも機能が増えそうな気がします。

そのAVAudioPlayerを早速試してみたところ、お手軽BGM再生APIという感じのようです。オーディオファイルのパスを渡してやるだけでWAV、AIFF、mp3、AAC、ロスレスあたりはいとも簡単に再生できます。これがあればやたらめんどくさいAudio Queue Serviceなんていりません。

ということでとりあえず再生してみるコードです。バンドルにmp3を入れてから実行すると延々とループして再生します。2つ以上あればミックスして再生されます。ちょっとお行儀の悪いコードですので、そのへんは適当に流してください。

- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    [window makeKeyAndVisible];
    
    NSBundle *bundle = [NSBundle mainBundle];
    NSArray *wavePaths = [NSBundle pathsForResourcesOfType:@"mp3" inDirectory:[bundle bundlePath]];
    
    for (NSString *path in wavePaths) {
        NSURL *url = [NSURL fileURLWithPath:path];
        NSError *error = nil;
        AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
        if (!error) {
            audioPlayer.numberOfLoops = -1;
            [audioPlayer play];
        }
    }
}

RemoteIOでのオーディオ再生

iPhone用のアプリも公開できて一段落つきまして、さらにNDAも緩和されたという事で、iPhoneのオーディオプログラミングネタを書いていきたいと思います。

iPhoneで音を再生する方法はいくつかあります。短いサンプルを再生するならSystemSoundService。BGMで使うような長い曲を再生するならAudioQueueService。レイテンシーを低く再生するならAudioUnit。あと、OpenALを使うという手もあります。

「Touch the Wave」で使っているのはAudioUnitです。SystemSoundは短すぎて役に立ちませんし、AudioQueueではリアルタイムに音を変化させるような事や逆再生が出来ないですし(たぶん)、OpenALはよくわからないって感じでしたので。

何はなくともとりあえずAudioUnitを使うクラスのサンプルコードです。このクラスを生成すればAudioUnitが開始されて、解放すれば停止します。シンプルにするためにエラー処理はまったく記述していません。

※最終修正 2011/10/24

//
//  YKAudioOutput.h
//
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
@interface YKAudioOutput : NSObject {
AudioUnit outputUnit;
}
@end
//
//  YKAudioOutput.m
//
#import "YKAudioOutput.h"
@implementation YKAudioOutput
static OSStatus OutputCallback(void *inRefCon,
                               AudioUnitRenderActionFlags *ioActionFlags,
                               const AudioTimeStamp *inTimeStamp,
                               UInt32 inBusNumber,
                               UInt32 inNumberFrames,
                               AudioBufferList *ioData)
{
    OSStatus err = noErr;
    for (NSInteger i = 0; i < ioData->mNumberBuffers; i++) {
        //2009/6/28 OS3.0対応
        SInt16 *ptr = ioData->mBuffers[i].mData;
        for (NSInteger j = 0; j < inNumberFrames; j++) {
            UInt32 channels = ioData->mBuffers[i].mNumberChannels;
            for (NSInteger k = 0; k < channels; k++) {
                ptr[j * channels + k] = sin(M_PI / inNumberFrames * j * 50) * INT16_MAX;
            }
        }
    }
    return err;
}
- (void)setupOutputUnit
{
    AudioComponent component;
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    
    component = AudioComponentFindNext(NULL, &desc);
    AudioComponentInstanceNew(component, &outputUnit);
    AudioUnitInitialize(outputUnit);
    AURenderCallbackStruct callback;
    callback.inputProc = OutputCallback;
    callback.inputProcRefCon = self;
    
    AudioUnitSetProperty(outputUnit,
                         kAudioUnitProperty_SetRenderCallback,
                         kAudioUnitScope_Global,
                         0,
                         &callback,
                         sizeof(AURenderCallbackStruct));
    
    AudioStreamBasicDescription outputFormat;
    UInt32 size = sizeof(AudioStreamBasicDescription);
    
    outputFormat.mSampleRate = 44100;
    outputFormat.mFormatID = kAudioFormatLinearPCM;
    outputFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
    outputFormat.mBitsPerChannel = 16;
    outputFormat.mChannelsPerFrame = 2;
    outputFormat.mFramesPerPacket = 1;
    outputFormat.mBytesPerFrame = outputFormat.mBitsPerChannel / 8 * outputFormat.mChannelsPerFrame;
    outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;
    AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputFormat, size);
    AudioOutputUnitStart(outputUnit);
}
- (void)dispose
{
    AudioOutputUnitStop(outputUnit);
    AudioUnitUninitialize(outputUnit);
    AudioComponentInstanceDispose(outputUnit);
    outputUnit = NULL;
}
- (id) init
{
    self = [super init];
    if (self != nil) {
        [self setupOutputUnit];
    }
    return self;
}
- (void) dealloc
{
    [self dispose];
    [super dealloc];
}
@end

まず、AudioUnitを使うからといってAudioUnitFrameworkをインポートしてもエラーが出ます。AudioUnitだけをつかうときでもAudioUnitFrameworkはインポートせず、AudioToolbox.Frameworkだけをインポートすれば良いようです。

コードを見ていきますと、setupOutputUnitメソッドがAudioUnitのセットアップしている部分になります。順番としては、

・AudioComponentDescriptionに呼び出すAudioUnitの情報を記述する
・AudioComponentNextでAudioComponentを取得する
・AudioComponentInstanceNewでAudioUnitを取得する
・AudioUnitInitializeでAudioUnitを初期化する
・AURenderCallbackStructでコールバックの情報を記述する
・AudioUnitSetPropertyでコールバックの設定をする
・AudioUnitStartでRemoteIOをスタートする

という流れになっています。Macでのときと違って関数の名前にAudioとかついていますが、使い方は変わりません。iPhoneでのアウトプットユニットはRemoteIOと名前がついてまして、サブタイプにkAudioUnitSubType_RemoteIOを指定します。RemoteIOがスタートしたらコールバックが定期的に呼び出されますので、そこでオーディオデータを渡します。

iPhone OS 2.2.1までのRemoteIOのデフォルトのフォーマットは8.24の固定小数点でしたが、OS 3.0からは16bit整数のインターリーブドに変更されたようです。ただ、あくまでデフォルトが変更されただけですので、OSのバージョンがなんだろうが、自分でフォーマットを設定しておけば互換性は問題なく保たれます。ミキサーのユニットなんかは変わらず8.24みたいですので注意が必要です。

iOS 5ではさらにデフォルトが32ビットfloatに変更されているようです。AudioUnitSetPropertyでフォーマットを設定するように変更しました。(2011/10/24)