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