Core Audio – Mac」カテゴリーアーカイブ

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〜という名前で始まる関数がありますので、それらを使用して情報をやり取りします。

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

Core Audio その2 AudioStreamBasicDescription

Core Audioにおいて、オーディオデータの状態(リニアPCM等のフォーマットとか、ビットやサンプリングレートとか、チャンネル数とか)を表す構造体がAudioStreamBasicDescriptionです。

struct AudioStreamBasicDescription
{
    Float64 mSampleRate;       //サンプリング周波数(1秒間のフレーム数)
    UInt32  mFormatID;         //フォーマットID(リニアPCM、MP3、AACなど)
    UInt32  mFormatFlags;      //フォーマットフラグ(エンディアン、整数or浮動小数点数)
    UInt32  mBytesPerPacket;   //1パケット(データを読み書きする単位)のバイト数
    UInt32  mFramesPerPacket;  //1パケットのフレーム数
    UInt32  mBytesPerFrame;    //1フレームのバイト数
    UInt32  mChannelsPerFrame; //1フレームのチャンネル数
    UInt32  mBitsPerChannel;   //1チャンネルのビット数
    UInt32  mReserved;         //意味なし。アラインメントを揃えるためのもの?
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

フォーマットIDは以下のような定数が定義されています。

enum
{
    kAudioFormatLinearPCM               = 'lpcm',
    kAudioFormatAC3                     = 'ac-3',
    kAudioFormat60958AC3                = 'cac3',
    kAudioFormatAppleIMA4               = 'ima4',
    kAudioFormatMPEG4AAC                = 'aac ',
    kAudioFormatMPEG4CELP               = 'celp',
    kAudioFormatMPEG4HVXC               = 'hvxc',
    kAudioFormatMPEG4TwinVQ             = 'twvq',
    kAudioFormatMACE3                   = 'MAC3',
    kAudioFormatMACE6                   = 'MAC6',
    kAudioFormatULaw                    = 'ulaw',
    kAudioFormatALaw                    = 'alaw',
    kAudioFormatQDesign                 = 'QDMC',
    kAudioFormatQDesign2                = 'QDM2',
    kAudioFormatQUALCOMM                = 'Qclp',
    kAudioFormatMPEGLayer1              = '.mp1',
    kAudioFormatMPEGLayer2              = '.mp2',
    kAudioFormatMPEGLayer3              = '.mp3',
    kAudioFormatTimeCode                = 'time',
    kAudioFormatMIDIStream              = 'midi',
    kAudioFormatParameterValueStream    = 'apvs',
    kAudioFormatAppleLossless           = 'alac',
    kAudioFormatMPEG4AAC_HE		= 'aach',
    kAudioFormatMPEG4AAC_LD		= 'aacl',
    kAudioFormatMPEG4AAC_HE_V2		= 'aacp',
    kAudioFormatMPEG4AAC_Spatial	= 'aacs',
    kAudioFormatAMR			= 'samr'
};

普通にオーディオデータを扱うときは、WAVやAIFFでおなじみの非圧縮フォーマットであるリニアPCM(kAudioFormatLinearPCM)になります。その他は、それぞれの圧縮フォーマットのファイルの読み書きをするときに使用します。オーディオファイルのフォーマット以外にもタイムコードやMIDIやParameterValueStreamなんてのがあるのがちょっと面白そうなところです。

フォーマットフラグは以下の定数が定義されています。

enum
{
    kAudioFormatFlagIsFloat                     = (1L << 0),
    kAudioFormatFlagIsBigEndian                 = (1L << 1),
    kAudioFormatFlagIsSignedInteger             = (1L << 2),
    kAudioFormatFlagIsPacked                    = (1L << 3),
    kAudioFormatFlagIsAlignedHigh               = (1L << 4),
    kAudioFormatFlagIsNonInterleaved            = (1L << 5),
    kAudioFormatFlagIsNonMixable                = (1L << 6),
    kAudioFormatFlagsAreAllClear                = (1L << 31),
    
    kLinearPCMFormatFlagIsFloat                 = kAudioFormatFlagIsFloat,
    kLinearPCMFormatFlagIsBigEndian             = kAudioFormatFlagIsBigEndian,
    kLinearPCMFormatFlagIsSignedInteger         = kAudioFormatFlagIsSignedInteger,
    kLinearPCMFormatFlagIsPacked                = kAudioFormatFlagIsPacked,
    kLinearPCMFormatFlagIsAlignedHigh           = kAudioFormatFlagIsAlignedHigh,
    kLinearPCMFormatFlagIsNonInterleaved        = kAudioFormatFlagIsNonInterleaved,
    kLinearPCMFormatFlagIsNonMixable            = kAudioFormatFlagIsNonMixable,
    kLinearPCMFormatFlagsAreAllClear            = kAudioFormatFlagsAreAllClear,
    
