6

I wrote a loop to encode pcm audio data generated by my app to aac using Extended Audio File Services. The encoding takes place in a background thread synchronously, and not in real-time.

The encoding works flawlessly on ipad 1 and iphone 3gs/4 for both ios 4 and 5. However, for dual-core devices (iphone 4s, ipad 2) the third call to ExtAudioFileWrite crashes the encoding thread with no stack trace and no error code.

Here is the code in question:

The data formats

AudioStreamBasicDescription AUCanonicalASBD(Float64 sampleRate, 
                                        UInt32 channel){
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate         = sampleRate;
audioFormat.mFormatID           = kAudioFormatLinearPCM;
audioFormat.mFormatFlags        = kAudioFormatFlagsAudioUnitCanonical;
audioFormat.mChannelsPerFrame   = channel;
audioFormat.mBytesPerPacket     = sizeof(AudioUnitSampleType);
audioFormat.mBytesPerFrame      = sizeof(AudioUnitSampleType);
audioFormat.mFramesPerPacket    = 1;
audioFormat.mBitsPerChannel     = 8 * sizeof(AudioUnitSampleType);
audioFormat.mReserved           = 0;
return audioFormat;
}

AudioStreamBasicDescription MixdownAAC(void){
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate         = 44100.0;
audioFormat.mFormatID           = kAudioFormatMPEG4AAC;
audioFormat.mFormatFlags        = kMPEG4Object_AAC_Main;
audioFormat.mChannelsPerFrame   = 2;
audioFormat.mBytesPerPacket     = 0;
audioFormat.mBytesPerFrame      = 0;
audioFormat.mFramesPerPacket    = 1024;
audioFormat.mBitsPerChannel     = 0;
audioFormat.mReserved           = 0;
return audioFormat;
}

The render loop

OSStatus err;
ExtAudioFileRef outFile;
NSURL *mixdownURL = [NSURL fileURLWithPath:filePath isDirectory:NO];

// internal data format
AudioStreamBasicDescription localFormat = AUCanonicalASBD(44100.0, 2);

// output file format
AudioStreamBasicDescription mixdownFormat = MixdownAAC();
err = ExtAudioFileCreateWithURL((CFURLRef)mixdownURL,
                             kAudioFileM4AType,
                             &mixdownFormat, 
                             NULL,
                             kAudioFileFlags_EraseFile,
                             &outFile);


err = ExtAudioFileSetProperty(outFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &localFormat);

// prep
AllRenderData *allData = &allRenderData;
writeBuffer = malloc(sizeof(AudioBufferList) + (2*sizeof(AudioBuffer)));
writeBuffer->mNumberBuffers = 2;
writeBuffer->mBuffers[0].mNumberChannels = 1;
writeBuffer->mBuffers[0].mDataByteSize = bufferBytes;
writeBuffer->mBuffers[0].mData = malloc(bufferBytes);
writeBuffer->mBuffers[1].mNumberChannels = 1;
writeBuffer->mBuffers[1].mDataByteSize = bufferBytes;
writeBuffer->mBuffers[1].mData = malloc(bufferBytes);

memset(writeBuffer->mBuffers[0].mData, 0, bufferBytes);
memset(writeBuffer->mBuffers[1].mData, 0, bufferBytes);

UInt32 framesToGet;
UInt32 frameCount = allData->gLoopStartFrame;
UInt32 startFrame = allData->gLoopStartFrame;
UInt32 lastFrame = allData->gLoopEndFrame;

// write one silent buffer
ExtAudioFileWrite(outFile, bufferFrames, writeBuffer);

while (frameCount < lastFrame){

    // how many frames do we need to get
    if (lastFrame - frameCount > bufferFrames)
        framesToGet = bufferFrames;
    else
        framesToGet = lastFrame - frameCount;

    // get dem frames
    err = theBigOlCallback((void*)&allRenderData,
                            NULL, NULL, 1,
                           framesToGet, writeBuffer);

    // write to output file
    ExtAudioFileWrite(outFile, framesToGet, writeBuffer);

    frameCount += framesToGet;
}

// write one trailing silent buffer
memset(writeBuffer->mBuffers[0].mData, 0, bufferBytes);
memset(writeBuffer->mBuffers[1].mData, 0, bufferBytes);
processLimiterInPlace8p24(limiter, writeBuffer->mBuffers[0].mData, writeBuffer->mBuffers[1].mData, bufferFrames);
ExtAudioFileWrite(outFile, bufferFrames, writeBuffer);

