月別アーカイブ: 2008年4月

再生スライダー

iPodとかQuickTimePlayerのような、再生する位置を表示・変更できるようなスライダーを使いたいと思って、NSSliderにジャカジャカ位置を送っていたら、うまいことクリックして変更できなくて、あれこれ調べたらNSSliderからNSSliderCellを取得してmouseDownFlagsってのをチェックすれば良い事が分かりました。

そこらへんを検証してみたコードが以下のような感じです。タイマーはNSEventTrackingRunLoopModeでスライダーを変更中も関係なく動かしていますが、マウスで操作中のときは値を送らないようにしています。

#import <Cocoa/Cocoa.h>

@interface Controller : NSObject {
	
    IBOutlet NSSlider *slider;
    float sliderValue;
}

- (IBAction)setValue:(id)sender;

@end

@implementation Controller

- (void)awakeFromNib
{
    [slider setMinValue:0];
    [slider setMaxValue:100];
    [slider setContinuous:NO];
	
    NSTimer *timer = 
        [NSTimer scheduledTimerWithTimeInterval:0.05
                                         target:self 
                                       selector:@selector(sendSliderValue) 
                                       userInfo:nil 
                                        repeats:YES];
	
    [[NSRunLoop currentRunLoop] addTimer:timer
        forMode:NSEventTrackingRunLoopMode];
}

- (void)sendSliderValue
{
    NSSliderCell *cell = [slider cell];

    if (![cell mouseDownFlags]) {
		
        sliderValue++;
		
        if (sliderValue > 100) {
            sliderValue = 0;
        }
		
        [slider setFloatValue:sliderValue];
    }
}

- (IBAction)setValue:(id)sender
{
    sliderValue = [sender floatValue];
}

@end

Core Audio その5 プロパティの設定

※2009/10/2追記《このエントリーはMac OS X 10.5での場合について書かれています。10.6以降の場合はこちらをご覧ください。》

今回はプロパティに値を設定してみたいと思います。

取得のときの〜GetPropertyの関数と同じような感じで、設定は〜SetPropertyと名前のついた関数を使います。<CoreAudio/AudioHardware.h>に以下のような関数があります。

extern OSStatus
AudioHardwareSetProperty(   AudioHardwarePropertyID inPropertyID,
                            UInt32                  inPropertyDataSize,
                            const void*             inPropertyData)

extern OSStatus
AudioDeviceSetProperty( AudioDeviceID           inDevice,
                        const AudioTimeStamp*   inWhen,
                        UInt32                  inChannel,
                        Boolean                 isInput,
                        AudioDevicePropertyID   inPropertyID,
                        UInt32                  inPropertyDataSize,
                        const void*             inPropertyData)

extern OSStatus
AudioStreamSetProperty( AudioStreamID           inStream,
                        const AudioTimeStamp*   inWhen,
                        UInt32                  inChannel,
                        AudioDevicePropertyID   inPropertyID,
                        UInt32                  inPropertyDataSize,
                        const void*             inPropertyData)

取得のときと違い、AudioDeviceとAudioStreamにAudioTimeStampを渡すinWhenという引数があります。これは設定するタイミングを指定できるものですが、NULLを入れておけばすぐに変更されます。また、プロパティのサイズはポインタではなく値渡しになります。

それではAudioDeviceSetPropertyを使ってサンプリング周波数をアプリケーション側から変更してみたいと思います。今回もFoundation Toolでプロジェクトを作って、main関数に処理を書いてみます。オーディオデバイスのサンプリング周波数のプロパティはkAudioDevicePropertyNominalSampleRateです。

#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    OSStatus err = noErr;
    UInt32 size;
	
    //デフォルトのアウトプットに設定されているオーディオデバイスを取得する
    AudioDeviceID devID;
    size = sizeof(devID);
    err = AudioHardwareGetProperty(
        kAudioHardwarePropertyDefaultOutputDevice, &size, &devID);
    if (err != noErr) {
        NSLog(@"Get DefaultOutputDevice Err");
        goto catchErr;
    }
	
    //デバイスからサンプリング周波数を取得する
    Float64 sampleRate;
    size = sizeof(sampleRate);
    err = AudioDeviceGetProperty(
        devID, 0, false, kAudioDevicePropertyNominalSampleRate, 
        &size, &sampleRate);
    if (err != noErr) {
        NSLog(@"Get NominalSampleRate err");
        goto catchErr;
    }
	
    //プロパティが書き換え可能か調べる
    Boolean isWritable;
    err = AudioDeviceGetPropertyInfo(
        devID, 0, false, kAudioDevicePropertyNominalSampleRate, 
        NULL, &isWritable);
    if (err != noErr) {
        NSLog(@"Get isWritable err");
        goto catchErr;
    }
	
    if (isWritable == false) {
        NSLog(@"isWritable false");
        goto catchErr;
    }
	
    //サンプリング周波数を変える
    if (sampleRate == 44100) {
        sampleRate = 48000;
    } else {
        sampleRate = 44100;
    }
	
    //デバイスにサンプリング周波数を設定する
    size = sizeof(sampleRate);
    err = AudioDeviceSetProperty(
        devID, NULL, 0, false, kAudioDevicePropertyNominalSampleRate, 
        size, &sampleRate);
    if (err != noErr) {
        NSLog(@"Set NominalSampleRate err");
        goto catchErr;
    }

