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

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の情報だけでは、チャンネル数が分かっても、ビットとかサンプリング周波数とかオーディオデータのフォーマットは分かりません。それに関しては、また次回。