TN2097: デフォルト出力ユニットを使った音声ファイルの再生

原文 Technical Note TN2097: Playing a sound file using the Default Output Audio Unit (http://developer.apple.com/technotes/tn2004/tn2097.html)

Core Audio での音声ファイルの再生は、音声データの取り扱いにおいて、より高い柔軟性をもたらします。 Mac OS X において、Core Audio は Sound Manager によって強いられてきた、いくつかの制限を取り除きます。 これにより、音声データはより高い解像度を持つ事ができ、Core Audio を使った処理ではパフォーマンスが改善されます。 Core Audio API はハードウェア、コーデック、音声データへの容易なアクセス手段を提供し、プログラマに、上質な音声の作成/演出の幅広い機会を与えます。

この技術メモは、Core Audio SDK の Public Utility Cクラスを使用せずに、小さな音声ファイルを再生する方法の道筋を示します。 この技術文章の目的は、プログラマにデフォルト出力ユニットを使った Core Audio プログラミングの概念の、いくつかの本質を曝すことです。 本文章は Audio Unit、Audio Converter、そして音声データを保持するために使用される、いくつかの Core Audio データ構造体の使い方について扱います。 ---- Core Audio で音声ファイルを再生するのには、いくつかの基本的な概念を必要とします。 最も重要な概念のうちの1つは、Core Audio における AudioUnit の役割です。 AudioUnit は信号処理ユニットの単体で、音声データ源(例えばソフトウェアシンセサイザ)や、音声データの出力先(例えば音声デバイスをラップした AudioUnit)、あるいはリバーブといった、音声データを受け取りそのデータに処理や変更を加える DSP ユニットのように、入力と出力の両方を持つものだったりします。 Audio Unit に関する更なる情報は [[http://developer.apple.com/audio/pdf/coreaudio.pdf

    // AudioUnit は OS のコンポーネントです。
    // AudioUnit の初期化に使う Component Description を用意します。
 
    ComponentDescription desc;
    Component comp;
 
    // 何種類かの AudioUnit タイプがあります。
    // 出力ユニット、ミキサユニット、DSP ユニットなどとして用いられるユニットがあります。
    // 一覧は AUComponent.h をご覧下さい。
 
    desc.componentType = kAudioUnitType_Output;
 
    // それぞれのコンポーネントは、そのコンポーネントの機能を明確にするサブタイプを持ちます。
 
    desc.componentSubType = kAudioUnitSubType_DefaultOutput;
 
    // AUComponent.h の AudioUnit の製造者は、全て "kAudioUnitManufacturer_Apple" が用いられています。
 
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
 
    // desc の要件に適合するコンポーネントを探します。
    comp = FindNextComponent(NULL, &desc);
    if (comp == NULL) exit (-1);
 
    // コンポーネントが提供するサービスへのアクセス手段を得ます。
    err = OpenAComponent(comp, theOutputUnit);

AudioUnit は音声ストリームの様々な処理に使用されるので、多くの内部パラメータを持ちます。 AudioUnit の内部情報は AudioUnitGetPropertyAudioUnitSetProperty の呼び出しで以て、簡単に変更することができます。 AudioUnitGetPropertyInfo は、内部情報サイズの取得や、情報が変更可能かどうかを調べるのに使用します。 AudioUnitSetPropertyInfo は、AudioUnitGetPropertyAudioUnitSetProperty の呼び出しの前に、エラーを回避するために使用されるでしょう。 AudioUnit の最も重要な内部情報の1つに、ストリーム形式があります。 ストリーム形式は、音声データストリームの特徴を表わします。 これにより、サンプリング周波数、データパケット情報、エンコードタイプのような、データストリームの形式情報が提供されます。 ストリーム形式は AudioStreamBasicDescriptions (ASBD) と呼ばれる構造体に格納され、Core Audio 全体を通して幅広く使用されます。 AudioUnit は、入力と出力の2つの端子を持つと見なせるので、AudioUnit の使用前に、入出力端子のストリーム形式を設定しておかねばなりません。 ユーザーが選択した、現在の出力ストリーム形式を得るには AudioUnitGetProperty()kAudioUnitScope_OutputkAudioUnitProperty_StreamFormat パラメータで呼び出します。その結果は現在の ASBD へ格納されます。 Listing 2: AudioUnit の Get/Set 系ルーチンを使う

//AudioUnit *theUnit - 現在の AudioUnit へのポインタ
//AudioStreamBasicDescription *theDesc  - 現在のユーザー出力用 ASBD
 
/***内部情報のサイズを得る***/
UInt32 size;
 
 
// Stream Format 情報のサイズと書き込み可能かどうかを得る
OSStatus result = AudioUnitGetPropertyInfo(*theUnit,
                            kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Output,
                            0,
                            &size,
                            &outWritable);
 
// 出力の現在のストリーム形式を得る
result = AudioUnitGetProperty (*theUnit,
                            kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Output,
                            0,
                            theDesc,
                            &size);
 
// 入力に適合する出力のストリーム形式を設定する
result = AudioUnitSetProperty (*theUnit,
                            kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Input,
                            theInputBus,
                            theDesc,
                            size);

ストリーム形式が確定した後は、AudioUnitInitialize で AudioUnit を初期化します。 資源(例えば、MusicDevice 用のサウンドバンク)の取得や、ユニット内での処理の関係で要求されるメモリバッファの確保などを伴う AudioUnit の初期化は、高コストな作業になる可能性があります。 一旦ユニットが初期化されてしまえば、殆どの場合において、ユニットは基本的に仕事をする事が可能な状態になります。 また、我々は AudioUnit にレンダーコールバックを設定することで、入力データをどこから得るのかを明記しなければなりません。 これは AURenderCallbackStructkAudioUnitProperty_SetRenderCallback プロパティで設定することができます。 Listing 3: AudioUnit のレンダリングコールバックの設定

OSStatus SetupCallbacks(AudioUnit *theOutputUnit,
                                    AURenderCallbackStruct *renderCallback)
{
    OSStatus err= noErr;
    memset(renderCallback, 0, sizeof(AURenderCallbackStruct));
 
    // inputProc は AudioUnit にレンダリングデータを与える際に、入力手続きとして使用されるメソッド名を指定します。
    // 入力手続きは、Audio Converter が処理のためにより多くのデータを必要とした際にだけ呼ばれます。
 
    // 入力手続き名として "fileRenderProc" を設定します。
    renderCallback->inputProc = MyFileRenderProc;
    // コールバックに refCon を渡すことができますが、出力では必須ではありません。
    renderCallback->inputProcRefCon =0;
 
    // AudioUnit のコールバックを renderCallback に設定します。
 
    err = AudioUnitSetProperty (*theOutputUnit,
                                kAudioUnitProperty_SetRenderCallback,
                                kAudioUnitScope_Input,
                                0,
                                renderCallback,
                                sizeof(AURenderCallbackStruct));
    // 備考: 古い V1 のサンプルには "kAudioUnitProperty_SetRenderCallback" の代わりに、
    // 古い API である "kAudioUnitProperty_SetInputCallback" を使用しているものもあるかもしれません。
    // 現在では "kAudioUnitProperty_SetRenderCallback" の方を使うべきです。
 
    return err;
}

===== Audio File から情報を得る ===== AudioFile API は、音声ファイルの作成、オープン、修正、保存の為のインターフェースを提供します。 音声ファイルを開くと、形式とファイルサイズに関する情報を得る事が出来ます。 AudioFile の再生の前に、パケットの総数、ファイルサイズ、そして音声ファイルからデータを得るために必要な最大パケットサイズを取得しておくのが、よい考えと言えるでしょう。 Listing 4: 音声ファイルから情報を得る

UInt64 gTotalPacketCount=0;
UInt64 gFileByteCount =0;
UInt32 gMaxPacketSize =0;
 
...
 
OSStatus GetFileInfo(FSRef *fileRef,
                     AudioFileID *fileID,
                     AudioStreamBasicDescription *fileASBD,
                     const char *fileName)
{
    OSStatus err= noErr;
    UInt32 size;
 
 
    // ファイルパスを用いて、音声ファイルへのファイルシステム参照を得ます。
    FSPathMakeRef ((const UInt8 *)fileName, fileRef, 0);
    // AudioFile を開き、ファイルシステム参照を使って AudioFileID を得ます。
    err = AudioFileOpen(fileRef, fsRdPerm,0,fileID);
 
    size = sizeof(AudioStreamBasicDescription);
    memset(fileASBD, 0, size);
 
    // 音声ファイルの AudioStreamBasicDescription を得ます。
    // 既に ASBD のサイズは分かっている為 AudioFileGetPropertyInfo の呼び出しは省略できます。
    err = AudioFileGetProperty(*fileID,
                               kAudioFilePropertyDataFormat,
                               &size,
                               fileASBD);
    if(err)
      return err;
 
    // パケット総数、バイト数、最大パケットサイズを得る必要があります。
    // これらの値は、後の入力コールバック手続きで、音声ファイルからデータを取り込む際に必要となります。
    size = sizeof(gTotalPacketCount); // 型は UInt64
    err = AudioFileGetProperty(*fileID,
                               kAudioFilePropertyAudioDataPacketCount,
                               &size,
                               &gTotalPacketCount);
    if(err)
      return err;
 
    size = sizeof(gFileByteCount); // 型は UInt64
    err = AudioFileGetProperty(*fileID,
                               kAudioFilePropertyAudioDataByteCount,
                               &size,
                               &gFileByteCount);
   if(err)
      return err;
 
    size = sizeof(gMaxPacketSize); // 型は UInt32
    err = AudioFileGetProperty(*fileID,
                               kAudioFilePropertyMaximumPacketSize,
                               &size,
                               &gMaxPacketSize);
   if(err)
      return err;
 
    return err;
}

===== Audio Converter の設定 ===== Audio Converter は、音声データのデコードやエンコードの際に使用されます。 Audio Converter は、サンプリング周波数の変換と、整数値⇔浮動小数点値の変換を扱う事が可能です。 Audio Converter の極めて一般的な使い方は、音声ファイルからの得たデータを PCM データへと変換し、再生できるようにする事です。 Audio Converter は、ある形式から別の形式へと簡単にデコードする方法を提供します。 Audio Converter は、データの源と行き先のそれぞれのフォーマットの作成に見合った、音声コーデックを要求します。 Listing 5: 新規 Audio Converter インスタンスの生成

AudioStreamBasicDescription *source_AudioStreamBasicDescription;
AudioStreamBasicDescription *destination_AudioStreamBasicDescription;
AudioConverterRef *converter;
 
...
 
AudioConverterNew(source_AudioStreamBasicDescription,
                  destination_AudioStreamBasicDescription,
                  converter);

===== 音声データの生成 ===== 音声ファイルからデータを読み込むには AudioFileReadPackets を使います。 本サンプルでは、非常に小さな音声ファイルからデータを読み込んでいます。従って、データを変換せずに直接メモリへ格納しています。 巨大なファイルの時は、ファイル全体をメモリに読み込むのは賢明ではありません。より多くのデータを必要とした際に、データは AudioConverter を通すべきです。 通常、音声データの読み込みと処理は、それぞれ独立したスレッドで行われなければなりません。 Core Audio SDK に含まれる Public Utility C のクラス AudioFilePlayer は、音声データの読み込みを別のスレッドに譲渡します(サンプルコード PlayAudioFile をご覧下さい)。 I/Oスレッド内での音声ファイル読み込みはブロックする可能性があり、故に別スレッドで実装しなければなりません。

Listing 6: 音声ファイルをメモリに読み込む

//音声ファイルを全部メモリに読み込む。ここでは何の変換もしない。
OSStatus ReadFileIntoMem()
{
    OSStatus err = noErr;
 
    // 音声ファイルから読み込んだバイト総数。
    UInt32  bytesReturned = 0;
 
    // 音声ファイルに含まれるパケットの総数。
    UInt32 packets =gTotalPacketCount;
 
    // ディスクから読み込んだデータを格納するメモリバッファの確保。
    gEntireFileBuffer = malloc(gFileByteCount);
    memset(gEntireFileBuffer, 0, gFileByteCount);
 
    // ファイル全体をメモリに読み込む。
    err = AudioFileReadPackets (*gSourceAudioFileID,
                                false,
                                &bytesReturned,
                                NULL,
                                0,
                                &packets,
                                gEntireFileBuffer);
 
    return err;
}

実際に Audio File から得たデータの変換を始めるには、AudioOutputUnitStart を呼んで AudioUnit を始動させます。 すると、AudioUnitはデータを、設定した入力から引っ張って(pull)きます。 今のところ本サンプルでは、AudioUnitの入力手続きは MyFileRenderProc となっています。 しかし、AudioUnit の入力手続きは、まだ作成していません。 入力手続の内部では、Audio File から変換済みの音声データを得る事が望まれます。 データ生成コールバックで AudioConverterFillComplexBuffer を呼べば、AudioUnit に変換済みデータを返すことができます。 Because this rendering callback is demand driven and will call the method you provided every time the AudioUnit needs more data. データは AudioBufferList に格納され、それを処理に使う事が可能です。 しかしながら、AudioConverterFillComplexBuffer は、Audio Converter にデータを提供するための、もう1つの入力手続きの記述を要求します。

Listing 7: AudioConverterFillComplexBuffer を使ったデータ生成例

OSStatus MyFileRenderProc(void *inRefCon,
                          AudioUnitRenderActionFlags *inActionFlags,
                          const AudioTimeStamp *inTimeStamp,
                          UInt32 inBusNumber,
                          UInt32 inNumFrames,
                          AudioBufferList *ioData)
{
    OSStatus err= noErr;
    // 複雑な入力源(圧縮ファイルなど)から変換済みデータのバッファを得る為には、
    // AudioConverterFillComplexBuffer を用います。
    AudioConverterFillComplexBuffer(converter,
                                    MyACComplexInputProc,
                                    0,
                                    &inNumFrames,
                                    ioData,
                                    0);
 
    return err;
}
 
/*
AudioConverterFillComplexBuffer() 関数の引数
 
converter - 使用する Audio Converter です。
MyACComplexInputProc() - Audio Converter にデータを提供する入力手続です。
inNumFrames - 入力で要求されるデータ量です。実はこの数字は、出力で受け取られる量でもあります。
ioData - 返ってきた変換済みデータを受け取るバッファです。
*/

これは、Audio Converter にデータを提供する入力手続きの例です。 このコールバックの引数は、AudioConverterFillComplexBuffer() に含まれる引数と対応しています。 返される新規データは、引数中で与えられる AudioBufferList(この例では ioData)です。

Listing 8: Audio File からデータを読む複雑な入力手続の例

OSStatus MyACComplexInputProc (AudioConverterRef inAudioConverter,
                               UInt32 *ioNumberDataPackets,
                               AudioBufferList *ioData,
                               AudioStreamPacketDescription **outDataPacketDescription,
                               void *inUserData)
{
    OSStatus    err = noErr;
    UInt32  bytesCopied = 0;
 
    // 失敗した場合のための初期化
    ioData->mBuffers[0].mData = NULL;
    ioData->mBuffers[0].mDataByteSize = 0;
 
    // 要求を満たすだけのパケットを得られなかった場合、残っているものを読み出す
    if (gPacketOffset + *ioNumberDataPackets > gTotalPacketCount)
        *ioNumberDataPackets = gTotalPacketCount - gPacketOffset;
 
    // 利用可能なパケットがなければ、何もしない
    if (*ioNumberDataPackets)
    {
        if (gSourceBuffer != NULL) {
            free(gSourceBuffer);
            gSourceBuffer = NULL;
        }
 
        // AudioConverter が要求する総データ量
        bytesCopied = *ioNumberDataPackets * gMaxPacketSize;
        // AudioConverter が使用するための小さなバッファを確保する
        gSourceBuffer = (void *) calloc (1, bytesCopied);
 
        // Audio File のバッファから、必要なデータ分(bytesCopied)をコピーする
        memcpy(gSourceBuffer, gEntireFileBuffer + gByteOffset,bytesCopied);
 
        // 次に読み込みたいトラックの場所を保持する
        gByteOffset +=*ioNumberDataPackets * gMaxPacketSize;
        gPacketOffset += *ioNumberDataPackets;
 
        // Audio Converter にデータ源を教える
        ioData->mBuffers[0].mData = gSourceBuffer;
        // Audio Converter にデータ源に含まれるデータ量を教える
        ioData->mBuffers[0].mDataByteSize = bytesCopied;
    }
    else
    {
        // これ以上読むパケットがなかったら、読み込むデータ量(mDataByteSize)を0にする。
        // そして noErr を返して、AudioConverter にパケットの残りがない事を知らせる。
 
        ioData->mBuffers[0].mData = NULL;
        ioData->mBuffers[0].mDataByteSize = 0;
        gIsPlaying=FALSE;
        err = noErr;
    }
 
    return err;
 
}

Core Audio は Mac OS X における、より高度な音声処理を提供します。 Core Audio における音声データ処理は、Sound Manager を卓越した解像度と性能を持ちます。そして、それがクライアントのより素晴らしいオーディオ体験を可能にするのです。

日付 内容
2006-11-15 第1版
  • translation/adc/audio/technical_note/tn2097_playing_a_sound_file_using_the_default_output_audio_unit.txt
  • 最終更新: 2015-01-06 11:51
  • (外部編集)