How can I get AAC encoding with ExtAudioFile on iOS to work?

Sebastian picture Sebastian · Feb 10, 2011 · Viewed 10.6k times · Source

I need to convert a WAVE file into an AAC encoded M4A file on iOS. I'm aware that AAC encoding is not supported on older devices or in the simulator. I'm testing that before I run the code. But I still can't get it to work.

I looked into Apple's very own iPhoneExtAudioFileConvertTest example and I thought I followed it exactly, but still no luck!

Currently, I get a -50 (= error in user parameter list) while trying to set the client format on the destination file. On the source file, it works.

Below is my code. Any help is very much appreciated, thanks!

UInt32 size;

// Open a source audio file.
ExtAudioFileRef sourceAudioFile;
ExtAudioFileOpenURL( (CFURLRef)sourceURL, &sourceAudioFile );

// Get the source data format
AudioStreamBasicDescription sourceFormat;
size = sizeof( sourceFormat );
result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &sourceFormat );

// Define the output format (AAC).
AudioStreamBasicDescription outputFormat;
outputFormat.mFormatID = kAudioFormatMPEG4AAC;
outputFormat.mSampleRate = 44100;
outputFormat.mChannelsPerFrame = 2;

// Use AudioFormat API to fill out the rest of the description.
size = sizeof( outputFormat );
AudioFormatGetProperty( kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outputFormat);

// Make a destination audio file with this output format.
ExtAudioFileRef destAudioFile;
ExtAudioFileCreateWithURL( (CFURLRef)destURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &destAudioFile );

 // Create canonical PCM client format.
AudioStreamBasicDescription clientFormat;
clientFormat.mSampleRate = sourceFormat.mSampleRate;
clientFormat.mFormatID = kAudioFormatLinearPCM;
clientFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
clientFormat.mChannelsPerFrame = 2;
clientFormat.mBitsPerChannel = 16;
clientFormat.mBytesPerFrame = 4;
clientFormat.mBytesPerPacket = 4;
clientFormat.mFramesPerPacket = 1;

// Set the client format in source and destination file.
size = sizeof( clientFormat );
ExtAudioFileSetProperty( sourceAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
size = sizeof( clientFormat );
ExtAudioFileSetProperty( destAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );

// Make a buffer
int bufferSizeInFrames = 8000;
int bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );
UInt8 * buffer = (UInt8 *)malloc( bufferSize );
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mData = buffer;
bufferList.mBuffers[0].mDataByteSize = ( bufferSize );

while( TRUE )
{
    // Try to fill the buffer to capacity.
    UInt32 framesRead = bufferSizeInFrames;
    ExtAudioFileRead( sourceAudioFile, &framesRead, &bufferList );

    // 0 frames read means EOF.
    if( framesRead == 0 )
        break;

    // Write.
    ExtAudioFileWrite( destAudioFile, framesRead, &bufferList );
}

free( buffer );

// Close the files.
ExtAudioFileDispose( sourceAudioFile );
ExtAudioFileDispose( destAudioFile );

Answer

Sebastian picture Sebastian · Jun 11, 2011

Answered my own question: I had to pass this problem to my colleague and he got it to work! I never had the chance to analyze my original problem but I thought, I'd post it here for the sake of completeness. The following method is called from within an NSThread. Parameters are set via the 'threadDictionary' and he created a custom delegate to transmit progress feedback (sorry, SO doesn't understand the formatting properly, the following is supposed to be one block of method implementation):

