1 /* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #if !defined(__has_feature) || !__has_feature(objc_arc) 12 #error "This file requires ARC support." 13 #endif 14 15 #import <UIKit/UIKit.h> 16 17 #import "webrtc/modules/video_capture/ios/device_info_ios_objc.h" 18 #import "webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.h" 19 20 #include "webrtc/system_wrappers/interface/trace.h" 21 22 using namespace webrtc; 23 using namespace webrtc::videocapturemodule; 24 25 @interface RTCVideoCaptureIosObjC (hidden) 26 - (int)changeCaptureInputWithName:(NSString*)captureDeviceName; 27 @end 28 29 @implementation RTCVideoCaptureIosObjC { 30 webrtc::videocapturemodule::VideoCaptureIos* _owner; 31 webrtc::VideoCaptureCapability _capability; 32 AVCaptureSession* _captureSession; 33 int _captureId; 34 AVCaptureConnection* _connection; 35 BOOL _captureChanging; // Guarded by _captureChangingCondition. 36 NSCondition* _captureChangingCondition; 37 } 38 39 @synthesize frameRotation = _framRotation; 40 41 - (id)initWithOwner:(VideoCaptureIos*)owner captureId:(int)captureId { 42 if (self == [super init]) { 43 _owner = owner; 44 _captureId = captureId; 45 _captureSession = [[AVCaptureSession alloc] init]; 46 #if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 47 NSString* version = [[UIDevice currentDevice] systemVersion]; 48 if ([version integerValue] >= 7) { 49 _captureSession.usesApplicationAudioSession = NO; 50 } 51 #endif 52 _captureChanging = NO; 53 _captureChangingCondition = [[NSCondition alloc] init]; 54 55 if (!_captureSession || !_captureChangingCondition) { 56 return nil; 57 } 58 59 // create and configure a new output (using callbacks) 60 AVCaptureVideoDataOutput* captureOutput = 61 [[AVCaptureVideoDataOutput alloc] init]; 62 NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; 63 64 NSNumber* val = [NSNumber 65 numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]; 66 NSDictionary* videoSettings = 67 [NSDictionary dictionaryWithObject:val forKey:key]; 68 captureOutput.videoSettings = videoSettings; 69 70 // add new output 71 if ([_captureSession canAddOutput:captureOutput]) { 72 [_captureSession addOutput:captureOutput]; 73 } else { 74 WEBRTC_TRACE(kTraceError, 75 kTraceVideoCapture, 76 _captureId, 77 "%s:%s:%d Could not add output to AVCaptureSession ", 78 __FILE__, 79 __FUNCTION__, 80 __LINE__); 81 } 82 83 NSNotificationCenter* notify = [NSNotificationCenter defaultCenter]; 84 [notify addObserver:self 85 selector:@selector(onVideoError:) 86 name:AVCaptureSessionRuntimeErrorNotification 87 object:_captureSession]; 88 [notify addObserver:self 89 selector:@selector(statusBarOrientationDidChange:) 90 name:@"StatusBarOrientationDidChange" 91 object:nil]; 92 } 93 94 return self; 95 } 96 97 - (void)directOutputToSelf { 98 [[self currentOutput] 99 setSampleBufferDelegate:self 100 queue:dispatch_get_global_queue( 101 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 102 } 103 104 - (void)directOutputToNil { 105 [[self currentOutput] setSampleBufferDelegate:nil queue:NULL]; 106 } 107 108 - (void)statusBarOrientationDidChange:(NSNotification*)notification { 109 [self setRelativeVideoOrientation]; 110 } 111 112 - (void)dealloc { 113 [[NSNotificationCenter defaultCenter] removeObserver:self]; 114 } 115 116 - (BOOL)setCaptureDeviceByUniqueId:(NSString*)uniqueId { 117 [self waitForCaptureChangeToFinish]; 118 // check to see if the camera is already set 119 if (_captureSession) { 120 NSArray* currentInputs = [NSArray arrayWithArray:[_captureSession inputs]]; 121 if ([currentInputs count] > 0) { 122 AVCaptureDeviceInput* currentInput = [currentInputs objectAtIndex:0]; 123 if ([uniqueId isEqualToString:[currentInput.device localizedName]]) { 124 return YES; 125 } 126 } 127 } 128 129 return [self changeCaptureInputByUniqueId:uniqueId]; 130 } 131 132 - (BOOL)startCaptureWithCapability:(const VideoCaptureCapability&)capability { 133 [self waitForCaptureChangeToFinish]; 134 if (!_captureSession) { 135 return NO; 136 } 137 138 // check limits of the resolution 139 if (capability.maxFPS < 0 || capability.maxFPS > 60) { 140 return NO; 141 } 142 143 if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) { 144 if (capability.width > 1920 || capability.height > 1080) { 145 return NO; 146 } 147 } else if ([_captureSession 148 canSetSessionPreset:AVCaptureSessionPreset1280x720]) { 149 if (capability.width > 1280 || capability.height > 720) { 150 return NO; 151 } 152 } else if ([_captureSession 153 canSetSessionPreset:AVCaptureSessionPreset640x480]) { 154 if (capability.width > 640 || capability.height > 480) { 155 return NO; 156 } 157 } else if ([_captureSession 158 canSetSessionPreset:AVCaptureSessionPreset352x288]) { 159 if (capability.width > 352 || capability.height > 288) { 160 return NO; 161 } 162 } else if (capability.width < 0 || capability.height < 0) { 163 return NO; 164 } 165 166 _capability = capability; 167 168 AVCaptureVideoDataOutput* currentOutput = [self currentOutput]; 169 if (!currentOutput) 170 return NO; 171 172 [self directOutputToSelf]; 173 174 _captureChanging = YES; 175 dispatch_async( 176 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 177 ^(void) { [self startCaptureInBackgroundWithOutput:currentOutput]; }); 178 return YES; 179 } 180 181 - (AVCaptureVideoDataOutput*)currentOutput { 182 return [[_captureSession outputs] firstObject]; 183 } 184 185 - (void)startCaptureInBackgroundWithOutput: 186 (AVCaptureVideoDataOutput*)currentOutput { 187 NSString* captureQuality = 188 [NSString stringWithString:AVCaptureSessionPresetLow]; 189 if (_capability.width >= 1920 || _capability.height >= 1080) { 190 captureQuality = 191 [NSString stringWithString:AVCaptureSessionPreset1920x1080]; 192 } else if (_capability.width >= 1280 || _capability.height >= 720) { 193 captureQuality = [NSString stringWithString:AVCaptureSessionPreset1280x720]; 194 } else if (_capability.width >= 640 || _capability.height >= 480) { 195 captureQuality = [NSString stringWithString:AVCaptureSessionPreset640x480]; 196 } else if (_capability.width >= 352 || _capability.height >= 288) { 197 captureQuality = [NSString stringWithString:AVCaptureSessionPreset352x288]; 198 } 199 200 // begin configuration for the AVCaptureSession 201 [_captureSession beginConfiguration]; 202 203 // picture resolution 204 [_captureSession setSessionPreset:captureQuality]; 205 206 // take care of capture framerate now 207 NSArray* sessionInputs = _captureSession.inputs; 208 AVCaptureDeviceInput* deviceInput = [sessionInputs count] > 0 ? 209 sessionInputs[0] : nil; 210 AVCaptureDevice* inputDevice = deviceInput.device; 211 if (inputDevice) { 212 AVCaptureDeviceFormat* activeFormat = inputDevice.activeFormat; 213 NSArray* supportedRanges = activeFormat.videoSupportedFrameRateRanges; 214 AVFrameRateRange* targetRange = [supportedRanges count] > 0 ? 215 supportedRanges[0] : nil; 216 // Find the largest supported framerate less than capability maxFPS. 217 for (AVFrameRateRange* range in supportedRanges) { 218 if (range.maxFrameRate <= _capability.maxFPS && 219 targetRange.maxFrameRate <= range.maxFrameRate) { 220 targetRange = range; 221 } 222 } 223 if (targetRange && [inputDevice lockForConfiguration:NULL]) { 224 inputDevice.activeVideoMinFrameDuration = targetRange.minFrameDuration; 225 inputDevice.activeVideoMaxFrameDuration = targetRange.minFrameDuration; 226 [inputDevice unlockForConfiguration]; 227 } 228 } 229 230 _connection = [currentOutput connectionWithMediaType:AVMediaTypeVideo]; 231 [self setRelativeVideoOrientation]; 232 233 // finished configuring, commit settings to AVCaptureSession. 234 [_captureSession commitConfiguration]; 235 236 [_captureSession startRunning]; 237 [self signalCaptureChangeEnd]; 238 } 239 240 - (void)setRelativeVideoOrientation { 241 if (!_connection.supportsVideoOrientation) 242 return; 243 switch ([UIApplication sharedApplication].statusBarOrientation) { 244 case UIInterfaceOrientationPortrait: 245 #if defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 246 case UIInterfaceOrientationUnknown: 247 #endif 248 _connection.videoOrientation = AVCaptureVideoOrientationPortrait; 249 break; 250 case UIInterfaceOrientationPortraitUpsideDown: 251 _connection.videoOrientation = 252 AVCaptureVideoOrientationPortraitUpsideDown; 253 break; 254 case UIInterfaceOrientationLandscapeLeft: 255 _connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft; 256 break; 257 case UIInterfaceOrientationLandscapeRight: 258 _connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight; 259 break; 260 } 261 } 262 263 - (void)onVideoError:(NSNotification*)notification { 264 NSLog(@"onVideoError: %@", notification); 265 // TODO(sjlee): make the specific error handling with this notification. 266 WEBRTC_TRACE(kTraceError, 267 kTraceVideoCapture, 268 _captureId, 269 "%s:%s:%d [AVCaptureSession startRunning] error.", 270 __FILE__, 271 __FUNCTION__, 272 __LINE__); 273 } 274 275 - (BOOL)stopCapture { 276 [self waitForCaptureChangeToFinish]; 277 [self directOutputToNil]; 278 279 if (!_captureSession) { 280 return NO; 281 } 282 283 _captureChanging = YES; 284 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 285 ^(void) { [self stopCaptureInBackground]; }); 286 return YES; 287 } 288 289 - (void)stopCaptureInBackground { 290 [_captureSession stopRunning]; 291 [self signalCaptureChangeEnd]; 292 } 293 294 - (BOOL)changeCaptureInputByUniqueId:(NSString*)uniqueId { 295 [self waitForCaptureChangeToFinish]; 296 NSArray* currentInputs = [_captureSession inputs]; 297 // remove current input 298 if ([currentInputs count] > 0) { 299 AVCaptureInput* currentInput = 300 (AVCaptureInput*)[currentInputs objectAtIndex:0]; 301 302 [_captureSession removeInput:currentInput]; 303 } 304 305 // Look for input device with the name requested (as our input param) 306 // get list of available capture devices 307 int captureDeviceCount = [DeviceInfoIosObjC captureDeviceCount]; 308 if (captureDeviceCount <= 0) { 309 return NO; 310 } 311 312 AVCaptureDevice* captureDevice = 313 [DeviceInfoIosObjC captureDeviceForUniqueId:uniqueId]; 314 315 if (!captureDevice) { 316 return NO; 317 } 318 319 // now create capture session input out of AVCaptureDevice 320 NSError* deviceError = nil; 321 AVCaptureDeviceInput* newCaptureInput = 322 [AVCaptureDeviceInput deviceInputWithDevice:captureDevice 323 error:&deviceError]; 324 325 if (!newCaptureInput) { 326 const char* errorMessage = [[deviceError localizedDescription] UTF8String]; 327 328 WEBRTC_TRACE(kTraceError, 329 kTraceVideoCapture, 330 _captureId, 331 "%s:%s:%d deviceInputWithDevice error:%s", 332 __FILE__, 333 __FUNCTION__, 334 __LINE__, 335 errorMessage); 336 337 return NO; 338 } 339 340 // try to add our new capture device to the capture session 341 [_captureSession beginConfiguration]; 342 343 BOOL addedCaptureInput = NO; 344 if ([_captureSession canAddInput:newCaptureInput]) { 345 [_captureSession addInput:newCaptureInput]; 346 addedCaptureInput = YES; 347 } else { 348 addedCaptureInput = NO; 349 } 350 351 [_captureSession commitConfiguration]; 352 353 return addedCaptureInput; 354 } 355 356 - (void)captureOutput:(AVCaptureOutput*)captureOutput 357 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 358 fromConnection:(AVCaptureConnection*)connection { 359 const int kFlags = 0; 360 CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer); 361 362 if (CVPixelBufferLockBaseAddress(videoFrame, kFlags) != kCVReturnSuccess) { 363 return; 364 } 365 366 const int kYPlaneIndex = 0; 367 const int kUVPlaneIndex = 1; 368 369 uint8_t* baseAddress = 370 (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(videoFrame, kYPlaneIndex); 371 int yPlaneBytesPerRow = 372 CVPixelBufferGetBytesPerRowOfPlane(videoFrame, kYPlaneIndex); 373 int yPlaneHeight = CVPixelBufferGetHeightOfPlane(videoFrame, kYPlaneIndex); 374 int uvPlaneBytesPerRow = 375 CVPixelBufferGetBytesPerRowOfPlane(videoFrame, kUVPlaneIndex); 376 int uvPlaneHeight = CVPixelBufferGetHeightOfPlane(videoFrame, kUVPlaneIndex); 377 int frameSize = 378 yPlaneBytesPerRow * yPlaneHeight + uvPlaneBytesPerRow * uvPlaneHeight; 379 380 VideoCaptureCapability tempCaptureCapability; 381 tempCaptureCapability.width = CVPixelBufferGetWidth(videoFrame); 382 tempCaptureCapability.height = CVPixelBufferGetHeight(videoFrame); 383 tempCaptureCapability.maxFPS = _capability.maxFPS; 384 tempCaptureCapability.rawType = kVideoNV12; 385 386 _owner->IncomingFrame(baseAddress, frameSize, tempCaptureCapability, 0); 387 388 CVPixelBufferUnlockBaseAddress(videoFrame, kFlags); 389 } 390 391 - (void)signalCaptureChangeEnd { 392 [_captureChangingCondition lock]; 393 _captureChanging = NO; 394 [_captureChangingCondition signal]; 395 [_captureChangingCondition unlock]; 396 } 397 398 - (void)waitForCaptureChangeToFinish { 399 [_captureChangingCondition lock]; 400 while (_captureChanging) { 401 [_captureChangingCondition wait]; 402 } 403 [_captureChangingCondition unlock]; 404 } 405 @end 406