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

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

Issue 2964703002: [iOS] Fix incorrectly oriented frames when rapidly switching between cameras. (Closed)
Patch Set: Moved common code to new class. Created 3 years, 5 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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698