前回はAudio Queueとバッファを作成するところまで説明を行った。今回は、バッファにデータを格納して、音を再生する手順を説明しよう。

オーディオキューからのコールバック

Audio Queueにオーディオデータを渡すには、作成時に登録したコールバック関数を使う事になる。コールバックの仕組みに関する大枠は、前回も紹介したAppleのドキュメントを参考にしてほしい。

コールバック関数自体は、C関数として登録する事になる。だが、この関数の中で様々な処理を行うのは都合が悪い。なぜなら、Audio Queueの管理はObjective-Cのコントロールクラスで行っているからだ。そこで、コールバックであるC関数からは、すぐにObjective-Cメソッドを呼び出してしまうようにしよう。

List 1.

static void audioQueueOutputCallback(
        void* userData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer)
{
    [(OrganViewController*)userData
            _audioQueueOutputWithQueue:audioQueue queueBuffer:audioQueueBuffer];
}

第一引き数はアプリケーションが任意に指定できる値なので、ここにObjective-Cのインスタンスのポインタを入れておく。これを使えば、適当なメソッドに処理をパススルーすることができるだろう。

パケットデータの読み込み

コールバックから続く処理では、まずオーディオデータを読み込みを行う。これには、Audio FileライブラリにあるAudioFileReadPacketsを使う事ができる。これを使えば、指定したパケットの大きさだけデータを読み込む事ができる。さらに、読み込んだパケットデータの詳細情報を知るために、AudioStreamPacketDescriptionという構造体が用意されている。この構造体の定義は、次のようになっている。

List 2.

struct AudioStreamPacketDescription {
    SInt64  mStartOffset;
    UInt32  mVariableFramesInPacket;
    UInt32  mDataByteSize;
};

実はAudio Queueにパケットデータを設定するときは、この情報もあわせて指定してやる必要がある。あらかじめインスタンス変数などにバッファを確保しておこう。

パケットデータを読み込むための呼び出しは、次のようになるだろう。

List 3.

    // パケットデータを読み込む
    UInt32  numBytes;
    UInt32  numPackets = _packetSizeToRead;
    status = AudioFileReadPackets(
            _audioFileId, NO, &numBytes, &_audioPacketDesc,
            _startPacketNumber, &numPackets, audioQueueBuffer->mAudioData);

AudioFileReadPacketsには、7つの引き数がある。これらを説明しよう。まず第一引き数は、オープンしているオーディオファイルを表すAudio File ID。第に引き数は、データをキャッシュするかどうか指定するもの。第三引き数は、読み込んだバイト数を出力するためのもの。第四引き数は、パケットの詳細情報を表すAudioStreamPacketDescriptionを出力するためのもの。第五引き数は、読み込むパケットの開始位置を表すもの。先頭から読み込む場合は0になり、続きから読み込みたい場合は順次インクリメントさせていく。第六引き数は、読み込むパケット数。このパケット数は、前回単位時間あたりのパケット数を計算したので、そこから求める事ができるだろう。そして第七引き数は、読み込んだパケットデータを格納するバッファになる。これには、AudioQueueBufferを直接している。

これでオーディオファイルからパケットデータを読み込む事ができる。かなり複雑だ。これは、オーディオデータを単なるバイト列として扱うのではなく、パケットごとに区切られたデータとして見る必要があるためだ。

データの読み込みに成功したら、これをAudio Queueに追加しよう。

List 4.

    // 読み込みに成功した場合
    if (numPackets > 0) {
        // バッファの大きさを、読み込んだパケットデータのサイズに設定する
        audioQueueBuffer->mAudioDataByteSize = numBytes;

        // バッファをキューに追加する
        status = AudioQueueEnqueueBuffer(
                audioQueue, audioQueueBuffer, numPackets, _audioPacketDescs[index]);

再生音のループ

これでデータの読み込みは行えたのだが、もう1つ考慮しなくてはいけないものがある。それは、再生音のループだ。ここでは楽器アプリを作っているのだが、ドラムやピアノのように、単純に用意した音を一度だけ再生すれば十分なものもあるが、オルガンや管楽器のように、ボタンを押している間は継続して音を鳴らす必要があるものもある。この場合、音のループ再生を行わなくてはいけない。

しかも、楽器のシミュレートの場合は、単純なループではだめである。なぜなら、楽器音は始まりの音(アタック音)と、伸ばしているときの音(経過音)とで構成されているからだ。単にループするとアタック音が何度も再生されてしまうので、適切な経過音のポイントに戻ってループを行う必要がある。

Audio Queueでは、バッファに書き込むデータのパケット位置を管理する事で、これらを実現する事ができる。具体的には、読み込んだパケット位置を覚えておき、ループ終了点を超えたら、ループ開始点まで戻してやる事になる。次のようなソースコードで対応できるだろう。

List 5.

        // パケット位置を動かす
        _startPacketNumber += numPackets;

        // ループの処理を行う
        if (_startPacketNumber > 40) {
            _startPacketNumber = 8;
        }

ここでは、40パケット目がループの終了地点。8パケット目がループの開始地点となっている。

気をつけなくてはいけないのは、一度音が止まったら、再び音を出すときは0パケット目から再生する必要がある事だ。8パケット目はループの戻り地点なので、ここからスタートすることはない。

これらを組み込んだ、アプリケーションのソースコードを公開しよう。楽器としては、オルガンをシミュレートしている。ボタンを押している間、音が鳴り続けているのが分かると思う。ただし、ループの継ぎ目があまりきれいではない。違和感のないループ音を作成するには、そのための技術が必要だ。今回はAudio Queueの説明に重きを置いたため、そこは省略させていただいた。

次回は、OpenALを使った再生について議論しよう。

ここまでのソースコード: Organ-1.zip