Blog

Easy AAC compressed audio conversion on iOS

From the iPhone 3Gs up, it’s possible to encode compressed AAC audio from PCM audio data. That means great things for apps that deal with audio sharing and transmission, as the audio can be sent in compressed form, rather than sending huge PCM audio files over the network.

Apple’s produced some sample code (iPhoneExtAudioFileConvertTest), which demonstrates how it’s done, but their implementation isn’t particularly easy to use in existing projects, as it requires some wrapping to make it play nice.

For my upcoming looper app Loopy, I’ve put together a simple Objective-C class that performs the conversion of any audio file to an AAC-encoded m4a, asynchronously with a delegate, or converts any audio provided by a data source class (which provides for recording straight to AAC) and I thought I’d share it.

Grab the code, and a sample project demonstrating its use at the GitHub repository for TPAACAudioConverter.

To use it:

  • Include the class in your project, and make sure you’ve got the AudioToolbox framework added, too.
  • Audio session setup:

If you already have an audio session set up in your app, make sure you disable mixing with other device audio for the duration of the copy operation, as this stops the hardware encoder from working (you’ll see funny errors like kAudioQueueErr_InvalidCodecAccess (Error 66672)). I know that AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategorySoloAmbient and AVAudioSessionCategoryAudioProcessing work for sure. TPAACAudioConverter will automatically disable kAudioSessionProperty_OverrideCategoryMixWithOthers, if it’s set.

If you’re not already setting up an audio session, you could do so just before you start the conversion process.

You’ll need to provide an interruption handler to be notified of audio session interruptions, which impact the encoding process. You’ll also need to create a member variable to store the converter instance, so you can tell it when interruptions begin and end (via interrupt and resume).

// Callback to be notified of audio session interruptions (which have an impact on the conversion process)
static void interruptionListener(void *inClientData, UInt32 inInterruption)
{
	AACConverterViewController *THIS = (AACConverterViewController *)inClientData;
 
	if (inInterruption == kAudioSessionEndInterruption) {
		// make sure we are again the active session
		checkResult(AudioSessionSetActive(true), "resume audio session");
        if ( THIS->audioConverter ) [THIS->audioConverter resume];
	}
 
	if (inInterruption == kAudioSessionBeginInterruption) {
        if ( THIS->audioConverter ) [THIS->audioConverter interrupt];
    }
}
 
/*snip*/
 
-(void)startConverting {
 
    /*snip*/
 
    // Initialise audio session, and register an interruption listener, important for AAC conversion
    if ( !checkResult(AudioSessionInitialize(NULL, NULL, interruptionListener, self), "initialise audio session") ) {
        [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Converting audio", @"")
                                     message:NSLocalizedString(@"Couldn't initialise audio session!", @"")
                                    delegate:nil
                           cancelButtonTitle:nil
                           otherButtonTitles:NSLocalizedString(@"OK", @""), nil] autorelease] show];
        return;
    }
 
 
    // Set up an audio session compatible with AAC conversion.  Note that AAC conversion is incompatible with any session that provides mixing with other device audio.
    UInt32 audioCategory = kAudioSessionCategory_MediaPlayback;
    if ( !checkResult(AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory), "setup session category") ) {
        [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Converting audio", @"")
                                     message:NSLocalizedString(@"Couldn't setup audio category!", @"")
                                    delegate:nil
                           cancelButtonTitle:nil
                           otherButtonTitles:NSLocalizedString(@"OK", @""), nil] autorelease] show];
        return;
    } 
 
 
    /*snip*/
}
  • Make the relevant view controller implement the TPAACAudioConverterDelegate protocol: That means implementing AACAudioConverterDidFinishConversion:, and AACAudioConverter:didFailWithError:, and optionally AACAudioConverter:didMakeProgress: to receive progress updates.
  • Create an instance of the converter, pass it the view controller as the delegate, and call start:
audioConverter = [[[TPAACAudioConverter alloc] initWithDelegate:self 
                                                         source:mySourcePath
                                                    destination:myDestinationPath] autorelease];
 
