Attention! Translated article might be found on my English blog.
ラベル AudioToolbox の投稿を表示しています。 すべての投稿を表示
ラベル AudioToolbox の投稿を表示しています。 すべての投稿を表示

2016年7月30日土曜日

non-interleaved(kAudioFormatFlagIsNonInterleaved)なデータを扱うためのノウハウ


  1. AudioBufferListの設定方法
  2. AudioStreamBasicDescription(ASBD)の設定方法
  3. frame <-> bytes 変換方法

1. AudioBufferListの設定方法

non-interleavedの場合、チャンネル数の分だけ余分にAudioBuffer領域を確保する必要があります。
例えば、
    AudioBufferList *list = calloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (numCh - 1), 1);

とすると良いかもしれません。
参考: Core Audio その1 AudioBufferとAudioBufferList | Objective-Audio

2. AudioStreamBasicDescription(ASBD)の設定方法

non-interleavedの場合、ASBDのmBytesPerFrameは1チャンネルのみのバイトサイズを指定する必要があります。

However, when an ASBD has the kAudioFormatFlagIsNonInterleaved flag, the
                    AudioBufferList has a different structure and semantic. In this case, the ASBD
                    fields will describe the format of ONE of the AudioBuffers that are contained in
                    the list, AND each AudioBuffer in the list is determined to have a single (mono)
                    channel of audio data. Then, the ASBD's mChannelsPerFrame will indicate the
                    total number of AudioBuffers that are contained within the AudioBufferList -
                    where each buffer contains one channel. This is used primarily with the
                    AudioUnit (and AudioConverter) representation of this list - and won't be found
                    in the AudioHardware usage of this structure.


参考: CoreAudioTypes.h


3. frame <-> bytes 変換方法

結果として、non-ineterleavedの場合はLPCMであっても

mBytesPerFrame == (mChannelsPerFrame * mBitsPerChannel / 8)

 という式を満たさなくなってしまいます。

そのため、mBytesPerFrameを使っているコードは
bytesPerFrame = (mChannelsPerFrame * mBitsPerChannel / 8)

という変換を常に行う必要があります。

私は以下のコードを利用しています。

