blob: 5365d9821df6f1c3544750dcd54b663bfd3ec700 [file] [log] [blame]
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00001/*
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+Internal.h"
33
34#import <GLKit/GLKit.h>
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000035
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000036#import "RTCOpenGLVideoRenderer.h"
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000037#import "RTCVideoRenderer.h"
38#import "RTCVideoTrack.h"
39
tkchin@webrtc.org738df892014-06-04 20:19:39 +000040// RTCDisplayLinkTimer wraps a CADisplayLink and is set to fire every two screen
41// refreshes, which should be 30fps. We wrap the display link in order to avoid
42// a retain cycle since CADisplayLink takes a strong reference onto its target.
43// The timer is paused by default.
44@interface RTCDisplayLinkTimer : NSObject
45
46@property(nonatomic) BOOL isPaused;
47
48- (instancetype)initWithTimerHandler:(void (^)(void))timerHandler;
49- (void)invalidate;
50
51@end
52
53@implementation RTCDisplayLinkTimer {
54 CADisplayLink* _displayLink;
55 void (^_timerHandler)(void);
56}
57
58- (instancetype)initWithTimerHandler:(void (^)(void))timerHandler {
59 NSParameterAssert(timerHandler);
60 if (self = [super init]) {
61 _timerHandler = timerHandler;
62 _displayLink =
63 [CADisplayLink displayLinkWithTarget:self
64 selector:@selector(displayLinkDidFire:)];
65 _displayLink.paused = YES;
66 // Set to half of screen refresh, which should be 30fps.
67 [_displayLink setFrameInterval:2];
68 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop]
69 forMode:NSRunLoopCommonModes];
70 }
71 return self;
72}
73
74- (void)dealloc {
75 [self invalidate];
76}
77
78- (BOOL)isPaused {
79 return _displayLink.paused;
80}
81
82- (void)setIsPaused:(BOOL)isPaused {
83 _displayLink.paused = isPaused;
84}
85
86- (void)invalidate {
87 [_displayLink invalidate];
88}
89
90- (void)displayLinkDidFire:(CADisplayLink*)displayLink {
91 _timerHandler();
92}
93
94@end
95
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000096@interface RTCEAGLVideoView () <GLKViewDelegate>
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000097// |i420Frame| is set when we receive a frame from a worker thread and is read
98// from the display link callback so atomicity is required.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000099@property(atomic, strong) RTCI420Frame* i420Frame;
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000100@property(nonatomic, readonly) GLKView* glkView;
101@property(nonatomic, readonly) RTCOpenGLVideoRenderer* glRenderer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000102@end
103
104@implementation RTCEAGLVideoView {
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000105 RTCDisplayLinkTimer* _timer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000106 GLKView* _glkView;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000107 RTCOpenGLVideoRenderer* _glRenderer;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000108 RTCVideoRenderer* _videoRenderer;
109}
110
111- (instancetype)initWithFrame:(CGRect)frame {
112 if (self = [super initWithFrame:frame]) {
113 EAGLContext* glContext =
114 [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000115 _glRenderer = [[RTCOpenGLVideoRenderer alloc] initWithContext:glContext];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000116
117 // GLKView manages a framebuffer for us.
118 _glkView = [[GLKView alloc] initWithFrame:CGRectZero
119 context:glContext];
120 _glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
121 _glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
122 _glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
123 _glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
124 _glkView.delegate = self;
125 _glkView.layer.masksToBounds = YES;
126 [self addSubview:_glkView];
127
128 // Listen to application state in order to clean up OpenGL before app goes
129 // away.
130 NSNotificationCenter* notificationCenter =
131 [NSNotificationCenter defaultCenter];
132 [notificationCenter addObserver:self
133 selector:@selector(willResignActive)
134 name:UIApplicationWillResignActiveNotification
135 object:nil];
136 [notificationCenter addObserver:self
137 selector:@selector(didBecomeActive)
138 name:UIApplicationDidBecomeActiveNotification
139 object:nil];
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000140
141 // Frames are received on a separate thread, so we poll for current frame
142 // using a refresh rate proportional to screen refresh frequency. This
143 // occurs on the main thread.
144 __weak RTCEAGLVideoView* weakSelf = self;
145 _timer = [[RTCDisplayLinkTimer alloc] initWithTimerHandler:^{
146 RTCEAGLVideoView* strongSelf = weakSelf;
147 // Don't render if frame hasn't changed.
148 if (strongSelf.glRenderer.lastDrawnFrame == strongSelf.i420Frame) {
149 return;
150 }
151 // This tells the GLKView that it's dirty, which will then call the
152 // GLKViewDelegate method implemented below.
153 [strongSelf.glkView setNeedsDisplay];
154 }];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000155 _videoRenderer = [[RTCVideoRenderer alloc] initWithDelegate:self];
156 [self setupGL];
157 }
158 return self;
159}
160
161- (void)dealloc {
162 [[NSNotificationCenter defaultCenter] removeObserver:self];
163 UIApplicationState appState =
164 [UIApplication sharedApplication].applicationState;
165 if (appState == UIApplicationStateActive) {
166 [self teardownGL];
167 }
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000168 [_timer invalidate];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000169}
170
171- (void)setVideoTrack:(RTCVideoTrack*)videoTrack {
172 if (_videoTrack == videoTrack) {
173 return;
174 }
175 [_videoTrack removeRenderer:_videoRenderer];
176 _videoTrack = videoTrack;
177 [_videoTrack addRenderer:_videoRenderer];
178 // TODO(tkchin): potentially handle changes in track state - e.g. render
179 // black if track fails.
180}
181
182#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 {
194 if (self.i420Frame) {
195 // The renderer will draw the frame to the framebuffer corresponding to the
196 // one used by |view|.
197 [_glRenderer drawFrame:self.i420Frame];
198 }
199}
200
201#pragma mark - Private
202
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000203- (void)setupGL {
204 [_glRenderer setupGL];
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000205 _timer.isPaused = NO;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000206}
207
208- (void)teardownGL {
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000209 _timer.isPaused = YES;
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000210 [_glkView deleteDrawable];
211 [_glRenderer teardownGL];
212}
213
214- (void)didBecomeActive {
215 [self setupGL];
216}
217
218- (void)willResignActive {
219 [self teardownGL];
220}
221
222@end
223
224@implementation RTCEAGLVideoView (Internal)
225
226#pragma mark - RTCVideoRendererDelegate
227
228// These methods are called when the video track has frame information to
229// provide. This occurs on non-main thread.
230- (void)renderer:(RTCVideoRenderer*)renderer
231 didSetSize:(CGSize)size {
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000232 __weak RTCEAGLVideoView* weakSelf = self;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000233 dispatch_async(dispatch_get_main_queue(), ^{
tkchin@webrtc.org738df892014-06-04 20:19:39 +0000234 RTCEAGLVideoView* strongSelf = weakSelf;
235 [strongSelf.delegate videoView:strongSelf didChangeVideoSize:size];
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000236 });
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000237}
238
239- (void)renderer:(RTCVideoRenderer*)renderer
240 didReceiveFrame:(RTCI420Frame*)frame {
241 self.i420Frame = frame;
242}
243
244@end