- (void)encodeToAAC
{
    RXAudioEncoderStatusType encoderStatus;
    OSStatus result = noErr;
    BOOL success = NO;
    BOOL cancelled = NO;
    UInt32 size;

    ExtAudioFileRef sourceAudioFile,destAudioFile;
    AudioStreamBasicDescription sourceFormat,outputFormat, clientFormat;

    SInt64 totalFrames;
    unsigned long long encodedBytes, totalBytes;

    int bufferSizeInFrames, bufferSize;
    UInt8 * buffer;
    AudioBufferList bufferList;

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSFileManager * fileManager = [[[NSFileManager alloc] init] autorelease];

    NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];

    NSObject<RXAudioEncodingDelegate> * delegate = (NSObject<RXAudioEncodingDelegate> *)[threadDict objectForKey:@"Delegate"];

    NSString *sourcePath = (NSString *)[threadDict objectForKey:@"SourcePath"];
    NSString *destPath = (NSString *)[threadDict objectForKey:@"DestinationPath"];

    NSURL * sourceURL = [NSURL fileURLWithPath:sourcePath];
    NSURL * destURL = [NSURL fileURLWithPath:destPath];

    // Open a source audio file.
    result = ExtAudioFileOpenURL( (CFURLRef)sourceURL, &sourceAudioFile );
    if( result != noErr )
    {
        DLog( @"Error in ExtAudioFileOpenURL: %ld", result );
        goto bailout;
    }

    // Get the source data format
    size = sizeof( sourceFormat );
    result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &sourceFormat );
    if( result != noErr )
    {
        DLog( @"Error in ExtAudioFileGetProperty: %ld", result );
        goto bailout;
    }

    // Define the output format (AAC).
    memset(&outputFormat, 0, sizeof(outputFormat));
    outputFormat.mFormatID = kAudioFormatMPEG4AAC;
    outputFormat.mSampleRate = 44100;
    outputFormat.mFormatFlags = kMPEG4Object_AAC_Main;
    outputFormat.mChannelsPerFrame = 2;
    outputFormat.mBitsPerChannel = 0;
    outputFormat.mBytesPerFrame = 0;
    outputFormat.mBytesPerPacket = 0;
    outputFormat.mFramesPerPacket = 1024;


    // Use AudioFormat API to fill out the rest of the description.
    //size = sizeof( outputFormat );
    //AudioFormatGetProperty( kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outputFormat);

    // Make a destination audio file with this output format.
    result = ExtAudioFileCreateWithURL( (CFURLRef)destURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &destAudioFile );
    if( result != noErr )
    {
        DLog( @"Error creating destination file: %ld", result );
        goto bailout;
    }

    // Create the canonical PCM client format.
    memset(&clientFormat, 0, sizeof(clientFormat));
    clientFormat.mSampleRate = sourceFormat.mSampleRate;
    clientFormat.mFormatID = kAudioFormatLinearPCM;
    clientFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; //kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
    clientFormat.mChannelsPerFrame = 2;
    clientFormat.mBitsPerChannel = 16;
    clientFormat.mBytesPerFrame = 4;
    clientFormat.mBytesPerPacket = 4;
    clientFormat.mFramesPerPacket = 1;

    // Set the client format in source and destination file.
    size = sizeof( clientFormat );
    result = ExtAudioFileSetProperty( sourceAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
    if( result != noErr )
    {
        DLog( @"Error while setting client format in source file: %ld", result );
        goto bailout;
    }
    size = sizeof( clientFormat );
    result = ExtAudioFileSetProperty( destAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
    if( result != noErr )
    {
        DLog( @"Error while setting client format in destination file: %ld", result );
        goto bailout;
    }

    // Make a buffer
    bufferSizeInFrames = 8000;
    bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );
    buffer = (UInt8 *)malloc( bufferSize );

    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
    bufferList.mBuffers[0].mData = buffer;
    bufferList.mBuffers[0].mDataByteSize = ( bufferSize );

    // Obtain total number of audio frames to encode
    size = sizeof( totalFrames );
    result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileLengthFrames, &size, &totalFrames );
    if( result != noErr )
    {
        DLog( @"Error in ExtAudioFileGetProperty, could not get kExtAudioFileProperty_FileLengthFrames from sourceFile: %ld", result );
        goto bailout;
    }

    encodedBytes = 0;
    totalBytes = totalFrames * sourceFormat.mBytesPerFrame;
    [threadDict setValue:[NSValue value:&totalBytes withObjCType:@encode(unsigned long long)] forKey:@"TotalBytes"];

    if (delegate != nil)
        [self performSelectorOnMainThread:@selector(didStartEncoding) withObject:nil waitUntilDone:NO];

    while( TRUE )
    {
        // Try to fill the buffer to capacity.
        UInt32 framesRead = bufferSizeInFrames;
        result = ExtAudioFileRead( sourceAudioFile, &framesRead, &bufferList );
        if( result != noErr )
        {
            DLog( @"Error in ExtAudioFileRead: %ld", result );
            success = NO;
            break;
        }

        // 0 frames read means EOF.
        if( framesRead == 0 ) {
            success = YES;
            break;
        }

        // Write.
        result = ExtAudioFileWrite( destAudioFile, framesRead, &bufferList );
        if( result != noErr )
        {
            DLog( @"Error in ExtAudioFileWrite: %ld", result );
            success = NO;
            break;
        }

        encodedBytes += framesRead * sourceFormat.mBytesPerFrame;

        if (delegate != nil)
            [self performSelectorOnMainThread:@selector(didEncodeBytes:) withObject:[NSValue value:&encodedBytes withObjCType:@encode(unsigned long long)] waitUntilDone:NO];

        if ([[NSThread currentThread] isCancelled]) {
            cancelled = YES;
            DLog( @"Encoding was cancelled." );
            success = NO;
            break;
        }
    }

    free( buffer );

    // Close the files.
    ExtAudioFileDispose( sourceAudioFile );
    ExtAudioFileDispose( destAudioFile );

bailout:
    encoderStatus.result = result;
    [threadDict setValue:[NSValue value:&encoderStatus withObjCType:@encode(RXAudioEncoderStatusType)] forKey:@"EncodingError"];

    // Report to the delegate if one exists
    if (delegate != nil)
        if (success)
            [self performSelectorOnMainThread:@selector(didEncodeFile) withObject:nil waitUntilDone:YES];
        else if (cancelled)
            [self performSelectorOnMainThread:@selector(encodingCancelled) withObject:nil waitUntilDone:YES];
        else
            [self performSelectorOnMainThread:@selector(failedToEncodeFile) withObject:nil waitUntilDone:YES];

    // Clear the partially encoded file if encoding failed or is cancelled midway
    if ((cancelled || !success) && [fileManager fileExistsAtPath:destPath])
        [fileManager removeItemAtURL:destURL error:NULL];

    [threadDict setValue:[NSNumber numberWithBool:NO] forKey:@"isEncoding"];

    [pool release];
}