+ (UInt32)BPFofASBD:(ASBD)desc {
    if ((desc.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == kAudioFormatFlagIsNonInterleaved) {
        return desc.mChannelsPerFrame * desc.mBitsPerChannel / 8;
    }
    
    return desc.mBytesPerFrame;
}


2016年7月28日木曜日

ExtAudioFileSeek()で-66568(kExtAudioFileError_InvalidSeek)が返ってきてシークできなかった

シークする前にExtAudioFileSetProperty()で kExtAudioFileProperty_ClientDataFormatをセットする必要があるようです。

参考: Audio Unit:音楽ファイルを再生する

以下のコードでは、ファイルと同じASBD(AudioStreamBasicDescription)をセットしています。
- (void)prepareForRead {
    NSAssert(self.fileURL != nil && self.fileRef == NULL, @"");
    
    ExtAudioFileRef fileRef;
    OSStatus err = ExtAudioFileOpenURL((__bridge CFURLRef _Nonnull)(self.fileURL),
                                       &fileRef);
    _ASSERT_NOERR;
    
    self.fileRef = fileRef;
    
    UInt32 size = sizeof(ASBD);
    ASBD desc;
    err = ExtAudioFileGetProperty(self.fileRef,
                                  kExtAudioFileProperty_FileDataFormat,
                                  &size,
                                  &desc);
    _ASSERT_NOERR;
    self.format = desc;
    
    // ExtAudioFileSeek()を行うために必要。
    err = ExtAudioFileSetProperty(self.fileRef,
                                  kExtAudioFileProperty_ClientDataFormat,
                                  size,
                                  &desc);
    _ASSERT_NOERR;
}
.mp3や.m4aなどのnon-LPCMを扱う場合はLPCM用のASBDをclientDataFormatとしてセットしてください。

なお、上記の対策を施してもなおkExtAudioFileError_InvalidSeekが返る場合は、
他の原因があるかもしれません。

参考: ExtAudioFileSeek - Extended Audio File Services Reference
参考: ExtAudioFileSeek returning -66568 (kExtAudioFileError_InvalidSeek)
参考: ExtAudioFile woes

2016年7月8日金曜日

インストールされているAudioUnit一覧を確認する

Finding Audio Units plug-ins on your computer - Apple Supportによると、

  • Apple純正のAudioUnitはファイルとして存在しない
  • サードパーティ製のAudioUnitは/Library/Audio/Plug-Ins/Componentsか~/Library/Audio/Plug-Ins/Componentsに有る
だそうです。
Apple純正のものも含めて確認したい場合はauvaltoolを使うと良さげです。
こちらの環境での実行結果は以下の通りです。

$ auvaltool -a

    AU Validation Tool
    Version: 1.6.1a1 
    Copyright 2003-2013, Apple Inc. All Rights Reserved.
    Specify -h (-help) for command options

aufx bpas appl  -  Apple: AUBandpass
aufx dcmp appl  -  Apple: AUDynamicsProcessor
aufx dely appl  -  Apple: AUDelay
aufx dist appl  -  Apple: AUDistortion
aufx filt appl  -  Apple: AUFilter
aufx greq appl  -  Apple: AUGraphicEQ
aufx hpas appl  -  Apple: AUHipass
aufx hshf appl  -  Apple: AUHighShelfFilter
aufx lmtr appl  -  Apple: AUPeakLimiter
aufx lpas appl  -  Apple: AULowpass
aufx lshf appl  -  Apple: AULowShelfFilter
aufx mcmp appl  -  Apple: AUMultibandCompressor
aufx mrev appl  -  Apple: AUMatrixReverb
aufx nbeq appl  -  Apple: AUNBandEQ
aufx nsnd appl  -  Apple: AUNetSend
aufx nutp appl  -  Apple: AUNewPitch
aufx pmeq appl  -  Apple: AUParametricEQ
aufx raac appl  -  Apple: AURoundTripAAC
aufx rogr appl  -  Apple: AURogerBeep
aufx sdly appl  -  Apple: AUSampleDelay
aufx tmpt appl  -  Apple: AUPitch
aupn ambi appl  -  Apple: AUSoundFieldPanner
aupn hrtf appl  -  Apple: HRTFPanner
aupn sphr appl  -  Apple: AUSphericalHeadPanner
aupn vbas appl  -  Apple: AUVectorPanner
aumu dls  appl  -  Apple: DLSMusicDevice
aumu msyn appl  -  Apple: AUMIDISynth
aumu samp appl  -  Apple: AUSampler
aumu ssyn appl  -  Apple: SinSynth
aumu synt YMCK  -  Magical 8bit Plug
aumu Pas2 Demo  -  __MyCompanyName__: MyInstrument
aumx 3dem appl  -  Apple: AUSpatialMixer
aumx 3dmx appl  -  Apple: AUMixer3D
aumx mcmx appl  -  Apple: AUMultiChannelMixer
aumx mspl appl  -  Apple: AUMultiSplitter
aumx mxmx appl  -  Apple: AUMatrixMixer
aumx smxr appl  -  Apple: AUMixer
aufc conv appl  -  Apple: AUConverter
aufc defr appl  -  Apple: AUDeferredRenderer
aufc ipto appl  -  Apple: AUNotQuiteSoSimpleTime
aufc merg appl  -  Apple: AUMerger
aufc nutp appl  -  Apple: AUNewTimePitch
aufc splt appl  -  Apple: AUSplitter
aufc tmpt appl  -  Apple: AUTimePitch
aufc vari appl  -  Apple: AUVarispeed
auou ahal appl  -  Apple: AudioDeviceOutput
auou def  appl  -  Apple: DefaultOutputUnit
auou genr appl  -  Apple: GenericOutput
auou sys  appl  -  Apple: SystemOutputUnit
auou vpio appl  -  Apple: AUVoiceProcessor
augn afpl appl  -  Apple: AUAudioFilePlayer
augn nrcv appl  -  Apple: AUNetReceive
augn sspl appl  -  Apple: AUScheduledSoundPlayer
augn ttsp appl  -  Apple: AUSpeechSynthesis
auol tmpt appl  -  Apple: AUTimePitch
auol vari appl  -  Apple: AUVarispeed

左からタイプ、サブタイプ、製造元、ユニット名、だったと思います。
詳しくはauvaltool -hで確認願います。

ExtAudioFileを使ってMP3ファイルをPCMデータとして取得する

-- 2016/07/10追記 --

私が下の方で書いたコードだと、ファイルによってはいつまで経っても
whileループから抜け出さないケースに遭遇しました。

どうやらkExtAudioFileProperty_FileLengthFramesが返す総フレーム数と
実際の総フレーム数が異なるためwhileループの条件がfalseにならず
延々と読み込み処理を続けてしまうようでした。

ExtAudioFileRead()はファイルがEOFに達するとフレーム数には0が入るようです。
参考: Extended Audio File Services Reference

この動作を利用してExtAudioFileRead()の後に
        // 0ならend-of-fileらしい
        if (numFramesToRead == 0) {
            break;
        }
という処理を入れ、ループを抜ける仕組みを入れた方が良いでしょう。

-- 2016/07/10追記終了 --

前回の続き、というかExtAudioFileを使えば簡単にnon-LPCMを変換できることに気づきました…。
というわけで変更後のソースは以下の通り。
#include <CoreServices/CoreServices.h>
#include <AudioToolbox/AudioToolbox.h>

#define _ERR_RETURN(err)  {if(noErr != err){printf("%d - err:%d\n", __LINE__, err); return err;}}

typedef AudioStreamBasicDescription ASBD;
typedef AudioBufferList ABL;

OSStatus DecodeExtFileAtPath(const char *inputFilePath, const char *outputFilePath);
void SetStandardDescription(AudioStreamBasicDescription *descPtr);
static inline ABL MakeABL(UInt32 ch, UInt32 bytes, void *buf);

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: ./Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            OSStatus err = DecodeExtFileAtPath(argv[1], argv[2]);
            printf("done. err:%d\n", err);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    return 0;
}

