blob: 61040ba1025c2f23eed07f916df93d36b2955d76 [file] [log] [blame]
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00001/*
2 * libjingle
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00003 * Copyright 2014 Google Inc.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00004 *
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
tkchin@webrtc.org81257442014-11-04 23:06:15 +000032#import "RTCEAGLVideoView.h"
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000033
34#import <GLKit/GLKit.h>
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000035
tkchin@webrtc.org81257442014-11-04 23:06:15 +000036#import "RTCI420Frame.h"
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000037#import "RTCOpenGLVideoRenderer.h"
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000038
tkchin@webrtc.org738df892014-06-04 20:19:39 +000039// 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
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000095@interface RTCEAGLVideoView () <GLKViewDelegate>
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000096// |i420Frame| is set when we receive a frame from a worker thread and is read
97// from the display link callback so atomicity is required.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000098@property(atomic, strong) RTCI420Frame* i420Frame;
tkchin@webrtc.org738df892014-06-04 20:19:39 +000099@property(nonatomic, readonly) GLKView* glkView;
100@property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000101@end
102
103@implementation RTCEAGLVideoView {
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000104 RTCDisplayLinkTimer* _timer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000105 GLKView* _glkView;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000106 RTCOpenGLVideoRenderer* _glRenderer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000107}
108
109- (instancetype)initWithFrame:(CGRect)frame {
110 if (self = [super initWithFrame:frame]) {
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000111 [self configure];
112 }
113 return self;
114}
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000115
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000116- (instancetype)initWithCoder:(NSCoder *)aDecoder {
117 if (self = [super initWithCoder:aDecoder]) {
118 [self configure];
119 }
120 return self;
121}
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000122
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000123- (void)configure {
124 EAGLContext* glContext =
Zeke Chinac7d97f2015-04-20 14:33:25 -0700125 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
126 if (!glContext) {
127 glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
128 }
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000129 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext];
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000130
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000131 // GLKView manages a framebuffer for us.
132 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
133 context:glContext];
134 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
135 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
136 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
137 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
138 _glkView.delegate = self;
139 _glkView.layer.masksToBounds = YES;
140 [self addSubview:_glkView];
141
142 // Listen to application state in order to clean up OpenGL before app goes
143 // away.
144 NSNotificationCenter* notificationCenter =
145 [NSNotificationCenter defaultCenter];
146 [notificationCenter addObserver:self
147 selector:@selector(willResignActive)
148 name:UIApplicationWillResignActiveNotification
149 object:nil];
150 [notificationCenter addObserver:self
151 selector:@selector(didBecomeActive)
152 name:UIApplicationDidBecomeActiveNotification
153 object:nil];
154
155 // Frames are received on a separate thread, so we poll for current frame
156 // using a refresh rate proportional to screen refresh frequency. This
157 // occurs on the main thread.
158 __weak RTCEAGLVideoView* weakSelf = self;
159 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000160 RTCEAGLVideoView* strongSelf = weakSelf;
161 // Don't render if frame hasn't changed.
162 if (strongSelf.glRenderer.lastDrawnFrame == strongSelf.i420Frame) {
163 return;
164 }
165 // This tells the GLKView that it's dirty, which will then call the
166 // GLKViewDelegate method implemented below.
167 [strongSelf.glkView setNeedsDisplay];
168 }];
tkchin@webrtc.org7ce4a582014-12-19 20:47:35 +0000169 [self setupGL];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000170}
171
172- (void)dealloc {
173 [[NSNotificationCenter defaultCenter] removeObserver:self];
174 UIApplicationState appState =
175 [UIApplication sharedApplication].applicationState;
176 if (appState == UIApplicationStateActive) {
177 [self teardownGL];
178 }
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000179 [_timer invalidate];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000180}
181
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000182#pragma mark - UIView
183
184- (void)layoutSubviews {
185 [super layoutSubviews];
186 _glkView.frame = self.bounds;
187}
188
189#pragma mark - GLKViewDelegate
190
191// This method is called when the GLKView's content is dirty and needs to be
192// redrawn. This occurs on main thread.
193- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000194 // The renderer will draw the frame to the framebuffer corresponding to the
195 // one used by |view|.
196 [_glRenderer drawFrame:self.i420Frame];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000197}
198
tkchin@webrtc.org81257442014-11-04 23:06:15 +0000199#pragma mark - RTCVideoRenderer
200
201// These methods may be called on non-main thread.
202- (void)setSize:(CGSize)size {
203 __weak RTCEAGLVideoView* weakSelf = self;
204 dispatch_async(dispatch_get_main_queue(), ^{
205 RTCEAGLVideoView* strongSelf = weakSelf;
206 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
207 });
208}
209
210- (void)renderFrame:(RTCI420Frame*)frame {
211 self.i420Frame = frame;
212}
213
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000214#pragma mark - Private
215
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000216- (void)setupGL {
tkchin@webrtc.org81257442014-11-04 23:06:15 +0000217 self.i420Frame = nil;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000218 [_glRenderer setupGL];
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000219 _timer.isPaused = NO;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000220}
221
222- (void)teardownGL {
tkchin@webrtc.org81257442014-11-04 23:06:15 +0000223 self.i420Frame = nil;
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000224 _timer.isPaused = YES;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000225 [_glkView deleteDrawable];
226 [_glRenderer teardownGL];
227}
228
229- (void)didBecomeActive {
230 [self setupGL];
231}
232
233- (void)willResignActive {
234 [self teardownGL];
235}
236
237@end