Merge pull request #1397 from photomadic/fix/exposure-while-recording

Fix underexposed beginning of video recording when AVCaptureMovieFileOutput is added
This commit is contained in:
Sibelius Seraphini
2018-03-25 11:45:35 -03:00
committed by GitHub
+101 -94
View File
@@ -59,7 +59,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
// selector:@selector(bridgeDidBackground:)
// name:EX_UNVERSIONED(@"EXKernelBridgeDidBackgroundNotification")
// object:self.bridge];
}
return self;
}
@@ -128,7 +128,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
AVCaptureDevice *device = [self.videoCaptureDeviceInput device];
NSError *error = nil;
if (self.flashMode == RNCameraFlashModeTorch) {
if (![device hasTorch])
return;
@@ -176,7 +176,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
}
[device unlockForConfiguration];
}
@@ -184,14 +184,14 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
AVCaptureDevice *device = [self.videoCaptureDeviceInput device];
NSError *error = nil;
if (![device lockForConfiguration:&error]) {
if (error) {
RCTLogError(@"%s: %@", __func__, error);
}
return;
}
if ([device isFocusModeSupported:self.autoFocus]) {
if ([device lockForConfiguration:&error]) {
[device setFocusMode:self.autoFocus];
@@ -201,7 +201,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
}
[device unlockForConfiguration];
}
@@ -209,23 +209,23 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
AVCaptureDevice *device = [self.videoCaptureDeviceInput device];
NSError *error = nil;
if (self.autoFocus < 0 || device.focusMode != RNCameraAutoFocusOff) {
return;
}
if (![device respondsToSelector:@selector(isLockingFocusWithCustomLensPositionSupported)] || ![device isLockingFocusWithCustomLensPositionSupported]) {
RCTLogWarn(@"%s: Setting focusDepth isn't supported for this camera device", __func__);
return;
}
if (![device lockForConfiguration:&error]) {
if (error) {
RCTLogError(@"%s: %@", __func__, error);
}
return;
}
__weak __typeof__(device) weakDevice = device;
[device setFocusModeLockedWithLensPosition:self.focusDepth completionHandler:^(CMTime syncTime) {
[weakDevice unlockForConfiguration];
@@ -235,16 +235,16 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
- (void)updateZoom {
AVCaptureDevice *device = [self.videoCaptureDeviceInput device];
NSError *error = nil;
if (![device lockForConfiguration:&error]) {
if (error) {
RCTLogError(@"%s: %@", __func__, error);
}
return;
}
device.videoZoomFactor = (device.activeFormat.videoMaxZoomFactor - 1.0) * self.zoom + 1.0;
[device unlockForConfiguration];
}
@@ -252,14 +252,14 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
AVCaptureDevice *device = [self.videoCaptureDeviceInput device];
NSError *error = nil;
if (![device lockForConfiguration:&error]) {
if (error) {
RCTLogError(@"%s: %@", __func__, error);
}
return;
}
if (self.whiteBalance == RNCameraWhiteBalanceAuto) {
[device setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
[device unlockForConfiguration];
@@ -280,7 +280,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
}
[device unlockForConfiguration];
}
@@ -313,27 +313,27 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (imageSampleBuffer && !error) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
UIImage *takenImage = [UIImage imageWithData:imageData];
CGRect frame = [_previewLayer metadataOutputRectOfInterestForRect:self.frame];
CGImageRef takenCGImage = takenImage.CGImage;
size_t width = CGImageGetWidth(takenCGImage);
size_t height = CGImageGetHeight(takenCGImage);
CGRect cropRect = CGRectMake(frame.origin.x * width, frame.origin.y * height, frame.size.width * width, frame.size.height * height);
takenImage = [RNImageUtils cropImage:takenImage toRect:cropRect];
if ([options[@"mirrorImage"] boolValue]) {
takenImage = [RNImageUtils mirrorImage:takenImage];
}
if ([options[@"forceUpOrientation"] boolValue]) {
takenImage = [RNImageUtils forceUpOrientation:takenImage];
}
if ([options[@"width"] integerValue]) {
takenImage = [RNImageUtils scaleImage:takenImage toWidth:[options[@"width"] integerValue]];
}
NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
float quality = [options[@"quality"] floatValue];
NSData *takenImageData = UIImageJPEGRepresentation(takenImage, quality);
@@ -341,13 +341,13 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
response[@"uri"] = [RNImageUtils writeImage:takenImageData toPath:path];
response[@"width"] = @(takenImage.size.width);
response[@"height"] = @(takenImage.size.height);
if ([options[@"base64"] boolValue]) {
response[@"base64"] = [takenImageData base64EncodedStringWithOptions:0];
}
if ([options[@"exif"] boolValue]) {
int imageRotation;
switch (takenImage.imageOrientation) {
@@ -370,7 +370,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
[RNImageUtils updatePhotoMetadata:imageSampleBuffer withAdditionalData:@{ @"Orientation": @(imageRotation) } inResponse:response]; // TODO
}
resolve(response);
} else {
reject(@"E_IMAGE_CAPTURE_FAILED", @"Image could not be captured", error);
@@ -389,49 +389,51 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
#endif
[self setupMovieFileCapture];
}
if (self.movieFileOutput != nil && !self.movieFileOutput.isRecording && _videoRecordedResolve == nil && _videoRecordedReject == nil) {
if (options[@"maxDuration"]) {
Float64 maxDuration = [options[@"maxDuration"] floatValue];
self.movieFileOutput.maxRecordedDuration = CMTimeMakeWithSeconds(maxDuration, 30);
}
if (options[@"maxFileSize"]) {
self.movieFileOutput.maxRecordedFileSize = [options[@"maxFileSize"] integerValue];
}
if (options[@"quality"]) {
[self updateSessionPreset:[RNCameraUtils captureSessionPresetForVideoResolution:(RNCameraVideoResolution)[options[@"quality"] integerValue]]];
}
[self updateSessionAudioIsMuted:!!options[@"mute"]];
AVCaptureConnection *connection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:[RNCameraUtils videoOrientationForInterfaceOrientation:[[UIApplication sharedApplication] statusBarOrientation]]];
if (options[@"codec"]) {
AVVideoCodecType videoCodecType = options[@"codec"];
if (@available(iOS 10, *)) {
if ([self.movieFileOutput.availableVideoCodecTypes containsObject:videoCodecType]) {
[self.movieFileOutput setOutputSettings:@{AVVideoCodecKey:videoCodecType} forConnection:connection];
self.videoCodecType = videoCodecType;
} else {
RCTLogWarn(@"%s: Video Codec '%@' is not supported on this device.", __func__, videoCodecType);
}
} else {
RCTLogWarn(@"%s: Setting videoCodec is only supported above iOS version 10.", __func__);
}
}
dispatch_async(self.sessionQueue, ^{
[self updateFlashMode];
NSString *path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingString:@"Camera"] withExtension:@".mov"];
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
self.videoRecordedResolve = resolve;
self.videoRecordedReject = reject;
});
if (self.movieFileOutput == nil || self.movieFileOutput.isRecording || _videoRecordedResolve != nil || _videoRecordedReject != nil) {
return;
}
if (options[@"maxDuration"]) {
Float64 maxDuration = [options[@"maxDuration"] floatValue];
self.movieFileOutput.maxRecordedDuration = CMTimeMakeWithSeconds(maxDuration, 30);
}
if (options[@"maxFileSize"]) {
self.movieFileOutput.maxRecordedFileSize = [options[@"maxFileSize"] integerValue];
}
if (options[@"quality"]) {
[self updateSessionPreset:[RNCameraUtils captureSessionPresetForVideoResolution:(RNCameraVideoResolution)[options[@"quality"] integerValue]]];
}
[self updateSessionAudioIsMuted:!!options[@"mute"]];
AVCaptureConnection *connection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:[RNCameraUtils videoOrientationForInterfaceOrientation:[[UIApplication sharedApplication] statusBarOrientation]]];
if (options[@"codec"]) {
AVVideoCodecType videoCodecType = options[@"codec"];
if (@available(iOS 10, *)) {
if ([self.movieFileOutput.availableVideoCodecTypes containsObject:videoCodecType]) {
[self.movieFileOutput setOutputSettings:@{AVVideoCodecKey:videoCodecType} forConnection:connection];
self.videoCodecType = videoCodecType;
} else {
RCTLogWarn(@"%s: Video Codec '%@' is not supported on this device.", __func__, videoCodecType);
}
} else {
RCTLogWarn(@"%s: Setting videoCodec is only supported above iOS version 10.", __func__);
}
}
dispatch_async(self.sessionQueue, ^{
[self updateFlashMode];
NSString *path = [RNFileSystem generatePathInDirectory:[[RNFileSystem cacheDirectoryPath] stringByAppendingString:@"Camera"] withExtension:@".mov"];
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
self.videoRecordedResolve = resolve;
self.videoRecordedReject = reject;
});
}
- (void)stopRecording
@@ -453,7 +455,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
return;
}
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
if ([self.session canAddOutput:stillImageOutput]) {
stillImageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};
@@ -461,12 +463,18 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[stillImageOutput setHighResolutionStillImageOutputEnabled:YES];
self.stillImageOutput = stillImageOutput;
}
#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
[_faceDetectorManager maybeStartFaceDetectionOnSession:_session withPreviewLayer:_previewLayer];
#else
// If AVCaptureVideoDataOutput is not required because of Google Vision
// (see comment in -record), we go ahead and add the AVCaptureMovieFileOutput
// to avoid an exposure rack on some devices that can cause the first few
// frames of the recorded output to be underexposed.
[self setupMovieFileCapture];
#endif
[self setupOrDisableBarcodeScanner];
__weak RNCamera *weakSelf = self;
[self setRuntimeErrorHandlingObserver:
[NSNotificationCenter.defaultCenter addObserverForName:AVCaptureSessionRuntimeErrorNotification object:self.session queue:nil usingBlock:^(NSNotification *note) {
@@ -478,7 +486,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[strongSelf onReady:nil];
});
}]];
[self.session startRunning];
[self onReady:nil];
});
@@ -499,7 +507,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
for (AVCaptureInput *input in self.session.inputs) {
[self.session removeInput:input];
}
for (AVCaptureOutput *output in self.session.outputs) {
[self.session removeOutput:output];
}
@@ -512,7 +520,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
return;
}
__block UIInterfaceOrientation interfaceOrientation;
void (^statusBlock)() = ^() {
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
};
@@ -521,24 +529,24 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
} else {
dispatch_sync(dispatch_get_main_queue(), statusBlock);
}
AVCaptureVideoOrientation orientation = [RNCameraUtils videoOrientationForInterfaceOrientation:interfaceOrientation];
dispatch_async(self.sessionQueue, ^{
[self.session beginConfiguration];
NSError *error = nil;
AVCaptureDevice *captureDevice = [RNCameraUtils deviceWithMediaType:AVMediaTypeVideo preferringPosition:self.presetCamera];
AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
if (error || captureDeviceInput == nil) {
RCTLog(@"%s: %@", __func__, error);
return;
}
[self.session removeInput:self.videoCaptureDeviceInput];
if ([self.session canAddInput:captureDeviceInput]) {
[self.session addInput:captureDeviceInput];
self.videoCaptureDeviceInput = captureDeviceInput;
[self updateFlashMode];
[self updateZoom];
@@ -548,7 +556,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[self.previewLayer.connection setVideoOrientation:orientation];
[self _updateMetadataObjectsToRecognize];
}
[self.session commitConfiguration];
});
}
@@ -574,7 +582,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
dispatch_async(self.sessionQueue, ^{
[self.session beginConfiguration];
for (AVCaptureDeviceInput* input in [self.session inputs]) {
if ([input.device hasMediaType:AVMediaTypeAudio]) {
if (isMuted) {
@@ -584,30 +592,30 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
return;
}
}
if (!isMuted) {
NSError *error = nil;
AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
if (error || audioDeviceInput == nil) {
RCTLogWarn(@"%s: %@", __func__, error);
return;
}
if ([self.session canAddInput:audioDeviceInput]) {
[self.session addInput:audioDeviceInput];
}
}
[self.session commitConfiguration];
});
}
- (void)bridgeDidForeground:(NSNotification *)notification
{
if (![self.session isRunning] && [self isSessionPaused]) {
self.paused = NO;
dispatch_async( self.sessionQueue, ^{
@@ -672,17 +680,17 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
if (_metadataOutput == nil) {
return;
}
NSArray<AVMetadataObjectType> *availableRequestedObjectTypes = [[NSArray alloc] init];
NSArray<AVMetadataObjectType> *requestedObjectTypes = [NSArray arrayWithArray:self.barCodeTypes];
NSArray<AVMetadataObjectType> *availableObjectTypes = _metadataOutput.availableMetadataObjectTypes;
for(AVMetadataObjectType objectType in requestedObjectTypes) {
if ([availableObjectTypes containsObject:objectType]) {
availableRequestedObjectTypes = [availableRequestedObjectTypes arrayByAddingObject:objectType];
}
}
[_metadataOutput setMetadataObjectTypes:availableRequestedObjectTypes];
}
@@ -694,12 +702,12 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
AVMetadataMachineReadableCodeObject *codeMetadata = (AVMetadataMachineReadableCodeObject *) metadata;
for (id barcodeType in self.barCodeTypes) {
if ([metadata.type isEqualToString:barcodeType]) {
NSDictionary *event = @{
@"type" : codeMetadata.type,
@"data" : codeMetadata.stringValue
};
[self onCodeRead:event];
}
}
@@ -712,7 +720,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
- (void)setupMovieFileCapture
{
AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([self.session canAddOutput:movieFileOutput]) {
[self.session addOutput:movieFileOutput];
self.movieFileOutput = movieFileOutput;
@@ -750,9 +758,9 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
self.videoRecordedReject = nil;
self.videoCodecType = nil;
#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
[self cleanupMovieFileCapture];
#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
// If face detection has been running prior to recording to file
// we reenable it here (see comment in -record).
[_faceDetectorManager maybeStartFaceDetectionOnSession:_session withPreviewLayer:_previewLayer];
@@ -769,7 +777,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
{
Class faceDetectorManagerClass = NSClassFromString(@"RNFaceDetectorManager");
Class faceDetectorManagerStubClass = NSClassFromString(@"RNFaceDetectorManagerStub");
#if __has_include(<GoogleMobileVision/GoogleMobileVision.h>)
if (faceDetectorManagerClass) {
return [[faceDetectorManagerClass alloc] initWithSessionQueue:_sessionQueue delegate:self];
@@ -792,4 +800,3 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
@end