#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;
}