    kAppleLosslessFormatFlag_16BitSourceData    = 1,
    kAppleLosslessFormatFlag_20BitSourceData    = 2,
    kAppleLosslessFormatFlag_24BitSourceData    = 3,
    kAppleLosslessFormatFlag_32BitSourceData    = 4
};

enum
{
#if TARGET_RT_BIG_ENDIAN
    kAudioFormatFlagsNativeEndian       = kAudioFormatFlagIsBigEndian,
#else
    kAudioFormatFlagsNativeEndian       = 0,
#endif
    kAudioFormatFlagsCanonical =
        kAudioFormatFlagIsFloat |
        kAudioFormatFlagsNativeEndian |
        kAudioFormatFlagIsPacked,
    kAudioFormatFlagsNativeFloatPacked =
        kAudioFormatFlagIsFloat |
        kAudioFormatFlagsNativeEndian |
        kAudioFormatFlagIsPacked
};

フラグですから、設定したい定数をビット演算で組み合わせて指定します。例としてAIFFの16bit整数のオーディオファイルを作成する場合には、ビッグエンディアンで符号付き整数でPackedになりますから、

AudioStreamBasicDescription desc;
desc.mFormatFlags =
    kAudioFormatFlagIsBigEndian |
    kLinearPCMFormatFlagIsSignedInteger |
    kAudioFormatFlagIsPacked;

といった感じになります。

Core Audioではデフォルトだとオーディオデータは32bitのFloatのPackedのネイティブなエンディアンで扱われますから、それらが既に組み合わせられた、kAudioFormatFlagsNativeFloatPackedなんていう便利な定数も用意されています。

ちなみにPackedとは何かというと、オーディオデータの1サンプルに割り当てられたデータ領域の全てのビットを使った状態です。例えば、オーディオデータの1サンプルに32bitのメモリ領域が割り当てられているときに、ぴったり32bitのデータが入っている状態がPackedという事になります。32bitのメモリ領域に20bitのデータが入っているような場合はPackedはセットせず、その20bitのデータが上位ビットに寄せられていればAlignedHigh、下位ビットに寄せられていればAlignedLow(AlignedHighをセットしない)になります。

Core Audioでオーディオデータを扱うときには基本的にPackedなので、Packedでない状態が実際に使われているところがないかと探したら、自分の使っているMacBook Proのオーディオデバイスがそうでした。デバイスのビットを20bitや24bitに設定したときにはpackedではなく、32bitの領域が割り当てられていてAlignedLowになっています。とはいってもデバイス側のフォーマットなので、CoreAudio経由で使うときは基本的に32bitFloatに変換された状態で渡ってきますから、実際に意識する事はないと思います。

mBytesPerPacketからmBitsPerChannelまでの5つのメンバは、オーディオのデータがどんな状態で並んでいるかが表されます。

リニアPCMでのそれぞれの関係性を見ていくと、mBitsPerChannelで指定されたビット数の1サンプルをチャンネル数分まとめたものがフレームで、そのフレームをまとめて一回分の読み書きの単位としているのがパケットです。

mChannelsPerFrameで1フレーム内のチャンネル数、mBytesPerFrameで1フレームの容量、mFramesPerPacketで1パケット内のフレーム数、mBytesPerPacketで1パケットの容量が表される事になります。

リニアPCMならフレーム単位で一つのデータが成立するのでmFramesPerPacketが1となり、他の全てのメンバにもフォーマットに応じた値が設定されますが、圧縮フォーマットであれば、いくらかのフレームがまとめられて1パケットに圧縮されているので、mFramesPerPacketとmChannelsPerFrameのみが設定され、他の値は0という場合もあります。

リニアPCMでPackedの場合、5つのうち3つ決まれば残り2つは自然と値が計算で求められます。たとえば32bitのステレオのInterleavedだと、

desc.mBitsPerChannel = 32;
desc.mFramesPerPacket = 1;
desc.mChannelsPerFrame = 2;
desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame;
desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;

という感じです。

例として、オーディオファイルでよく使われそうなフォーマットの設定値を載せておきます。

// AIFF 16bit 44.1kHz STEREOの場合

AudioStreamBasicDescription aiffFormat;
aiffFormat.mSampleRate = 44100.0;
aiffFormat.mFormatID = kAudioFormatLinearPCM;
aiffFormat.mFormatFlags = 
    kAudioFormatFlagIsBigEndian |
    kAudioFormatFlagIsSignedInteger |
    kAudioFormatFlagIsPacked;
aiffFormat.mBitsPerChannel = 16;
aiffFormat.mChannelsPerFrame = 2;
aiffFormat.mFramesPerPacket = 1;
aiffFormat.mBytesPerFrame = 4;
aiffFormat.mBytesPerPacket = 4;
aiffFormat.mReserved = 0;


// WAVE 8bit 48kHz MONOの場合

AudioStreamBasicDescription wavFormat;
wavFormat.mSampleRate = 48000.0;
wavFormat.mFormatID = kAudioFormatLinearPCM;
wavFormat.mFormatFlags = kAudioFormatFlagIsPacked; //WAVの8bitはunsigned
wavFormat.mBitsPerChannel = 8;
wavFormat.mChannelsPerFrame = 1;
wavFormat.mFramesPerPacket = 1;
wavFormat.mBytesPerFrame = 1;
wavFormat.mBytesPerPacket = 1;
wavFormat.mReserved = 0;


// AAC 44.1kHz STEREOの場合

AudioStreamBasicDescription m4aFormat;
m4aFormat.mSampleRate = 44100.0;
m4aFormat.mFormatID = kAudioFormatMPEG4AAC;
m4aFormat.mFormatFlags = kAudioFormatFlagIsBigEndian;
m4aFormat.mBytesPerPacket = 0;
m4aFormat.mFramesPerPacket = 1024;
m4aFormat.mBytesPerFrame = 0;
m4aFormat.mChannelsPerFrame = 2;
m4aFormat.mBitsPerChannel = 0;
m4aFormat.mReserved = 0;

前回から見てきたAudioBufferListやAudioStreamBasicDescriptionが扱えれば、オーディオデバイスやオーディオファイル等と、オーディオデータやフォーマット情報のやり取りが出来るようになります。その方法については次回やってみたいと思います。

Core Audio その1 AudioBufferとAudioBufferList

いままでvDSPやら何やらと、オーディオプログラミングでも補助的なものをネタにしていたので、ちょっとここらへんで基本に立ち返ってCore Audioの基本的な部分を書いていこうと思います。自分的にもちゃんと書いておかないと、オーディオ系のプログラミングからしばらく離れたりしたときに忘れてしまいそうになるので。

Core Audioというと、広い意味ではAudioUnitプラグインやCore MIDIまで含まれていると思いますが、ここではCoreAudio Frameworkという、まさにCore Audioな部分を見ていこうと思います。

どこから始めようかとと考えましたが、なにはなくともオーディオのデータが扱えなくては始まりません。Core Audioにはオーディオデータを表す構造体として、AudioBufferとAudioBufferListというものが定義されていますので、とりあえず、そこを見ていきます。

AudioBufferはひとつのオーディオデータを表していて、そのAudioBufferを配列でまとめて持っているのがAudioBufferListになります。

struct AudioBuffer
{
    UInt32  mNumberChannels;
    UInt32  mDataByteSize;
    void*   mData;
};
struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[kVariableLengthArray];
};
typedef struct AudioBufferList  AudioBufferList;