catchErr:
	
    [pool drain];
    return 0;
}
<foundation><coreaudio></coreaudio></foundation>

これを実行すると、オーディオデバイスのサンプリング周波数を44.1kHzであれば48kHzに切り替え、その他であれば44.1kHzに切り替えます。Audio MIDI 設定を立ち上げた状態で実行すると切り替わっているのが分かると思います。(本来ならAudio MIDI 設定のようにサポートされているサンプリング周波数を取得して、その中から選ぶのが正しいやり方だと思います。とりあえずサンプルですので。)

いちおうこれで設定はできるのですが、inWhenにNULLを設定してすぐに設定が行われるようにしても、デバイス本体はプログラムとは関係なく動作しますので、設定直後にサンプリング周波数が変更されたと想定して処理を進めてしまうと、実は設定が変更されていなくて問題が出てくる場合があります。今回のサンプルでも、main関数内のAudioDeviceSetPropertyの後に再びGetPropertyでサンプリング周波数を取得しても、設定直後だと変更前の値が返ってくるときがあります。

このようにデバイスに値が設定された事を確認してから続きの処理を開始したい場合や、Audio MIDI 設定などで外部から設定が変更されたときに何か処理を行いたい場合の方法は、次回にやってみたいと思います。

Core Audio その4 プロパティの取得

※2009/10/2追記《このエントリーはMac OS X 10.5での場合について書かれています。10.6以降の場合はこちらをご覧ください。》

今回は、Core Audioのデバイスなどから情報(※プロパティ)を実際にやりとりする方法を見ていきたいと思います。(※使用する関数名や引数でpropertyと名前が付けられているので、この情報の事を以後プロパティと表記する事にします。)

まず、プロパティを取得するにはGetPropertyと名前のついた関数を使います。それぞれ以下の関数があります。

//オーディオシステム全体からの情報を取得する
extern OSStatus
AudioHardwareGetProperty(   AudioHardwarePropertyID inPropertyID,
                            UInt32*                 ioPropertyDataSize,
                            void*                   outPropertyData)

//オーディオデバイスの情報を取得する
extern OSStatus
AudioDeviceGetProperty( AudioDeviceID           inDevice,
                        UInt32                  inChannel,
                        Boolean                 isInput,
                        AudioDevicePropertyID   inPropertyID,
                        UInt32*                 ioPropertyDataSize,
                        void*                   outPropertyData)

//オーディオデバイス内のストリーム(音声の入出力部分)の情報を取得する
extern OSStatus
AudioStreamGetProperty( AudioStreamID           inStream,
                        UInt32                  inChannel,
                        AudioDevicePropertyID   inPropertyID,
                        UInt32*                 ioPropertyDataSize,
                        void*                   outPropertyData)

それぞれの関数の、〜PropertyIDというところにはプロパティIDという、取得したいプロパティの種類を表す定数を指定します(<CoreAudio/AudioHardware.h>で定義されています。)。ioPropertyDataSizeには取得するプロパティのサイズの変数へのポインタを、outPropertyDataには取得するプロパティへのポインタを渡して、プロパティを取得します。

AudioDeviceGetPropertyのisInputは音声の入力側の情報を取得するのであればtrueを、出力側であればfalseを渡します。inChannelは、kAudioDevicePropertyChannel〜あたりを使うのでなければ、0を入れておけば良いようです。

ではためしに、「Audio MIDI 設定」でデフォルトに設定されている出力デバイスを取得して、そのデバイスの名前を取得してみます。

New ProjectでFoundation Toolを選択して新規プロジェクトを作成し、CoreAudio.Frameworkを追加して、mainメソッドを以下のように書き換えてみます。

#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    OSStatus err = noErr; //エラーコードを受け取る変数
    UInt32 size; //プロパティのサイズを指定する変数

    //デフォルトアウトプットデバイスを取得する

    AudioDeviceID devID;
    size = sizeof(devID);
    err = AudioHardwareGetProperty(
        kAudioHardwarePropertyDefaultOutputDevice, &size, &devID);
    if (err != noErr) {
        NSLog(@"AudioHardwareGetProperty err");
        goto catchErr;
    }
	
    NSLog(@"deviceID = %u", devID);

    //オーディオデバイスの名前を取得する

    CFStringRef strRef;
    size = sizeof(strRef);
    err = AudioDeviceGetProperty(
        devID, 0, false, kAudioDevicePropertyDeviceNameCFString, &size, &strRef);
    if (err != noErr) {
        NSLog(@"AudioDeviceGetProperty err");
        goto catchErr;
    }

    NSLog(@"deviceName = %@", (NSString *)strRef);

