AudioUnit細かいことまとめ

AudioUnitに出てくる細かいことについて


void *
int *と同じようにポインタですが、int *との違いはポインタの指す先です。int *の場合はintですが、void *の場合はどのような型でもよいです。使用するときには状況に応じてキャストすることもある。



関数ポインタ
関数ポインタは検索すると説明しているページがたくさん出てきます。関数へのポインタなのですが、AudioUnitでは関数の引数として関数を渡すときに使います。
関数ポインタの中でもtypedefを使った宣言の理解が難しい。
普通のtypedefは
typedef int abc;
で「abcをintとして扱う」「abcにintと同じ役割を与える」というような意味になります。「何を」と「何として」がスペースを挟んで書かれています。
関数ポインタのtypedefは
typedef int (*abcd)(int a,int b);
というようになります。
これに対し、同じように「何を」と「何として」をスペースを挟んで解釈しようとすると混乱します。
関数ポインタの場合は
「何を」abcd
「何として」int (*)(int a,int b) 引数をintで2つ持つ、戻り値がintの関数のポインタ
という変則的な読み方をします。



AudioUnitSampleTypeとAudioSampleType
iOS5.xだと実機でもシミュレータでもIntのほうが定義されています。
/*!
@typedef AudioSampleType
@abstract The canonical audio sample type used by the various CoreAudio APIs
*/
#if !CA_PREFER_FIXED_POINT
typedef Float32 AudioSampleType;
typedef Float32 AudioUnitSampleType;
#else
typedef SInt16 AudioSampleType;
typedef SInt32 AudioUnitSampleType;
#define kAudioUnitSampleFractionBits 24
#endif

ちなみにCA_PREFER_FIXED_POINTは
#if TARGET_OS_IPHONE
#if (TARGET_CPU_X86 || TARGET_CPU_X86_64 || TARGET_CPU_PPC || TARGET_CPU_PPC64) && !TARGET_IPHONE_SIMULATOR
#define CA_PREFER_FIXED_POINT 0
#else
#define CA_PREFER_FIXED_POINT 1
#endif
#else
#define CA_PREFER_FIXED_POINT 0
#endif
iOSはCA_PREFER_FIXED_POINTが1のようです。
AudioUnitSampleTypeとAudioSampleTypeにこだわらずにいっそのことアプリ全体で自分でFloat32に統一したほうがいいかもしれません。


8.24形式
AudioUnitSampleTypeがSInt32(32ビット整数)の場合、ソースコード上は32ビット整数であっても中に入れるデータの解釈は8.24形式で行う。中に入れる値の事実上の最大値最小値は
最大値 00000001 0000…0000 (8.24解釈で1)
最小値 11111111 0000…0000 (8.24解釈で-1)
あるいは
最大値 00000000 1111…1111 (8.24解釈で1よりほんのちょっと小さい値)
最小値 11111111 0000…0000 (8.24解釈で-1)
のどちらかだろう。
下24ビットにメインのデータが入り、符号フラグの役割は上8ビットで行う。
値が収められている下24ビットのほうにだけ意識があると符号フラグは24ビットの最上位かと勘違いしてしまうことがあるので注意。



AudioBufferListとAudioBuffer
AudioUnitからのコールバックを受けて、関数内でAudioUnitに返すデータを生成しますが、そのときにデータを入れる入れ物としての使い方がメイン。
データを格納するAudioBufferListとAudioBufferというよく似た名前の構造体ですが、定義はこうなってます。
struct AudioBufferList {
UInt32      mNumberBuffers;
AudioBuffer mBuffers[1];
};
typedef struct AudioBufferList AudioBufferList;

struct AudioBuffer {
UInt32 mNumberChannels;
UInt32 mDataByteSize;
void   *mData;
};
typedef struct AudioBuffer AudioBuffer;
この2つの構造体、データの個数を保持する変数がそれぞれにあります。これは混乱しやすいです。

こちらによい記事があります。
http://objective-audio.jp/2008/03/core-audio-audiobufferaudiobuf.html

ステレオのインターリーブ(LRLRLRLR…)
AudioBufferListのmNumberBuffers 1
AudioBufferのmNumberChannels 2
バッファは1個。そのバッファに2チャンネル分のデータがある。
つまり(LRLRLRLR…)でひとつのバッファです。

ステレオの非インターリーブ(LLLL…RRRR…)
AudioBufferListのmNumberBuffers 2
AudioBufferのmNumberChannels 1
バッファは2つ。それぞれのバッファが1チャンネルのデータを持つ。
つまりLLLL…でひとつのバッファ、RRRR…でひとつのバッファです。

AudioBufferListをがりがり作成していくときにはオーディオデータフォーマットが決定していることが前提になるんでしょう。
インターリーブの時にAudioBufferのmNumberChannelsが2になるようですが、それ以外でも1以外の値を取ることがあるんでしょうか?3チャンネルのインターリーブとか…気になります。



関数の引数に& がつく
値をコピーして渡すのではなく、元の値そのものを渡す。intやfloatなどデフォルトではコピー渡しだが、それを関数内で変更したい場合などに使う。C++のみの文法。