AudioBufferから見ていくと、mDataはオーディオデータのあるメモリ領域へのポインタです。mDataByteSizeはmDataの領域のサイズで、mNumberChannelsは含まれるチャンネル数になります。

AudioBufferListに移りまして、mBuffers[kVariableLengthArray]がAudioBufferの配列で、mNumberBuffersが配列の要素数になります。

なぜこんな風に2重構造になっているのかというと、オーディオデータが複数チャンネルある場合に、データの状態がインターリーブドになっているかいないかで2通りの扱い方があるからです。オーディオファイルなどではインターリーブドで渡す場合が多いですし、AudioUnitなどではインターリーブドでない形で渡す事になります。

例として、それぞれの場合でAudioBufferListを作成してみます。

長さが256フレームの32bitのステレオのデータをAudioBufferListを作ってみると、Interleavedならこんな感じです。

UInt32 frames = 256;
UInt32 channels = 2;
AudioBufferList list;
list.mNumberBuffers = 1;
list.mBuffers[0].mNumberChannels = channels;
list.mBuffers[0].mDataByteSize = frames * sizeof(float) * channels;
list.mBuffers[0].mData = calloc(1, frames * sizeof(float) * channels);

NonInterleavedだと、ちょっと工夫しなくてはいけません。

AudioBufferListのmBuffersに「kVariableLengthArray」という定数が記述されていますが、コマンド+ダブルクリックでたどってみると、