err = ExtAudioFileDispose(outFile);

The pcm frames are properly created, but ExtAudioFileWrite fails the 2nd/3rd time it is called.

Any ideas? Thank you!

roperklacks
  • 1,081
  • 11
  • 13
  • Can you post how you fill the buffers and how you are calling ExtAudioFileWrite? – sbooth Jan 05 '12 at 02:27
  • After much frustration and no help from Apple, my co-worker figured out the problem. Apparently on the newer iOS devices (iPad 2 and iPhone 4S), 44.1 kHz is not a valid sample rate for AAC encoding, at least using External Audio File Services. 48 kHz works just fine. I've filed this to Apple as a bug, hopefully they will take care of it. – roperklacks Jan 12 '12 at 04:28

1 Answers1

18

I had a very similar problem where I was attempting to use Extended Audio File Services in order to stream PCM sound into an m4a file on an iPad 2. Everything appeared to work except that every call to ExtAudioFileWrite returned the error code -66567 (kExtAudioFileError_MaxPacketSizeUnknown). The fix I eventually found was to set the "Codec Manufacturer" to software instead of hardware. So place

UInt32 codecManf = kAppleSoftwareAudioCodecManufacturer;
ExtAudioFileSetProperty(FileToWrite, kExtAudioFileProperty_CodecManufacturer, sizeof(UInt32), &codecManf);

just before you set the client data format.

This would lead me to believe that Apple's hardware codecs can only support very specific encoding, but the software codecs can more reliably do what you want. In my case, the software codec translation to m4a takes 50% longer than writing the exact same file to LPCM format.

Does anyone know whether Apple specifies somewhere what their audio codec hardware is capable of? It seems that software engineers are stuck playing the hours-long guessing game of setting the ~20 parameters in the AudioStreamBasicDescription and AudioChannelLayout for the client and for the file to every possible permutation until something works...

jeremywhuff
  • 2,911
  • 3
  • 29
  • 33
  • I spent two days debugging why AAC encoding suddenly stopped working across multiple apps on my iPhone 4S but not my other iOS devices. This did the trick. I have no idea how you figured it out, but much kudos to you and hope you post more on SO. – Michael Chinen May 01 '12 at 01:47
  • I just dug through the Extended Audio File Services reference and tried setting every combination of variables until something worked :P Glad I was able to help! Now if someone could just explain why the hardware capabilities of the devices are so inconsistent... – jeremywhuff May 02 '12 at 02:24
  • Man... big kudos to you. I was wondering what was going so terribly wrong in my code for the ExtAudioFileWrite to crash with no backtrace. Thanks for your help. I have already filled a bug, on this thanks to you. Hopefully it will get merged with the other bug report from roperklacks. Thanks again. – Dan1one Sep 18 '12 at 04:22
  • Funny update, although this appears to solve the issue and our app now builds and run on iPhones and iPads, the new iPad still presents the same issue. Same untraceable crash even with softwareaudiocodecmanufacturer flag on. Weird... – Dan1one Sep 18 '12 at 06:30
  • @Dan1one: I'm experiencing the same issue on the iPhone 5. When capturing audio from my audio unit mixer output and writing it to an AAC file via CAAudioUnitOutputCapturer, the ExtAudioFileWriteAsync() API fails. I previously set the codec manufacturer to use the software codec and this solved the issue on the 4s, but it's back again on the iPhone 5, even with the software codec setting used. Have you found a work-around to this? Thanks! –  Oct 01 '12 at 12:10
  • @Dan1one: Dan, could you provide the Apple Bug Reporter bug number you filed? Apple is looking at this issue for me now and they'd like any other bugs filed that exhibit the same behavior so they can try to reproduce it on the iPhone 5. Thanks! – user1723717 Oct 05 '12 at 17:28
  • Hey @ccocco. Well I dont know, I have managed to make my code work fine on all devices at this stage. it just always require to set either the audio hardware codec or the software depending of the device. That was working. However, due to some project constraints we ended up ditching AAC and began using ALAC, which so far has been performing very good. Compression is not as good, but quality for our purposes probed to be superior, no weird padding at the beginning and at end of the file. – Dan1one Oct 15 '12 at 14:07