Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * libjingle | 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. |
| 3 * Copyright 2014 Google Inc. | |
| 4 * | 3 * |
| 5 * Redistribution and use in source and binary forms, with or without | 4 * Use of this source code is governed by a BSD-style license |
| 6 * modification, are permitted provided that the following conditions are met: | 5 * that can be found in the LICENSE file in the root of the source |
| 7 * | 6 * tree. An additional intellectual property rights grant can be found |
| 8 * 1. Redistributions of source code must retain the above copyright notice, | 7 * in the file PATENTS. All contributing project authors may |
| 9 * this list of conditions and the following disclaimer. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 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 */ | 9 */ |
| 27 | 10 |
| 28 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 29 #error "This file requires ARC support." | |
| 30 #endif | |
| 31 | |
| 32 #import "RTCEAGLVideoView.h" | 11 #import "RTCEAGLVideoView.h" |
| 33 | 12 |
| 34 #import <GLKit/GLKit.h> | 13 #import <GLKit/GLKit.h> |
| 35 | 14 |
| 36 #import "RTCI420Frame.h" | 15 #import "RTCI420Frame.h" |
| 37 #import "RTCOpenGLVideoRenderer.h" | 16 #import "RTCOpenGLVideoRenderer.h" |
| 38 | 17 |
| 39 // RTCDisplayLinkTimer wraps a CADisplayLink and is set to fire every two screen | 18 // RTCDisplayLinkTimer wraps a CADisplayLink and is set to fire every two screen |
| 40 // refreshes, which should be 30fps. We wrap the display link in order to avoid | 19 // refreshes, which should be 30fps. We wrap the display link in order to avoid |
| 41 // a retain cycle since CADisplayLink takes a strong reference onto its target. | 20 // a retain cycle since CADisplayLink takes a strong reference onto its target. |
| 42 // The timer is paused by default. | 21 // The timer is paused by default. |
| 43 @interface RTCDisplayLinkTimer : NSObject | 22 @interface RTCDisplayLinkTimer : NSObject |
| 44 | 23 |
| 45 @property(nonatomic) BOOL isPaused; | 24 @property(nonatomic) BOOL isPaused; |
| 46 | 25 |
| 47 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler; | 26 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler; |
| 48 - (void)invalidate; | 27 - (void)invalidate; |
| 49 | 28 |
| 50 @end | 29 @end |
| 51 | 30 |
| 52 @implementation RTCDisplayLinkTimer { | 31 @implementation RTCDisplayLinkTimer { |
| 53 CADisplayLink* _displayLink; | 32 CADisplayLink *_displayLink; |
| 54 void (^_timerHandler)(void); | 33 void (^_timerHandler)(void); |
| 55 } | 34 } |
| 56 | 35 |
| 57 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler { | 36 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler { |
| 58 NSParameterAssert(timerHandler); | 37 NSParameterAssert(timerHandler); |
| 59 if (self = [super init]) { | 38 if (self = [super init]) { |
| 60 _timerHandler = timerHandler; | 39 _timerHandler = timerHandler; |
| 61 _displayLink = | 40 _displayLink = |
| 62 [CADisplayLink displayLinkWithTarget:self | 41 [CADisplayLink displayLinkWithTarget:self |
| 63 selector:@selector(displayLinkDidFire:)]; | 42 selector:@selector(displayLinkDidFire:)]; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 79 } | 58 } |
| 80 | 59 |
| 81 - (void)setIsPaused:(BOOL)isPaused { | 60 - (void)setIsPaused:(BOOL)isPaused { |
| 82 _displayLink.paused = isPaused; | 61 _displayLink.paused = isPaused; |
| 83 } | 62 } |
| 84 | 63 |
| 85 - (void)invalidate { | 64 - (void)invalidate { |
| 86 [_displayLink invalidate]; | 65 [_displayLink invalidate]; |
| 87 } | 66 } |
| 88 | 67 |
| 89 - (void)displayLinkDidFire:(CADisplayLink*)displayLink { | 68 - (void)displayLinkDidFire:(CADisplayLink *)displayLink { |
| 90 _timerHandler(); | 69 _timerHandler(); |
| 91 } | 70 } |
| 92 | 71 |
| 93 @end | 72 @end |
| 94 | 73 |
| 95 // RTCEAGLVideoView wraps a GLKView which is setup with | 74 // RTCEAGLVideoView wraps a GLKView which is setup with |
| 96 // enableSetNeedsDisplay = NO for the purpose of gaining control of | 75 // enableSetNeedsDisplay = NO for the purpose of gaining control of |
| 97 // exactly when to call -[GLKView display]. This need for extra | 76 // exactly when to call -[GLKView display]. This need for extra |
| 98 // control is required to avoid triggering method calls on GLKView | 77 // control is required to avoid triggering method calls on GLKView |
| 99 // that results in attempting to bind the underlying render buffer | 78 // that results in attempting to bind the underlying render buffer |
| 100 // when the drawable size would be empty which would result in the | 79 // when the drawable size would be empty which would result in the |
| 101 // error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is | 80 // error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is |
| 102 // the method that will trigger the binding of the render | 81 // the method that will trigger the binding of the render |
| 103 // buffer. Because the standard behaviour of -[UIView setNeedsDisplay] | 82 // buffer. Because the standard behaviour of -[UIView setNeedsDisplay] |
| 104 // is disabled for the reasons above, the RTCEAGLVideoView maintains | 83 // is disabled for the reasons above, the RTCEAGLVideoView maintains |
| 105 // its own |isDirty| flag. | 84 // its own |isDirty| flag. |
| 106 | 85 |
| 107 @interface RTCEAGLVideoView () <GLKViewDelegate> | 86 @interface RTCEAGLVideoView () <GLKViewDelegate> |
| 108 // |i420Frame| is set when we receive a frame from a worker thread and is read | 87 // |i420Frame| is set when we receive a frame from a worker thread and is read |
| 109 // from the display link callback so atomicity is required. | 88 // from the display link callback so atomicity is required. |
| 110 @property(atomic, strong) RTCI420Frame* i420Frame; | 89 @property(atomic, strong) RTCI420Frame *i420Frame; |
| 111 @property(nonatomic, readonly) GLKView* glkView; | 90 @property(nonatomic, readonly) GLKView *glkView; |
| 112 @property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer; | 91 @property(nonatomic, readonly) RTCOpenGLVideoRenderer *glRenderer; |
| 113 @end | 92 @end |
| 114 | 93 |
| 115 @implementation RTCEAGLVideoView { | 94 @implementation RTCEAGLVideoView { |
| 116 RTCDisplayLinkTimer* _timer; | 95 RTCDisplayLinkTimer *_timer; |
| 117 GLKView* _glkView; | |
| 118 RTCOpenGLVideoRenderer* _glRenderer; | |
| 119 // This flag should only be set and read on the main thread (e.g. by | 96 // This flag should only be set and read on the main thread (e.g. by |
| 120 // setNeedsDisplay) | 97 // setNeedsDisplay) |
| 121 BOOL _isDirty; | 98 BOOL _isDirty; |
| 122 } | 99 } |
| 123 | 100 |
| 101 @synthesize delegate = _delegate; | |
| 102 @synthesize i420Frame = _i420Frame; | |
| 103 @synthesize glkView = _glkView; | |
| 104 @synthesize glRenderer = _glRenderer; | |
| 105 | |
| 124 - (instancetype)initWithFrame:(CGRect)frame { | 106 - (instancetype)initWithFrame:(CGRect)frame { |
| 125 if (self = [super initWithFrame:frame]) { | 107 if (self = [super initWithFrame:frame]) { |
| 126 [self configure]; | 108 [self configure]; |
| 127 } | 109 } |
| 128 return self; | 110 return self; |
| 129 } | 111 } |
| 130 | 112 |
| 131 - (instancetype)initWithCoder:(NSCoder *)aDecoder { | 113 - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
| 132 if (self = [super initWithCoder:aDecoder]) { | 114 if (self = [super initWithCoder:aDecoder]) { |
| 133 [self configure]; | 115 [self configure]; |
| 134 } | 116 } |
| 135 return self; | 117 return self; |
| 136 } | 118 } |
| 137 | 119 |
| 138 - (void)configure { | 120 - (void)configure { |
| 139 EAGLContext* glContext = | 121 EAGLContext *glContext = |
| 140 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; | 122 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; |
| 141 if (!glContext) { | 123 if (!glContext) { |
| 142 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; | 124 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
| 143 } | 125 } |
| 144 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext]; | 126 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext]; |
| 145 | 127 |
| 146 // GLKView manages a framebuffer for us. | 128 // GLKView manages a framebuffer for us. |
| 147 _glkView = [[GLKView alloc] initWithFrame:CGRectZero | 129 _glkView = [[GLKView alloc] initWithFrame:CGRectZero |
| 148 context:glContext]; | 130 context:glContext]; |
| 149 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; | 131 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; |
| 150 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; | 132 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone; |
| 151 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; | 133 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone; |
| 152 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; | 134 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone; |
| 153 _glkView.delegate = self; | 135 _glkView.delegate = self; |
| 154 _glkView.layer.masksToBounds = YES; | 136 _glkView.layer.masksToBounds = YES; |
| 155 _glkView.enableSetNeedsDisplay = NO; | 137 _glkView.enableSetNeedsDisplay = NO; |
| 156 [self addSubview:_glkView]; | 138 [self addSubview:_glkView]; |
| 157 | 139 |
| 158 // Listen to application state in order to clean up OpenGL before app goes | 140 // Listen to application state in order to clean up OpenGL before app goes |
| 159 // away. | 141 // away. |
| 160 NSNotificationCenter* notificationCenter = | 142 NSNotificationCenter *notificationCenter = |
| 161 [NSNotificationCenter defaultCenter]; | 143 [NSNotificationCenter defaultCenter]; |
| 162 [notificationCenter addObserver:self | 144 [notificationCenter addObserver:self |
| 163 selector:@selector(willResignActive) | 145 selector:@selector(willResignActive) |
| 164 name:UIApplicationWillResignActiveNotification | 146 name:UIApplicationWillResignActiveNotification |
| 165 object:nil]; | 147 object:nil]; |
| 166 [notificationCenter addObserver:self | 148 [notificationCenter addObserver:self |
| 167 selector:@selector(didBecomeActive) | 149 selector:@selector(didBecomeActive) |
| 168 name:UIApplicationDidBecomeActiveNotification | 150 name:UIApplicationDidBecomeActiveNotification |
| 169 object:nil]; | 151 object:nil]; |
| 170 | 152 |
| 171 // Frames are received on a separate thread, so we poll for current frame | 153 // Frames are received on a separate thread, so we poll for current frame |
| 172 // using a refresh rate proportional to screen refresh frequency. This | 154 // using a refresh rate proportional to screen refresh frequency. This |
| 173 // occurs on the main thread. | 155 // occurs on the main thread. |
| 174 __weak RTCEAGLVideoView* weakSelf = self; | 156 __weak RTCEAGLVideoView *weakSelf = self; |
| 175 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ | 157 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{ |
| 176 RTCEAGLVideoView* strongSelf = weakSelf; | 158 RTCEAGLVideoView *strongSelf = weakSelf; |
| 177 [strongSelf displayLinkTimerDidFire]; | 159 [strongSelf displayLinkTimerDidFire]; |
| 178 }]; | 160 }]; |
| 179 [self setupGL]; | 161 [self setupGL]; |
| 180 } | 162 } |
| 181 | 163 |
| 182 - (void)dealloc { | 164 - (void)dealloc { |
| 183 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 165 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 184 UIApplicationState appState = | 166 UIApplicationState appState = |
| 185 [UIApplication sharedApplication].applicationState; | 167 [UIApplication sharedApplication].applicationState; |
| 186 if (appState == UIApplicationStateActive) { | 168 if (appState == UIApplicationStateActive) { |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 203 | 185 |
| 204 - (void)layoutSubviews { | 186 - (void)layoutSubviews { |
| 205 [super layoutSubviews]; | 187 [super layoutSubviews]; |
| 206 _glkView.frame = self.bounds; | 188 _glkView.frame = self.bounds; |
| 207 } | 189 } |
| 208 | 190 |
| 209 #pragma mark - GLKViewDelegate | 191 #pragma mark - GLKViewDelegate |
| 210 | 192 |
| 211 // This method is called when the GLKView's content is dirty and needs to be | 193 // This method is called when the GLKView's content is dirty and needs to be |
| 212 // redrawn. This occurs on main thread. | 194 // redrawn. This occurs on main thread. |
| 213 - (void)glkView:(GLKView*)view drawInRect:(CGRect)rect { | 195 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { |
| 214 // The renderer will draw the frame to the framebuffer corresponding to the | 196 // The renderer will draw the frame to the framebuffer corresponding to the |
| 215 // one used by |view|. | 197 // one used by |view|. |
| 216 [_glRenderer drawFrame:self.i420Frame]; | 198 [_glRenderer drawFrame:_i420Frame]; |
|
tkchin_webrtc
2016/01/05 17:19:08
ditto self.i420Frame
hjon
2016/01/06 00:07:17
Done.
| |
| 217 } | 199 } |
| 218 | 200 |
| 219 #pragma mark - RTCVideoRenderer | 201 #pragma mark - RTCVideoRenderer |
| 220 | 202 |
| 221 // These methods may be called on non-main thread. | 203 // These methods may be called on non-main thread. |
| 222 - (void)setSize:(CGSize)size { | 204 - (void)setSize:(CGSize)size { |
| 223 __weak RTCEAGLVideoView* weakSelf = self; | 205 __weak RTCEAGLVideoView *weakSelf = self; |
| 224 dispatch_async(dispatch_get_main_queue(), ^{ | 206 dispatch_async(dispatch_get_main_queue(), ^{ |
| 225 RTCEAGLVideoView* strongSelf = weakSelf; | 207 RTCEAGLVideoView *strongSelf = weakSelf; |
| 226 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; | 208 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size]; |
| 227 }); | 209 }); |
| 228 } | 210 } |
| 229 | 211 |
| 230 - (void)renderFrame:(RTCI420Frame*)frame { | 212 - (void)renderFrame:(RTCI420Frame *)frame { |
| 231 self.i420Frame = frame; | 213 _i420Frame = frame; |
|
tkchin_webrtc
2016/01/05 17:19:08
revert this please. self. is needed here for atomi
hjon
2016/01/06 00:07:17
Done.
| |
| 232 } | 214 } |
| 233 | 215 |
| 234 #pragma mark - Private | 216 #pragma mark - Private |
| 235 | 217 |
| 236 - (void)displayLinkTimerDidFire { | 218 - (void)displayLinkTimerDidFire { |
| 237 // Don't render unless video frame have changed or the view content | 219 // Don't render unless video frame have changed or the view content |
| 238 // has explicitly been marked dirty. | 220 // has explicitly been marked dirty. |
| 239 if (!_isDirty && _glRenderer.lastDrawnFrame == self.i420Frame) { | 221 if (!_isDirty && _glRenderer.lastDrawnFrame == _i420Frame) { |
| 240 return; | 222 return; |
| 241 } | 223 } |
| 242 | 224 |
| 243 // Always reset isDirty at this point, even if -[GLKView display] | 225 // Always reset isDirty at this point, even if -[GLKView display] |
| 244 // won't be called in the case the drawable size is empty. | 226 // won't be called in the case the drawable size is empty. |
| 245 _isDirty = NO; | 227 _isDirty = NO; |
| 246 | 228 |
| 247 // Only call -[GLKView display] if the drawable size is | 229 // Only call -[GLKView display] if the drawable size is |
| 248 // non-empty. Calling display will make the GLKView setup its | 230 // non-empty. Calling display will make the GLKView setup its |
| 249 // render buffer if necessary, but that will fail with error | 231 // render buffer if necessary, but that will fail with error |
| 250 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty. | 232 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty. |
| 251 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { | 233 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) { |
| 252 [_glkView display]; | 234 [_glkView display]; |
| 253 } | 235 } |
| 254 } | 236 } |
| 255 | 237 |
| 256 - (void)setupGL { | 238 - (void)setupGL { |
| 257 self.i420Frame = nil; | 239 _i420Frame = nil; |
| 258 [_glRenderer setupGL]; | 240 [_glRenderer setupGL]; |
| 259 _timer.isPaused = NO; | 241 _timer.isPaused = NO; |
| 260 } | 242 } |
| 261 | 243 |
| 262 - (void)teardownGL { | 244 - (void)teardownGL { |
| 263 self.i420Frame = nil; | 245 _i420Frame = nil; |
| 264 _timer.isPaused = YES; | 246 _timer.isPaused = YES; |
| 265 [_glkView deleteDrawable]; | 247 [_glkView deleteDrawable]; |
| 266 [_glRenderer teardownGL]; | 248 [_glRenderer teardownGL]; |
| 267 } | 249 } |
| 268 | 250 |
| 269 - (void)didBecomeActive { | 251 - (void)didBecomeActive { |
| 270 [self setupGL]; | 252 [self setupGL]; |
| 271 } | 253 } |
| 272 | 254 |
| 273 - (void)willResignActive { | 255 - (void)willResignActive { |
| 274 [self teardownGL]; | 256 [self teardownGL]; |
| 275 } | 257 } |
| 276 | 258 |
| 277 @end | 259 @end |
| OLD | NEW |