elementとbusの違い
同じものです。その箇所で何を表現したいかで変わるそう。信号の流れに着目するならbus。



setPropertyとgetProperty
両方共sizeを指定しますがsetの場合はダイレクトに、getの場合は&をつけてアドレス指定です。getの場合は関数の内部でサイズを入れます(確認用?)のでアドレス渡しにするようです。



currentとpreferredの違い
パラメータの名前にこのcurrentとpreferredが付いて、current◯◯とpreferred◯◯という似たような名前になっているものがあります。パラメータの設定があらかじめ決められている値にしか設定出来ない場合に、アプリから要求した通りに設定されるとは限らないことに対応して2つ用意してあります。違いは
preferred:アプリからの要求する値
current:実際に設定されている値
例えばkAudioSessionProperty_PreferredHardwareSampleRateを47000に設定しても、kAudioSessionProperty_CurrentHardwareSampleRateはそれに近い48000となります。
このような性質上preferredは書き込み可、currentは書き込み不可となっています。



割り込み
割り込み完了後にオーディオを再開させるときAudioSessionSetActive(YES)が必要です(AVAudioSession使用時は必要ないが)。このときAudioUnitを再開させるにはAUGraphStart(mAuGraph)も必要な模様。割り込み開始時に念の為にAUGraphStop(mAuGraph)したほうがいいかもしれない。



RemoteIO
RemoteIOについてですが、いくつか疑問が出てきます。
・AudioUnitと他のAPIを併用したときに、他のAPIによる出力はどういう経路で出てくるのか
・AudioUnitを使用しないときでもRemoteIOは生きているのか。生きていないとすれば入力と出力はどこにつながっているのか。



可変長(?)配列
struct AudioBufferList
{
UInt32 mNumberBuffers;
AudioBuffer mBuffers[1]; // this is a variable length array of mNumberBuffers elements
};
typedef struct AudioBufferList AudioBufferList;

このmBuffers[]は定義が1なので、「?」と思い、よく見ると説明でthis is a variable length array of mNumberBuffers elementsと書いてあります。長さ(要素数)が変更出来るようです。変更するときは元のやつを伸び縮みさせるのではなく、別のものと入れ替えるのかもしれませんが。



CoreAudioのコールバック関数の引数のAudioTimeStamp*
CoreAudioのコールバック関数の引数をよく見るとAudioTimeStamp*というのがあります。時間関係の変数のようですが、コールバック関数に与えられる時間情報といえば思いつくのは、バッファに入れたデータが実際に出力される時間でしょうか。コールバックを読んだ時点でいつ出力されるかの計算は可能でしょうから、この推測はありえなくもありません。いろいろ調べるとその推測でよいようです。
ところがこのAudioTimeStamp構造体の中のmHostTimeというものがシミュレータと実機で挙動が違います。ある時点からの経過時間の値を持っているのですが、シミュレータでは10の9乗分の1秒単位で与えられるので単純に9桁ずらせば秒がわかるのですが、実機のほうはなにかよくわからない単位で送られてきます。この値は現在時間を取得するmach_absolute_time()という関数で得られる値となにか性質が似ているということがわかり、同じ間隔をあけてmach_absolute_time()を実行して得られた値の差を見るということをシミュレータと実機の両方でやってみると、実機のほうはシミュレータにくらべて約1/41.7しか値が変化しません。
さらに調べると基準の単位が違うということがわかりました。その情報はmach_timebase_info_data_t構造体(のポインタ)で取得します。
mach_timebase_info_data_t base;
mach_timebase_info(&base);
NSLog(@"base.numer %d",base.numer);
NSLog(@"base.denom %d",base.denom);
ここでbase.numer / base.denomが単位(という表現が正しいかわからないが)のようです。
実機ではbase.numer125とbase.denom3でしたので125/3 = 41.66666…となります。
ちなみにシミュレータはbase.numer1とbase.denom1でしたので実機とシミュレータで約41.7倍違うというのも説明が付きます。
ところがさらに調べていくと実機では正確に125/3ではなく、誤差があるようです。同じくAudioTimeStamp構造体の中のmSampleTimeを元に44100サンプルあたりにどれだけmHostTimeが進むかを調べたのですが、iPod touch(4th)では125/3ではなく12505/300あたりで他の変数と辻褄のとれる割合になりました(12500/300から分子を加減して計算し、計測データと近くなる値を探した)。iPad(1st)ではこのような誤差はありません。これが何に起因しているかはわかりません。
その後の調べによりどうも誤差があるのはmHostTimeではなくmSampleTimeらしいということがわかりました。44100サンプルごとに音がなるようにして10分ぐらい放置しておくとずれてきます。mHostTimeの方を基準にするとずれません。
つまり実機は1秒間に44100サンプルの設定でも何かの原因によりその値が変化(私の場合は増)するようです。


コメント

このブログの人気の投稿

Swiftのコンパイルエラー寄せ集め

コンパイルエラー覚え書き(Objective-C)

AVAudioSession細かいことまとめ(late 2014)