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 thetestFilter()
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;
}