blob: f9845901897a17801ba1ca5bba4efcef39f0d927 [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"
31
32#import <AVFoundation/AVFoundation.h>
33#import <Foundation/Foundation.h>
34#import <UIKit/UIKit.h>
35
hayscedd8fef2015-12-08 11:08:39 -080036#import "webrtc/base/objc/RTCDispatcher.h"
hjona1cf3662016-03-14 20:55:22 -070037#import "webrtc/base/objc/RTCLogging.h"
hayscedd8fef2015-12-08 11:08:39 -080038
Zeke Chin57cc74e2015-05-05 07:52:31 -070039// TODO(tkchin): support other formats.
40static NSString* const kDefaultPreset = AVCaptureSessionPreset640x480;
41static cricket::VideoFormat const kDefaultFormat =
42 cricket::VideoFormat(640,
43 480,
44 cricket::VideoFormat::FpsToInterval(30),
45 cricket::FOURCC_NV12);
46
Zeke Chin57cc74e2015-05-05 07:52:31 -070047// This class used to capture frames using AVFoundation APIs on iOS. It is meant
48// to be owned by an instance of AVFoundationVideoCapturer. The reason for this
49// because other webrtc objects own cricket::VideoCapturer, which is not
50// ref counted. To prevent bad behavior we do not expose this class directly.
51@interface RTCAVFoundationVideoCapturerInternal : NSObject
52 <AVCaptureVideoDataOutputSampleBufferDelegate>
53
54@property(nonatomic, readonly) AVCaptureSession* captureSession;
55@property(nonatomic, readonly) BOOL isRunning;
hjona1cf3662016-03-14 20:55:22 -070056@property(nonatomic, readonly) BOOL canUseBackCamera;
Zeke Chin57cc74e2015-05-05 07:52:31 -070057@property(nonatomic, assign) BOOL useBackCamera; // Defaults to NO.
58
59// We keep a pointer back to AVFoundationVideoCapturer to make callbacks on it
60// when we receive frames. This is safe because this object should be owned by
61// it.
62- (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer*)capturer;
63- (void)startCaptureAsync;
64- (void)stopCaptureAsync;
65
66@end
67
68@implementation RTCAVFoundationVideoCapturerInternal {
69 // Keep pointers to inputs for convenience.
70 AVCaptureDeviceInput* _frontDeviceInput;
71 AVCaptureDeviceInput* _backDeviceInput;
72 AVCaptureVideoDataOutput* _videoOutput;
73 // The cricket::VideoCapturer that owns this class. Should never be NULL.
74 webrtc::AVFoundationVideoCapturer* _capturer;
75 BOOL _orientationHasChanged;
76}
77
78@synthesize captureSession = _captureSession;
79@synthesize useBackCamera = _useBackCamera;
80@synthesize isRunning = _isRunning;
81
Zeke Chin57cc74e2015-05-05 07:52:31 -070082- (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer*)capturer {
83 NSParameterAssert(capturer);
84 if (self = [super init]) {
85 _capturer = capturer;
86 if (![self setupCaptureSession]) {
87 return nil;
88 }
89 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
90 [center addObserver:self
91 selector:@selector(deviceOrientationDidChange:)
92 name:UIDeviceOrientationDidChangeNotification
93 object:nil];
94 [center addObserverForName:AVCaptureSessionRuntimeErrorNotification
95 object:nil
96 queue:nil
97 usingBlock:^(NSNotification* notification) {
98 NSLog(@"Capture session error: %@", notification.userInfo);
99 }];
100 }
101 return self;
102}
103
104- (void)dealloc {
105 [self stopCaptureAsync];
106 [[NSNotificationCenter defaultCenter] removeObserver:self];
107 _capturer = nullptr;
108}
109
hjona1cf3662016-03-14 20:55:22 -0700110- (BOOL)canUseBackCamera {
111 return _backDeviceInput != nil;
112}
113
Zeke Chin57cc74e2015-05-05 07:52:31 -0700114- (void)setUseBackCamera:(BOOL)useBackCamera {
115 if (_useBackCamera == useBackCamera) {
116 return;
117 }
hjona1cf3662016-03-14 20:55:22 -0700118 if (!self.canUseBackCamera) {
119 RTCLog(@"No rear-facing camera exists or it cannot be used;"
120 "not switching.");
121 return;
122 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700123 _useBackCamera = useBackCamera;
124 [self updateSessionInput];
125}
126
127- (void)startCaptureAsync {
128 if (_isRunning) {
129 return;
130 }
131 _orientationHasChanged = NO;
132 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
133 AVCaptureSession* session = _captureSession;
hayscedd8fef2015-12-08 11:08:39 -0800134 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
135 block:^{
Zeke Chin57cc74e2015-05-05 07:52:31 -0700136 [session startRunning];
hayscedd8fef2015-12-08 11:08:39 -0800137 }];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700138 _isRunning = YES;
139}
140
141- (void)stopCaptureAsync {
142 if (!_isRunning) {
143 return;
144 }
Jon Hjelle2f65ac12015-06-12 11:33:45 -0700145 [_videoOutput setSampleBufferDelegate:nil queue:nullptr];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700146 AVCaptureSession* session = _captureSession;
hayscedd8fef2015-12-08 11:08:39 -0800147 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
148 block:^{
Zeke Chin57cc74e2015-05-05 07:52:31 -0700149 [session stopRunning];
hayscedd8fef2015-12-08 11:08:39 -0800150 }];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700151 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
152 _isRunning = NO;
153}
154
155#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
156
157- (void)captureOutput:(AVCaptureOutput*)captureOutput
158 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
159 fromConnection:(AVCaptureConnection*)connection {
160 NSParameterAssert(captureOutput == _videoOutput);
161 if (!_isRunning) {
162 return;
163 }
164 _capturer->CaptureSampleBuffer(sampleBuffer);
165}
166
167- (void)captureOutput:(AVCaptureOutput*)captureOutput
168 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
169 fromConnection:(AVCaptureConnection*)connection {
170 NSLog(@"Dropped sample buffer.");
171}
172
173#pragma mark - Private
174
175- (BOOL)setupCaptureSession {
176 _captureSession = [[AVCaptureSession alloc] init];
177#if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0
178 NSString* version = [[UIDevice currentDevice] systemVersion];
179 if ([version integerValue] >= 7) {
180 _captureSession.usesApplicationAudioSession = NO;
181 }
182#endif
183 if (![_captureSession canSetSessionPreset:kDefaultPreset]) {
184 NSLog(@"Default video capture preset unsupported.");
185 return NO;
186 }
187 _captureSession.sessionPreset = kDefaultPreset;
188
189 // Make the capturer output NV12. Ideally we want I420 but that's not
190 // currently supported on iPhone / iPad.
191 _videoOutput = [[AVCaptureVideoDataOutput alloc] init];
192 _videoOutput.videoSettings = @{
193 (NSString*)kCVPixelBufferPixelFormatTypeKey :
194 @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
195 };
196 _videoOutput.alwaysDiscardsLateVideoFrames = NO;
197 [_videoOutput setSampleBufferDelegate:self
198 queue:dispatch_get_main_queue()];
199 if (![_captureSession canAddOutput:_videoOutput]) {
200 NSLog(@"Default video capture output unsupported.");
201 return NO;
202 }
203 [_captureSession addOutput:_videoOutput];
204
205 // Find the capture devices.
206 AVCaptureDevice* frontCaptureDevice = nil;
207 AVCaptureDevice* backCaptureDevice = nil;
208 for (AVCaptureDevice* captureDevice in
209 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
210 if (captureDevice.position == AVCaptureDevicePositionBack) {
211 backCaptureDevice = captureDevice;
212 }
213 if (captureDevice.position == AVCaptureDevicePositionFront) {
214 frontCaptureDevice = captureDevice;
215 }
216 }
hjona1cf3662016-03-14 20:55:22 -0700217 if (!frontCaptureDevice) {
218 RTCLog(@"Failed to get front capture device.");
Zeke Chin57cc74e2015-05-05 07:52:31 -0700219 return NO;
220 }
hjona1cf3662016-03-14 20:55:22 -0700221 if (!backCaptureDevice) {
222 RTCLog(@"Failed to get back capture device");
223 // Don't return NO here because devices exist (16GB 5th generation iPod
224 // Touch) that don't have a rear-facing camera.
225 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700226
227 // Set up the session inputs.
228 NSError* error = nil;
229 _frontDeviceInput =
230 [AVCaptureDeviceInput deviceInputWithDevice:frontCaptureDevice
231 error:&error];
232 if (!_frontDeviceInput) {
233 NSLog(@"Failed to get capture device input: %@",
234 error.localizedDescription);
235 return NO;
236 }
hjona1cf3662016-03-14 20:55:22 -0700237 if (backCaptureDevice) {
238 error = nil;
239 _backDeviceInput =
240 [AVCaptureDeviceInput deviceInputWithDevice:backCaptureDevice
241 error:&error];
242 if (error) {
243 RTCLog(@"Failed to get capture device input: %@",
244 error.localizedDescription);
245 _backDeviceInput = nil;
246 }
Zeke Chin57cc74e2015-05-05 07:52:31 -0700247 }
248
249 // Add the inputs.
250 if (![_captureSession canAddInput:_frontDeviceInput] ||
hjona1cf3662016-03-14 20:55:22 -0700251 (_backDeviceInput && ![_captureSession canAddInput:_backDeviceInput])) {
Zeke Chin57cc74e2015-05-05 07:52:31 -0700252 NSLog(@"Session does not support capture inputs.");
253 return NO;
254 }
255 [self updateSessionInput];
256
257 return YES;
258}
259
260- (void)deviceOrientationDidChange:(NSNotification*)notification {
261 _orientationHasChanged = YES;
262 [self updateOrientation];
263}
264
265- (void)updateOrientation {
266 AVCaptureConnection* connection =
267 [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
268 if (!connection.supportsVideoOrientation) {
269 // TODO(tkchin): set rotation bit on frames.
270 return;
271 }
272 AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationPortrait;
273 switch ([UIDevice currentDevice].orientation) {
274 case UIDeviceOrientationPortrait:
Zeke Chin7be99bd2015-05-29 16:34:38 -0700275 orientation = AVCaptureVideoOrientationPortrait;
Jon Hjelle14c26952015-05-29 15:24:52 -0700276 break;
Jon Hjellec2cb2662015-05-29 16:38:26 -0700277 case UIDeviceOrientationPortraitUpsideDown:
278 orientation = AVCaptureVideoOrientationPortraitUpsideDown;
279 break;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700280 case UIDeviceOrientationLandscapeLeft:
281 orientation = AVCaptureVideoOrientationLandscapeRight;
282 break;
283 case UIDeviceOrientationLandscapeRight:
284 orientation = AVCaptureVideoOrientationLandscapeLeft;
285 break;
286 case UIDeviceOrientationFaceUp:
287 case UIDeviceOrientationFaceDown:
288 case UIDeviceOrientationUnknown:
289 if (!_orientationHasChanged) {
290 connection.videoOrientation = orientation;
291 }
292 return;
293 }
294 connection.videoOrientation = orientation;
295}
296
297- (void)updateSessionInput {
298 // Update the current session input to match what's stored in _useBackCamera.
299 [_captureSession beginConfiguration];
300 AVCaptureDeviceInput* oldInput = _backDeviceInput;
301 AVCaptureDeviceInput* newInput = _frontDeviceInput;
302 if (_useBackCamera) {
303 oldInput = _frontDeviceInput;
304 newInput = _backDeviceInput;
305 }
306 // Ok to remove this even if it's not attached. Will be no-op.
307 [_captureSession removeInput:oldInput];
308 [_captureSession addInput:newInput];
309 [self updateOrientation];
310 [_captureSession commitConfiguration];
311}
312
313@end
314
315namespace webrtc {
316
317AVFoundationVideoCapturer::AVFoundationVideoCapturer()
magjedb09b6602015-10-01 03:02:44 -0700318 : _capturer(nil), _startThread(nullptr) {
Zeke Chin57cc74e2015-05-05 07:52:31 -0700319 // Set our supported formats. This matches kDefaultPreset.
320 std::vector<cricket::VideoFormat> supportedFormats;
321 supportedFormats.push_back(cricket::VideoFormat(kDefaultFormat));
322 SetSupportedFormats(supportedFormats);
323 _capturer =
324 [[RTCAVFoundationVideoCapturerInternal alloc] initWithCapturer:this];
325}
326
327AVFoundationVideoCapturer::~AVFoundationVideoCapturer() {
328 _capturer = nil;
329}
330
331cricket::CaptureState AVFoundationVideoCapturer::Start(
332 const cricket::VideoFormat& format) {
333 if (!_capturer) {
334 LOG(LS_ERROR) << "Failed to create AVFoundation capturer.";
335 return cricket::CaptureState::CS_FAILED;
336 }
337 if (_capturer.isRunning) {
338 LOG(LS_ERROR) << "The capturer is already running.";
339 return cricket::CaptureState::CS_FAILED;
340 }
341 if (format != kDefaultFormat) {
342 LOG(LS_ERROR) << "Unsupported format provided.";
343 return cricket::CaptureState::CS_FAILED;
344 }
345
346 // Keep track of which thread capture started on. This is the thread that
347 // frames need to be sent to.
henrikg91d6ede2015-09-17 00:24:34 -0700348 RTC_DCHECK(!_startThread);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700349 _startThread = rtc::Thread::Current();
350
351 SetCaptureFormat(&format);
352 // This isn't super accurate because it takes a while for the AVCaptureSession
353 // to spin up, and this call returns async.
354 // TODO(tkchin): make this better.
355 [_capturer startCaptureAsync];
Zeke Chin57cc74e2015-05-05 07:52:31 -0700356 SetCaptureState(cricket::CaptureState::CS_RUNNING);
357
358 return cricket::CaptureState::CS_STARTING;
359}
360
361void AVFoundationVideoCapturer::Stop() {
362 [_capturer stopCaptureAsync];
363 SetCaptureFormat(NULL);
364 _startThread = nullptr;
365}
366
367bool AVFoundationVideoCapturer::IsRunning() {
368 return _capturer.isRunning;
369}
370
371AVCaptureSession* AVFoundationVideoCapturer::GetCaptureSession() {
372 return _capturer.captureSession;
373}
374
hjona1cf3662016-03-14 20:55:22 -0700375bool AVFoundationVideoCapturer::CanUseBackCamera() const {
376 return _capturer.canUseBackCamera;
377}
378
Zeke Chin57cc74e2015-05-05 07:52:31 -0700379void AVFoundationVideoCapturer::SetUseBackCamera(bool useBackCamera) {
380 _capturer.useBackCamera = useBackCamera;
381}
382
383bool AVFoundationVideoCapturer::GetUseBackCamera() const {
384 return _capturer.useBackCamera;
385}
386
387void AVFoundationVideoCapturer::CaptureSampleBuffer(
388 CMSampleBufferRef sampleBuffer) {
389 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
390 !CMSampleBufferIsValid(sampleBuffer) ||
391 !CMSampleBufferDataIsReady(sampleBuffer)) {
392 return;
393 }
394
395 CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
396 if (imageBuffer == NULL) {
397 return;
398 }
399
400 // Base address must be unlocked to access frame data.
401 CVOptionFlags lockFlags = kCVPixelBufferLock_ReadOnly;
402 CVReturn ret = CVPixelBufferLockBaseAddress(imageBuffer, lockFlags);
403 if (ret != kCVReturnSuccess) {
404 return;
405 }
406
407 static size_t const kYPlaneIndex = 0;
408 static size_t const kUVPlaneIndex = 1;
409 uint8_t* yPlaneAddress =
410 (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, kYPlaneIndex);
411 size_t yPlaneHeight =
412 CVPixelBufferGetHeightOfPlane(imageBuffer, kYPlaneIndex);
413 size_t yPlaneWidth =
414 CVPixelBufferGetWidthOfPlane(imageBuffer, kYPlaneIndex);
415 size_t yPlaneBytesPerRow =
416 CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, kYPlaneIndex);
417 size_t uvPlaneHeight =
418 CVPixelBufferGetHeightOfPlane(imageBuffer, kUVPlaneIndex);
419 size_t uvPlaneBytesPerRow =
420 CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, kUVPlaneIndex);
421 size_t frameSize =
422 yPlaneBytesPerRow * yPlaneHeight + uvPlaneBytesPerRow * uvPlaneHeight;
423
424 // Sanity check assumption that planar bytes are contiguous.
425 uint8_t* uvPlaneAddress =
426 (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, kUVPlaneIndex);
henrikg91d6ede2015-09-17 00:24:34 -0700427 RTC_DCHECK(
428 uvPlaneAddress == yPlaneAddress + yPlaneHeight * yPlaneBytesPerRow);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700429
430 // Stuff data into a cricket::CapturedFrame.
Peter Boström0c4e06b2015-10-07 12:23:21 +0200431 int64_t currentTime = rtc::TimeNanos();
Zeke Chin57cc74e2015-05-05 07:52:31 -0700432 cricket::CapturedFrame frame;
433 frame.width = yPlaneWidth;
434 frame.height = yPlaneHeight;
435 frame.pixel_width = 1;
436 frame.pixel_height = 1;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200437 frame.fourcc = static_cast<uint32_t>(cricket::FOURCC_NV12);
Zeke Chin57cc74e2015-05-05 07:52:31 -0700438 frame.time_stamp = currentTime;
Zeke Chin57cc74e2015-05-05 07:52:31 -0700439 frame.data = yPlaneAddress;
440 frame.data_size = frameSize;
441
442 if (_startThread->IsCurrent()) {
443 SignalFrameCaptured(this, &frame);
444 } else {
445 _startThread->Invoke<void>(
446 rtc::Bind(&AVFoundationVideoCapturer::SignalFrameCapturedOnStartThread,
447 this, &frame));
448 }
449 CVPixelBufferUnlockBaseAddress(imageBuffer, lockFlags);
450}
451
452void AVFoundationVideoCapturer::SignalFrameCapturedOnStartThread(
453 const cricket::CapturedFrame* frame) {
henrikg91d6ede2015-09-17 00:24:34 -0700454 RTC_DCHECK(_startThread->IsCurrent());
Zeke Chin57cc74e2015-05-05 07:52:31 -0700455 // This will call a superclass method that will perform the frame conversion
456 // to I420.
457 SignalFrameCaptured(this, frame);
458}
459
460} // namespace webrtc