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

Core Audio その1 AudioBufferとAudioBufferList」への2件のフィードバック

  1. ONO

    初めまして。すごくわかりやすい解説でとても勉強になります。
    疑問になったのですが、
    AudioBuffer および AudioBufferListというものは、
    AudioQueueとは別のものになるのでしょうか??
    また、AudioBuffer内にあるmDataのデータ領域に保存されるデータは数値かなにかで確認することはできないのでしょうか??
    例えば
    NSLog(@"mData :%d",(SInt32)&list->mBuffers[i].mData);
    みたいな形になるのでしょうか??
    個人的にはAudioQueueのAudioQueueBuffer->mAudioDataが確認したいのですが・・・
    ※メールアドレスはスパム対策のために、最後に”s”を付けています。

    返信
  2. Yasoshima

    お役に立てているようで幸いです。

    AudioQueueServiseで使われているAudioQueueBufferと、AudioBufferは別ものですね。
    AudioBufferListでfloatのオーディオデータを確認する場合だとこんな感じになりますが、

    for (int i = 0; i &lt list->mNumberBuffers; i++) {
    float *data = list->mBuffers[i].mData;
    UInt32 frames = list->mBuffers[i].mDataByteSize / sizeof(Float32);
    for (int j = 0; j &lt frames; j++) {
    printf(“%.2f “, data[j]);
    }
    printf(“\n”);
    }
    AudioQueueBufferのmAudioDataを確認する場合は、
    AudioQueueBuffer *buffer
    にfloatのオーディオデータがあるとして、
    UInt32 frames = buffer->mAudioDataByteSize / sizeof(float);
    float *data = buffer->mAudioData;
    for (int i = 0; i &lt frames; i++) {
    printf(“%.2f”, data[i]);
    }
    という感じじゃないでしょうか。

    返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です