検証用コードを書いてみました。
なんとか意図した通り動くようになりました。
とりあえずノンリニアな場合はASPD(AudioStreamPacketDescription)とmagic cookieを
意識する必要がありますね。
昔(OS X 10.3辺り)はもっと雑なコードでも読めていたはずなんですが…。
ファイルの読み込み処理そのものはAudioConverterの入力コールバックで行っていて、
その際得られたASPDがあれば一緒に返してあげる感じですね。
検証用コードは以下の通りで、次はこれをworker-threadで行った時にどうなるかを
調べたいと思います。
ちなみにプログラムに興味ない人はafconvertコマンドを使うと良いです。#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;}