[audioConverter start];

Alternatively, if you wish to encode live audio, or provide another source of audio data, you can implement the TPAACAudioConverterDataSource protocol, which defines AACAudioConverter:nextBytes:length:, which provides a buffer to copy at most “length” bytes of audio into, and then expects you to update “length” to the amount of bytes provided. For that you’ll need to use the second initialiser, initWithDelegate:dataSource:audioFormat:destination:.

I noted previously that you can’t encode AAC live, which is what Apple’s docs say, but Alex in the comments informed me that it wasn’t so. So, I added the datasource method, and sure enough, it does work live!

The one caveat is that it’s a relatively heavy process. As it turns out, because my app Loopy is busily mixing and displaying visualisations and such, it was too much to also encode straight to AAC, and I was getting glitches. But it would probably work fine for plain recording. Thanks, Alex!

Tagged , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

71 Comments

  1. Posted June 19, 2013 at 9:17 pm | Permalink

    Hi Michael,

    Great work on this and your other projects! I’m thinking about adapting this class for an iOS project that will be integrated with Audiobus. I noticed that Audiobus’s documentation states that integrated apps should use an audio session category that allows mixing, while the TPACC converter class requires that mixing be disabled. Are you aware of a way to resolve this apparent conflict, without making a huge mess? Thanks!

    • Posted June 20, 2013 at 5:06 pm | Permalink

      Hey Travis – Ah, to be honest, I’ve not really had any problems! I’m using AEAudioFileWriter in Loopy, which writes to AAC and gives no trouble.

  2. Shikha
    Posted August 25, 2013 at 6:59 pm | Permalink

    Hello Michael Tyson,

    I am using your library in my App and I must say it is very nicely written class and very easy to use. So far I am able to convert my pcm file into aac..So for the first time when I come to my view contoller I am able to convert successfully..but I am having a problem at time when I record again and call convert method in my class, it raises the pop up “Couldn’t initialize audio session”…I am not able to solve this..I even try to set Audio session Active , but nothing happens ,same error again. Please help me solve this..Any help is highly appreciated..Thanks..waiting for your reply.

  3. Manish
    Posted September 4, 2013 at 12:59 pm | Permalink

    Hi Michael, At first i would like to thank you for sharing such a wonderful code, i use this earlier and its working fine till now but as i upgrade to ios5.1.1 it started giving me error on device “Couldn’t setup intermediate conversion format”. On simulator it works Can you help me on this to know how can i resolve this issue.I am converting wav to m4a.Sample code too is showing same error so audio session should not be an issue here.

  4. Desmond
    Posted September 19, 2013 at 9:26 am | Permalink

    Hi Michael,

    i have issues with your code once i updated to iOS 7.

    error given “setup session category result 560557673 21696E69 ini!” error given “initialise audio session result 1768843636 696E6974 tini”

    how can i fix it ? and sometimes it works.

    cheers

    des

  5. Jules
    Posted September 25, 2013 at 9:54 pm | Permalink

    Hi Michael,

    Thank you so much for providing this code which I have been using in my app. However, it doesn’t seem to be compatible with iOS7. When I try to initiate a conversion I get ‘Converter not available’.

    Any prospect of an update?

    Many thanks

  6. Jules
    Posted September 25, 2013 at 10:00 pm | Permalink

    Hi Michael, This snippet of code is returning ‘NO’ rather than ‘YES’ like it used to. There are also some deprecated methods used later in the code but it doesn’t make it that far

    if ( encoderDescriptions[i].mSubType == kAudioFormatMPEG4AAC && encoderDescriptions[i].mManufacturer == kAppleHardwareAudioCodecManufacturer ) { available_set = YES; available = YES; return YES; }

  7. Jules
    Posted September 25, 2013 at 10:34 pm | Permalink

    Managed to fix it myself, simply by returning YES at the end of AACConverterAvailable rather than NO. Everything else then works fine. Thanks again.

One Trackback

  1. […] also found someone else’s adaptation of the sample code to make it easier to work […]