fix(ios): Remove flickering due to audio input (#2539)

* Use connection for muting instead of adding/removing outputs to reduce/remove flickering while recording.

* Add a tiny delay before recording starts to prevent the first frame of the video from being underexposed/black.
This commit is contained in:
cristianoccazinsp 2019-10-14 10:37:39 -03:00 committed by Sibelius Seraphini
parent 6b7d7f4a64
commit 127da64382
2 changed files with 79 additions and 49 deletions

View File

@ -16,6 +16,7 @@
@property(nonatomic, strong) dispatch_queue_t sessionQueue;
@property(nonatomic, strong) AVCaptureSession *session;
@property(nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
@property(nonatomic, strong) AVCaptureDeviceInput *audioCaptureDeviceInput;
@property(nonatomic, strong) AVCaptureStillImageOutput *stillImageOutput;
@property(nonatomic, strong) AVCaptureMovieFileOutput *movieFileOutput;
@property(nonatomic, strong) AVCaptureMetadataOutput *metadataOutput;

View File

@ -764,6 +764,13 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
NSInteger orientation = [options[@"orientation"] integerValue];
// some operations will change our config
// so we batch config updates, even if inner calls
// might also call this, only the outermost commit will take effect
// making the camera changes much faster.
[self.session beginConfiguration];
if (_movieFileOutput == nil) {
// At the time of writing AVCaptureMovieFileOutput and AVCaptureVideoDataOutput (> GMVDataOutput)
@ -782,6 +789,7 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
if (self.movieFileOutput == nil || self.movieFileOutput.isRecording || _videoRecordedResolve != nil || _videoRecordedReject != nil) {
[self.session commitConfiguration];
return;
}
@ -809,11 +817,6 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
// only update audio session when mute is not set or set to false, because otherwise there will be a flickering
if ([options valueForKey:@"mute"] == nil || ([options valueForKey:@"mute"] != nil && ![options[@"mute"] boolValue])) {
[self updateSessionAudioIsMuted:NO];
}
AVCaptureConnection *connection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if (self.videoStabilizationMode != 0) {
if (connection.isVideoStabilizationSupported == NO) {
@ -846,6 +849,16 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
}
}
}
// sound recording connection, we can easily turn it on/off without manipulating inputs, this prevents flickering.
AVCaptureConnection *audioConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeAudio];
if ([options valueForKey:@"mute"] == nil || ([options valueForKey:@"mute"] != nil && ![options[@"mute"] boolValue])) {
audioConnection.enabled = YES;
}
else{
audioConnection.enabled = NO;
}
dispatch_async(self.sessionQueue, ^{
@ -863,21 +876,44 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
[connection setVideoMirrored:YES];
}
}
// finally, commit our config changes before starting to record
[self.session commitConfiguration];
// after everything is set, start recording with a tiny delay
// to ensure the camera already has focus and exposure set.
double delayInSeconds = 0.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, self.sessionQueue, ^(void){
// our session might have stopped in between the timeout
// so make sure it is still valid, otherwise, error and cleanup
if(self.movieFileOutput != nil && self.videoCaptureDeviceInput != nil && self.session.isRunning){
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
self.videoRecordedResolve = resolve;
self.videoRecordedReject = reject;
}
else{
reject(@"E_VIDEO_CAPTURE_FAILED", @"Camera is not ready.", nil);
[self cleanupCamera];
}
});
NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:path];
[self.movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
self.videoRecordedResolve = resolve;
self.videoRecordedReject = reject;
});
}
- (void)stopRecording
{
if ([self.movieFileOutput isRecording]) {
[self.movieFileOutput stopRecording];
} else {
RCTLogWarn(@"Video is not recording.");
}
dispatch_async(self.sessionQueue, ^{
if ([self.movieFileOutput isRecording]) {
[self.movieFileOutput stopRecording];
} else {
RCTLogWarn(@"Video is not recording.");
}
});
}
- (void)resumePreview
@ -973,6 +1009,12 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
for (AVCaptureOutput *output in self.session.outputs) {
[self.session removeOutput:output];
}
// clean these up as well since we've removed
// all inputs and outputs from session
self.videoCaptureDeviceInput = nil;
self.audioCaptureDeviceInput = nil;
self.movieFileOutput = nil;
});
}
@ -1053,6 +1095,28 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
else{
RCTLog(@"The selected device does not work with the Preset [%@] or configuration provided", self.session.sessionPreset);
}
// if we have not yet set our audio capture device
// set it. Setting it early will prevent flickering when
// recording a video
if(self.audioCaptureDeviceInput == nil){
AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
if (error || audioDeviceInput == nil) {
RCTLogWarn(@"%s: %@", __func__, error);
}
else{
if ([self.session canAddInput:audioDeviceInput]) {
[self.session addInput:audioDeviceInput];
self.audioCaptureDeviceInput = audioDeviceInput;
}
else{
RCTLog(@"Cannot add audio input");
}
}
}
[self.session commitConfiguration];
});
@ -1089,41 +1153,6 @@ static NSDictionary *defaultFaceDetectorOptions = nil;
#endif
}
- (void)updateSessionAudioIsMuted:(BOOL)isMuted
{
dispatch_async(self.sessionQueue, ^{
[self.session beginConfiguration];
for (AVCaptureDeviceInput* input in [self.session inputs]) {
if ([input.device hasMediaType:AVMediaTypeAudio]) {
if (isMuted) {
[self.session removeInput:input];
}
[self.session commitConfiguration];
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);
[self.session commitConfiguration];
return;
}
if ([self.session canAddInput:audioDeviceInput]) {
[self.session addInput:audioDeviceInput];
}
}
[self.session commitConfiguration];
});
}
- (void)bridgeDidForeground:(NSNotification *)notification
{