Attention! For Japanese customers, my Japanese blog might help.

Sep 5, 2016

Convert non-LPCM(such as MP3 or AAC) to LPCM using AudioToolbox

NOTE: This is the English version of my Japanese article.
NOTE: You can read non-LPCM files with ExtAudioFile functions more easily than with AudioFile functions.

I wrote a snippet code because I could not read .mp3 files on my app somehow.
As a result, I got an expected result finally.

Through a workaround, in case of non-LPCM, I found that we should care for ASPD(AudioStreamPacketDescription) and magic cookies.

I believe I cloud have read non-LPCM files with more lazy code... (in OS X 10.3 panther)

A file reading process is done in an input callback of AudioConverter, and the ASPD acquired in the callback should be returned with wave data.

The snippet code is as follows. Next, I want to check the code can be run in 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;
}

/*
   read an audio file specified by inputFilePath and write as CD-DA format(big-endian) binary data to outputFilePath. 
  This method is mainly intended to verify how to convert non-LPCM data such as MP3 or AAC to LPCM data. (.wav or .aiff are also readable)
   When failed, this method returns an OSStatus that is returned by internal CoreAudio functoins.
 */
OSStatus DecodeFileAtPath(const char *inputFilePath, const char *outputFilePath) {
    OSStatus err = noErr;
    AudioFileID fileID;
    MyConverterData myConverterData = {000};
    
    // open a file and get its ASBD and the number of packets.
    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);
    }
    
    // sets output format(ASBD)
    AudioStreamBasicDescription outDesc;
    SetStandardDescription(&outDesc);
    
    // creates an AudioConverter using file's ASBD and output ASBD.
    AudioConverterRef converter;
    {
        err = AudioConverterNew(&fileDesc, &outDesc, &converter);
        _ERR_RETURN(err);
    }

    // set magic cookie if exits.
    // (otherwise !dat will be returned by AudioConverterFillComplexBuffer() 
    {
        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// the number of packets to read in a loop
    FILE *fp = fopen(outputFilePath, "w");
    if (fp == NULL) {
        printf"fopen failed.");
        return writErr;
    }
    
    err = noErr;
    
    // repeat until reading and decoding are finished.
    while(err == noErr){
        // exit loop if the last packet was processed
        if (myConverterData.packetOffset == myConverterData.totalPacketCountbreak;
        
        UInt32 numPackets = maxNumPacketsInACycle; // the max number of packets to read
        UInt32 numFrames = fileDesc.mFramesPerPacket * numPackets; // the max number of frames to read
        
        AudioBufferList list;
        // preparation for list is needed (otherwise, -50 will be returned immediately without calling inputProc)
        list.mNumberBuffers = 1;
        list.mBuffers[0].mNumberChannels = 2;
        list.mBuffers[0].mDataByteSize = numFrames * outDesc.mBytesPerFrame// maximum byte size after decoding.
        list.mBuffers[0].mData = malloc(list.mBuffers[0].mDataByteSize);
        err = AudioConverterFillComplexBuffer(converter,
                                              MyACComplexInputProc,
                                              &myConverterData,
                                              &numPackets,
                                              &list,
                                              NULL);
        
        if (err == noErr) {
            // read decoded data
            fwrite(list.mBuffers[0].mData, list.mBuffers[0].mDataByteSize1, fp);
        }
        free(list.mBuffers[0].mData);
    }
    
    // finalize
    fclose(fp);
    err = AudioConverterDispose(converter);
    _ERR_RETURN(err);
    
    err = AudioFileClose(fileID);
    return err;
}

/*
 A callback for AudioConvereter.
 This read required packets from file and set them to ioData.
 If these ASBDs are acquired, this returns them via outDataPacketDescription.
 An inUserData must be the pointer to a MyConverterData variable.
 */
OSStatus MyACComplexInputProc(AudioConverterRef inAudioConverter,
                              UInt32 *ioNumberDataPackets,
                              AudioBufferList *ioData,
                              AudioStreamPacketDescription **outDataPacketDescription,
                              void* inUserData) {
    // fileIDを取得する
    MyConverterData *myConverterDataPtr = (MyConverterData *)inUserData;
    AudioFileID fileID = myConverterDataPtr->fileID;
    
    // determine a data size to read from file
    // the size is calculated by multiplying the probable max packet size and the number of packets.
    // FIXME: the max packet size is NOT needed to be acquired in every callback.
    UInt32 maxPacketSize;
    UInt32 dataSize = sizeof(maxPacketSize);
    OSStatus err = AudioFileGetProperty(fileID,
                                        kAudioFilePropertyMaximumPacketSize,
                                        &dataSize,
                                        &maxPacketSize);
    _ERR_RETURN(err);
    
    UInt32 numBytes = maxPacketSize * *ioNumberDataPackets; // the probable maximum data size
    
    // allocate buffer
    static void *buffer = NULL;
    if (buffer != NULL)
        free(buffer);
    buffer = malloc(numBytes);
    
    // allocate memory for ASPD.
    static AudioStreamPacketDescription *descs = NULL;
    if (descs != NULL)
        free(descs);
    descs = malloc(sizeof(AudioStreamPacketDescription) * *ioNumberDataPackets);
    
    // reading from a file
    err = AudioFileReadPacketData(fileID,
                                  false,
                                  &numBytes,
                                  descs,
                                  myConverterDataPtr->packetOffset,
                                  ioNumberDataPackets,
                                  buffer);
    _ERR_RETURN(err);
    
    // sets acquired wave data to ioData
    ioData->mBuffers[0].mDataByteSize = numBytes;
    ioData->mBuffers[0].mData = buffer;
    
    // sets acquired ASPDs. For .wav or .aiff files, the code was skipped because NULL was set
    if (outDataPacketDescription != NULL) {
        *outDataPacketDescription = descs;
    }
    
    // update the number of read packets
    myConverterDataPtr->packetOffset += *ioNumberDataPackets;
    
    // output for debug
    if (myConverterDataPtr->packetOffset % 100 == 0) {
        printf("%llu/%llu\n", myConverterDataPtr->packetOffset, myConverterDataPtr->totalPacketCount);
    }
    
    return err;
}

/*
 sets CDDA-quality ASBD to descPtr
 Take care for use with little-endian processors because this function returns big-endian format.
 */
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;
}

By the way, you should use an afconvert command is if you are not familiar with programming.