在 iOS 个相机中停止相机捕获会话
Stop camera capturing session in iOS camera
我使用 AVCaptureSession, AVCaptureDeviceInput, AVCaptureVideoDataOutput
从 iPhone 相机拍摄图像。
图像捕获实现为
dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
[self setSessionQueue:sessionQueue];
//we will use a separate dispatch session not to block the main queue in processing
dispatch_queue_t im_processingQueue = dispatch_queue_create("im_processing queue", DISPATCH_QUEUE_SERIAL);
[self setIm_processingQueue:im_processingQueue];
dispatch_async(sessionQueue, ^{
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
NSError *error = nil;
AVCaptureDevice *videoDevice = [RecordViewController deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (error)
{
NSLog(@"%@", error);
}
[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] setVideoGravity:AVLayerVideoGravityResizeAspectFill];
if ([session canAddInput:videoDeviceInput])
{
[session addInput:videoDeviceInput];
[self setVideoDeviceInput:videoDeviceInput];
dispatch_async(dispatch_get_main_queue(), ^{
// Why are we dispatching this to the main queue?
// Because AVCaptureVideoPreviewLayer is the backing layer for AVCamPreviewView and UIView can only be manipulated on main thread.
// Note: As an exception to the above rule, it is not necessary to serialize video orientation changes on the AVCaptureVideoPreviewLayer’s connection with other session manipulation.
//[self previewView] layer
[[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] setVideoOrientation:(AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation]];
});
}
AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
if (error)
{
NSLog(@"%@", error);
}
if ([session canAddInput:audioDeviceInput])
{
[session addInput:audioDeviceInput];
}
AVCaptureVideoDataOutput *vid_Output = [[AVCaptureVideoDataOutput alloc] init];
[vid_Output setSampleBufferDelegate:self queue:im_processingQueue];
vid_Output.alwaysDiscardsLateVideoFrames = YES;
// Set the video output to store frame in BGRA (It is supposed to be faster)
NSDictionary* videoSettings = @{(__bridge NSString*)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]};
[vid_Output setVideoSettings:videoSettings];
if ([session canAddOutput:vid_Output])
{
[session addOutput:vid_Output];
AVCaptureConnection *connection = [vid_Output connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoStabilizationSupported])
//[connection setEnablesVideoStabilizationWhenAvailable:YES];
connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
[self setVid_Output:vid_Output];
}
});
在 viewWillAppear
内,捕获会话 运行 为
- (void)viewWillAppear:(BOOL)animated
{
//[super viewWillAppear:YES];
dispatch_async([self sessionQueue], ^{
[self addObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:SessionRunningAndDeviceAuthorizedContext];
[self addObserver:self forKeyPath:@"vid_Output.recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
__weak RecordViewController *weakSelf = self;
[self setRuntimeErrorHandlingObserver:[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification object:[self session] queue:nil usingBlock:^(NSNotification *note) {
RecordViewController *strongSelf = weakSelf;
dispatch_async([strongSelf sessionQueue], ^{
// Manually restarting the session since it must have been stopped due to an error.
[[strongSelf session] startRunning];
});
}]];
[[self session] startRunning];
});
}
然后停止为
- (void) stopCapturingCameraImages
{
dispatch_async([self sessionQueue], ^{
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
});
}
问题在于移除观察者,
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
运行这两个 removeObservers
后程序崩溃了。
有什么问题吗?
编辑:
stopCapturingCameraImages is called from
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
@autoreleasepool {
[_processing Searchobject_using_CPU_CascadeClassifier_for:img with_return_Rect:_rects];
dispatch_async(dispatch_get_main_queue(), ^{
for (int lc = 0; lc <
if(_rects.count >0){
[ self stopCapturingCameraImages];
}
});
}
编辑 1:
根据@SwiftArchitect 的建议,我把if ([[self session] isRunning])
。然后就可以了。
我实施为
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[self stopCapturingCameraImages];
}
- (void) stopCapturingCameraImages
{
dispatch_async([self sessionQueue], ^{
if ([[self session] isRunning]){
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
}
});
}
截止时间:
dispatch_async([self sessionQueue], ^{
// ...
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
});
被执行,self
(UIViewController
)可能已经执行了它的viewWillDisappear
,并删除了观察者。
Whats的执行顺序在dispatch_get_main_queue
和sessionQueue
中执行不一定是你所期望的,甚至是可以预测的。
修复可能很简单,只需在执行 removeObserver
之前添加检查 if [[self session] isRunning]
,而不是添加信号量。
我使用 AVCaptureSession, AVCaptureDeviceInput, AVCaptureVideoDataOutput
从 iPhone 相机拍摄图像。
图像捕获实现为
dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
[self setSessionQueue:sessionQueue];
//we will use a separate dispatch session not to block the main queue in processing
dispatch_queue_t im_processingQueue = dispatch_queue_create("im_processing queue", DISPATCH_QUEUE_SERIAL);
[self setIm_processingQueue:im_processingQueue];
dispatch_async(sessionQueue, ^{
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
NSError *error = nil;
AVCaptureDevice *videoDevice = [RecordViewController deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];
AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (error)
{
NSLog(@"%@", error);
}
[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] setVideoGravity:AVLayerVideoGravityResizeAspectFill];
if ([session canAddInput:videoDeviceInput])
{
[session addInput:videoDeviceInput];
[self setVideoDeviceInput:videoDeviceInput];
dispatch_async(dispatch_get_main_queue(), ^{
// Why are we dispatching this to the main queue?
// Because AVCaptureVideoPreviewLayer is the backing layer for AVCamPreviewView and UIView can only be manipulated on main thread.
// Note: As an exception to the above rule, it is not necessary to serialize video orientation changes on the AVCaptureVideoPreviewLayer’s connection with other session manipulation.
//[self previewView] layer
[[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] setVideoOrientation:(AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation]];
});
}
AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
if (error)
{
NSLog(@"%@", error);
}
if ([session canAddInput:audioDeviceInput])
{
[session addInput:audioDeviceInput];
}
AVCaptureVideoDataOutput *vid_Output = [[AVCaptureVideoDataOutput alloc] init];
[vid_Output setSampleBufferDelegate:self queue:im_processingQueue];
vid_Output.alwaysDiscardsLateVideoFrames = YES;
// Set the video output to store frame in BGRA (It is supposed to be faster)
NSDictionary* videoSettings = @{(__bridge NSString*)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]};
[vid_Output setVideoSettings:videoSettings];
if ([session canAddOutput:vid_Output])
{
[session addOutput:vid_Output];
AVCaptureConnection *connection = [vid_Output connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoStabilizationSupported])
//[connection setEnablesVideoStabilizationWhenAvailable:YES];
connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
[self setVid_Output:vid_Output];
}
});
在 viewWillAppear
内,捕获会话 运行 为
- (void)viewWillAppear:(BOOL)animated
{
//[super viewWillAppear:YES];
dispatch_async([self sessionQueue], ^{
[self addObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:SessionRunningAndDeviceAuthorizedContext];
[self addObserver:self forKeyPath:@"vid_Output.recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
__weak RecordViewController *weakSelf = self;
[self setRuntimeErrorHandlingObserver:[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification object:[self session] queue:nil usingBlock:^(NSNotification *note) {
RecordViewController *strongSelf = weakSelf;
dispatch_async([strongSelf sessionQueue], ^{
// Manually restarting the session since it must have been stopped due to an error.
[[strongSelf session] startRunning];
});
}]];
[[self session] startRunning];
});
}
然后停止为
- (void) stopCapturingCameraImages
{
dispatch_async([self sessionQueue], ^{
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
});
}
问题在于移除观察者,
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
运行这两个 removeObservers
后程序崩溃了。
有什么问题吗?
编辑:
stopCapturingCameraImages is called from
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
@autoreleasepool {
[_processing Searchobject_using_CPU_CascadeClassifier_for:img with_return_Rect:_rects];
dispatch_async(dispatch_get_main_queue(), ^{
for (int lc = 0; lc <
if(_rects.count >0){
[ self stopCapturingCameraImages];
}
});
}
编辑 1:
根据@SwiftArchitect 的建议,我把if ([[self session] isRunning])
。然后就可以了。
我实施为
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:YES];
[self stopCapturingCameraImages];
}
- (void) stopCapturingCameraImages
{
dispatch_async([self sessionQueue], ^{
if ([[self session] isRunning]){
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
}
});
}
截止时间:
dispatch_async([self sessionQueue], ^{
// ...
[self removeObserver:self forKeyPath:@"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:@"vid_Output.recording" context:RecordingContext];
});
被执行,self
(UIViewController
)可能已经执行了它的viewWillDisappear
,并删除了观察者。
Whats的执行顺序在dispatch_get_main_queue
和sessionQueue
中执行不一定是你所期望的,甚至是可以预测的。
修复可能很简单,只需在执行 removeObserver
之前添加检查 if [[self session] isRunning]
,而不是添加信号量。