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

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

Issue 2176623002: iOS render: Handle frame rotation in OpenGL (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@nv21_texture_render
Patch Set: Use webrtc::VideoRotation instead of int Created 4 years, 4 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 2015 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 "WebRTC/RTCEAGLVideoView.h"
12
13 #import <GLKit/GLKit.h>
14
15 #import "RTCOpenGLVideoRenderer.h"
16 #import "WebRTC//RTCVideoFrame.h"
17
18 // RTCDisplayLinkTimer wraps a CADisplayLink and is set to fire every two screen
19 // refreshes, which should be 30fps. We wrap the display link in order to avoid
20 // a retain cycle since CADisplayLink takes a strong reference onto its target.
21 // The timer is paused by default.
22 @interface RTCDisplayLinkTimer : NSObject
23
24 @property(nonatomic) BOOL isPaused;
25
26 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler;
27 - (void)invalidate;
28
29 @end
30
31 @implementation RTCDisplayLinkTimer {
32 CADisplayLink *_displayLink;
33 void (^_timerHandler)(void);
34 }
35
36 - (instancetype)initWithTimerHandler:(void (^)(void))timerHandler {
37 NSParameterAssert(timerHandler);
38 if (self = [super init]) {
39 _timerHandler = timerHandler;
40 _displayLink =
41 [CADisplayLink displayLinkWithTarget:self
42 selector:@selector(displayLinkDidFire:)];
43 _displayLink.paused = YES;
44 // Set to half of screen refresh, which should be 30fps.
45 [_displayLink setFrameInterval:2];
46 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop]
47 forMode:NSRunLoopCommonModes];
48 }
49 return self;
50 }
51
52 - (void)dealloc {
53 [self invalidate];
54 }
55
56 - (BOOL)isPaused {
57 return _displayLink.paused;
58 }
59
60 - (void)setIsPaused:(BOOL)isPaused {
61 _displayLink.paused = isPaused;
62 }
63
64 - (void)invalidate {
65 [_displayLink invalidate];
66 }
67
68 - (void)displayLinkDidFire:(CADisplayLink *)displayLink {
69 _timerHandler();
70 }
71
72 @end
73
74 // RTCEAGLVideoView wraps a GLKView which is setup with
75 // enableSetNeedsDisplay = NO for the purpose of gaining control of
76 // exactly when to call -[GLKView display]. This need for extra
77 // control is required to avoid triggering method calls on GLKView
78 // that results in attempting to bind the underlying render buffer
79 // when the drawable size would be empty which would result in the
80 // error GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. -[GLKView display] is
81 // the method that will trigger the binding of the render
82 // buffer. Because the standard behaviour of -[UIView setNeedsDisplay]
83 // is disabled for the reasons above, the RTCEAGLVideoView maintains
84 // its own |isDirty| flag.
85
86 @interface RTCEAGLVideoView () <GLKViewDelegate>
87 // |videoFrame| is set when we receive a frame from a worker thread and is read
88 // from the display link callback so atomicity is required.
89 @property(atomic, strong) RTCVideoFrame *videoFrame;
90 @property(nonatomic, readonly) GLKView *glkView;
91 @property(nonatomic, readonly) RTCOpenGLVideoRenderer *glRenderer;
92 @end
93
94 @implementation RTCEAGLVideoView {
95 RTCDisplayLinkTimer *_timer;
96 // This flag should only be set and read on the main thread (e.g. by
97 // setNeedsDisplay)
98 BOOL _isDirty;
99 }
100
101 @synthesize delegate = _delegate;
102 @synthesize videoFrame = _videoFrame;
103 @synthesize glkView = _glkView;
104 @synthesize glRenderer = _glRenderer;
105
106 - (instancetype)initWithFrame:(CGRect)frame {
107 if (self = [super initWithFrame:frame]) {
108 [self configure];
109 }
110 return self;
111 }
112
113 - (instancetype)initWithCoder:(NSCoder *)aDecoder {
114 if (self = [super initWithCoder:aDecoder]) {
115 [self configure];
116 }
117 return self;
118 }
119
120 - (void)configure {
121 EAGLContext *glContext =
122 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
123 if (!glContext) {
124 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
125 }
126 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext];
127
128 // GLKView manages a framebuffer for us.
129 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
130 context:glContext];
131 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
132 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
133 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
134 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
135 _glkView.delegate = self;
136 _glkView.layer.masksToBounds = YES;
137 _glkView.enableSetNeedsDisplay = NO;
138 [self addSubview:_glkView];
139
140 // Listen to application state in order to clean up OpenGL before app goes
141 // away.
142 NSNotificationCenter *notificationCenter =
143 [NSNotificationCenter defaultCenter];
144 [notificationCenter addObserver:self
145 selector:@selector(willResignActive)
146 name:UIApplicationWillResignActiveNotification
147 object:nil];
148 [notificationCenter addObserver:self
149 selector:@selector(didBecomeActive)
150 name:UIApplicationDidBecomeActiveNotification
151 object:nil];
152
153 // Frames are received on a separate thread, so we poll for current frame
154 // using a refresh rate proportional to screen refresh frequency. This
155 // occurs on the main thread.
156 __weak RTCEAGLVideoView *weakSelf = self;
157 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
158 RTCEAGLVideoView *strongSelf = weakSelf;
159 [strongSelf displayLinkTimerDidFire];
160 }];
161 [self setupGL];
162 }
163
164 - (void)dealloc {
165 [[NSNotificationCenter defaultCenter] removeObserver:self];
166 UIApplicationState appState =
167 [UIApplication sharedApplication].applicationState;
168 if (appState == UIApplicationStateActive) {
169 [self teardownGL];
170 }
171 [_timer invalidate];
172 }
173
174 #pragma mark - UIView
175
176 - (void)setNeedsDisplay {
177 [super setNeedsDisplay];
178 _isDirty = YES;
179 }
180
181 - (void)setNeedsDisplayInRect:(CGRect)rect {
182 [super setNeedsDisplayInRect:rect];
183 _isDirty = YES;
184 }
185
186 - (void)layoutSubviews {
187 [super layoutSubviews];
188 _glkView.frame = self.bounds;
189 }
190
191 #pragma mark - GLKViewDelegate
192
193 // This method is called when the GLKView's content is dirty and needs to be
194 // redrawn. This occurs on main thread.
195 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
196 // The renderer will draw the frame to the framebuffer corresponding to the
197 // one used by |view|.
198 [_glRenderer drawFrame:self.videoFrame];
199 }
200
201 #pragma mark - RTCVideoRenderer
202
203 // These methods may be called on non-main thread.
204 - (void)setSize:(CGSize)size {
205 __weak RTCEAGLVideoView *weakSelf = self;
206 dispatch_async(dispatch_get_main_queue(), ^{
207 RTCEAGLVideoView *strongSelf = weakSelf;
208 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
209 });
210 }
211
212 - (void)renderFrame:(RTCVideoFrame *)frame {
213 #if !TARGET_OS_IPHONE
214 // Generate the i420 frame on video send thread instead of main thread.
215 // TODO(tkchin): Remove this once RTCEAGLVideoView supports uploading
216 // CVPixelBuffer textures on OSX.
217 [frame convertBufferIfNeeded];
218 #endif
219 self.videoFrame = frame;
220 }
221
222 #pragma mark - Private
223
224 - (void)displayLinkTimerDidFire {
225 // Don't render unless video frame have changed or the view content
226 // has explicitly been marked dirty.
227 if (!_isDirty && _glRenderer.lastDrawnFrame == self.videoFrame) {
228 return;
229 }
230
231 // Always reset isDirty at this point, even if -[GLKView display]
232 // won't be called in the case the drawable size is empty.
233 _isDirty = NO;
234
235 // Only call -[GLKView display] if the drawable size is
236 // non-empty. Calling display will make the GLKView setup its
237 // render buffer if necessary, but that will fail with error
238 // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT if size is empty.
239 if (self.bounds.size.width > 0 && self.bounds.size.height > 0) {
240 [_glkView display];
241 }
242 }
243
244 - (void)setupGL {
245 self.videoFrame = nil;
246 [_glRenderer setupGL];
247 _timer.isPaused = NO;
248 }
249
250 - (void)teardownGL {
251 self.videoFrame = nil;
252 _timer.isPaused = YES;
253 [_glkView deleteDrawable];
254 [_glRenderer teardownGL];
255 }
256
257 - (void)didBecomeActive {
258 [self setupGL];
259 }
260
261 - (void)willResignActive {
262 [self teardownGL];
263 }
264
265 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698