2010/11/26

Audio Queue Serviceを用いたネットワークストリーミングによるオーディオ再生

以前の記事「CFNetworkフレームワークを用いた入力HTTPストリームの作成」でネットワークからデータを取得する方法について述べました。今回はここから、Audio File Stream ServicesとAudio Queue Servicesを用いてオーディオ再生を行う方法について説明します。なお、詳細はAppleのドキュメント「Audio Queue Services Programming Guide」を参照してください。

概要

ストリーミングオーディオを再生するには次の手順を踏みます。

  1. CFNetworkを用いてストリームデータを取得
  2. Audio File Stream Servicesを用いてネットワークパケットを解析
  3. Audio Queue Servicesを用いてそのオーディオを再生

この流れを次に示します。矩形はオブジェクト、丸四角はコールバックを示し、実線はオブジェクトなどの生成、破線はオーディオデータの流れを示しています。そして、破線上の赤い文字は発生条件、黒い文字は呼び出し関数を示しています。

1. CFReadSream (CFNetwork)

CFNetworkのCFReadSreamを用いてネットワーク越しにデータを取得します。基本的な部分は以前の記事「CFNetworkフレームワークを用いた入力HTTPストリームの作成」と同様なので、ここでは詳細は述べません。

データを取得したときにイベントkCFStreamEventHasBytesAvailableが発生で、AudioFileStreamParseBytesによってAudioFileStreamにデータを渡すようにします。具体的には次のように書きます。

switch(event) {
    case kCFStreamEventHasBytesAvailable: {
        UInt8 buf[4096];
        CFIndex bytesRead = CFReadStreamRead(stream, buf, sizeof(buf));
        AudioFileStreamParseBytes(globalAudioFileStream, bytesRead, buf, 0);
    } break;

2. Audio File Stream Services

ネットワークから得られたデータはオーディオパケット単位で得られるわけではない上、オーディオパケット以外のデータとしてプロパティが混じっています。次の図はこれを概念化したものです。

オーディオデータは(上図の状態0)のようにオーディオパケット(青)とプロパティ(紫)が混じっています。さらに、ネットワークから得られるデータ場合はオーディオパケットなどの境界で適切に分割されていません(上図の状態1)。そのため、Audio File Stream Servicesによってオーディオデータをパケット単位で取得することと、ストリーム中のプロパティを取得することを行います(上図の状態2)。

Audio File Stream Servicesで行うべき処理について次に示します。

  1. 関数AudioFileStreamOpenによって新規オーディオストリーム解析器を作成
    オーディオデータとメタデータ (プロパティ) のためにコールバック関数AudioFileStreamPacketsProcとAudioFileStreamPropertyListenerProcを渡す。
  2. 先ほどのCFReadSreamからストリームデータを取得
    実際には可能な限りギャップなしでデータを解析器に継続的に渡すようにする必要があります。
  3. ストリームの解析が終了時に関数AudioFileStreamCloseを呼び出して、解析器のクローズとデアロケートを行う

また、2つのコールバックでは次のような処理を行います。

  • プロパティコールバック
    プロパティ値を取得することができます (関数AudioFileStreamGetPropertyInfoとAudioFileStreamGetPropertyを呼ぶ) 今回は再生可能な状態になった時点でAudio Queueを作成します。
  • オーディオデータコールバック
    データの再生、ファイルへの保存といった処理を行うとよいです。
    今回はバッファを作成して、それをオーディオキューに追加します。

次の2つのコード片はこれらコールバックの重要な部分だけの抜き出したものです。

// myAudioFileStream_PropertyListenerProc
switch(inPropertyID){
    case kAudioFileStreamProperty_ReadyToProducePackets: {
        AudioStreamBasicDescription asbd;
        UInt32 size = sizeof(asbd);
        AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat,
                     &size, (void*) &asbd);
        AudioQueueNewOutput(&asbd, &myAudioQueueOutputCallback,
                     NULL, CFRunLoopGetCurrent(), 
                     kCFRunLoopCommonModes, 0,
                     &globalAudioQueue);
        AudioQueueSetParameter(globalAudioQueue, kAudioQueueParam_Volume, 1.0);
        AudioQueueStart(globalAudioQueue, NULL);
        break;


// myAudioFileStream_PacketsProc
AudioQueueBufferRef aqbuf;
OSStatus err = AudioQueueAllocateBufferWithPacketDescriptions(globalAudioQueue, inNumberBytes,
                                                              inNumberPackets, &aqbuf);
memcpy(aqbuf->mAudioData, inInputData, inNumberBytes);
aqbuf->mAudioDataByteSize = inNumberBytes;    
AudioQueueEnqueueBuffer(globalAudioQueue, aqbuf, inNumberPackets, inPacketDescriptions);

3. Audio Queue Services

通常、オーディオキューコールバック関数ではバッファを再利用するようなコードを書きますが、今回の例では作成したバッファを削除しています。

AudioQueueFreeBuffer(inAQ, inBuffer);

まとめ

Audio FileStream ServicesとAudio Queue Servicesを用いてオーディオ再生を行う方法について説明しました。なお、この例ではコードを簡潔にするためにオーディオバッファを毎回作成、破棄を行っていますが、実際にはメモリ効率のため使い回す必要があります。

関連項目

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。