0
votes

My app has an audio recording callback which is called by the RemoteIO AudioUnit framework. The AudioUnit calls the callback in a thread different from the main thread, so it does not have an autorelease pool. In this callback I do the following:

  • Alloc a buffer for the recorded samples.
  • Call AudioUnitRender to fill up this buffer.
  • Open a file for logging using freopen.
  • Call an audio processing method.
  • Depending on the audio, update the UI on the main thread, using performSelectorOnMainThread (sent to the view controller).

In addition, I have a function called testFilter() that does some benchmarking, which I call in the view controller's viewDidLoad, before the audio session is initialized, hence before the audio callback is called for the first time. In this function I allocate a buffer (using malloc/free) and call the same audio processing method I mentioned above.

Now, the problem is (on the device, not the simulator):

  • If I comment out the call to testFilter(), I don't get any memory-leak-related messages.
  • If I do call testFilter(), I start getting a bunch of messages from within the audio callback (The first 5 messages are my logs from the testFilter() method):

2011-01-20 23:05:10.358 TimeKeeper[389:307] initializing buffer...

2011-01-20 23:05:10.693 TimeKeeper[389:307] done...

2011-01-20 23:05:10.696 TimeKeeper[389:307] processing buffer...

2011-01-20 23:05:15.772 TimeKeeper[389:307] done...

2011-01-20 23:05:15.775 TimeKeeper[389:307] elapsed time 5.073843

2011-01-20 23:05:16.319 TimeKeeper[389:660f] * __NSAutoreleaseNoPool(): Object 0x137330 of class __NSCFData autoreleased with no pool in place - just leaking

2011-01-20 23:05:16.327 TimeKeeper[389:660f] * __NSAutoreleaseNoPool(): Object 0x1373a0 of class __NSCFData autoreleased with no pool in place - just leaking

and so on. The callback has a different thread, as can be seen in the log.

How come these warnings only happen if I call a function which is called and finished before the audio session is even initialized? How can I detect the leak?

Appendix

The relevant methods:

void testFilter () {
#if TARGET_IPHONE_SIMULATOR == 0
    freopen([@"/tmp/console.log" cStringUsingEncoding:NSASCIIStringEncoding],"a",stderr);
#endif
    int bufSize = 2048;
    int numsec=100;
    OnsetDetector * onsetDetector = [[OnsetDetector alloc] init];
    AudioSampleDataType *buffer = (AudioSampleDataType*) malloc (44100*numsec * sizeof(AudioSampleDataType)); // numsec seconds of audio @44100

    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = buffer;
    bufferList.mBuffers[0].mDataByteSize = sizeof (AudioSampleDataType) * bufSize;
    bufferList.mBuffers[0].mNumberChannels = 1; 

    //--- init buffer
    NSLog(@"\n\n---***---***---");
    NSLog(@"initializing buffer...");
    for (int i = 0; i < 44100*numsec; ++i) {
        *(buffer+i) = (AudioSampleDataType)rand();
    }
    NSLog(@"done...");

    NSLog(@"processing buffer...");
    CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent();
    for (int i = 0; (i+1)*bufSize < 44100*numsec; ++i) {
        bufferList.mBuffers[0].mData = buffer + i * bufSize;
        [onsetDetector process:&bufferList];
    }   
    CFAbsoluteTime t1 = CFAbsoluteTimeGetCurrent();
    NSLog(@"done...");
    NSLog(@"elapsed time %1.6f",(double)(t1-t0));
    free(buffer);
    [onsetDetector release];
}

And:

OSStatus recordingCallback (void *inRefCon, 
                            AudioUnitRenderActionFlags *ioActionFlags, 
                            const AudioTimeStamp *inTimeStamp, 
                            UInt32 inBusNumber, 
                            UInt32 inNumberFrames, 
                            AudioBufferList *ioData) {

    AudioBufferList bufferList;

    // redundant
    SInt16 *buffer = (SInt16 *)malloc (sizeof (AudioSampleDataType) * inNumberFrames);
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mData = buffer;
    bufferList.mBuffers[0].mDataByteSize = sizeof (AudioSampleDataType) * inNumberFrames;
    bufferList.mBuffers[0].mNumberChannels = 1;

    ioData = &bufferList;

    // Obtain recorded samples  
    OSStatus status;

    MainViewController  *mainViewController = (MainViewController *)inRefCon;
    AudioManager        *audioManager       = [mainViewController audioManager];
    SampleManager       *sampleManager      = [audioManager sampleManager];

    status = AudioUnitRender([audioManager audioUnit], 
                             ioActionFlags, 
                             inTimeStamp, 
                             inBusNumber, //1
                             inNumberFrames, 
                             ioData);

#if TARGET_IPHONE_SIMULATOR == 0
    freopen([@"/tmp/console.log" cStringUsingEncoding:NSASCIIStringEncoding],"a",stdout);
#endif

    // send to onset detector
    SInt32 onset = [[audioManager onsetDetector] process:ioData];
    if (onset > 0) {
        NSLog(@"onset - %ld\n", sampleManager.recCnt + onset);

        //--- updating the UI - must be done on main thread
        [mainViewController performSelectorOnMainThread:@selector(tapButtonPressed) withObject:nil waitUntilDone:NO];
        [mainViewController performSelectorOnMainThread:@selector(onsetLedOn) withObject:nil waitUntilDone:NO];
    }

    sampleManager.recCnt += inNumberFrames; 

    free(buffer);
    return noErr;
}
2

2 Answers

2
votes

The "just leaking" error message is what you get where your method (or some other method your method calls) tries to autorelease an object when there is no autorelease pool in place. Without a pool, there is nothing to keep track of which objects need releasing, and therefore they just leak.

On the main thread, such a pool is automatically created and managed for you, but on secondary threads you have to do it yourself.

What you need to do is alter your functions to allocate a pool:

void myFunction() {
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  

   // do stuff here

   [pool release];
}

And this will make the error messages go away, and fix the leaks.

0
votes

Seems the problem was caused by freopen not followed by fclose. I am passing stderr as a 3rd argument to freopen, so the log messages are redirected to a file. If I call fclose at the end of the callback, no memory-leak errors appear.

Some questions still need an answer though:

  • Why using autorelease pool did not solve the problem? I guess it is because freopen is a C function, so no autorelease messages are sent anyway.
  • The only situation in which the error messages are thrown is:
    • freopen is used in a function on the main thread, without fclose
    • freopen is used in an audio callback on a different thread, without fclose.

That is, if freopen is used only in the audio callback thread (and not on the main thread), no errors appear.

Edit

Another weird behavior is that even if do use fclose in both threads, but I pass stdout on the main thread and stderr in the audio callback thread, I get the error messages. I think it's got something to do with the way NSLog is called when there is more then one thread. Any insights on this are welcomed.