catchErr:
	
    [pool drain];
    return 0;
}

Core Audioの関数はOSStatusという型のエラーコードを返してきますので、noErrでなければエラー処理をします。とりあえず今回は関数の最後にgotoで飛ばしています。

MacBook Proで実行してみると以下のような感じでログに表示されて、情報を取得できている事が分かります。

[Session started at 2008-04-09 23:54:39 +0900.]
2008-04-09 23:54:39.625 PropertyTest01[5027:10b] deviceID = 264
2008-04-09 23:54:39.631 PropertyTest01[5027:10b] deviceName = Built-in Output

The Debugger has exited with status 0.

プロパティはものによってスカラ値であったり構造体であったり配列であったりしますので、サイズをioPropertyDataSizeに渡す必要があります。今回のように前もってサイズが分かるものは良いのですが、すべてのデバイスを取得する場合などは、接続されているデバイスの数によってサイズが変動しますので、事前にそのサイズを取得しておかなければなりません。

取得しようとしているプロパティのサイズと、プロパティが書き込み可能か、という情報を取得するのが以下のGetPropertyInfoの関数です。

extern OSStatus
AudioHardwareGetPropertyInfo(   AudioHardwarePropertyID inPropertyID,
                                UInt32*                 outSize,
                                Boolean*                outWritable)

extern OSStatus
AudioDeviceGetPropertyInfo( AudioDeviceID           inDevice,
                            UInt32                  inChannel,
                            Boolean                 isInput,
                            AudioDevicePropertyID   inPropertyID,
                            UInt32*                 outSize,
                            Boolean*                outWritable)

extern OSStatus
AudioStreamGetPropertyInfo( AudioStreamID           inStream,
                            UInt32                  inChannel,
                            AudioDevicePropertyID   inPropertyID,
                            UInt32*                 outSize,
                            Boolean*                outWritable)

これらを使って情報を取得してみます。マックにつながっているオーディオデバイスを全て取得して、アウトプットがあればフォーマットを取得してみる、というコードが以下のような感じになります。

#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    OSStatus err = noErr;
    UInt32 size;
    NSInteger i, j;

    AudioStreamID *streamIDs = NULL;
    AudioDeviceID *devIDs = NULL;

    //全てのデバイスを取得する

    err = AudioHardwareGetPropertyInfo(
        kAudioHardwarePropertyDevices, &size, NULL);
    if (err != noErr) {
        NSLog(@"AudioHardwareGetPropertyInfo err");
        goto catchErr;
    }

    devIDs = malloc(size);
    err = AudioHardwareGetProperty(
        kAudioHardwarePropertyDevices, &size, devIDs);
    if (err != noErr) {
        NSLog(@"AudioHardwareGetProperty err");
        goto catchErr;
    }


    //デバイスの数を計算して繰り返す

    UInt32 count = size / sizeof(AudioDeviceID);

    for (i = 0; i < count ; i++) {

        //デバイスの名前を取得する(isInputがfalseだがアウトが無くても取得できる)
        CFStringRef strRef;
        size = sizeof(strRef);
        err = AudioDeviceGetProperty(
            devIDs[i], 0, false, kAudioDevicePropertyDeviceNameCFString,
            &size, &strRef);
        if (err != noErr) {
            NSLog(@"AudioDeviceGetProperty deviceNameCFString err");
            goto catchErr;
        }

        //デバイス内のストリームIDを取得する(ここではアウト側のみが取得できる)
        err = AudioDeviceGetPropertyInfo(
            devIDs[i], 0, false, kAudioDevicePropertyStreams, &size, NULL);
        if (err != noErr) {
            NSLog(@"AudioDeviceGetPropertyInfo err");
            goto catchErr;
        }

        if (size) NSLog(@"%@ / deviceID = %u", (NSString *)strRef, devIDs[i]);

        streamIDs = malloc(size);
        UInt32 nStream = size / sizeof(AudioStreamID);
        err = AudioDeviceGetProperty(
            devIDs[i], 0, false, kAudioDevicePropertyStreams, &size, streamIDs);
        if (err != noErr) {
            NSLog(@"AudioDeviceGetProperty streams err");
            goto catchErr;
        }

        //ストリームIDの数だけ繰り返す

        for (j = 0; j < nStream; j++) {

            //フォーマットを取得する
            AudioStreamBasicDescription format;
            size = sizeof(format);
            err = AudioStreamGetProperty(
                streamIDs[j], 0, kAudioStreamPropertyVirtualFormat, &size, &format);
            if (err != noErr) {
                NSLog(@"AudioStreamGetProperty virtualFormat err");
                goto catchErr;
            }

            NSLog(@"streamID = %u / channels = %u",
                streamIDs[j], format.mChannelsPerFrame);
        }

        free(streamIDs);
        streamIDs = NULL;
    }

