Why does switching cameras stop my AVCaptureSession to MovieFileOutput? - ios

The iOS documentation says you can add and remove inputs while a session is running, for example to switch between front and back cameras.
However when I try this, my session stops. I'm locking the session with beginConfiguration and commitConfiguration calls as follows:
- (void)switchCamera:(UIButton *)sender {
dispatch_async([self sessionQueue], ^{
AVCaptureSession *session = self.captureSession;
[session beginConfiguration];
AVCaptureInput *currentInput = self.currentCameraIsBack ? self.videoDeviceInputBack : self.videoDeviceInputFront;
AVCaptureInput *newInput = self.currentCameraIsBack ? self.videoDeviceInputFront : self.videoDeviceInputBack;
[session removeInput:currentInput];
[session addInput:newInput];
self.currentCameraIsBack = !self.currentCameraIsBack;
[session setSessionPreset:AVCaptureSessionPresetMedium];
[self setCameraOutputProperties];
[session commitConfiguration];
});
}
I am outputting to an AVCaptureMovieFileOutput. Is there anything I need to do to configure this session so it is switchable?
(Note that the OP in this question is trying to add a new input without removing the old one, which isn't the problem here)

Turns out that to do this you need to use an AVAssetWriter and call appendSampleBuffer: yourself with the resulting CMSampleBuffer like so:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
if (self.recording && self.videoWriterInput.isReadyForMoreMediaData) {
[self.videoWriterInput appendSampleBuffer:sampleBuffer];
}
}

Related

Method captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection only called a few times

I'm capturing audio from external bluetooth microphone. But I can't record anything.
This method is only called one time, at the beginning of the current AvCaptureSession.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
After that I never get called this method for process the audio.
For instantiate the capture session I do this:
self.captureSession.usesApplicationAudioSession = true;
self.captureSession.automaticallyConfiguresApplicationAudioSession = true;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
/* Audio */
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
audioIn = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:nil];
if ( [_captureSession canAddInput:audioIn] ) {
[_captureSession addInput:audioIn];
}
[audioIn release];
audioOut = [[AVCaptureAudioDataOutput alloc] init];
// Put audio on its own queue to ensure that our video processing doesn't cause us to drop audio
dispatch_queue_t audioCaptureQueue = dispatch_queue_create( "com.apple.sample.capturepipeline.audio", DISPATCH_QUEUE_SERIAL );
[audioOut setSampleBufferDelegate:self queue:audioCaptureQueue];
[audioCaptureQueue release];
if ( [self.captureSession canAddOutput:audioOut] ) {
[self.captureSession addOutput:audioOut];
}
_audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];
[audioOut release];
If I use another bluetooth device is always working, but not with this one.
I thought this device could be faulty, but actually is working in another apps to record audio.
Is really strange the problem. Anyone knows what could be happening?
Thanks!

AVCaptureVideoDataOutput on iOS 8 does not post sample buffers on the specified dispatch queue

When using AVCaptureVideoDataOutput and defining a sample buffer delegate with a dispatch queue (setSampleBufferDelegate:queue), we are experiencing on iOS 8 that AVFoundation does not post the sample buffers on the specified dispatch queue but rather always uses "com.apple.avfoundation.videodataoutput.bufferqueue".
This works as expected on iOS7.
Has anyone else experienced this?
An obvious workaround is to manually call dispatch_sync in the callback to sync processing to the custom dispatch queue, but this, strangely, causes a deadlock...
Sample code that produces this issue:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:captureVideoPreviewLayer];
[session addInput:[AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:nil]];
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
queue = dispatch_queue_create("our.dispatch.queue", DISPATCH_QUEUE_SERIAL);
[output setSampleBufferDelegate:self queue:queue];
[session addOutput:output];
[session startRunning];
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
NSLog(#"Running on queue %#, queue that was set is %#, this is %s", dispatch_get_current_queue(),
[captureOutput performSelector:#selector(sampleBufferCallbackQueue)],
queue == dispatch_get_current_queue() ? "our queue" : "not our queue!!!");
}
What's probably happening here is that their queue, com.apple.avfoundation.videodataoutput.bufferqueue, has been set to target yours using dispatch_set_target_queue. This is functionally equivalent to dispatching to your queue, but would explain the name, and would also explain the deadlock when you tried to dispatch back to your queue.
In other words, just because the queue name isn't equal to your queue's name doesn't mean the block isn't executing on your queue.
To get around this issue, I had to modify my -captureOutput::
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
dispatch_queue_t queue = ((MyAppDelegate *)UIApplication.sharedApplication.delegate).videoDataOutputQueue;
CFRetain(sampleBuffer);
dispatch_async(queue, ^{
for (id<AVCaptureVideoDataOutputSampleBufferDelegate> target in captureTargets.copy)
[target captureOutput:captureOutput didOutputSampleBuffer:sampleBuffer fromConnection:connection];
CFRelease(sampleBuffer);
});
}

