Core Audio においてもっとも低レベルに位置する Audio Unit。リアルタイムで高度なオーディオ波形処理を行いたい場合や複雑なルーティングによるオーディオ処理を実現したい場合、これを使用する必要が出てきます。
が、このフレームワーク、個人的には使用頻度があまり高くない *1 ので、ひさびさに触ってみた際にとっつきにくさを感じました。ただ慣れてしまえば 全体的なコンセプトはシンプル で、単に関数の引数がやたら多かったり、構造体の要素がやたら多かったり、慣れてないC言語APIだったりするだけだなと。
そんなわけで、次に久しぶりに Audio Unit をいじるときに、「シンプルな全体感」と、「複雑に感じてしまう部分」を切り分けて見ることができるよう、メモっておきます。
基本的な考え方
Audio Unit の基本コンセプトは、「いろいろなユニットを複数接続し、オーディオ処理を実現する」というもの。
で、そのひとつひとつのユニットがノード(AUNode)、それらが繋がっている全体がグラフ(AUGraph)。この考え方や呼称は直観的にもわかりやすいです。
そして実際の実装手順としても、下記のように非常にわかりやすいです。この流れさえ把握しておけば、後述する「一見複雑そう」な諸々が出てきても全体感は見失わずにすむかと。
1. グラフ(AUGraph)作成
NewAUGraph(&graph); AUGraphOpen(graph);
2. ノードをグラフに追加する
AUGraphAddNode(graph, &ioUnitDescription, &ioNode);
3. ノードを接続する
AUGraphConnectNodeInput(graph, ioNode, 1, ioNode, 0);
4. グラフを初期化して処理開始
AUGraphInitialize(graph); AUGraphStart(graph);
で、これに、下記要素が絡んできます。
- AudioComponentDescription
- Audio Unit のプロパティ設定
- AudioStreamBasicDescription (ASBD)
- コールバック
- Audio Converter Sevices や Extended Audio File Services 等の関連サービス
このあたりが絡んでくることによって、コードも長く複雑になり、オーディオフォーマットや C言語の知識も必要になってきて、そのあたりの知識の乏しい自分が久々に Audio Unit に触るとうわー難しいってなるのかなと。そんなわけで、整理のため以下にひとつひとつ紐解いておきます。
AudioComponentDescription
前述した基本手順の「ノードをグラフに追加する」手順において、 「どのユニットをノードとして追加するか」 を指定するものが AudioComponentDescription という構造体。
たとえば Remote IO ユニットの場合は下記のようになります。
AudioComponentDescription cd; cd.componentType = kAudioUnitType_Output; cd.componentSubType = kAudioUnitSubType_RemoteIO; cd.componentManufacturer = kAudioUnitManufacturer_Apple; cd.componentFlags = 0; cd.componentFlagsMask = 0; AUGraphAddNode(graph, &cd, &ioNode);
こうやって書いてしまうと全然難しいものではないのですが、構造体の要素の数が多く、各定義名も長いので、パッと見のコードの複雑さを助長している気がします。
そんなわけで TTMAudioUnitHelper という Audio Unit のヘルパーライブラリでは、下記のように サブタイプだけ指定すれば AudioComponentDescription を取得できる ラッパーメソッドを用意してあります *2。
+ (AudioComponentDescription)audioComponentDescriptionForSubType:(OSType)subType;
Audio Unit のプロパティ設定
グラフに追加した各ノード(のユニット)にプロパティをセットするには、まずノードから AudioUnit 構造体を取得します。
AudioUnit ioUnit; AUGraphNodeInfo(graph, ioNode, &cd, &ioUnit);
で、取得した AudioUnit 構造体を引数に渡しつつ、プロパティをセットします。
AudioUnitSetProperty(ioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));
引数が多いですが、関数の定義を見ればわかるかと。
AudioUnitSetProperty(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void * inData, UInt32 inDataSize)
プロパティのgetも同様です。
AudioUnitGetProperty(AudioUnit inUnit,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
void * outData,
UInt32 * ioDataSize);
プロパティを set / get する実装は、引数が多いので何となく難しい感じに見えてしまうところがありますが、上記の通りやってることはシンプルです。
AudioStreamBasicDescription (ASBD)
オーディオデータフォーマットを表現するための構造体。
struct AudioStreamBasicDescription { Float64 mSampleRate; UInt32 mFormatID; UInt32 mFormatFlags; UInt32 mBytesPerPacket; UInt32 mFramesPerPacket; UInt32 mBytesPerFrame; UInt32 mChannelsPerFrame; UInt32 mBitsPerChannel; UInt32 mReserved; }; typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
ユニットごとにサポートしているオーディオデータフォーマットが違うため、ノード間で滞りなくオーディオデータを流すためにこれをプロパティからセットしてやる必要があります。
この ASBD、以下の点を個人的に整理できてないので、また別途記事を書こうと思っています。
- どのノード間において明示的に get / set する必要があるのか
- どのノード間において AUConverter ユニットや後述する Audio Converter Sevices で ASBD を変換する必要があるのか
- ASBD が合ってなければ AUGraphInitialize 実行時にエラーを返してくれる?
コールバック
再生するにしても録音するにしてもこのコールバックの実装は不可欠だし、リアルタイム波形処理もここで行うことになるので、Audio Unit のキモになる部分といえます。が、下記理由により個人的にはややこしく感じてしまいます。
- いろいろなコールバックの登録方法がある
- いろいろなコールバックの種類がある
- 後述する Audio Converter Sevices もコールバック内で処理を行う
- 引数それぞれの役割を把握してないとAudioUnitのキモである波形処理を書けない
- C言語的な知識がしっかりと要求される
ここでは、コールバックの何がややこしいのか、という把握だけにとどめておいて、コールバックの詳しい話は別記事で行いたいと思います。
- [Audio Unitのコールバック関数を登録する方法3種の比較](http://d.hatena.ne.jp/shu223/20130223/1361624406)
Audio Converter Sevices や Extended Audio File Services 等の関連サービス
サウンドファイルの再生は、AVAudioPlayer を使用すれば恐ろしく簡単にできるのですが、Audio Unit を使う場合、オーディオデータを RemoteIO で再生できるフォーマットに変換するために、Audio Converter Sevices や Extended Audio File Services を利用する必要があります。
高レベルAPIに慣れてしまった僕のようなゆとりiOSエンジニアからすると「たかがファイル再生」と油断しているところに、Audio Converter Sevices では変換用コールバックが再生用とは別途必要だったり、マジッククッキーなるよくわからない概念が登場したりするので、「Audio Unitこわい」という印象を持ってしまう要因のひとつになってしまっている気がします。
また Audio Converter Sevices を使うにしろ Extended Audio File Services を使うにしろ、この手の実装はファイルから読み込んだオーディオデータを保持しておくために独自の構造体を定義して取り扱うことが多く、他人のサンプルコードを参考にしようとしても、「パッと把握しづらい」というつらさもあります。
このあたり、AUConverter ユニットや AUAudioFilePlayer ユニットを使用すればもっとシンプルにできるのかなと期待しつつ、また別途記事を書こうと思います。
参考記事
Audio Unit を含む、iOSのオーディオ処理に役立つ参考書籍を下記記事にまとめています。
Audio Unit のユニット種別は AudioComponentDescription 構造体の componentSubType によって規定されますが、その一覧を下記記事にまとめています。
参考になるサンプルコードのまとめ。使用されているユニットも付記してあるので手前味噌ながら便利です。