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

Side by Side Diff: webrtc/sdk/objc/Framework/Classes/RTCCameraVideoCapturer.m

Issue 2776703002: New RTCCameraVideoCapturer. (Closed)
Patch Set: Address denicija's comments. Created 3 years, 8 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 * Copyright 2017 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 #import <Foundation/Foundation.h>
12
13 #import "WebRTC/RTCCameraVideoCapturer.h"
14 #import "WebRTC/RTCLogging.h"
15
16 #if TARGET_OS_IPHONE
17 #import "WebRTC/UIDevice+RTCDevice.h"
18 #endif
19
20 #import "RTCDispatcher+Private.h"
21
22 const int64_t kNanosecondsPerSecond = 1000000000;
23
24 static inline BOOL IsMediaSubTypeSupported(FourCharCode mediaSubType) {
25 return (mediaSubType == kCVPixelFormatType_420YpCbCr8PlanarFullRange ||
26 mediaSubType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
27 }
28
29 @interface RTCCameraVideoCapturer ()<AVCaptureVideoDataOutputSampleBufferDelegat e>
30 @property(nonatomic, readonly) dispatch_queue_t frameQueue;
31 @end
32
33 @implementation RTCCameraVideoCapturer {
34 AVCaptureVideoDataOutput *_videoDataOutput;
35 AVCaptureSession *_captureSession;
36 AVCaptureDevice *_currentDevice;
37 RTCVideoRotation _rotation;
38 BOOL _hasRetriedOnFatalError;
39 BOOL _isRunning;
40 // Will the session be running once all asynchronous operations have been comp leted?
41 BOOL _willBeRunning;
42 }
43
44 @synthesize frameQueue = _frameQueue;
45 @synthesize captureSession = _captureSession;
46
47 - (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)delegate {
48 if (self = [super initWithDelegate:delegate]) {
49 // Create the capture session and all relevant inputs and outputs. We need
50 // to do this in init because the application may want the capture session
51 // before we start the capturer for e.g. AVCapturePreviewLayer. All objects
52 // created here are retained until dealloc and never recreated.
53 if (![self setupCaptureSession]) {
54 return nil;
55 }
56 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
57 #if TARGET_OS_IPHONE
58 [center addObserver:self
59 selector:@selector(deviceOrientationDidChange:)
60 name:UIDeviceOrientationDidChangeNotification
61 object:nil];
62 [center addObserver:self
63 selector:@selector(handleCaptureSessionInterruption:)
64 name:AVCaptureSessionWasInterruptedNotification
65 object:_captureSession];
66 [center addObserver:self
67 selector:@selector(handleCaptureSessionInterruptionEnded:)
68 name:AVCaptureSessionInterruptionEndedNotification
69 object:_captureSession];
70 [center addObserver:self
71 selector:@selector(handleApplicationDidBecomeActive:)
72 name:UIApplicationDidBecomeActiveNotification
73 object:[UIApplication sharedApplication]];
74 #endif
75 [center addObserver:self
76 selector:@selector(handleCaptureSessionRuntimeError:)
77 name:AVCaptureSessionRuntimeErrorNotification
78 object:_captureSession];
79 [center addObserver:self
80 selector:@selector(handleCaptureSessionDidStartRunning:)
81 name:AVCaptureSessionDidStartRunningNotification
82 object:_captureSession];
83 [center addObserver:self
84 selector:@selector(handleCaptureSessionDidStopRunning:)
85 name:AVCaptureSessionDidStopRunningNotification
86 object:_captureSession];
87 }
88 return self;
89 }
90
91 - (void)dealloc {
92 NSAssert(
93 !_willBeRunning,
94 @"Session was still running in RTCCameraVideoCapturer dealloc. Forgot to c all stopCapture?");
95 [[NSNotificationCenter defaultCenter] removeObserver:self];
96 }
97
98 + (NSArray<AVCaptureDevice *> *)captureDevices {
99 return [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
100 }
101
102 + (NSArray<AVCaptureDeviceFormat *> *)supportedFormatsForDevice:(AVCaptureDevice *)device {
103 NSMutableArray<AVCaptureDeviceFormat *> *eligibleDeviceFormats = [NSMutableArr ay array];
104
105 for (AVCaptureDeviceFormat *format in device.formats) {
106 // Filter out subTypes that we currently don't support in the stack
107 FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(format.format Description);
108 if (IsMediaSubTypeSupported(mediaSubType)) {
109 [eligibleDeviceFormats addObject:format];
110 }
111 }
112
113 return eligibleDeviceFormats;
114 }
115
116 - (void)startCaptureWithDevice:(AVCaptureDevice *)device
117 format:(AVCaptureDeviceFormat *)format
118 fps:(int)fps {
119 _willBeRunning = true;
120 [RTCDispatcher
121 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
122 block:^{
123 RTCLogInfo("startCaptureWithDevice %@ @ %d fps", format, f ps);
124
125 #if TARGET_OS_IPHONE
126 [[UIDevice currentDevice] beginGeneratingDeviceOrientation Notifications];
127 #endif
128
129 _currentDevice = device;
130
131 NSError *error = nil;
132 if ([_currentDevice lockForConfiguration:&error]) {
133 [self updateDeviceCaptureFormat:format fps:fps];
134 } else {
135 RTCLogError(@"Failed to lock device %@. Error: %@", _cur rentDevice,
136 error.userInfo);
137 return;
138 }
139
140 [self reconfigureCaptureSessionInput];
141 [self updateOrientation];
142 [_captureSession startRunning];
143
144 [_currentDevice unlockForConfiguration];
145 _isRunning = true;
146 }];
147 }
148
149 - (void)stopCapture {
150 _willBeRunning = false;
151 [RTCDispatcher
152 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
153 block:^{
154 RTCLogInfo("Stop");
155 _currentDevice = nil;
156 for (AVCaptureDeviceInput *oldInput in [_captureSession.in puts copy]) {
157 [_captureSession removeInput:oldInput];
158 }
159 [_captureSession stopRunning];
160
161 #if TARGET_OS_IPHONE
162 [[UIDevice currentDevice] endGeneratingDeviceOrientationNo tifications];
163 #endif
164 _isRunning = false;
165 }];
166 }
167
168 #pragma mark iOS notifications
169
170 #if TARGET_OS_IPHONE
171 - (void)deviceOrientationDidChange:(NSNotification *)notification {
172 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
173 block:^{
174 [self updateOrientation];
175 }];
176 }
177 #endif
178
179 #pragma mark AVCaptureVideoDataOutputSampleBufferDelegate
180
181 - (void)captureOutput:(AVCaptureOutput *)captureOutput
182 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
183 fromConnection:(AVCaptureConnection *)connection {
184 NSParameterAssert(captureOutput == _videoDataOutput);
185
186 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(s ampleBuffer) ||
187 !CMSampleBufferDataIsReady(sampleBuffer)) {
188 return;
189 }
190
191 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
192 if (pixelBuffer == nil) {
193 return;
194 }
195
196 int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp( sampleBuffer)) *
197 kNanosecondsPerSecond;
198 RTCVideoFrame *videoFrame = [[RTCVideoFrame alloc] initWithPixelBuffer:pixelBu ffer
199 rotation:_rotati on
200 timeStampNs:timeSta mpNs];
201 [self.delegate capturer:self didCaptureVideoFrame:videoFrame];
202 }
203
204 - (void)captureOutput:(AVCaptureOutput *)captureOutput
205 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
206 fromConnection:(AVCaptureConnection *)connection {
207 RTCLogError(@"Dropped sample buffer.");
208 }
209
210 #pragma mark - AVCaptureSession notifications
211
212 - (void)handleCaptureSessionInterruption:(NSNotification *)notification {
213 NSString *reasonString = nil;
214 #if defined(__IPHONE_9_0) && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \
215 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
216 if ([UIDevice isIOS9OrLater]) {
217 NSNumber *reason = notification.userInfo[AVCaptureSessionInterruptionReasonK ey];
218 if (reason) {
219 switch (reason.intValue) {
220 case AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableInBackgrou nd:
221 reasonString = @"VideoDeviceNotAvailableInBackground";
222 break;
223 case AVCaptureSessionInterruptionReasonAudioDeviceInUseByAnotherClient:
224 reasonString = @"AudioDeviceInUseByAnotherClient";
225 break;
226 case AVCaptureSessionInterruptionReasonVideoDeviceInUseByAnotherClient:
227 reasonString = @"VideoDeviceInUseByAnotherClient";
228 break;
229 case AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultip leForegroundApps:
230 reasonString = @"VideoDeviceNotAvailableWithMultipleForegroundApps";
231 break;
232 }
233 }
234 }
235 #endif
236 RTCLog(@"Capture session interrupted: %@", reasonString);
237 }
238
239 - (void)handleCaptureSessionInterruptionEnded:(NSNotification *)notification {
240 RTCLog(@"Capture session interruption ended.");
241 }
242
243 - (void)handleCaptureSessionRuntimeError:(NSNotification *)notification {
244 NSError *error = [notification.userInfo objectForKey:AVCaptureSessionErrorKey] ;
245 RTCLogError(@"Capture session runtime error: %@", error);
246
247 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
248 block:^{
249 #if TARGET_OS_IPHONE
250 if (error.code == AVErrorMediaServicesWereReset ) {
251 [self handleNonFatalError];
252 } else {
253 [self handleFatalError];
254 }
255 #else
256 [self handleFatalError];
257 #endif
258 }];
259 }
260
261 - (void)handleCaptureSessionDidStartRunning:(NSNotification *)notification {
262 RTCLog(@"Capture session started.");
263
264 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
265 block:^{
266 // If we successfully restarted after an unknow n error,
267 // allow future retries on fatal errors.
268 _hasRetriedOnFatalError = NO;
269 }];
270 }
271
272 - (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification {
273 RTCLog(@"Capture session stopped.");
274 }
275
276 - (void)handleFatalError {
277 [RTCDispatcher
278 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
279 block:^{
280 if (!_hasRetriedOnFatalError) {
281 RTCLogWarning(@"Attempting to recover from fatal capture error.");
282 [self handleNonFatalError];
283 _hasRetriedOnFatalError = YES;
284 } else {
285 RTCLogError(@"Previous fatal error recovery failed.");
286 }
287 }];
288 }
289
290 - (void)handleNonFatalError {
291 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
292 block:^{
293 RTCLog(@"Restarting capture session after error .");
294 if (_isRunning) {
295 [_captureSession startRunning];
296 }
297 }];
298 }
299
300 #if TARGET_OS_IPHONE
301
302 #pragma mark - UIApplication notifications
303
304 - (void)handleApplicationDidBecomeActive:(NSNotification *)notification {
305 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession
306 block:^{
307 if (_isRunning && !_captureSession.isRunning) {
308 RTCLog(@"Restarting capture session on active .");
309 [_captureSession startRunning];
310 }
311 }];
312 }
313
314 #endif // TARGET_OS_IPHONE
315
316 #pragma mark - Private
317
318 - (dispatch_queue_t)frameQueue {
319 if (!_frameQueue) {
320 _frameQueue =
321 dispatch_queue_create("org.webrtc.avfoundationvideocapturer.video", DISP ATCH_QUEUE_SERIAL);
322 dispatch_set_target_queue(_frameQueue,
323 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_ HIGH, 0));
324 }
325 return _frameQueue;
326 }
327
328 - (BOOL)setupCaptureSession {
329 NSAssert(_captureSession == nil, @"Setup capture session called twice.");
330 _captureSession = [[AVCaptureSession alloc] init];
331 #if defined(WEBRTC_IOS)
332 _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;
333 _captureSession.usesApplicationAudioSession = NO;
334 #endif
335 [self setupVideoDataOutput];
336 // Add the output.
337 if (![_captureSession canAddOutput:_videoDataOutput]) {
338 RTCLogError(@"Video data output unsupported.");
339 return NO;
340 }
341 [_captureSession addOutput:_videoDataOutput];
342
343 return YES;
344 }
345
346 - (void)setupVideoDataOutput {
347 NSAssert(_videoDataOutput == nil, @"Setup video data output called twice.");
348 // Make the capturer output NV12. Ideally we want I420 but that's not
349 // currently supported on iPhone / iPad.
350 AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
351 videoDataOutput.videoSettings = @{
352 (NSString *)
353 // TODO(denicija): Remove this color conversion and use the original capture format directly.
354 kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFu llRange)
355 };
356 videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
357 [videoDataOutput setSampleBufferDelegate:self queue:self.frameQueue];
358 _videoDataOutput = videoDataOutput;
359 }
360
361 #pragma mark - Private, called inside capture queue
tkchin_webrtc 2017/04/06 18:13:45 assert queue in these methods?
sakal 2017/04/07 10:31:38 Done.
362
363 - (void)updateDeviceCaptureFormat:(AVCaptureDeviceFormat *)format fps:(int)fps {
364 @try {
365 _currentDevice.activeFormat = format;
366 _currentDevice.activeVideoMinFrameDuration = CMTimeMake(1, fps);
367 } @catch (NSException *exception) {
368 RTCLogError(@"Failed to set active format!\n User info:%@", exception.userIn fo);
369 return;
370 }
371 }
372
373 - (void)reconfigureCaptureSessionInput {
374 NSError *error = nil;
375 AVCaptureDeviceInput *input =
376 [AVCaptureDeviceInput deviceInputWithDevice:_currentDevice error:&error];
377 if (!input) {
378 RTCLogError(@"Failed to create front camera input: %@", error.localizedDescr iption);
379 return;
380 }
381 for (AVCaptureDeviceInput *oldInput in [_captureSession.inputs copy]) {
382 [_captureSession removeInput:oldInput];
383 }
384 if ([_captureSession canAddInput:input]) {
385 [_captureSession addInput:input];
386 } else {
387 RTCLogError(@"Cannot add camera as an input to the session.");
388 return;
389 }
390 }
391
392 - (void)updateOrientation {
393 #if TARGET_OS_IPHONE
394 BOOL usingFrontCamera = _currentDevice.position == AVCaptureDevicePositionFron t;
395 switch ([UIDevice currentDevice].orientation) {
396 case UIDeviceOrientationPortrait:
397 _rotation = RTCVideoRotation_90;
398 break;
399 case UIDeviceOrientationPortraitUpsideDown:
400 _rotation = RTCVideoRotation_270;
401 break;
402 case UIDeviceOrientationLandscapeLeft:
403 _rotation = usingFrontCamera ? RTCVideoRotation_180 : RTCVideoRotation_0;
404 break;
405 case UIDeviceOrientationLandscapeRight:
406 _rotation = usingFrontCamera ? RTCVideoRotation_0 : RTCVideoRotation_180;
407 break;
408 case UIDeviceOrientationFaceUp:
409 case UIDeviceOrientationFaceDown:
410 case UIDeviceOrientationUnknown:
411 // Ignore.
412 break;
413 }
414 #endif
415 }
416
417 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698