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:
parent
6b7d7f4a64
commit
127da64382
@ -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;
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user