/*
 inputFilePathで指定したオーディオファイルを読み込み
 CDフォーマット(ただしbig-endian)のバイナリデータとして
 outputFilePathにファイルとして書き出す。
 主にMP3, AACなどnon-LPCMデータのLPCMデータへの変換動作確認用。(一応.wav, .aiffも読める)
 処理に失敗した場合は失敗箇所でのOSStatusを返す。
 */
OSStatus DecodeExtFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    CFURLRef url = CFURLCreateWithBytes(NULL,
                                        (const UInt8 *)inputFilePath,
                                        strlen(inputFilePath),
                                        kCFStringEncodingUTF8,
                                        NULL);
    
    ExtAudioFileRef file;
    OSStatus err = ExtAudioFileOpenURL(url,
                                       &file);
    _ERR_RETURN(err);
    
    CFRelease(url);
    
    ASBD clientDesc;
    SetStandardDescription(&clientDesc);
    UInt32 size = sizeof(clientDesc);
    
    err = ExtAudioFileSetProperty(file,
                                  kExtAudioFileProperty_ClientDataFormat,
                                  size,
                                  &clientDesc);
    _ERR_RETURN(err);
    
    SInt64 fileFrameLength;
    size = sizeof(fileFrameLength);
    err = ExtAudioFileGetProperty(file,
                                  kExtAudioFileProperty_FileLengthFrames,
                                  &size,
                                  &fileFrameLength);
    _ERR_RETURN(err);
    
    const UInt32 numFramesToReadInACycle = 1024*1024;
    const UInt32 bufferSize = clientDesc.mBytesPerFrame * numFramesToReadInACycle;
    void *buffer = malloc(bufferSize);
    FILE *fp = fopen(outputFilePath, "w");
    SInt64 frameOffset = 0;
    while (frameOffset != fileFrameLength) {
        UInt32 numFramesToRead = numFramesToReadInACycle;
        AudioBufferList list = MakeABL(clientDesc.mChannelsPerFrame,
                                       bufferSize,
                                       buffer);
        err = ExtAudioFileRead(file,
                               &numFramesToRead,
                               &list);
        _ERR_RETURN(err); 
        
        fwrite(list.mBuffers[0].mData,
               list.mBuffers[0].mDataByteSize,
               1,
               fp);
        
        frameOffset += numFramesToRead;
    }
    fclose(fp);
    free(buffer);
    
    err = ExtAudioFileDispose(file);
    return err;
}

