OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. | 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #import <Foundation/Foundation.h> | 11 #import <Foundation/Foundation.h> |
12 | 12 |
13 #import "WebRTC/RTCCameraVideoCapturer.h" | 13 #import "WebRTC/RTCCameraVideoCapturer.h" |
14 #import "WebRTC/RTCLogging.h" | 14 #import "WebRTC/RTCLogging.h" |
15 #import "WebRTC/RTCVideoFrameBuffer.h" | 15 #import "WebRTC/RTCVideoFrameBuffer.h" |
16 | 16 |
17 #if TARGET_OS_IPHONE | 17 #if TARGET_OS_IPHONE |
18 #import "WebRTC/UIDevice+RTCDevice.h" | 18 #import "WebRTC/UIDevice+RTCDevice.h" |
19 #endif | 19 #endif |
20 | 20 |
21 #import "RTCDispatcher+Private.h" | 21 #import "RTCDispatcher+Private.h" |
22 #import "RTCImageHelper.h" | |
22 | 23 |
23 const int64_t kNanosecondsPerSecond = 1000000000; | 24 const int64_t kNanosecondsPerSecond = 1000000000; |
24 | 25 |
25 static inline BOOL IsMediaSubTypeSupported(FourCharCode mediaSubType) { | 26 static inline BOOL IsMediaSubTypeSupported(FourCharCode mediaSubType) { |
26 return (mediaSubType == kCVPixelFormatType_420YpCbCr8PlanarFullRange || | 27 return (mediaSubType == kCVPixelFormatType_420YpCbCr8PlanarFullRange || |
27 mediaSubType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange); | 28 mediaSubType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange); |
28 } | 29 } |
29 | 30 |
30 @interface RTCCameraVideoCapturer ()<AVCaptureVideoDataOutputSampleBufferDelegat e> | 31 @interface RTCCameraVideoCapturer ()<AVCaptureVideoDataOutputSampleBufferDelegat e> |
31 @property(nonatomic, readonly) dispatch_queue_t frameQueue; | 32 @property(nonatomic, readonly) dispatch_queue_t frameQueue; |
33 @property(assign) BOOL switchingCameras; | |
magjed_webrtc
2017/07/24 10:12:50
It looks like we are accessing this variable from
| |
32 @end | 34 @end |
33 | 35 |
34 @implementation RTCCameraVideoCapturer { | 36 @implementation RTCCameraVideoCapturer { |
35 AVCaptureVideoDataOutput *_videoDataOutput; | 37 AVCaptureVideoDataOutput *_videoDataOutput; |
36 AVCaptureSession *_captureSession; | 38 AVCaptureSession *_captureSession; |
37 AVCaptureDevice *_currentDevice; | 39 AVCaptureDevice *_currentDevice; |
38 RTCVideoRotation _rotation; | |
39 BOOL _hasRetriedOnFatalError; | 40 BOOL _hasRetriedOnFatalError; |
40 BOOL _isRunning; | 41 BOOL _isRunning; |
41 // Will the session be running once all asynchronous operations have been comp leted? | 42 // Will the session be running once all asynchronous operations have been comp leted? |
42 BOOL _willBeRunning; | 43 BOOL _willBeRunning; |
44 BOOL _switchingCameras; | |
45 #if TARGET_OS_IPHONE | |
46 UIDeviceOrientation _orientation; | |
47 #endif | |
43 } | 48 } |
44 | 49 |
45 @synthesize frameQueue = _frameQueue; | 50 @synthesize frameQueue = _frameQueue; |
46 @synthesize captureSession = _captureSession; | 51 @synthesize captureSession = _captureSession; |
52 @synthesize switchingCameras = _switchingCameras; | |
47 | 53 |
48 - (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)delegate { | 54 - (instancetype)initWithDelegate:(__weak id<RTCVideoCapturerDelegate>)delegate { |
49 if (self = [super initWithDelegate:delegate]) { | 55 if (self = [super initWithDelegate:delegate]) { |
50 // Create the capture session and all relevant inputs and outputs. We need | 56 // Create the capture session and all relevant inputs and outputs. We need |
51 // to do this in init because the application may want the capture session | 57 // to do this in init because the application may want the capture session |
52 // before we start the capturer for e.g. AVCapturePreviewLayer. All objects | 58 // before we start the capturer for e.g. AVCapturePreviewLayer. All objects |
53 // created here are retained until dealloc and never recreated. | 59 // created here are retained until dealloc and never recreated. |
54 if (![self setupCaptureSession]) { | 60 if (![self setupCaptureSession]) { |
55 return nil; | 61 return nil; |
56 } | 62 } |
57 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; | 63 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; |
64 self.switchingCameras = NO; | |
58 #if TARGET_OS_IPHONE | 65 #if TARGET_OS_IPHONE |
66 _orientation = UIDeviceOrientationPortrait; | |
59 [center addObserver:self | 67 [center addObserver:self |
60 selector:@selector(deviceOrientationDidChange:) | 68 selector:@selector(deviceOrientationDidChange:) |
61 name:UIDeviceOrientationDidChangeNotification | 69 name:UIDeviceOrientationDidChangeNotification |
62 object:nil]; | 70 object:nil]; |
63 [center addObserver:self | 71 [center addObserver:self |
64 selector:@selector(handleCaptureSessionInterruption:) | 72 selector:@selector(handleCaptureSessionInterruption:) |
65 name:AVCaptureSessionWasInterruptedNotification | 73 name:AVCaptureSessionWasInterruptedNotification |
66 object:_captureSession]; | 74 object:_captureSession]; |
67 [center addObserver:self | 75 [center addObserver:self |
68 selector:@selector(handleCaptureSessionInterruptionEnded:) | 76 selector:@selector(handleCaptureSessionInterruptionEnded:) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
110 [eligibleDeviceFormats addObject:format]; | 118 [eligibleDeviceFormats addObject:format]; |
111 } | 119 } |
112 } | 120 } |
113 | 121 |
114 return eligibleDeviceFormats; | 122 return eligibleDeviceFormats; |
115 } | 123 } |
116 | 124 |
117 - (void)startCaptureWithDevice:(AVCaptureDevice *)device | 125 - (void)startCaptureWithDevice:(AVCaptureDevice *)device |
118 format:(AVCaptureDeviceFormat *)format | 126 format:(AVCaptureDeviceFormat *)format |
119 fps:(NSInteger)fps { | 127 fps:(NSInteger)fps { |
120 _willBeRunning = true; | 128 _willBeRunning = YES; |
121 [RTCDispatcher | 129 [RTCDispatcher |
122 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession | 130 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession |
123 block:^{ | 131 block:^{ |
124 RTCLogInfo("startCaptureWithDevice %@ @ %zd fps", format, fps); | 132 RTCLogInfo("startCaptureWithDevice %@ @ %zd fps", format, fps); |
125 | 133 |
126 #if TARGET_OS_IPHONE | 134 #if TARGET_OS_IPHONE |
127 [[UIDevice currentDevice] beginGeneratingDeviceOrientation Notifications]; | 135 [[UIDevice currentDevice] beginGeneratingDeviceOrientation Notifications]; |
128 #endif | 136 #endif |
129 | 137 |
130 _currentDevice = device; | 138 _currentDevice = device; |
131 | 139 |
132 NSError *error = nil; | 140 NSError *error = nil; |
133 if (![_currentDevice lockForConfiguration:&error]) { | 141 if (![_currentDevice lockForConfiguration:&error]) { |
134 RTCLogError( | 142 RTCLogError( |
135 @"Failed to lock device %@. Error: %@", _currentDevi ce, error.userInfo); | 143 @"Failed to lock device %@. Error: %@", _currentDevi ce, error.userInfo); |
136 return; | 144 return; |
137 } | 145 } |
138 | 146 |
147 self.switchingCameras = YES; | |
139 [self reconfigureCaptureSessionInput]; | 148 [self reconfigureCaptureSessionInput]; |
140 [self updateOrientation]; | 149 [self updateOrientation]; |
141 [_captureSession startRunning]; | 150 [_captureSession startRunning]; |
142 [self updateDeviceCaptureFormat:format fps:fps]; | 151 [self updateDeviceCaptureFormat:format fps:fps]; |
143 [_currentDevice unlockForConfiguration]; | 152 [_currentDevice unlockForConfiguration]; |
144 _isRunning = true; | 153 self.switchingCameras = NO; |
154 _isRunning = YES; | |
145 }]; | 155 }]; |
146 } | 156 } |
147 | 157 |
148 - (void)stopCapture { | 158 - (void)stopCapture { |
149 _willBeRunning = false; | 159 _willBeRunning = NO; |
150 [RTCDispatcher | 160 [RTCDispatcher |
151 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession | 161 dispatchAsyncOnType:RTCDispatcherTypeCaptureSession |
152 block:^{ | 162 block:^{ |
153 RTCLogInfo("Stop"); | 163 RTCLogInfo("Stop"); |
154 _currentDevice = nil; | 164 _currentDevice = nil; |
155 for (AVCaptureDeviceInput *oldInput in [_captureSession.in puts copy]) { | 165 for (AVCaptureDeviceInput *oldInput in [_captureSession.in puts copy]) { |
156 [_captureSession removeInput:oldInput]; | 166 [_captureSession removeInput:oldInput]; |
157 } | 167 } |
158 [_captureSession stopRunning]; | 168 [_captureSession stopRunning]; |
159 | 169 |
160 #if TARGET_OS_IPHONE | 170 #if TARGET_OS_IPHONE |
161 [[UIDevice currentDevice] endGeneratingDeviceOrientationNo tifications]; | 171 [[UIDevice currentDevice] endGeneratingDeviceOrientationNo tifications]; |
162 #endif | 172 #endif |
163 _isRunning = false; | 173 _isRunning = NO; |
164 }]; | 174 }]; |
165 } | 175 } |
166 | 176 |
167 #pragma mark iOS notifications | 177 #pragma mark iOS notifications |
168 | 178 |
169 #if TARGET_OS_IPHONE | 179 #if TARGET_OS_IPHONE |
170 - (void)deviceOrientationDidChange:(NSNotification *)notification { | 180 - (void)deviceOrientationDidChange:(NSNotification *)notification { |
171 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession | 181 [RTCDispatcher dispatchAsyncOnType:RTCDispatcherTypeCaptureSession |
172 block:^{ | 182 block:^{ |
173 [self updateOrientation]; | 183 [self updateOrientation]; |
(...skipping 11 matching lines...) Expand all Loading... | |
185 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(s ampleBuffer) || | 195 if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(s ampleBuffer) || |
186 !CMSampleBufferDataIsReady(sampleBuffer)) { | 196 !CMSampleBufferDataIsReady(sampleBuffer)) { |
187 return; | 197 return; |
188 } | 198 } |
189 | 199 |
190 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); | 200 CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); |
191 if (pixelBuffer == nil) { | 201 if (pixelBuffer == nil) { |
192 return; | 202 return; |
193 } | 203 } |
194 | 204 |
205 #if TARGET_OS_IPHONE | |
206 // Default to portrait orientation on iPhone. | |
207 RTCVideoRotation rotation = RTCVideoRotation_90; | |
208 // Check here, which camera this frame is from, to avoid any race conditions. | |
209 AVCaptureDeviceInput *deviceInput = | |
210 (AVCaptureDeviceInput *)((AVCaptureInputPort *)connection.inputPorts.first Object).input; | |
211 BOOL usingFrontCamera = deviceInput.device.position == AVCaptureDevicePosition Front; | |
212 // We gate checking the image's EXIF only if we're switching cameras as we don 't need to parse | |
213 // the image's attachments and dictionaries for every video image. | |
214 if (self.switchingCameras) { | |
215 // Check the image's EXIF for the actual camera the image came as the image could have been | |
216 // delayed as we set alwaysDiscardsLateVideoFrames to NO. | |
217 usingFrontCamera = [RTCImageHelper isFrontCameraFromSampleBuffer:sampleBuffe r]; | |
magjed_webrtc
2017/07/24 10:12:50
Is this method guaranteed to work on all devices?
jtt_webrtc
2017/07/24 16:40:36
I've tested it with an iPhone SE and iPhone 7 and
| |
218 } | |
219 switch (_orientation) { | |
220 case UIDeviceOrientationPortrait: | |
221 rotation = RTCVideoRotation_90; | |
222 break; | |
223 case UIDeviceOrientationPortraitUpsideDown: | |
224 rotation = RTCVideoRotation_270; | |
225 break; | |
226 case UIDeviceOrientationLandscapeLeft: | |
227 rotation = usingFrontCamera ? RTCVideoRotation_180 : RTCVideoRotation_0; | |
228 break; | |
229 case UIDeviceOrientationLandscapeRight: | |
230 rotation = usingFrontCamera ? RTCVideoRotation_0 : RTCVideoRotation_180; | |
231 break; | |
232 case UIDeviceOrientationFaceUp: | |
233 case UIDeviceOrientationFaceDown: | |
234 case UIDeviceOrientationUnknown: | |
235 // Ignore. | |
236 break; | |
237 } | |
238 #else | |
239 // No rotation on Mac. | |
240 RTCVideoRotation rotation = RTCVideoRotation_0; | |
241 #endif | |
242 | |
195 RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuff er:pixelBuffer]; | 243 RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuff er:pixelBuffer]; |
196 int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp( sampleBuffer)) * | 244 int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp( sampleBuffer)) * |
197 kNanosecondsPerSecond; | 245 kNanosecondsPerSecond; |
198 RTCVideoFrame *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuff er | 246 RTCVideoFrame *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuff er |
199 rotation:_rotation | 247 rotation:rotation |
200 timeStampNs:timeStampNs] ; | 248 timeStampNs:timeStampNs] ; |
201 [self.delegate capturer:self didCaptureVideoFrame:videoFrame]; | 249 [self.delegate capturer:self didCaptureVideoFrame:videoFrame]; |
202 } | 250 } |
203 | 251 |
204 - (void)captureOutput:(AVCaptureOutput *)captureOutput | 252 - (void)captureOutput:(AVCaptureOutput *)captureOutput |
205 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer | 253 didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer |
206 fromConnection:(AVCaptureConnection *)connection { | 254 fromConnection:(AVCaptureConnection *)connection { |
207 RTCLogError(@"Dropped sample buffer."); | 255 RTCLogError(@"Dropped sample buffer."); |
208 } | 256 } |
209 | 257 |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
392 } else { | 440 } else { |
393 RTCLogError(@"Cannot add camera as an input to the session."); | 441 RTCLogError(@"Cannot add camera as an input to the session."); |
394 } | 442 } |
395 [_captureSession commitConfiguration]; | 443 [_captureSession commitConfiguration]; |
396 } | 444 } |
397 | 445 |
398 - (void)updateOrientation { | 446 - (void)updateOrientation { |
399 NSAssert([RTCDispatcher isOnQueueForType:RTCDispatcherTypeCaptureSession], | 447 NSAssert([RTCDispatcher isOnQueueForType:RTCDispatcherTypeCaptureSession], |
400 @"updateOrientation must be called on the capture queue."); | 448 @"updateOrientation must be called on the capture queue."); |
401 #if TARGET_OS_IPHONE | 449 #if TARGET_OS_IPHONE |
402 BOOL usingFrontCamera = _currentDevice.position == AVCaptureDevicePositionFron t; | 450 _orientation = [UIDevice currentDevice].orientation; |
403 switch ([UIDevice currentDevice].orientation) { | |
404 case UIDeviceOrientationPortrait: | |
405 _rotation = RTCVideoRotation_90; | |
406 break; | |
407 case UIDeviceOrientationPortraitUpsideDown: | |
408 _rotation = RTCVideoRotation_270; | |
409 break; | |
410 case UIDeviceOrientationLandscapeLeft: | |
411 _rotation = usingFrontCamera ? RTCVideoRotation_180 : RTCVideoRotation_0; | |
412 break; | |
413 case UIDeviceOrientationLandscapeRight: | |
414 _rotation = usingFrontCamera ? RTCVideoRotation_0 : RTCVideoRotation_180; | |
415 break; | |
416 case UIDeviceOrientationFaceUp: | |
417 case UIDeviceOrientationFaceDown: | |
418 case UIDeviceOrientationUnknown: | |
419 // Ignore. | |
420 break; | |
421 } | |
422 #endif | 451 #endif |
423 } | 452 } |
424 | 453 |
425 @end | 454 @end |
OLD | NEW |