Search code examples
iosobjective-cgrand-central-dispatchavcapturesession

AVCaptureSession and Camera Threads Not Closing [iOS]


Problem
Threads created during my AVCaptureSession do not close when I stopRunning AVCaptureSession.

Symptoms
Usually my dispatch_queue that gets frames from the camera starts instantly. But after about four times opening and closing the ViewController that opens/closes the AVCaptureSession the dispatch_queue takes about ten seconds to start.

Prognosis
It appears that the threads associated with the AVCaptureSession are not clearing.

After I close the AVCaptureSession I see these threads remain:

com.apple.coremedia.capturesource.connections(serial) 1 Pending Block
com.apple.coremedia.capturesession.connections(serial) 1 Pending Block
<AVCMNotificationDispatcher: 0x16bce00> serial queue(serial) 4 Pending Blocks
com.apple.avfoundation.videocapturedevice.observed_properties_queue(serial)
com.apple.tcc.cache_queue(serial) 1 Pending Block
com.apple.tcc.preflight.kTCCServiceCamera(serial) 1 Pending Block

And after I open/close the ViewController with the AVCaptureSession, the same threads remain but these three threads have increased number of Pending Blocks

<AVCMNotificationDispatcher: 0x17c441a0> serial queue (serial) 9 Pending Blocks
com.apple.avfoundation.videocapturedevice.observed_properties_queue(serial)
com.apple.tcc.preflight.kTCCServiceCamera(serial)  5 Pending Blocks

Code Setup

VideoSource.h and VideoSource.mm

In my ViewController I initialize it like this:

self.videoSource = [[VideoSource alloc] init];
self.videoSource.delegate = self;
[self.videoSource setResolution:AVCaptureSessionPreset352x288]; // was 640
[self.videoSource startWithDevicePosition:AVCaptureDevicePositionFront];

I start and stop the captureSession as follows and it starts and stop just great. The actual frame grabbing works really well.

    [self.videoSource.captureSession startRunning];
    [self.videoSource.captureSession stopRunning];

The relevant parts of the VideoSource, please let me know if you need to see more.

From VideoSource.mm

- (void)dealloc {
NSLog(@"Cleaning Up Video Source");
[_captureSession stopRunning];

AVCaptureInput* input = [_captureSession.inputs objectAtIndex:0];
[_captureSession removeInput:input];
input = nil;

AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[_captureSession.outputs objectAtIndex:0];
[_captureSession removeOutput:output];
output = nil;

_captureSession = nil;
_deviceInput = nil;
_delegate = nil;

//  [super dealloc]; // compiler handles this for you with ARC
}


- (void) addVideoDataOutput {
// (1) Instantiate a new video data output object
AVCaptureVideoDataOutput * captureOutput = [[AVCaptureVideoDataOutput alloc] init ];
//    captureOutput.alwaysDiscardsLateVideoFrames = YES;

NSLog(@"Create Dispatch Queue");


// (2) The sample buffer delegate requires a serial dispatch queue
dispatch_queue_t queue;
queue = dispatch_queue_create("com.name.test", DISPATCH_QUEUE_SERIAL);
[captureOutput setSampleBufferDelegate:self queue:queue];

//    dispatch_release(queue); // compiler handles this for you with ARC

// (3) Define the pixel format for the video data output
NSString * key = (NSString*)kCVPixelBufferPixelFormatTypeKey;
NSNumber * value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];
NSDictionary * settings = @{key:value};

NSLog(@"Set Video Settings");

[captureOutput setVideoSettings:settings];

NSLog(@"Always Discard Late Video Frames");

[captureOutput setAlwaysDiscardsLateVideoFrames:YES];
// (4) Configure the output port on the captureSession property

[self.captureSession addOutput:captureOutput];
}

And from VideoSource.h

@interface VideoSource : NSObject

@property (nonatomic, strong) AVCaptureSession * captureSession;  
@property (nonatomic, strong) AVCaptureDeviceInput * deviceInput;
@property (nonatomic, weak) id<VideoSourceDelegate> delegate;

- (BOOL)startWithDevicePosition:(AVCaptureDevicePosition)devicePosition;
- (void) setResolution:(NSString*)resolution;

@end

Request

How do I make sure these threads close when I deallocate the VideoSource?


Solution

  • Solved it!

    Solution: Call startRunning and stopRunning from the same dispatch_queue as you used for the SampleBuffer queue for captureOutput.

    Here's my new setup:

    #import "VideoSource.h"
    
    @interface VideoSource () <AVCaptureVideoDataOutputSampleBufferDelegate>
    
    // Session management.
    @property (nonatomic) dispatch_queue_t sessionQueue;
    @property (nonatomic) AVCaptureSession *captureSession;
    @property (nonatomic) AVCaptureDeviceInput *deviceInput;
    
    /*@property (nonatomic, strong) AVCaptureSession * captureSession;
    @property (nonatomic, strong) AVCaptureDeviceInput * deviceInput; */
    
    @end
    
    @implementation VideoSource
    
    
    -(id) init{
        if(self = [super init]){
            self.captureSession = [[AVCaptureSession alloc] init];
            self.sessionQueue = dispatch_queue_create( "session queue", DISPATCH_QUEUE_SERIAL );
    
        }
        return self;
    }
    

    Then use that very same sessionQueue for your setSampleBufferDelegate queue.

    [captureOutput setSampleBufferDelegate:self queue:self.sessionQueue];
    

    Now for the most important part, make sure to call startRunning/stopRunning from the very SAME queue:

    dispatch_async( self.sessionQueue, ^{
        [self.captureSession startRunning];
    
    });
    

    Similarly, you can create a nice little function that cleans up and stops the captureSession:

    -(void)closeCaptureSession {
    
         dispatch_async(self.sessionQueue, ^{
    
             if([_captureSession isRunning])[_captureSession stopRunning];
    
             [_captureSession stopRunning];
    
             // Remove all inputs
             for(AVCaptureInput *input1 in _captureSession.inputs) {
                 [_captureSession removeInput:input1];
             }
    
             // Remove all outputs
             for(AVCaptureVideoDataOutput *output1 in _captureSession.outputs) {
                 [output1 setSampleBufferDelegate:nil queue:NULL];
                 [_captureSession removeOutput:output1];
             }
    
             // Set to Nil to make ARC's job a little easier
             self.captureSession = nil;
             self.deviceInput = nil;
             self.delegate = nil;
             self.sessionQueue=nil;
         });
    
    }