/*
 descPtrにCD音質のASBDをセットする。
 ただし、big-endianなのでlittle-endianなプロセッサで扱う場合は注意
 */
void SetStandardDescription(AudioStreamBasicDescription *descPtr) {
    descPtr->mSampleRate = 44100.0;
    descPtr->mFormatID = kAudioFormatLinearPCM;
    descPtr->mFormatFlags = kAudioFormatFlagIsBigEndian |
                            kAudioFormatFlagIsSignedInteger |
                            kAudioFormatFlagIsPacked;
    descPtr->mBytesPerPacket = 4;
    descPtr->mBytesPerFrame = 4;
    descPtr->mFramesPerPacket = 1;
    descPtr->mChannelsPerFrame = 2;
    descPtr->mBitsPerChannel = 16;
}

static inline ABL MakeABL(UInt32 ch, UInt32 bytes, void *buf) {
    ABL list;
    list.mNumberBuffers = 1;
    list.mBuffers[0].mNumberChannels = ch;
    list.mBuffers[0].mDataByteSize = bytes;
    list.mBuffers[0].mData = buf;
    return list;
}
AudioConverterが不要なのは良いですね。(実際はExtAudioFileが内包しているようです)
kExtAudioFileProperty_FileLengthFramesで総フレーム数が取得できるのもありがたい…。
まだVBRなファイルは試してませんが、問題なく行けそうな気がします。

2016年7月7日木曜日

AudioFile, AudioConverter関連の関数をメインスレッド以外で実行させてみた。

前回の記事AudioToolboxを使ってMP3やAACなどのnon-LPCMをLPCMデータに変換するの続きです。
メインスレッド以外で実行できるか試してみました。
main()の中だけ修正し、他はいじりませんでした。
main()のコードは以下の通りです。

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: ./Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            OSStatus err = DecodeFileAtPath(argv[1], argv[2]);
            printf("done. err:%d\n", err);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    return 0;
}
参考: apply.c - Dispatch_Samples - Apple Developer
参考: エキスパートObjective-Cプログラミング -iOS/OS Xのメモリ管理とマルチスレッド- - Amazon

実行した結果、サブスレッドでも正常動作することを確認できました。
(ただし、Cocoaアプリ内のサブスレッドでも正常動作することを裏付けるものではないと思います)

Objc-CやSwiftを使っているならNSOperationを使うほうが楽そうですね。

あー、あとGrand Central Dispatch(GCD)関連のドキュメントを漁っていて、
Apple公式の"Introducing Blocks and Grand Central Dispatch"が入手できなかったのは
非常に残念&腹立たしかったです。

2016年7月6日水曜日

AudioToolboxを使ってMP3やAACなどのnon-LPCMをLPCMデータに変換する

MP3の読み込みがどうしても上手くいかないので
検証用コードを書いてみました。
なんとか意図した通り動くようになりました。

とりあえずノンリニアな場合はASPD(AudioStreamPacketDescription)とmagic cookieを
意識する必要がありますね。
昔(OS X 10.3辺り)はもっと雑なコードでも読めていたはずなんですが…。

ファイルの読み込み処理そのものはAudioConverterの入力コールバックで行っていて、
その際得られたASPDがあれば一緒に返してあげる感じですね。

検証用コードは以下の通りで、次はこれをworker-threadで行った時にどうなるかを
調べたいと思います。
#include <CoreServices/CoreServices.h>
#include <AudioToolbox/AudioToolbox.h>

#define _ERR_RETURN(err)  {if(noErr != err){printf("%d - err:%d\n", __LINE__, err); return err;}}

OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath);
OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData);
void SetStandardDescription(AudioStreamBasicDescription *descPtr);

typedef struct MyConverterData {
    AudioFileID fileID;
    UInt64 totalPacketCount;
    UInt64 packetOffset;
} MyConverterData;