Delayed background color and label updating but NSLog works fine

I am writing a program that reads a QR code and then uses the data collected. I am having a strange problem though. I believe it may have to do something with threads. Basically all the NSLogs in the if statement work fine and print rapidly every second yet when i try to update a label text or change the background color it takes ~40 seconds. I don't understand why. Here is the code.
#import "VerifyPassViewController.h"
#import "NewPassViewController.h"
#interface VerifyPassViewController ()
-(BOOL)startReading;
-(void)stopReading;
-(void)loadBeepSound;
#end
#implementation VerifyPassViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Initially make the captureSession object nil.
_captureSession = nil;
// Set the initial value of the flag to NO.
_isReading = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - IBAction method implementation
- (IBAction)startStopReading:(id)sender {
if (!_isReading) {
// This is the case where the app should read a QR code when the start button is tapped.
if ([self startReading]) {
// If the startReading methods returns YES and the capture session is successfully
// running, then change the start button title and the status message.
[_bbITem setTitle:#"Stop"];
[_bbITem setTitle:#"Scanning for QR Code..."];
}
}
else{
// In this case the app is currently reading a QR code and it should stop doing so.
[self stopReading];
// The bar button item's title should change again.
[_bbITem setTitle:#"Start!"];
}
// Set to the flag the exact opposite value of the one that currently has.
_isReading = !_isReading;
}
#pragma mark - Private method implementation
- (BOOL)startReading {
NSError *error;
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video
// as the media type parameter.
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
if (!input) {
// If any error occurs, simply log the description of it and don't continue any more.
NSLog(#"%#", [error localizedDescription]);
return NO;
}
// Initialize the captureSession object.
_captureSession = [[AVCaptureSession alloc] init];
// Set the input device on the capture session.
[_captureSession addInput:input];
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[_captureSession addOutput:captureMetadataOutput];
// Create a new serial dispatch queue.
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create("myQueue", NULL);
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
[captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[_videoPreviewLayer setFrame:_viewPreview.layer.bounds];
[_viewPreview.layer addSublayer:_videoPreviewLayer];
// Start video capture.
[_captureSession startRunning];
return YES;
}
-(void)stopReading{
// Stop video capture and make the capture session object nil.
[_captureSession stopRunning];
_captureSession = nil;
// Remove the video preview layer from the viewPreview view's layer.
[_videoPreviewLayer removeFromSuperlayer];
}
#pragma mark - AVCaptureMetadataOutputObjectsDelegate method implementation
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
for(AVMetadataObject *metadataObject in metadataObjects)
{
AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)metadataObject;
if([metadataObject.type isEqualToString:AVMetadataObjectTypeQRCode])
{
NSLog(#"QR Code = %#", readableObject.stringValue);
self.view.backgroundColor = [UIColor greenColor];
NSLog(#"If the background isnt green i might throw up");
}
else if ([metadataObject.type isEqualToString:AVMetadataObjectTypeEAN13Code])
{
NSLog(#"EAN 13 = %#", readableObject.stringValue);
}
}
}
#end
Let me know if you have any ideas. Thank you
You can only update the UI from the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
... update here your ui ..
});
To state clearly what the other posters hint at: Your code is setting up a dispatch queue that runs on a background thread to process any QR codes it recognizes.
Your captureOutput:didOutputMetadataObjects:fromConnection: method will be run on a background thread.
UI code must be performed on the main thread. rafaperez posted code that you can use from a background thread to dispatch UI changes to a queue on the main thread.
You could also use the method performSelectorOnMainThread:withObject:waitUntilDone: in order to send single method calls to the main thread.

Red status bar shown while switching camera using AVFoundation

I am facing very weird issue while switching between camera. When user switch the camera from front to rear, user can see the red status bar for a second then disappears automatically with slide up animation. I searched a lot on google & stack-overflow but no luck. I found this question , but its related to audio recording. Here is my code
-(void)toggleCameraIsFront:(BOOL)isFront
{
AVCaptureDevicePosition desiredPosition;
if (isFront) {
desiredPosition = AVCaptureDevicePositionFront;
self.videoDeviceType = VideoDeviceTypeFrontCamera;
}
else {
desiredPosition = AVCaptureDevicePositionBack;
self.videoDeviceType = VideoDeviceTypeRearCamera;
}
for (AVCaptureDevice *d in [AVCaptureDevice devicesWithMediaType: AVMediaTypeVideo])
{
if ([d position] == desiredPosition)
{
AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:d error:nil];
[self.session beginConfiguration];
[self.session removeInput:self.videoInput];
if ([self.session canAddInput:videoDeviceInput])
{
[self.session addInput:videoDeviceInput];
[self setVideoInput:videoDeviceInput];
}
else
{
[self.session addInput:self.videoInput];
}
[self.session commitConfiguration];
break;
}
}
}
Also after camera is switched & try to record the video then below method from AVCaptureVideoDataOutputSampleBufferDelegate not getting called.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
Any kind of help is highly appreciated. Thanks.
This red status bar appears due to audio recording, as you've mentioned a question which also describe that this is due to audio recording.
In order to avoid this you need to remove the audio input from AVCaptureSession
[self.captureSession removeInput:audioInput];
where audioInput is AVCaptureDeviceInput object.
Please check #bruno answer for more clarification.

AVCaptureSession in modalviewcontroller on iOS5 with ARC

I'm going insane trying to get an AVCaptureSession (in a view controller) to be presented and dismissed in my project. I'm currently on iOS5.1 and have ARC enabled.
I can get it to work fine the first time I present the viewcontroller and start the session but when I dismiss and present a second time the session will not start. I subscribed to the "AVCaptureSessionRuntimeErrorNotification" notification and receive the following error:
"Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action" UserInfo=0x1a4020 {NSLocalizedRecoverySuggestion=Try again later., NSLocalizedDescription=Cannot Complete Action}"
I'm assuming that something is not being properly released in my session, but with ARC there are no releases and I instead set everything to be released to nil.
my viewDidLoad methods basically just triggers initCamera
initCamera method:
AVCaptureSession *tmpSession = [[AVCaptureSession alloc] init];
session = tmpSession;
session.sessionPreset = AVCaptureSessionPresetMedium;
captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.frame = self.vImagePreview.bounds;
[self.vImagePreview.layer addSublayer:captureVideoPreviewLayer];
rearCamera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
input = [AVCaptureDeviceInput deviceInputWithDevice:rearCamera error:&error];
if (!input) {
// Handle the error appropriately.
NSLog(#"ERROR: trying to open camera: %#", error);
}
[session addInput:input];
videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, nil];
[videoDataOutput setVideoSettings:outputSettings];
[videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
queue = dispatch_queue_create("cameraQueue", DISPATCH_QUEUE_SERIAL);
[videoDataOutput setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
[session addOutput:videoDataOutput];
NSNotificationCenter *notify =
[NSNotificationCenter defaultCenter];
[notify addObserver: self
selector: #selector(onVideoError:)
name: AVCaptureSessionRuntimeErrorNotification
object: session];
[session startRunning];
[rearCamera lockForConfiguration:nil];
rearCamera.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
rearCamera.exposureMode = AVCaptureExposureModeContinuousAutoExposure;
rearCamera.focusMode = AVCaptureFocusModeContinuousAutoFocus;
[rearCamera unlockForConfiguration];
The method
captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
gets called no problem the first time I present the modal viewcontroller, but on the second attempt this method stops getting called (because the session does not start)
For clean up I'm calling stopSession from my parent viewcontroller before dismissing and that does the following:
if ([session isRunning]) {
[session removeInput:input];
[session stopRunning];
[vImagePreview removeFromSuperview];
vImagePreview = nil;
input = nil;
videoDataOutput = nil;
captureVideoPreviewLayer = nil;
session = nil;
queue = nil;
}
I feel like I've tried all sorts of things such as performing a dispatch_sync(queue, ^{}) on the queue to wait for it to be flushed, but that doesn't seem to make a difference (when calling the dispatch_sync I removed the dispatch_release call in my init camera method). I've also tried using the dispatch_set_finalizer_f(queue, capture_cleanup) method suggested in another question but I don't know what needs to actually go in the capture_cleanup method because all of the examples I find are non-ARC code where they call release on pointer to self. I've also combed through all of the sample code I can find from Apple (SquareCam and AVCam) but these are also non-ARC. Any help would be greatly appreciated.
I realized that I was performing a setFocusPointOfInterest on my rear camera and for some reason it was corrupting the session on relaunch. I don't understand why this caused the issue but I will be looking into that.
You might try converting the SquareCam project to ARC before using the source in your program. I was able to do so by using a __bridge cast in the places the converter was complaining, and also replace the "bail:" goto's with simple if statements.

Resources