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