enum {
  kVariableLengthArray          = 1
};

となっています。AudioBufferListを作成した時点で要素が一つのAudioBufferの配列が確保されているという事ですので、InterleavedならAudioBufferListだけを作っておけば良かったのですが、NonInterleavedで配列の要素が2以上の場合はその分メモリを確保しておかなくてはいけません。mBuffersは構造体の最後のメンバですので、その後ろにそのまま確保します。

アップルのサンプルにあったAudioBufferListの生成と解放をするコードを参考にして、Objective-Cのメソッドにしてみると、

- (AudioBufferList *)allocateAudioBufferList:(UInt32)numChannels size:(UInt32)size
{
    AudioBufferList *list;
    UInt32 i;
	
    list = (AudioBufferList*)calloc(1, sizeof(AudioBufferList)
        + numChannels * sizeof(AudioBuffer));
    if (list == NULL) return NULL;
	
    list->mNumberBuffers = numChannels;
	
    for(i = 0; i < numChannels; ++i) {
        list->mBuffers[i].mNumberChannels = 1;
        list->mBuffers[i].mDataByteSize = size;
        list->mBuffers[i].mData = malloc(size);
        if(list->mBuffers[i].mData == NULL) {
            [self removeAudioBufferList:list];
            return NULL;
        }
    }
	
    return list;
}

- (void)removeAudioBufferList:(AudioBufferList *)list
{
    UInt32 i;
	
    if(list) {
        for(i = 0; i < list->mNumberBuffers; i++) {
            if (list->mBuffers[i].mData) free(list->mBuffers[i].mData);
        }
        free(list);
    }
}

といった感じになります。ちなみに、メモリを確保するところで、

list = (AudioBufferList*)calloc(1, sizeof(AudioBufferList)
    + numChannels * sizeof(AudioBuffer));

となっていて、もともとAudioBufferListでAudioBufferが1チャンネル分確保されているのに、さらにチャンネル数分のAudioBufferを確保しているのが無駄なような気がしますが、余分に確保しておく分には動作に問題はなさそうなのと、MTCoreAudioでも同じなので、とりあえずそのままコピペしてきてます。(ヘッダのコメントを見てみると、以前はkVariableLengthArrayを0にしてたけど、ANSI Cだと駄目だから1にしてるんだ、と書いてあります。)

このメソッドを使ってAudioBufferListを作成してみると、こんな感じになります。

UInt32 frames = 256;
UInt32 channels = 2;
AudioBufferList *list =
    [self allocateAudioBufferList:channels size:frames * sizeof(float)];

と、今回はAudioBufferListを見てきましたが、AudioBufferListの情報だけでは、チャンネル数が分かっても、ビットとかサンプリング周波数とかオーディオデータのフォーマットは分かりません。それに関しては、また次回。

AudioUnitをオフラインで使う

オーディオファイルにエフェクトをバッチ処理でかけるような時に、AudioUnitを使って出来ないかと思ったのですが、そういう事ができそうなkAudioUnitType_OfflineEffectを取得しても、VariSpeedやTimePitchしか出てきません。しかし、Logicとかで出来てる事が出来ないはずはないと思い、普通のkAudioUnitType_Effectをオフラインで使える方法はないかと調べてみました。

リアルタイムでの処理をシミュレートするという形で調べた結果、ポイントのみをとりあえず書いておきます。

・AudioUnitRender関数を実行すると別のスレッドでエフェクト処理をしてしまうので、AudioUnitAddRenderNotifyで、エフェクト処理した後のタイミングでデータを受け取る関数を設定する。
・その関数内ではActionFlagsがkAudioUnitRenderAction_PostRenderの時が処理後のデータとなる。
・AudioUnitRender関数に渡すAudioBufferListを自前で用意する。NotInterleaved。1024フレーム分くらいがベスト(小さいと処理が遅く、大きいと受け付けてくれない)。
・AudioUnitRender関数に渡すAudioUnitRenderActionFlagsは0。
・AudioUnitRender関数に渡すAudioTimeStampはmSampleTimeだけ必要だが、同じ数字を連続で渡してはいけない(一つでもずらしていればOK)。

ここらへんを守って、一回の処理が終わるごとに次のAudioUnitRenderを呼び出す、といったところでしょうか。ちなみにこの方法だと再生しながらバックグラウンドで処理させるみたいな事は出来ないと思います。

なんか、オフラインモードに切り替えてAudioUnitRender呼ぶだけでいいなんていうスマートな方法ってないですかねぇ。