int main(int argc, char* argv[]) {
    if (argc != 3) {
        printf("usage: Mp3Decoder inFile outFile\n");
        return 0;
    }
    
    OSStatus err = DecodeFileAtPath((const char *)(argv[1]), (const char *)argv[2]);
    printf("done. err:%d\n", err);
    
    return 0;
}

/*
    inputFilePathで指定したオーディオファイルを読み込み
    CDフォーマット(ただしbig-endian)のバイナリデータとして
    outputFilePathにファイルとして書き出す。
    主にMP3, AACなどnon-LPCMデータのLPCMデータへの変換動作確認用。(一応.wav, .aiffも読める)
    処理に失敗した場合は失敗箇所でのOSStatusを返す。
 */
OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    OSStatus err = noErr;
    AudioFileID fileID;
    MyConverterData myConverterData = {0, 0, 0};
    
    // ファイルを開いてファイルのASBD, パケット数を取得する
    AudioStreamBasicDescription fileDesc;
    {
        CFURLRef url;
        url = CFURLCreateWithBytes(NULL,
                                   (const UInt8 *)inputFilePath,
                                   strlen(inputFilePath),
                                   kCFStringEncodingUTF8,
                                   NULL);
        
        err = AudioFileOpenURL(url,
                               fsRdPerm,
                               0,
                               &fileID);
        _ERR_RETURN(err);
        
        myConverterData.fileID = fileID;
        UInt32 size = sizeof(AudioStreamBasicDescription);
        memset(&fileDesc, 0, size);
        
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyDataFormat,
                                   &size,
                                   &fileDesc);
        _ERR_RETURN(err);
        size = sizeof(myConverterData.totalPacketCount);
        err = AudioFileGetProperty(fileID,
                                   kAudioFilePropertyAudioDataPacketCount,
                                   &size,
                                   &myConverterData.totalPacketCount);
        _ERR_RETURN(err);
    }
    
    // 出力フォーマット(ASBD)を設定する
    AudioStreamBasicDescription outDesc;
    SetStandardDescription(&outDesc);
    
    // ファイルのASBDと出力のASBDを使ってコンバータを作成する
    AudioConverterRef converter;
    {
        err = AudioConverterNew(&fileDesc, &outDesc, &converter);
        _ERR_RETURN(err);
    }

    // magic cookieがあればコンバータにセットする
    // (これをしないとAudioConverterFillComplexBuffer()で!datが返る
    {
        UInt32 size = 0;
        err = AudioFileGetPropertyInfo(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       NULL);
        if(noErr == err){
            void *magic = calloc(1, size);
            err = AudioFileGetProperty(fileID,
                                       kAudioFilePropertyMagicCookieData,
                                       &size,
                                       magic);
            if(noErr == err){
                printf("magic cookie info found.\n");
                err = AudioConverterSetProperty(converter,
                                                kAudioConverterDecompressionMagicCookie,
                                                size,
                                                magic);
                _ERR_RETURN(err);
            }
            err = noErr;
            if(NULL != magic){
                free(magic);
            }
            _ERR_RETURN(err);
        }else{
            printf("magic cookie info not found.\n");
            
        }
    }
    
    const UInt32 maxNumPacketsInACycle = 100; // 1ループあたりに読み込むパケット数
    FILE *fp = fopen(outputFilePath, "w");
    if (fp == NULL) {
        printf( "fopen failed.");
        return writErr;
    }
    
    err = noErr;
    
    // ファイル読み込み、デコード、書き込みが終わるまで繰り返す
    while(err == noErr){
        // 最後のパケットまで処理済みならループを抜ける
        if (myConverterData.packetOffset == myConverterData.totalPacketCount) break;
        
        UInt32 numPackets = maxNumPacketsInACycle; // 読み込みたい最大パケット数
        UInt32 numFrames = fileDesc.mFramesPerPacket * numPackets; // 読み込みたい最大フレーム数
        
        AudioBufferList list;
        // listの準備は必要(なければinputProc呼ばれず即-50)
        list.mNumberBuffers = 1;
        list.mBuffers[0].mNumberChannels = 2;
        list.mBuffers[0].mDataByteSize = numFrames * outDesc.mBytesPerFrame; // デコード後の最大バイトサイズ
        list.mBuffers[0].mData = malloc(list.mBuffers[0].mDataByteSize);
        err = AudioConverterFillComplexBuffer(converter,
                                              MyACComplexInputProc,
                                              &myConverterData,
                                              &numPackets,
                                              &list,
                                              NULL);
        
        if (err == noErr) {
            // デコードデータを書き込む
            fwrite(list.mBuffers[0].mData, list.mBuffers[0].mDataByteSize, 1, fp);
        }
        free(list.mBuffers[0].mData);
    }
    
    // 後始末
    fclose(fp);
    err = AudioConverterDispose(converter);
    _ERR_RETURN(err);
    
    err = AudioFileClose(fileID);
    return err;
}

