オーディオデバイスの設定が外部から変更された時や、前回のようにオーディオデバイスの設定を変更し、その変更がちゃんと適用された後に処理を行いたい場合、プロパティが変更されたという通知を受け取るように設定します。
変更された事を知りたいプロパティを、〜AddPropertyListenerと名前のついた関数で登録します。必要がなくなったら〜RemovePropertyListerで登録を解除します。以下が<CoreAudio/AudioHardware.h>で宣言されている関数です。
//AudioHardware用 extern OSStatus AudioHardwareAddPropertyListener(AudioHardwarePropertyID inPropertyID, AudioHardwarePropertyListenerProc inProc, void* inClientData) extern OSStatus AudioHardwareRemovePropertyListener(AudioHardwarePropertyID inPropertyID, AudioHardwarePropertyListenerProc inProc) //AudioDevice用 extern OSStatus AudioDeviceAddPropertyListener(AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, AudioDevicePropertyListenerProc inProc, void* inClientData) extern OSStatus AudioDeviceRemovePropertyListener(AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, AudioDevicePropertyListenerProc inProc) //AudioStream用 extern OSStatus AudioStreamAddPropertyListener(AudioStreamID inStream, UInt32 inChannel, AudioDevicePropertyID inPropertyID, AudioStreamPropertyListenerProc inProc, void* inClientData) extern OSStatus AudioStreamRemovePropertyListener(AudioStreamID inStream, UInt32 inChannel, AudioDevicePropertyID inPropertyID, AudioStreamPropertyListenerProc inProc)
だいたいの引数は〜GetPropertyや〜SetPropertyと一緒ですが、inProcというところにプロパティの変更を受け取る関数名を指定します。その関数は引数の構成がすでに決められていて、以下のように宣言されています。
typedef OSStatus (*AudioHardwarePropertyListenerProc)( AudioHardwarePropertyID inPropertyID, void* inClientData); typedef OSStatus (*AudioDevicePropertyListenerProc)( AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void* inClientData); typedef OSStatus (*AudioStreamPropertyListenerProc)( AudioStreamID inStream, UInt32 inChannel, AudioDevicePropertyID inPropertyID, void* inClientData);
一つの関数に対して一つのプロパティを登録した場合は気にしなくても良いですが、いくつかのプロパティを同じ関数に登録したときなどは、これらの引数で渡ってくる情報をもとに条件分岐して処理を行うという感じになると思います。また、この関数には変更されたプロパティの種類が来るだけなので、変更された値自体は別途〜GetPropertyで取得しなければいけません。
では例として、オーディオデバイスのサンプリング周波数の変更を受け取ってみたいと思います。今回はアプリケーションを起動した状態にしておかないといけないので、Cocoaアプリケーションを新規プロジェクトで作成して、CoreAudio.Frameworkをインポートし、以下のクラスをnibファイル(最近ではxib?)でインスタンス化しておきます。
#import <Cocoa/Cocoa.h> #import <CoreAudio/CoreAudio.h> @interface SampleRateListner : NSObject { } @end @implementation SampleRateListner static OSStatus PropertyListener(AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void *inClientData) { OSStatus err = noErr; UInt32 size; //サンプリング周波数を取得してログに表示 Float64 sampleRate; size = sizeof(sampleRate); err = AudioDeviceGetProperty( inDevice, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sampleRate); if (err == noErr) { printf("SampleRate = %f\n", sampleRate); } return noErr; } - (void)awakeFromNib { OSStatus err = noErr; //デフォルトのアウトプットに設定されているオーディオデバイスを取得する AudioDeviceID devID; UInt32 size = sizeof(devID); err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &size, &devID); if (err != noErr) goto catchErr; //プロパティリスナーを設定する err = AudioDeviceAddPropertyListener( devID, 0, false, kAudioDevicePropertyNominalSampleRate, PropertyListener, self); if (err != noErr) goto catchErr; return; catchErr: NSLog(@"catch err"); exit(0); } @end
実行して起動したら、Audio MIDI 設定でデフォルトの出力に割り当てられているデバイスのサンプリング周波数を変更すると、ログにその周波数が表示されると思います。
このサンプルでは周波数を表示するだけですが、実際はプロパティの変更を受けてあれこれと処理をしなくてはいけないと思うので、独自のデータなどをinClientDataで渡して、それをもとに何か処理を行うという感じでしょうか。
ちなみに、今回のプロパティリスナーのようなCore Audioでのコールバック系の関数は基本的にメインではないスレッドで呼ばれるので、インスタンス変数へのアクセスなどするならマルチスレッドを意識してコードを記述していかなくてはいけないと思います。
といったところで、次回はオーディオデバイスから音を出したり、取り込んだりするところを見ていきたいと思います。