// Copyright 2019 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "MPPCameraInputSource.h" #import @interface MPPCameraInputSource () @end @implementation MPPCameraInputSource { AVCaptureSession* _session; AVCaptureDeviceInput* _videoDeviceInput; AVCaptureVideoDataOutput* _videoDataOutput; AVCaptureDepthDataOutput* _depthDataOutput API_AVAILABLE(ios(11.0)); AVCaptureDevice* _currentDevice; matrix_float3x3 _cameraIntrinsicMatrix; OSType _pixelFormatType; BOOL _autoRotateBuffers; BOOL _didReadCameraIntrinsicMatrix; BOOL _setupDone; BOOL _useDepth; BOOL _useCustomOrientation; BOOL _videoMirrored; } - (instancetype)init { self = [super init]; if (self) { _cameraPosition = AVCaptureDevicePositionBack; _session = [[AVCaptureSession alloc] init]; _pixelFormatType = kCVPixelFormatType_32BGRA; AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; _authorized = status == AVAuthorizationStatusAuthorized; } return self; } - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue { [super setDelegate:delegate queue:queue]; // Note that _depthDataOutput and _videoDataOutput may not have been created yet. In that case, // this message to nil is ignored, and the delegate will be set later by setupCamera. [_videoDataOutput setSampleBufferDelegate:self queue:queue]; [_depthDataOutput setDelegate:self callbackQueue:queue]; } - (void)start { if (!_setupDone) [self setupCamera]; if (_autoRotateBuffers) { [self enableAutoRotateBufferObserver:YES]; } [_session startRunning]; } - (void)stop { if (_autoRotateBuffers) { [self enableAutoRotateBufferObserver:NO]; } [_session stopRunning]; } - (BOOL)isRunning { return _session.isRunning; } - (void)setCameraPosition:(AVCaptureDevicePosition)cameraPosition { BOOL wasRunning = [self isRunning]; if (wasRunning) { [self stop]; } _cameraPosition = cameraPosition; _setupDone = NO; if (wasRunning) { [self start]; } } - (void)setUseDepth:(BOOL)useDepth { if (useDepth == _useDepth) { return; } BOOL wasRunning = [self isRunning]; if (wasRunning) { [self stop]; } _useDepth = useDepth; _setupDone = NO; if (wasRunning) { [self start]; } } - (void)setOrientation:(AVCaptureVideoOrientation)orientation { if (orientation == _orientation) { return; } BOOL wasRunning = [self isRunning]; if (wasRunning) { [self stop]; } _orientation = orientation; _useCustomOrientation = YES; _setupDone = NO; if (wasRunning) { [self start]; } } - (void)setVideoMirrored:(BOOL)videoMirrored { if (videoMirrored == _videoMirrored) { return; } BOOL wasRunning = [self isRunning]; if (wasRunning) { [self stop]; } _videoMirrored = videoMirrored; _setupDone = NO; if (wasRunning) { [self start]; } } - (void)setAutoRotateBuffers:(BOOL)autoRotateBuffers { if (autoRotateBuffers == _autoRotateBuffers) { return; // State has not changed. } _autoRotateBuffers = autoRotateBuffers; if ([self isRunning]) { // Enable or disable observer this settings changes while this input source is running. [self enableAutoRotateBufferObserver:_autoRotateBuffers]; } } - (void)enableAutoRotateBufferObserver:(BOOL)enable { if (enable) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil]; // Trigger a device orientation change instead of waiting for the first change. [self deviceOrientationChanged]; } else { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; } } - (OSType)pixelFormatType { return _pixelFormatType; } - (void)setPixelFormatType:(OSType)pixelFormatType { _pixelFormatType = pixelFormatType; if ([self isRunning]) { _videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : @(_pixelFormatType)}; } } #pragma mark - Camera-specific methods - (NSString*)sessionPreset { return _session.sessionPreset; } - (void)setSessionPreset:(NSString*)sessionPreset { _session.sessionPreset = sessionPreset; } - (void)setupCamera { NSError* error = nil; if (_videoDeviceInput) { [_session removeInput:_videoDeviceInput]; } AVCaptureDeviceType deviceType = AVCaptureDeviceTypeBuiltInWideAngleCamera; if (@available(iOS 11.1, *)) { if (_cameraPosition == AVCaptureDevicePositionFront && _useDepth) { deviceType = AVCaptureDeviceTypeBuiltInTrueDepthCamera; } } AVCaptureDeviceDiscoverySession* deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[ deviceType ] mediaType:AVMediaTypeVideo position:_cameraPosition]; AVCaptureDevice* videoDevice = [deviceDiscoverySession devices] ? [deviceDiscoverySession devices].firstObject : [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; _videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; if (error) { NSLog(@"%@", error); return; } [_session addInput:_videoDeviceInput]; if (!_videoDataOutput) { _videoDataOutput = [[AVCaptureVideoDataOutput alloc] init]; [_session addOutput:_videoDataOutput]; // Set this when we have a handler. if (self.delegateQueue) [_videoDataOutput setSampleBufferDelegate:self queue:self.delegateQueue]; _videoDataOutput.alwaysDiscardsLateVideoFrames = YES; // Only a few pixel formats are available for capture output: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, // kCVPixelFormatType_32BGRA. _videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : @(_pixelFormatType)}; } // Remove Old Depth Depth if (_depthDataOutput) { [_session removeOutput:_depthDataOutput]; } if (@available(iOS 11.1, *)) { if (_useDepth) { // Add Depth Output _depthDataOutput = [[AVCaptureDepthDataOutput alloc] init]; _depthDataOutput.alwaysDiscardsLateDepthData = YES; if ([_session canAddOutput:_depthDataOutput]) { [_session addOutput:_depthDataOutput]; AVCaptureConnection* __unused connection = [_depthDataOutput connectionWithMediaType:AVMediaTypeDepthData]; // Set this when we have a handler. if (self.delegateQueue) { [_depthDataOutput setDelegate:self callbackQueue:self.delegateQueue]; } } else { _depthDataOutput = nil; } } } if (_useCustomOrientation) { AVCaptureConnection* connection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; connection.videoOrientation = _orientation; } if (@available(iOS 11.0, *)) { AVCaptureConnection* connection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; if ([connection isCameraIntrinsicMatrixDeliverySupported]) { [connection setCameraIntrinsicMatrixDeliveryEnabled:YES]; } } if (_videoMirrored) { AVCaptureConnection* connection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; connection.videoMirrored = _videoMirrored; } _setupDone = YES; } - (void)requestCameraAccessWithCompletionHandler:(void (^)(BOOL))handler { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { _authorized = granted; if (handler) { handler(granted); } }]; } #pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate methods // Receives frames from the camera. Invoked on self.frameHandlerQueue. - (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection { if (@available(iOS 11.0, *)) { if (!_didReadCameraIntrinsicMatrix) { // Get camera intrinsic matrix. CFTypeRef cameraIntrinsicData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil); if (cameraIntrinsicData != nil) { CFDataRef cfdr = (CFDataRef)cameraIntrinsicData; matrix_float3x3* intrinsicMatrix = (matrix_float3x3*)(CFDataGetBytePtr(cfdr)); if (intrinsicMatrix != nil) { _cameraIntrinsicMatrix = *intrinsicMatrix; } } _didReadCameraIntrinsicMatrix = YES; } } CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); if ([self.delegate respondsToSelector:@selector(processVideoFrame:timestamp:fromSource:)]) { [self.delegate processVideoFrame:imageBuffer timestamp:timestamp fromSource:self]; } else if ([self.delegate respondsToSelector:@selector(processVideoFrame:fromSource:)]) { [self.delegate processVideoFrame:imageBuffer fromSource:self]; } } #pragma mark - AVCaptureDepthDataOutputDelegate methods // Receives depth frames from the camera. Invoked on self.frameHandlerQueue. - (void)depthDataOutput:(AVCaptureDepthDataOutput*)output didOutputDepthData:(AVDepthData*)depthData timestamp:(CMTime)timestamp connection:(AVCaptureConnection*)connection API_AVAILABLE(ios(11.0)) { if (depthData.depthDataType != kCVPixelFormatType_DepthFloat32) { depthData = [depthData depthDataByConvertingToDepthDataType:kCVPixelFormatType_DepthFloat32]; } [self.delegate processDepthData:depthData timestamp:timestamp fromSource:self]; } #pragma mark - NSNotificationCenter event handlers - (void)deviceOrientationChanged { AVCaptureConnection* connection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; connection.videoOrientation = (AVCaptureVideoOrientation)[UIDevice currentDevice].orientation; } @end