Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: talk/app/webrtc/objc/avfoundationvideocapturer.mm

Issue 2296613002: Delete talk directory, and references from build_ios_libs.sh. (Closed)
Patch Set: Update filenames for c -> c++ conversion. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
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 #include "webrtc/base/checks.h"
32 #include "webrtc/base/thread.h"
33
34 #import <AVFoundation/AVFoundation.h>
35 #import <Foundation/Foundation.h>
36 #import <UIKit/UIKit.h>
37
38 #import "RTCDispatcher+Private.h"
39 #import "RTCLogging.h"
40
41 // TODO(tkchin): support other formats.
42 static NSString *const kDefaultPreset = AVCaptureSessionPreset640x480;
43 static cricket::VideoFormat const kDefaultFormat =
44 cricket::VideoFormat(640,
45 480,
46 cricket::VideoFormat::FpsToInterval(30),
47 cricket::FOURCC_NV12);
48
49 // This class used to capture frames using AVFoundation APIs on iOS. It is meant
50 // to be owned by an instance of AVFoundationVideoCapturer. The reason for this
51 // because other webrtc objects own cricket::VideoCapturer, which is not
52 // ref counted. To prevent bad behavior we do not expose this class directly.
53 @interface RTCAVFoundationVideoCapturerInternal : NSObject
54 <AVCaptureVideoDataOutputSampleBufferDelegate>
55
56 @property(nonatomic, readonly) AVCaptureSession *captureSession;
57 @property(nonatomic, readonly) BOOL isRunning;
58 @property(nonatomic, readonly) BOOL canUseBackCamera;
59 @property(nonatomic, assign) BOOL useBackCamera; // Defaults to NO.
60
61 // We keep a pointer back to AVFoundationVideoCapturer to make callbacks on it
62 // when we receive frames. This is safe because this object should be owned by
63 // it.
64 - (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer *)capturer;
65
66 // Starts and stops the capture session asynchronously. We cannot do this
67 // synchronously without blocking a WebRTC thread.
68 - (void)start;
69 - (void)stop;
70
71 @end
72
73 @implementation RTCAVFoundationVideoCapturerInternal {
74 // Keep pointers to inputs for convenience.
75 AVCaptureDeviceInput *_frontCameraInput;
76 AVCaptureDeviceInput *_backCameraInput;
77 AVCaptureVideoDataOutput *_videoDataOutput;
78 // The cricket::VideoCapturer that owns this class. Should never be NULL.
79 webrtc::AVFoundationVideoCapturer *_capturer;
80 BOOL _orientationHasChanged;
81 }
82
83 @synthesize captureSession = _captureSession;
84 @synthesize isRunning = _isRunning;
85 @synthesize useBackCamera = _useBackCamera;
86
87 // This is called from the thread that creates the video source, which is likely
88 // the main thread.
89 - (instancetype)initWithCapturer:(webrtc::AVFoundationVideoCapturer *)capturer {
90 RTC_DCHECK(capturer);
91 if (self = [super init]) {
92 _capturer = capturer;
93 // Create the capture session and all relevant inputs and outputs. We need
94 // to do this in init because the application may want the capture session
95 // before we start the capturer for e.g. AVCapturePreviewLayer. All objects
96 // created here are retained until dealloc and never recreated.
97 if (![self setupCaptureSession]) {
98 return nil;
99 }
100 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
101 [center addObserver:self
102 selector:@selector(deviceOrientationDidChange:)
103 name:UIDeviceOrientationDidChangeNotification
104 object:nil];
105 [center addObserverForName:AVCaptureSessionRuntimeErrorNotification
106 object:nil
107 queue:nil
108 usingBlock:^(NSNotification *notification) {
109 RTCLogError(@"Capture session error: %@", notification.userInfo);
110 }];
111 }
112 return self;
113 }
114
115 - (void)dealloc {
116 RTC_DCHECK(!_isRunning);
117 [[NSNotificationCenter defaultCenter] removeObserver:self];
118 _capturer = nullptr;
119 }
120
121 - (AVCaptureSession *)captureSession {
122 return _captureSession;
123 }
124
125 // Called from any thread (likely main thread).
126 - (BOOL)canUseBackCamera {
127 return _backCameraInput != nil;
128 }
129
130 // Called from any thread (likely main thread).
131 - (BOOL)useBackCamera {
132 @synchronized(self) {
133 return _useBackCamera;
134 }
135 }
136
137 // Called from any thread (likely main thread).
138 - (void)setUseBackCamera:(BOOL)useBackCamera {
139 if (!self.canUseBackCamera) {
140 if (useBackCamera) {
141 RTCLogWarning(@"No rear-facing camera exists or it cannot be used;"
142 "not switching.");
143 }
144 return;
145 }
146 @synchronized(self) {
147 if (_useBackCamera == useBackCamera) {
148 return;
149 }
150 _useBackCamera = useBackCamera;
151 [self updateSessionInputForUseBackCamera:useBackCamera];
152 }
153 }
154
155 // Called from WebRTC thread.
156 - (void)start {
157 if (_isRunning) {
158 return;
159 }
160 _isRunning = YES;
161 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
162 block:^{
163 _orientationHasChanged = NO;
164 [self updateOrientation];
165 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
166 AVCaptureSession *captureSession = self.captureSession;
167 [captureSession startRunning];
168 }];
169 }
170
171 // Called from same thread as start.
172 - (void)stop {
173 if (!_isRunning) {
174 return;
175 }
176 _isRunning = NO;
177 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
178 block:^{
179 [_videoDataOutput setSampleBufferDelegate:nil queue:nullptr];
180 [_captureSession stopRunning];
181 [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
182 }];
183 }
184
185 #pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
186
187 - (void)captureOutput:(AVCaptureOutput *)captureOutput
188 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
189 fromConnection:(AVCaptureConnection *)connection {
190 NSParameterAssert(captureOutput == _videoDataOutput);
191 if (!_isRunning) {
192 return;
193 }
194 _capturer->CaptureSampleBuffer(sampleBuffer);
195 }
196
197 - (void)captureOutput:(AVCaptureOutput *)captureOutput
198 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
199 fromConnection:(AVCaptureConnection *)connection {
200 RTCLogError(@"Dropped sample buffer.");
201 }
202
203 #pragma mark - Private
204
205 - (BOOL)setupCaptureSession {
206 AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
207 #if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0
208 NSString *version = [[UIDevice currentDevice] systemVersion];
209 if ([version integerValue] >= 7) {
210 captureSession.usesApplicationAudioSession = NO;
211 }
212 #endif
213 if (![captureSession canSetSessionPreset:kDefaultPreset]) {
214 RTCLogError(@"Session preset unsupported.");
215 return NO;
216 }
217 captureSession.sessionPreset = kDefaultPreset;
218
219 // Add the output.
220 AVCaptureVideoDataOutput *videoDataOutput = [self videoDataOutput];
221 if (![captureSession canAddOutput:videoDataOutput]) {
222 RTCLogError(@"Video data output unsupported.");
223 return NO;
224 }
225 [captureSession addOutput:videoDataOutput];
226
227 // Get the front and back cameras. If there isn't a front camera
228 // give up.
229 AVCaptureDeviceInput *frontCameraInput = [self frontCameraInput];
230 AVCaptureDeviceInput *backCameraInput = [self backCameraInput];
231 if (!frontCameraInput) {
232 RTCLogError(@"No front camera for capture session.");
233 return NO;
234 }
235
236 // Add the inputs.
237 if (![captureSession canAddInput:frontCameraInput] ||
238 (backCameraInput && ![captureSession canAddInput:backCameraInput])) {
239 RTCLogError(@"Session does not support capture inputs.");
240 return NO;
241 }
242 AVCaptureDeviceInput *input = self.useBackCamera ?
243 backCameraInput : frontCameraInput;
244 [captureSession addInput:input];
245 _captureSession = captureSession;
246 return YES;
247 }
248
249 - (AVCaptureVideoDataOutput *)videoDataOutput {
250 if (!_videoDataOutput) {
251 // Make the capturer output NV12. Ideally we want I420 but that's not
252 // currently supported on iPhone / iPad.
253 AVCaptureVideoDataOutput *videoDataOutput =
254 [[AVCaptureVideoDataOutput alloc] init];
255 videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
256 videoDataOutput.videoSettings = @{
257 (NSString *)kCVPixelBufferPixelFormatTypeKey :
258 @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
259 };
260 videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
261 dispatch_queue_t queue =
262 [RTCDispatcher dispatchQueueForType:RTCDispatcherTypeCaptureSession];
263 [videoDataOutput setSampleBufferDelegate:self queue:queue];
264 _videoDataOutput = videoDataOutput;
265 }
266 return _videoDataOutput;
267 }
268
269 - (AVCaptureDevice *)videoCaptureDeviceForPosition:
270 (AVCaptureDevicePosition)position {
271 for (AVCaptureDevice *captureDevice in
272 [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
273 if (captureDevice.position == position) {
274 return captureDevice;
275 }
276 }
277 return nil;
278 }
279
280 - (AVCaptureDeviceInput *)frontCameraInput {
281 if (!_frontCameraInput) {
282 AVCaptureDevice *frontCameraDevice =
283 [self videoCaptureDeviceForPosition:AVCaptureDevicePositionFront];
284 if (!frontCameraDevice) {
285 RTCLogWarning(@"Failed to find front capture device.");
286 return nil;
287 }
288 NSError *error = nil;
289 AVCaptureDeviceInput *frontCameraInput =
290 [AVCaptureDeviceInput deviceInputWithDevice:frontCameraDevice
291 error:&error];
292 if (!frontCameraInput) {
293 RTCLogError(@"Failed to create front camera input: %@",
294 error.localizedDescription);
295 return nil;
296 }
297 _frontCameraInput = frontCameraInput;
298 }
299 return _frontCameraInput;
300 }
301
302 - (AVCaptureDeviceInput *)backCameraInput {
303 if (!_backCameraInput) {
304 AVCaptureDevice *backCameraDevice =
305 [self videoCaptureDeviceForPosition:AVCaptureDevicePositionBack];
306 if (!backCameraDevice) {
307 RTCLogWarning(@"Failed to find front capture device.");
308 return nil;
309 }
310 NSError *error = nil;
311 AVCaptureDeviceInput *backCameraInput =
312 [AVCaptureDeviceInput deviceInputWithDevice:backCameraDevice
313 error:&error];
314 if (!backCameraInput) {
315 RTCLogError(@"Failed to create front camera input: %@",
316 error.localizedDescription);
317 return nil;
318 }
319 _backCameraInput = backCameraInput;
320 }
321 return _backCameraInput;
322 }
323
324 - (void)deviceOrientationDidChange:(NSNotification *)notification {
325 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
326 block:^{
327 _orientationHasChanged = YES;
328 [self updateOrientation];
329 }];
330 }
331
332 // Called from capture session queue.
333 - (void)updateOrientation {
334 AVCaptureConnection *connection =
335 [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
336 if (!connection.supportsVideoOrientation) {
337 // TODO(tkchin): set rotation bit on frames.
338 return;
339 }
340 AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationPortrait;
341 switch ([UIDevice currentDevice].orientation) {
342 case UIDeviceOrientationPortrait:
343 orientation = AVCaptureVideoOrientationPortrait;
344 break;
345 case UIDeviceOrientationPortraitUpsideDown:
346 orientation = AVCaptureVideoOrientationPortraitUpsideDown;
347 break;
348 case UIDeviceOrientationLandscapeLeft:
349 orientation = AVCaptureVideoOrientationLandscapeRight;
350 break;
351 case UIDeviceOrientationLandscapeRight:
352 orientation = AVCaptureVideoOrientationLandscapeLeft;
353 break;
354 case UIDeviceOrientationFaceUp:
355 case UIDeviceOrientationFaceDown:
356 case UIDeviceOrientationUnknown:
357 if (!_orientationHasChanged) {
358 connection.videoOrientation = orientation;
359 }
360 return;
361 }
362 connection.videoOrientation = orientation;
363 }
364
365 // Update the current session input to match what's stored in _useBackCamera.
366 - (void)updateSessionInputForUseBackCamera:(BOOL)useBackCamera {
367 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
368 block:^{
369 [_captureSession beginConfiguration];
370 AVCaptureDeviceInput *oldInput = _backCameraInput;
371 AVCaptureDeviceInput *newInput = _frontCameraInput;
372 if (useBackCamera) {
373 oldInput = _frontCameraInput;
374 newInput = _backCameraInput;
375 }
376 if (oldInput) {
377 // Ok to remove this even if it's not attached. Will be no-op.
378 [_captureSession removeInput:oldInput];
379 }
380 if (newInput) {
381 [_captureSession addInput:newInput];
382 }
383 [self updateOrientation];
384 [_captureSession commitConfiguration];
385 }];
386 }
387
388 @end
389
390 namespace webrtc {
391
392 enum AVFoundationVideoCapturerMessageType : uint32_t {
393 kMessageTypeFrame,
394 };
395
396 struct AVFoundationFrame {
397 AVFoundationFrame(CVImageBufferRef buffer, int64_t time)
398 : image_buffer(buffer), capture_time(time) {}
399 CVImageBufferRef image_buffer;
400 int64_t capture_time;
401 };
402
403 AVFoundationVideoCapturer::AVFoundationVideoCapturer()
404 : _capturer(nil), _startThread(nullptr) {
405 // Set our supported formats. This matches kDefaultPreset.
406 std::vector<cricket::VideoFormat> supportedFormats;
407 supportedFormats.push_back(cricket::VideoFormat(kDefaultFormat));
408 SetSupportedFormats(supportedFormats);
409 _capturer =
410 [[RTCAVFoundationVideoCapturerInternal alloc] initWithCapturer:this];
411 }
412
413 AVFoundationVideoCapturer::~AVFoundationVideoCapturer() {
414 _capturer = nil;
415 }
416
417 cricket::CaptureState AVFoundationVideoCapturer::Start(
418 const cricket::VideoFormat& format) {
419 if (!_capturer) {
420 LOG(LS_ERROR) << "Failed to create AVFoundation capturer.";
421 return cricket::CaptureState::CS_FAILED;
422 }
423 if (_capturer.isRunning) {
424 LOG(LS_ERROR) << "The capturer is already running.";
425 return cricket::CaptureState::CS_FAILED;
426 }
427 if (format != kDefaultFormat) {
428 LOG(LS_ERROR) << "Unsupported format provided.";
429 return cricket::CaptureState::CS_FAILED;
430 }
431
432 // Keep track of which thread capture started on. This is the thread that
433 // frames need to be sent to.
434 RTC_DCHECK(!_startThread);
435 _startThread = rtc::Thread::Current();
436
437 SetCaptureFormat(&format);
438 // This isn't super accurate because it takes a while for the AVCaptureSession
439 // to spin up, and this call returns async.
440 // TODO(tkchin): make this better.
441 [_capturer start];
442 SetCaptureState(cricket::CaptureState::CS_RUNNING);
443
444 return cricket::CaptureState::CS_STARTING;
445 }
446
447 void AVFoundationVideoCapturer::Stop() {
448 [_capturer stop];
449 SetCaptureFormat(NULL);
450 _startThread = nullptr;
451 }
452
453 bool AVFoundationVideoCapturer::IsRunning() {
454 return _capturer.isRunning;
455 }
456
457 AVCaptureSession* AVFoundationVideoCapturer::GetCaptureSession() {
458 return _capturer.captureSession;
459 }
460
461 bool AVFoundationVideoCapturer::CanUseBackCamera() const {
462 return _capturer.canUseBackCamera;
463 }
464
465 void AVFoundationVideoCapturer::SetUseBackCamera(bool useBackCamera) {
466 _capturer.useBackCamera = useBackCamera;
467 }
468
469 bool AVFoundationVideoCapturer::GetUseBackCamera() const {
470 return _capturer.useBackCamera;
471 }
472
473 void AVFoundationVideoCapturer::CaptureSampleBuffer(
474 CMSampleBufferRef sampleBuffer) {
475 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
476 !CMSampleBufferIsValid(sampleBuffer) ||
477 !CMSampleBufferDataIsReady(sampleBuffer)) {
478 return;
479 }
480
481 CVImageBufferRef image_buffer = CMSampleBufferGetImageBuffer(sampleBuffer);
482 if (image_buffer == NULL) {
483 return;
484 }
485
486 // Retain the buffer and post it to the webrtc thread. It will be released
487 // after it has successfully been signaled.
488 CVBufferRetain(image_buffer);
489 AVFoundationFrame frame(image_buffer, rtc::TimeNanos());
490 _startThread->Post(RTC_FROM_HERE, this, kMessageTypeFrame,
491 new rtc::TypedMessageData<AVFoundationFrame>(frame));
492 }
493
494 void AVFoundationVideoCapturer::OnMessage(rtc::Message *msg) {
495 switch (msg->message_id) {
496 case kMessageTypeFrame: {
497 rtc::TypedMessageData<AVFoundationFrame>* data =
498 static_cast<rtc::TypedMessageData<AVFoundationFrame>*>(msg->pdata);
499 const AVFoundationFrame& frame = data->data();
500 OnFrameMessage(frame.image_buffer, frame.capture_time);
501 delete data;
502 break;
503 }
504 }
505 }
506
507 void AVFoundationVideoCapturer::OnFrameMessage(CVImageBufferRef image_buffer,
508 int64_t capture_time) {
509 RTC_DCHECK(_startThread->IsCurrent());
510
511 // Base address must be unlocked to access frame data.
512 CVOptionFlags lock_flags = kCVPixelBufferLock_ReadOnly;
513 CVReturn ret = CVPixelBufferLockBaseAddress(image_buffer, lock_flags);
514 if (ret != kCVReturnSuccess) {
515 return;
516 }
517
518 static size_t const kYPlaneIndex = 0;
519 static size_t const kUVPlaneIndex = 1;
520 uint8_t* y_plane_address =
521 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(image_buffer,
522 kYPlaneIndex));
523 size_t y_plane_height =
524 CVPixelBufferGetHeightOfPlane(image_buffer, kYPlaneIndex);
525 size_t y_plane_width =
526 CVPixelBufferGetWidthOfPlane(image_buffer, kYPlaneIndex);
527 size_t y_plane_bytes_per_row =
528 CVPixelBufferGetBytesPerRowOfPlane(image_buffer, kYPlaneIndex);
529 size_t uv_plane_height =
530 CVPixelBufferGetHeightOfPlane(image_buffer, kUVPlaneIndex);
531 size_t uv_plane_bytes_per_row =
532 CVPixelBufferGetBytesPerRowOfPlane(image_buffer, kUVPlaneIndex);
533 size_t frame_size = y_plane_bytes_per_row * y_plane_height +
534 uv_plane_bytes_per_row * uv_plane_height;
535
536 // Sanity check assumption that planar bytes are contiguous.
537 uint8_t* uv_plane_address =
538 static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(image_buffer,
539 kUVPlaneIndex));
540 RTC_DCHECK(uv_plane_address ==
541 y_plane_address + y_plane_height * y_plane_bytes_per_row);
542
543 // Stuff data into a cricket::CapturedFrame.
544 cricket::CapturedFrame frame;
545 frame.width = y_plane_width;
546 frame.height = y_plane_height;
547 frame.pixel_width = 1;
548 frame.pixel_height = 1;
549 frame.fourcc = static_cast<uint32_t>(cricket::FOURCC_NV12);
550 frame.time_stamp = capture_time;
551 frame.data = y_plane_address;
552 frame.data_size = frame_size;
553
554 // This will call a superclass method that will perform the frame conversion
555 // to I420.
556 SignalFrameCaptured(this, &frame);
557
558 CVPixelBufferUnlockBaseAddress(image_buffer, lock_flags);
559 CVBufferRelease(image_buffer);
560 }
561
562 } // namespace webrtc
OLDNEW
« no previous file with comments | « talk/app/webrtc/objc/avfoundationvideocapturer.h ('k') | talk/app/webrtc/objc/public/RTCAVFoundationVideoSource.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698