/*
 コンバータ用コールバック関数
 必要なパケットだけファイルから読み込みioDataにセットして渡す。
 パケットのASBDが得られる場合はoutDataPacketDescriptionを通じて渡す。
 inUserDataはMyConverterData変数へのポインタでなければならない
 */

OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData) {
    // fileIDを取得する
    MyConverterData *myConverterDataPtr = (MyConverterData *)inUserData;
    AudioFileID fileID = myConverterDataPtr->fileID;
    
    // ファイルから読み込むデータサイズを決定する。
    // 最大パケットサイズを取得して、それに要求されているパケット数をかけて算出する。
    // FIXME: コールバックを呼ぶたびに取得しなくても良いはず
    UInt32 maxPacketSize;
    UInt32 dataSize = sizeof(maxPacketSize);
    OSStatus err = AudioFileGetProperty(fileID,
                                        kAudioFilePropertyMaximumPacketSize,
                                        &dataSize,
                                        &maxPacketSize);
    _ERR_RETURN(err);
    
    UInt32 numBytes = maxPacketSize * *ioNumberDataPackets; // 有り得る最大のデータサイズ
    
    // バッファ確保
    static void *buffer = NULL;
    if (buffer != NULL)
        free(buffer);
    buffer = malloc(numBytes);
    
    // ASPD用メモリ確保
    static AudioStreamPacketDescription *descs = NULL;
    if (descs != NULL)
        free(descs);
    descs = malloc(sizeof(AudioStreamPacketDescription) * *ioNumberDataPackets);
    
    // ファイル読み込み
    err = AudioFileReadPacketData(fileID,
                                  false,
                                  &numBytes,
                                  descs,
                                  myConverterDataPtr->packetOffset,
                                  ioNumberDataPackets,
                                  buffer);
    _ERR_RETURN(err);
    
    // 読み込んだデータをioDataにセットする
    ioData->mBuffers[0].mDataByteSize = numBytes;
    ioData->mBuffers[0].mData = buffer;
    
    // 得られたASPDをセットする。.wavや.aiffはNULLが入っているのでNULLならセットしない
    if (outDataPacketDescription != NULL) {
        *outDataPacketDescription = descs;
    }
    
    // 読み込み済みパケット数を更新する
    myConverterDataPtr->packetOffset += *ioNumberDataPackets;
    
    // デバッグ用出力
    if (myConverterDataPtr->packetOffset % 100 == 0) {
        printf("%llu/%llu\n", myConverterDataPtr->packetOffset, myConverterDataPtr->totalPacketCount);
    }
    
    return err;
}

/*
 descPtrにCD音質のASBDをセットする。
 ただし、big-endianなのでlittle-endianなプロセッサで扱う場合は注意
 */
void SetStandardDescription(AudioStreamBasicDescription *descPtr) {
    descPtr->mSampleRate = 44100.0;
    descPtr->mFormatID = kAudioFormatLinearPCM;
    descPtr->mFormatFlags = kAudioFormatFlagIsBigEndian |
                            kAudioFormatFlagIsSignedInteger |
                            kAudioFormatFlagIsPacked;
    descPtr->mBytesPerPacket = 4;
    descPtr->mBytesPerFrame = 4;
    descPtr->mFramesPerPacket = 1;
    descPtr->mChannelsPerFrame = 2;
    descPtr->mBitsPerChannel = 16;
}

ちなみにプログラムに興味ない人はafconvertコマンドを使うと良いです。