blob: 48e7b9aad1efe7adf03d7a259765dfcc9eedb707 [file] [log] [blame]
denicija070d5e32017-02-26 11:44:13 -08001/*
2 * Copyright 2017 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/RTCMTLVideoView.h"
12
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
16#import "WebRTC/RTCLogging.h"
17#import "WebRTC/RTCVideoFrame.h"
Anders Carlssone5960ce2017-06-22 15:26:30 +020018#import "WebRTC/RTCVideoFrameBuffer.h"
denicija070d5e32017-02-26 11:44:13 -080019
denicijad2088152017-04-28 02:14:54 -070020#import "RTCMTLI420Renderer.h"
denicija070d5e32017-02-26 11:44:13 -080021#import "RTCMTLNV12Renderer.h"
Peter Hanspers1c62b982018-05-03 14:06:04 +020022#import "RTCMTLRGBRenderer.h"
denicija070d5e32017-02-26 11:44:13 -080023
kthelgason954d9b92017-03-09 03:36:58 -080024// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
25// Linking errors occur when compiling for architectures that don't support Metal.
26#define MTKViewClass NSClassFromString(@"MTKView")
27#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
denicijad2088152017-04-28 02:14:54 -070028#define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
Peter Hanspers1c62b982018-05-03 14:06:04 +020029#define RTCMTLRGBRendererClass NSClassFromString(@"RTCMTLRGBRenderer")
kthelgason954d9b92017-03-09 03:36:58 -080030
denicija070d5e32017-02-26 11:44:13 -080031@interface RTCMTLVideoView () <MTKViewDelegate>
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020032@property(nonatomic) RTCMTLI420Renderer *rendererI420;
33@property(nonatomic) RTCMTLNV12Renderer *rendererNV12;
34@property(nonatomic) RTCMTLRGBRenderer *rendererRGB;
35@property(nonatomic) MTKView *metalView;
36@property(atomic) RTCVideoFrame *videoFrame;
37@property(nonatomic) CGSize videoFrameSize;
38@property(nonatomic) int64_t lastFrameTimeNs;
denicija070d5e32017-02-26 11:44:13 -080039@end
40
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020041@implementation RTCMTLVideoView
denicija070d5e32017-02-26 11:44:13 -080042
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +020043@synthesize delegate = _delegate;
denicijad2088152017-04-28 02:14:54 -070044@synthesize rendererI420 = _rendererI420;
45@synthesize rendererNV12 = _rendererNV12;
Peter Hanspers1c62b982018-05-03 14:06:04 +020046@synthesize rendererRGB = _rendererRGB;
denicija070d5e32017-02-26 11:44:13 -080047@synthesize metalView = _metalView;
48@synthesize videoFrame = _videoFrame;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020049@synthesize videoFrameSize = _videoFrameSize;
50@synthesize lastFrameTimeNs = _lastFrameTimeNs;
51@synthesize rotationOverride = _rotationOverride;
denicija070d5e32017-02-26 11:44:13 -080052
53- (instancetype)initWithFrame:(CGRect)frameRect {
54 self = [super initWithFrame:frameRect];
55 if (self) {
56 [self configure];
57 }
58 return self;
59}
60
61- (instancetype)initWithCoder:(NSCoder *)aCoder {
62 self = [super initWithCoder:aCoder];
63 if (self) {
64 [self configure];
65 }
66 return self;
67}
68
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020069- (BOOL)isEnabled {
70 return !self.metalView.paused;
71}
72
73- (void)setEnabled:(BOOL)enabled {
74 self.metalView.paused = !enabled;
75}
76
77- (UIViewContentMode)videoContentMode {
78 return self.metalView.contentMode;
79}
80
81- (void)setVideoContentMode:(UIViewContentMode)mode {
82 self.metalView.contentMode = mode;
83}
84
denicija070d5e32017-02-26 11:44:13 -080085#pragma mark - Private
86
87+ (BOOL)isMetalAvailable {
kthelgasona2fb30c2017-03-09 03:34:27 -080088#if defined(RTC_SUPPORTS_METAL)
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +020089 return MTLCreateSystemDefaultDevice() != nil;
kthelgasona2fb30c2017-03-09 03:34:27 -080090#else
denicija070d5e32017-02-26 11:44:13 -080091 return NO;
92#endif
93}
94
denicijad2088152017-04-28 02:14:54 -070095+ (RTCMTLNV12Renderer *)createNV12Renderer {
96 return [[RTCMTLNV12RendererClass alloc] init];
97}
98
99+ (RTCMTLI420Renderer *)createI420Renderer {
100 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800101}
102
Peter Hanspers1c62b982018-05-03 14:06:04 +0200103+ (RTCMTLRGBRenderer *)createRGBRenderer {
104 return [[RTCMTLRGBRenderer alloc] init];
105}
106
denicija070d5e32017-02-26 11:44:13 -0800107- (void)configure {
denicijad2088152017-04-28 02:14:54 -0700108 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800109
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200110 self.metalView = [[MTKViewClass alloc] initWithFrame:self.bounds];
111 self.metalView.delegate = self;
112 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
113 [self addSubview:self.metalView];
114 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800115}
kthelgason954d9b92017-03-09 03:36:58 -0800116
Danielac4a14322017-10-31 11:19:38 +0100117- (void)layoutSubviews {
118 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200119
JT Teh5c147252018-04-22 09:28:27 -0700120 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200121 self.metalView.frame = bounds;
122 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
123 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700124 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200125 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700126 }
Danielac4a14322017-10-31 11:19:38 +0100127}
128
denicija070d5e32017-02-26 11:44:13 -0800129#pragma mark - MTKViewDelegate methods
130
131- (void)drawInMTKView:(nonnull MTKView *)view {
132 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200133 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200134 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200135 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700136 return;
137 }
138
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200139 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200140 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200141 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
142 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
143 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
144 if (!self.rendererRGB) {
145 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
146 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
147 self.rendererRGB = nil;
148 RTCLogError(@"Failed to create RGB renderer");
149 return;
150 }
denicijad2088152017-04-28 02:14:54 -0700151 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200152 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200153 } else {
154 if (!self.rendererNV12) {
155 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
156 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
157 self.rendererNV12 = nil;
158 RTCLogError(@"Failed to create NV12 renderer");
159 return;
160 }
161 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200162 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000163 }
denicijad2088152017-04-28 02:14:54 -0700164 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000165 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700166 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
167 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
168 self.rendererI420 = nil;
169 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200170 return;
denicijad2088152017-04-28 02:14:54 -0700171 }
JT Teh6144fa52018-04-10 00:27:04 +0000172 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200173 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700174 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200175
176 renderer.rotationOverride = self.rotationOverride;
177
178 [renderer drawFrame:videoFrame];
179 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800180}
181
182- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
183}
184
Peter Hanspers7c32c862018-06-14 14:12:45 +0200185#pragma mark -
186
187- (void)setRotationOverride:(NSValue *)rotationOverride {
188 _rotationOverride = rotationOverride;
189
190 self.metalView.drawableSize = [self drawableSize];
191 [self setNeedsLayout];
192}
193
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200194- (RTCVideoRotation)frameRotation {
195 if (self.rotationOverride) {
196 RTCVideoRotation rotation;
197#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
198 if (@available(iOS 11, *)) {
199 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
200 } else
201#endif
202 {
203 [self.rotationOverride getValue:&rotation];
204 }
205 return rotation;
206 }
207
208 return self.videoFrame.rotation;
209}
210
211- (CGSize)drawableSize {
212 // Flip width/height if the rotations are not the same.
213 CGSize videoFrameSize = self.videoFrameSize;
214 RTCVideoRotation frameRotation = [self frameRotation];
215
216 BOOL useLandscape =
217 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
218 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
219 (self.videoFrame.rotation == RTCVideoRotation_180);
220
221 if (useLandscape == sizeIsLandscape) {
222 return videoFrameSize;
223 } else {
224 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
225 }
226}
227
denicija070d5e32017-02-26 11:44:13 -0800228#pragma mark - RTCVideoRenderer
229
230- (void)setSize:(CGSize)size {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200231 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200232 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200233 RTCMTLVideoView *strongSelf = weakSelf;
234
235 strongSelf.videoFrameSize = size;
236 CGSize drawableSize = [strongSelf drawableSize];
237
238 strongSelf.metalView.drawableSize = drawableSize;
239 [strongSelf setNeedsLayout];
240 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200241 });
denicija070d5e32017-02-26 11:44:13 -0800242}
243
244- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200245 if (!self.isEnabled) {
246 return;
247 }
248
denicija070d5e32017-02-26 11:44:13 -0800249 if (frame == nil) {
250 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
251 return;
252 }
253 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800254}
255
256@end