I have a small project that reads an HTTP stream from a remote server, demuxes it, extracts audio stream, decodes it into 16-bit PCM, and feeds into a corresponding AudioQueue. The decoder/demuxer/fetcher runs in a separate thread and it uses my home-grown blocking queue (see code below) to deliver the decoded frames to the AudioQueue callback. The queue uses NSMutableArray to store objects.
Once this thing is in-flight, it leaks objects inserted into the queue. Memory profiler says that the RefCt is 2 by the time I expect it to be 0 and to be released by ARC.
Here are the queue/dequeue methods:
- (id) dequeue {
dispatch_semaphore_wait(objectsReady, DISPATCH_TIME_FOREVER);
[lock lock];
id anObject = [queue objectAtIndex:0];
[queue removeObjectAtIndex:0];
[lock unlock];
dispatch_semaphore_signal(freeSlots);
return anObject;
}
- (void) enqueue:(id)element {
dispatch_semaphore_wait(freeSlots, DISPATCH_TIME_FOREVER);
[lock lock];
[queue addObject:element];
[lock unlock];
dispatch_semaphore_signal(objectsReady);
}
Producer thread does this:
[pAudioFrameQueue enqueue:[self convertAVFrameAudioToPcm:audioFrame]];
And the "convertAVFrameAudioToPcm" methods looks like this:
- (NSData*) convertAVFrameAudioToPcm:(AVFrame*)frame {
NSData* ret = nil;
int16_t* outputBuffer = malloc(outputByteLen);
// decode into outputBuffer and other stuff
ret = [NSData dataWithBytes:outputBuffer length:outputByteLen];
free(outputBuffer);
return ret;
}
Consumer does this:
- (void) fillAvailableAppleAudioBuffer:(AudioQueueBufferRef)bufferToFill {
@autoreleasepool {
NSData* nextAudioBuffer = [pAudioFrameQueue dequeue];
if (nextAudioBuffer != nil) {
[nextAudioBuffer getBytes:bufferToFill->mAudioData]; // I know this is not safe
bufferToFill->mAudioDataByteSize = nextAudioBuffer.length;
} else {
NSLog(@"ERR: End of stream...");
}
}
}
To me it looks like RefCt should become 0 when fillAvailableAppleAudioBuffer exits, but apparently ARC disagrees and does not release the object.
Am I having a bug in my simple queue code?
Or do I instantiate NSData in a wrong way?
Or am I missing some special rule of how ARC works between threads? By the way, the producer threads starts like this:
- (BOOL) startFrameFetcher {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL),
^(void) {
[self frameFetcherThread];
});
return YES;
}
Any hints will be much appreciated!
PS: and last but not the least, I do have another instance of the same blocking queue that stores video frames that I dequeue and show via NSTimer. Video frames do not leak! I am guessing this may have something to do with threading. Otherwise, I would have expected to see the leak in both queues.