Core Audioのオーディオデバイスと入出力のデータをやり取りするには、入出力を行う関数を登録したあと、デバイスの動作を開始させます。入出力の関数の登録を行う関数は<CoreAudio/AudioHardware.h>で宣言されていて、AudioDeviceCreateIOProcIDという関数になります。Tiger以前はAudioDeviceAddIOProcという関数でしたが、LeopardからはDeprecatedになって、こちらに変わっています。
extern OSStatus AudioDeviceCreateIOProcID( AudioDeviceID inDevice, AudioDeviceIOProc inProc, void* inClientData, AudioDeviceIOProcID* outIOProcID)
inDeviceはAudioDeviceID、inClientDataは任意のデータ、inProcには入出力に使う関数名を渡します。outIOProcIDには登録ごとにIDナンバーが返ってきます。これがLeopardの追加部分なのですが、関数名でなくIDで登録を管理するように
なり、同じIOProcに複数のデバイスを登録しても大丈夫みたいな感じのようです。IOProc関数の引数の構成は決められていて以下のように宣言されています。
typedef OSStatus (*AudioDeviceIOProc)( AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData);
AudioUnitと違って、オーディオデバイスにインプットとアウトプット両方ある場合、両方のデータが一回で渡ってきます。ただ当然レイテンシーなんてモノがありますから、インプットのデータは少し前に取り込まれたデータですし、アウトプットに渡すデータは少し後に再生されますので、それらの時間がAudioTimeStampで渡ってきます。DAWみたいに再生しながら録音する場合などは、これらの時間のギャップを計算して、録音されたデータを再生したものに合わせる事が必要になってくると思います。
IOProc関数を解除するのはAudioDeviceDestroyIOProcID関数です。こちらもLeopardから変更になって、以前のAudioDeviceRemoveIOProcはDeprecatedになっています。
extern OSStatus AudioDeviceDestroyIOProcID( AudioDeviceID inDevice, AudioDeviceIOProcID inIOProcID)
AudioDeviceCreateIOProcIDで関数が登録できたら、動作を開始させます。それが、AudioDeviceStart関数になります。動作を停止させるにはAudioDeviceStopを使います。
extern OSStatus AudioDeviceStart( AudioDeviceID inDevice, AudioDeviceIOProcID inProcID) extern OSStatus AudioDeviceStop( AudioDeviceID inDevice, AudioDeviceIOProcID inProcID)
では、これらを使ってオーディオデバイスを動かしてみたいと思います。Cocoaアプリケーションを新規プロジェクトで作成して、CoreAudio.Frameworkを追加しておき、以下のクラスをInterface Builderでインスタンス化して、実行します。
#import <Cocoa/Cocoa.h> #import <CoreAudio/CoreAudio.h> @interface IOProcTest : NSObject { AudioDeviceID devID; AudioDeviceIOProcID procID; } - (void)destroyIOProc; @end @implementation IOProcTest static OSStatus IOProc(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData) { printf("Output Time = %f\n", inOutputTime->mSampleTime); //念のためアウトをゼロでクリアする NSUInteger i; UInt32 buffers = outOutputData->mNumberBuffers; for (i = 0; i < buffers; i++) { float *ptr = outOutputData->mBuffers[i].mData; UInt32 byteSize = outOutputData->mBuffers[i].mDataByteSize; memset(ptr, 0, byteSize); } return noErr; } - (void)awakeFromNib { OSStatus err = noErr; UInt32 size; //デフォルトのアウトプットに設定されているオーディオデバイスを取得する size = sizeof(devID); err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &size, &devID); if (err != noErr) { NSLog(@"Get DefaultOutputDevice Err"); goto end; } //オーディオの入出力を行う関数を設定する err = AudioDeviceCreateIOProcID(devID, IOProc, NULL, &procID); if (err != noErr) { NSLog(@"Create IOProc Err"); goto end; } //オーディオデバイスを開始させる err = AudioDeviceStart(devID, procID); if (err != noErr) { NSLog(@"Start Err"); goto end; } end: return; } - (void) dealloc { [self destroyIOProc]; [super dealloc]; } - (void)destroyIOProc { OSStatus err; //オーディオデバイスを停止する err = AudioDeviceStop(devID, procID); if (err != noErr) { NSLog(@"Stop Err"); goto end; } //入出力を行う関数を取り除く err = AudioDeviceDestroyIOProcID(devID, procID); if (err != noErr) { NSLog(@"Destroy IOProc Err"); goto end; } end: return; } @end
実行してみるとログにダダダーッと文字が表示されてIOProcが(デフォルトだと512フレーム単位で)呼ばれている事が分かるとおもいます。
ちなみにCore AudioのIOProcは大丈夫だと思うのですが、AudioUnitのOutputUnitだと、アウトのデータを何も書き込まないままnoErrを返したら大音量でノイズが再生されてしまったというトラウマがあるので、いちおうアウトをゼロでクリアしています。何か音を出すにはここでデータを渡してください。それと、PropertyListenerと同じくIOProcの関数はCore Audio用の別スレッドで呼ばれますので、マルチスレッディングなコードを心がけてください。
といった感じで、7回ほど続けてきたCoreAudio.Frameworkはこんなところでしょうか。まあでも、普通にオーディオデバイス使うならAudioUnitのOutputUnitでやった方がいろいろと面倒見てくれて良いと思いますが、こうやって裸にして調べてみるとAudioUnitの中で何やってるのかもだいたい分かりますし、サンプリングレートの変換とか必要なければこっちでやった方がパフォーマンスを稼げると思いますので。
次はAudioToolboxに移ってオーディオファイル関係あたりを見ていこうと思います。CoreAudio.Frameworkに関しては気が向けばまた何か追加するかもしれません。