catchErr:

    if (devIDs) free(devIDs);
    if (streamIDs) free(streamIDs);
	
    [pool drain];
    return 0;
}<foundation><coreaudio> </coreaudio></foundation>

FIREBOXというインターフェースをつなげて、Bluetoothも生かしたMacBook Proで実行してみると、以下のようにログに表示されます。

[Session started at 2008-04-13 21:53:31 +0900.]
2008-04-13 21:53:31.970 PropertyTest[779:10b] 
    PreSonus FIREBOX (2343) / deviceID = 258
2008-04-13 21:53:31.974 PropertyTest[779:10b] streamID = 265 / channels = 1
2008-04-13 21:53:31.974 PropertyTest[779:10b] streamID = 266 / channels = 1
2008-04-13 21:53:31.975 PropertyTest[779:10b] streamID = 267 / channels = 1
2008-04-13 21:53:31.975 PropertyTest[779:10b] streamID = 268 / channels = 1
2008-04-13 21:53:31.976 PropertyTest[779:10b] streamID = 269 / channels = 1
2008-04-13 21:53:31.976 PropertyTest[779:10b] streamID = 270 / channels = 1
2008-04-13 21:53:31.977 PropertyTest[779:10b] streamID = 271 / channels = 1
2008-04-13 21:53:31.977 PropertyTest[779:10b] streamID = 272 / channels = 1
2008-04-13 21:53:31.982 PropertyTest[779:10b] Built-in Output / deviceID = 279
2008-04-13 21:53:31.982 PropertyTest[779:10b] streamID = 280 / channels = 2
2008-04-13 21:53:31.983 PropertyTest[779:10b] receiver01 / deviceID = 273
2008-04-13 21:53:31.983 PropertyTest[779:10b] streamID = 274 / channels = 2

The Debugger has exited with status 0.

こうしてみると、DeviceIDとStreamIDがかぶっていないという事も分かります。

こんな感じで、プロパティを取得するときは、AudioHardwareGetPropertyInfo → AudioHardwareGetProperty → AudioDeviceGetPropertyInfo → AudioDeviceGetProperty → AudioStreamGetPropertyInfo → AudioStreamGetPropertyという順番で必要な関数を選択して取得していく事になります。

Core Audio その3 AudioObjectID

※2009/10/2追記《このエントリーはMac OS X 10.5での場合について書かれています。10.6以降の場合はこちらをご覧ください。》

前回の最後に、情報のやり取りをすると書いていましたが、その前にもう一つ説明しておきたいものがあります。

マックにつなげているオーディオインターフェースのチャンネル数だとか、サンプリング周波数だとかを知りたいとしても、そのオーディオインターフェースを特定できなければ知りようもありません。

Core Audioでは、マックのオーディオシステム全体や、オーディオデバイスや、オーディオデバイス内の入出力に、それぞれIDナンバーが振られており、AudioObjectIDという型の変数で表されています。<CoreAudio/AudioHardware.h>にAudioObject〜という名前で始まる関数が用意されていますので、それらにAudioObjectIDを渡してオーディオデバイスなどの情報を取得したり設定したり動かしたりできます。

ですが、内部的にそうなっているというだけで、実際にAudioObjectというものを使う事はないと思います。

その代わりに、AudioDeviceIDとAudioStreamIDとAudioSystemObjectというものが別に用意されています。AudioDeviceIDはオーディオデバイス(オーディオインターフェースそのもの)を表し、AudioStreamIDはオーディオデバイス内の入出力を表します。これらのIDはオーディオインターフェースが接続された時点で自動的に割り振られます。ちなみにヘッダをたどってみると、これらはAudioObjectIDをtypedefしただけのものです。

typedef UInt32      AudioObjectID;

typedef AudioObjectID   AudioDeviceID;

typedef AudioObjectID   AudioStreamID;

AudioSystemObjectは、マックのオーディオシステム全体を表します。実際にコード上にAudioSystemObjectなどと書く事は無く、<CoreAudio/AudioHardware.h>にあるAudioHardware〜という名前で始まる関数を使えば、AudioSystemObjectと情報をやり取りするという事になります。

AudioDeviceIDとAudioStreamIDは、それぞれAudioDevice〜やAudioStream〜という名前で始まる関数がありますので、それらを使用して情報をやり取りします。

といったところで、次回はそれらの関数を使ってみたいと思います。