blob: fa1443d1da7cba9ea5a1881c294ff1b9264b4ecb [file] [log] [blame]
Zeke Chin57cc74e2015-05-05 07:52:31 -07001/*
2 * libjingle
3 * Copyright 2015 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/app/webrtc/objc/avfoundationvideocapturer.h"
29
30#include "webrtc/base/bind.h"
Niels Möller505945a2016-03-17 12:20:41 +010031#include "webrtc/base/thread.h"
Zeke Chin57cc74e2015-05-05 07:52:31 -070032
33#import <AVFoundation/AVFoundation.h>
34#import <Foundation/Foundation.h>
35#import <UIKit/UIKit.h>
36
hayscedd8fef2015-12-08 11:08:39 -080037#import "webrtc/base/objc/RTCDispatcher.h"
hjona1cf3662016-03-14 20:55:22 -070038#import "webrtc/base/objc/RTCLogging.h"
hayscedd8fef2015-12-08 11:08:39 -080039
Zeke Chin57cc74e2015-05-05 07:52:31 -070040// TODO(tkchin): support other formats.
41static NSString* const kDefaultPreset = AVCaptureSessionPreset640x480;
42static cricket::VideoFormat const kDefaultFormat =
43 cricket::VideoFormat(640,
44 480,
45 cricket::VideoFormat::FpsToInterval(30),
46 cricket::FOURCC_NV12);
47
Zeke Chin57cc74e2015-05-05 07:52:31 -070048// This class used to capture frames using AVFoundation APIs on iOS. It is meant
49// to be owned by an instance of AVFoundationVideoCapturer. The reason for this
50// because other webrtc objects own cricket::VideoCapturer, which is not
51// ref counted. To prevent bad behavior we do not expose this class directly.
52@interface RTCAVFoundationVideoCapturerInternal : NSObject
53 <AVCaptureVideoDataOutputSampleBufferDelegate>
54
55@property(nonatomic, readonly) AVCaptureSession* captureSession;
56@property(nonatomic, readonly) BOOL isRunning;
hjona1cf3662016-03-14 20:55:22 -070057@property(nonatomic, readonly) BOOL canUseBackCamera;
Zeke Chin57cc74e2015-05-05 07:52:31 -070058@property(nonatomic, assign) BOOL useBackCamera; // Defaults to NO.
59
60// We keep a pointer back to AVFoundationVideoCapturer to make callbacks on it
61// when we receive frames. This is safe because this object should be owned by
62// it.
63- (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer*)capturer;
64- (void)startCaptureAsync;
65- (void)stopCaptureAsync;
66
67@end
68
69@implementation RTCAVFoundationVideoCapturerInternal {
70 // Keep pointers to inputs for convenience.
71 AVCaptureDeviceInput* _frontDeviceInput;
72 AVCaptureDeviceInput* _backDeviceInput;
73 AVCaptureVideoDataOutput* _videoOutput;
74 // The cricket::VideoCapturer that owns this class. Should never be NULL.
75 webrtc::AVFoundationVideoCapturer* _capturer;
76 BOOL _orientationHasChanged;
77}
78
79@synthesize captureSession = _captureSession;
80@synthesize useBackCamera = _useBackCamera;
81@synthesize isRunning = _isRunning;
82
Zeke Chin57cc74e2015-05-05 07:52:31 -070083- (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer*)capturer {
84 NSParameterAssert(capturer);
85 if (self = [super init]) {
86 _capturer = capturer;
87 if (![self setupCaptureSession]) {
88 return nil;
89 }
90 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
91 [center addObserver:self
92 selector:@selector(deviceOrientationDidChange:)
93 name:UIDeviceOrientationDidChangeNotification
94 object:nil];
95 [center addObserverForName:AVCaptureSessionRuntimeErrorNotification
96 object:nil
97 queue:nil
98 usingBlock:^(NSNotification* notification) {
99 NSLog(@"Capture session error: %@", notification.userInfo);
100 }];
101 }
102 return self;
103}
104
105- (void)dealloc {
106 [self stopCaptureAsync];
107 [[NSNotificationCenter defaultCenter] removeObserver:self];
108 _capturer = nullptr;
109}
110
hjona1cf3662016-03-14 20:55:22 -0700111- (BOOL)canUseBackCamera {
112 return _backDeviceInput != nil;
113}
114
Zeke Chin57cc74e2015-05-05 07:52:31 -0700115- (void)setUseBackCamera:(BOOL)useBackCamera {
116 if (_useBackCamera == useBackCamera) {
117 return;
118 }
hjona1cf3662016-03-14 20:55:22 -0700119 if (!self.canUseBackCamera) {
120 RTCLog(@"No rear-facing camera exists or it cannot be used;"
121 "not switching.");
122 return;
123 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700124 _useBackCamera = useBackCamera;
125 [self updateSessionInput];
126}
127
128- (void)startCaptureAsync {
129 if (_isRunning) {
130 return;
131 }
132 _orientationHasChanged = NO;
133 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
134 AVCaptureSession* session = _captureSession;
hayscedd8fef2015-12-08 11:08:39 -0800135 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
136 block:^{
Zeke Chin57cc74e2015-05-05 07:52:31 -0700137 [session startRunning];
hayscedd8fef2015-12-08 11:08:39 -0800138 }];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700139 _isRunning = YES;
140}
141
142- (void)stopCaptureAsync {
143 if (!_isRunning) {
144 return;
145 }
Jon Hjelle2f65ac12015-06-12 11:33:45 -0700146 [_videoOutput setSampleBufferDelegate:nil queue:nullptr];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700147 AVCaptureSession* session = _captureSession;
hayscedd8fef2015-12-08 11:08:39 -0800148 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
149 block:^{
Zeke Chin57cc74e2015-05-05 07:52:31 -0700150 [session stopRunning];
hayscedd8fef2015-12-08 11:08:39 -0800151 }];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700152 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
153 _isRunning = NO;
154}
155
156#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
157
158- (void)captureOutput:(AVCaptureOutput*)captureOutput
159 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
160 fromConnection:(AVCaptureConnection*)connection {
161 NSParameterAssert(captureOutput == _videoOutput);
162 if (!_isRunning) {
163 return;
164 }
165 _capturer->CaptureSampleBuffer(sampleBuffer);
166}
167
168- (void)captureOutput:(AVCaptureOutput*)captureOutput
169 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
170 fromConnection:(AVCaptureConnection*)connection {
171 NSLog(@"Dropped sample buffer.");
172}
173
174#pragma mark - Private
175
176- (BOOL)setupCaptureSession {
177 _captureSession = [[AVCaptureSession alloc] init];
178#if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0
179 NSString* version = [[UIDevice currentDevice] systemVersion];
180 if ([version integerValue] >= 7) {
181 _captureSession.usesApplicationAudioSession = NO;
182 }
183#endif
184 if (![_captureSession canSetSessionPreset:kDefaultPreset]) {
185 NSLog(@"Default video capture preset unsupported.");
186 return NO;
187 }
188 _captureSession.sessionPreset = kDefaultPreset;
189
190 // Make the capturer output NV12. Ideally we want I420 but that's not
191 // currently supported on iPhone / iPad.
192 _videoOutput = [[AVCaptureVideoDataOutput alloc] init];
193 _videoOutput.videoSettings = @{
194 (NSString*)kCVPixelBufferPixelFormatTypeKey :
195 @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
196 };
197 _videoOutput.alwaysDiscardsLateVideoFrames = NO;
198 [_videoOutput setSampleBufferDelegate:self
199 queue:dispatch_get_main_queue()];
200 if (![_captureSession canAddOutput:_videoOutput]) {
201 NSLog(@"Default video capture output unsupported.");
202 return NO;
203 }
204 [_captureSession addOutput:_videoOutput];
205
206 // Find the capture devices.
207 AVCaptureDevice* frontCaptureDevice = nil;
208 AVCaptureDevice* backCaptureDevice = nil;
209 for (AVCaptureDevice* captureDevice in
210 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
211 if (captureDevice.position == AVCaptureDevicePositionBack) {
212 backCaptureDevice = captureDevice;
213 }
214 if (captureDevice.position == AVCaptureDevicePositionFront) {
215 frontCaptureDevice = captureDevice;
216 }
217 }
hjona1cf3662016-03-14 20:55:22 -0700218 if (!frontCaptureDevice) {
219 RTCLog(@"Failed to get front capture device.");
Zeke Chin57cc74e2015-05-05 07:52:31 -0700220 return NO;
221 }
hjona1cf3662016-03-14 20:55:22 -0700222 if (!backCaptureDevice) {
223 RTCLog(@"Failed to get back capture device");
224 // Don't return NO here because devices exist (16GB 5th generation iPod
225 // Touch) that don't have a rear-facing camera.
226 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700227
228 // Set up the session inputs.
229 NSError* error = nil;
230 _frontDeviceInput =
231 [AVCaptureDeviceInput deviceInputWithDevice:frontCaptureDevice
232 error:&error];
233 if (!_frontDeviceInput) {
234 NSLog(@"Failed to get capture device input: %@",
235 error.localizedDescription);
236 return NO;
237 }
hjona1cf3662016-03-14 20:55:22 -0700238 if (backCaptureDevice) {
239 error = nil;
240 _backDeviceInput =
241 [AVCaptureDeviceInput deviceInputWithDevice:backCaptureDevice
242 error:&error];
243 if (error) {
244 RTCLog(@"Failed to get capture device input: %@",
245 error.localizedDescription);
246 _backDeviceInput = nil;
247 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700248 }
249
250 // Add the inputs.
251 if (![_captureSession canAddInput:_frontDeviceInput] ||
hjona1cf3662016-03-14 20:55:22 -0700252 (_backDeviceInput && ![_captureSession canAddInput:_backDeviceInput])) {
Zeke Chin57cc74e2015-05-05 07:52:31 -0700253 NSLog(@"Session does not support capture inputs.");
254 return NO;
255 }
256 [self updateSessionInput];
257
258 return YES;
259}
260
261- (void)deviceOrientationDidChange:(NSNotification*)notification {
262 _orientationHasChanged = YES;
263 [self updateOrientation];
264}
265
266- (void)updateOrientation {
267 AVCaptureConnection* connection =
268 [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
269 if (!connection.supportsVideoOrientation) {
270 // TODO(tkchin): set rotation bit on frames.
271 return;
272 }
273 AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationPortrait;
274 switch ([UIDevice currentDevice].orientation) {
275 case UIDeviceOrientationPortrait:
Zeke Chin7be99bd2015-05-29 16:34:38 -0700276 orientation = AVCaptureVideoOrientationPortrait;
Jon Hjelle14c26952015-05-29 15:24:52 -0700277 break;
Jon Hjellec2cb2662015-05-29 16:38:26 -0700278 case UIDeviceOrientationPortraitUpsideDown:
279 orientation = AVCaptureVideoOrientationPortraitUpsideDown;
280 break;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700281 case UIDeviceOrientationLandscapeLeft:
282 orientation = AVCaptureVideoOrientationLandscapeRight;
283 break;
284 case UIDeviceOrientationLandscapeRight:
285 orientation = AVCaptureVideoOrientationLandscapeLeft;
286 break;
287 case UIDeviceOrientationFaceUp:
288 case UIDeviceOrientationFaceDown:
289 case UIDeviceOrientationUnknown:
290 if (!_orientationHasChanged) {
291 connection.videoOrientation = orientation;
292 }
293 return;
294 }
295 connection.videoOrientation = orientation;
296}
297
298- (void)updateSessionInput {
299 // Update the current session input to match what's stored in _useBackCamera.
300 [_captureSession beginConfiguration];
301 AVCaptureDeviceInput* oldInput = _backDeviceInput;
302 AVCaptureDeviceInput* newInput = _frontDeviceInput;
303 if (_useBackCamera) {
304 oldInput = _frontDeviceInput;
305 newInput = _backDeviceInput;
306 }
307 // Ok to remove this even if it's not attached. Will be no-op.
308 [_captureSession removeInput:oldInput];
309 [_captureSession addInput:newInput];
310 [self updateOrientation];
311 [_captureSession commitConfiguration];
312}
313
314@end
315
316namespace webrtc {
317
318AVFoundationVideoCapturer::AVFoundationVideoCapturer()
magjedb09b6602015-10-01 03:02:44 -0700319 : _capturer(nil), _startThread(nullptr) {
Zeke Chin57cc74e2015-05-05 07:52:31 -0700320 // Set our supported formats. This matches kDefaultPreset.
321 std::vector<cricket::VideoFormat> supportedFormats;
322 supportedFormats.push_back(cricket::VideoFormat(kDefaultFormat));
323 SetSupportedFormats(supportedFormats);
324 _capturer =
325 [[RTCAVFoundationVideoCapturerInternal alloc] initWithCapturer:this];
326}
327
328AVFoundationVideoCapturer::~AVFoundationVideoCapturer() {
329 _capturer = nil;
330}
331
332cricket::CaptureState AVFoundationVideoCapturer::Start(
333 const cricket::VideoFormat& format) {
334 if (!_capturer) {
335 LOG(LS_ERROR) << "Failed to create AVFoundation capturer.";
336 return cricket::CaptureState::CS_FAILED;
337 }
338 if (_capturer.isRunning) {
339 LOG(LS_ERROR) << "The capturer is already running.";
340 return cricket::CaptureState::CS_FAILED;
341 }
342 if (format != kDefaultFormat) {
343 LOG(LS_ERROR) << "Unsupported format provided.";
344 return cricket::CaptureState::CS_FAILED;
345 }
346
347 // Keep track of which thread capture started on. This is the thread that
348 // frames need to be sent to.
henrikg91d6ede2015-09-17 00:24:34 -0700349 RTC_DCHECK(!_startThread);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700350 _startThread = rtc::Thread::Current();
351
352 SetCaptureFormat(&format);
353 // This isn't super accurate because it takes a while for the AVCaptureSession
354 // to spin up, and this call returns async.
355 // TODO(tkchin): make this better.
356 [_capturer startCaptureAsync];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700357 SetCaptureState(cricket::CaptureState::CS_RUNNING);
358
359 return cricket::CaptureState::CS_STARTING;
360}
361
362void AVFoundationVideoCapturer::Stop() {
363 [_capturer stopCaptureAsync];
364 SetCaptureFormat(NULL);
365 _startThread = nullptr;
366}
367
368bool AVFoundationVideoCapturer::IsRunning() {
369 return _capturer.isRunning;
370}
371
372AVCaptureSession* AVFoundationVideoCapturer::GetCaptureSession() {
373 return _capturer.captureSession;
374}
375
hjona1cf3662016-03-14 20:55:22 -0700376bool AVFoundationVideoCapturer::CanUseBackCamera() const {
377 return _capturer.canUseBackCamera;
378}
379
Zeke Chin57cc74e2015-05-05 07:52:31 -0700380void AVFoundationVideoCapturer::SetUseBackCamera(bool useBackCamera) {
381 _capturer.useBackCamera = useBackCamera;
382}
383
384bool AVFoundationVideoCapturer::GetUseBackCamera() const {
385 return _capturer.useBackCamera;
386}
387
388void AVFoundationVideoCapturer::CaptureSampleBuffer(
389 CMSampleBufferRef sampleBuffer) {
390 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
391 !CMSampleBufferIsValid(sampleBuffer) ||
392 !CMSampleBufferDataIsReady(sampleBuffer)) {
393 return;
394 }
395
396 CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
397 if (imageBuffer == NULL) {
398 return;
399 }
400
401 // Base address must be unlocked to access frame data.
402 CVOptionFlags lockFlags = kCVPixelBufferLock_ReadOnly;
403 CVReturn ret = CVPixelBufferLockBaseAddress(imageBuffer, lockFlags);
404 if (ret != kCVReturnSuccess) {
405 return;
406 }
407
408 static size_t const kYPlaneIndex = 0;
409 static size_t const kUVPlaneIndex = 1;
410 uint8_t* yPlaneAddress =
411 (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, kYPlaneIndex);
412 size_t yPlaneHeight =
413 CVPixelBufferGetHeightOfPlane(imageBuffer, kYPlaneIndex);
414 size_t yPlaneWidth =
415 CVPixelBufferGetWidthOfPlane(imageBuffer, kYPlaneIndex);
416 size_t yPlaneBytesPerRow =
417 CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, kYPlaneIndex);
418 size_t uvPlaneHeight =
419 CVPixelBufferGetHeightOfPlane(imageBuffer, kUVPlaneIndex);
420 size_t uvPlaneBytesPerRow =
421 CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, kUVPlaneIndex);
422 size_t frameSize =
423 yPlaneBytesPerRow * yPlaneHeight + uvPlaneBytesPerRow * uvPlaneHeight;
424
425 // Sanity check assumption that planar bytes are contiguous.
426 uint8_t* uvPlaneAddress =
427 (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, kUVPlaneIndex);
henrikg91d6ede2015-09-17 00:24:34 -0700428 RTC_DCHECK(
429 uvPlaneAddress == yPlaneAddress + yPlaneHeight * yPlaneBytesPerRow);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700430
431 // Stuff data into a cricket::CapturedFrame.
Peter Boström0c4e06b2015-10-07 12:23:21 +0200432 int64_t currentTime = rtc::TimeNanos();
Zeke Chin57cc74e2015-05-05 07:52:31 -0700433 cricket::CapturedFrame frame;
434 frame.width = yPlaneWidth;
435 frame.height = yPlaneHeight;
436 frame.pixel_width = 1;
437 frame.pixel_height = 1;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200438 frame.fourcc = static_cast<uint32_t>(cricket::FOURCC_NV12);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700439 frame.time_stamp = currentTime;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700440 frame.data = yPlaneAddress;
441 frame.data_size = frameSize;
442
443 if (_startThread->IsCurrent()) {
444 SignalFrameCaptured(this, &frame);
445 } else {
446 _startThread->Invoke<void>(
447 rtc::Bind(&AVFoundationVideoCapturer::SignalFrameCapturedOnStartThread,
448 this, &frame));
449 }
450 CVPixelBufferUnlockBaseAddress(imageBuffer, lockFlags);
451}
452
453void AVFoundationVideoCapturer::SignalFrameCapturedOnStartThread(
454 const cricket::CapturedFrame* frame) {
henrikg91d6ede2015-09-17 00:24:34 -0700455 RTC_DCHECK(_startThread->IsCurrent());
Zeke Chin57cc74e2015-05-05 07:52:31 -0700456 // This will call a superclass method that will perform the frame conversion
457 // to I420.
458 SignalFrameCaptured(this, frame);
459}
460
461} // namespace webrtc