blob: 4c50bcf9c10842c32aadd7b0999bce068bbcc77b [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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020011#import "RTCMTLVideoView.h"
denicija070d5e32017-02-26 11:44:13 -080012
13#import <Metal/Metal.h>
14#import <MetalKit/MetalKit.h>
15
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020016#import "base/RTCLogging.h"
17#import "base/RTCVideoFrame.h"
18#import "base/RTCVideoFrameBuffer.h"
19#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
denicija070d5e32017-02-26 11:44:13 -080020
denicijad2088152017-04-28 02:14:54 -070021#import "RTCMTLI420Renderer.h"
denicija070d5e32017-02-26 11:44:13 -080022#import "RTCMTLNV12Renderer.h"
Peter Hanspers1c62b982018-05-03 14:06:04 +020023#import "RTCMTLRGBRenderer.h"
denicija070d5e32017-02-26 11:44:13 -080024
kthelgason954d9b92017-03-09 03:36:58 -080025// To avoid unreconized symbol linker errors, we're taking advantage of the objc runtime.
26// Linking errors occur when compiling for architectures that don't support Metal.
27#define MTKViewClass NSClassFromString(@"MTKView")
28#define RTCMTLNV12RendererClass NSClassFromString(@"RTCMTLNV12Renderer")
denicijad2088152017-04-28 02:14:54 -070029#define RTCMTLI420RendererClass NSClassFromString(@"RTCMTLI420Renderer")
Peter Hanspers1c62b982018-05-03 14:06:04 +020030#define RTCMTLRGBRendererClass NSClassFromString(@"RTCMTLRGBRenderer")
kthelgason954d9b92017-03-09 03:36:58 -080031
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020032@interface RTC_OBJC_TYPE (RTCMTLVideoView)
33()<MTKViewDelegate> @property(nonatomic) RTCMTLI420Renderer *rendererI420;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020034@property(nonatomic) RTCMTLNV12Renderer *rendererNV12;
35@property(nonatomic) RTCMTLRGBRenderer *rendererRGB;
36@property(nonatomic) MTKView *metalView;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020037@property(atomic) RTC_OBJC_TYPE(RTCVideoFrame) * videoFrame;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020038@property(nonatomic) CGSize videoFrameSize;
39@property(nonatomic) int64_t lastFrameTimeNs;
denicija070d5e32017-02-26 11:44:13 -080040@end
41
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020042@implementation RTC_OBJC_TYPE (RTCMTLVideoView)
denicija070d5e32017-02-26 11:44:13 -080043
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +020044@synthesize delegate = _delegate;
denicijad2088152017-04-28 02:14:54 -070045@synthesize rendererI420 = _rendererI420;
46@synthesize rendererNV12 = _rendererNV12;
Peter Hanspers1c62b982018-05-03 14:06:04 +020047@synthesize rendererRGB = _rendererRGB;
denicija070d5e32017-02-26 11:44:13 -080048@synthesize metalView = _metalView;
49@synthesize videoFrame = _videoFrame;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020050@synthesize videoFrameSize = _videoFrameSize;
51@synthesize lastFrameTimeNs = _lastFrameTimeNs;
52@synthesize rotationOverride = _rotationOverride;
denicija070d5e32017-02-26 11:44:13 -080053
54- (instancetype)initWithFrame:(CGRect)frameRect {
55 self = [super initWithFrame:frameRect];
56 if (self) {
57 [self configure];
58 }
59 return self;
60}
61
62- (instancetype)initWithCoder:(NSCoder *)aCoder {
63 self = [super initWithCoder:aCoder];
64 if (self) {
65 [self configure];
66 }
67 return self;
68}
69
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020070- (BOOL)isEnabled {
71 return !self.metalView.paused;
72}
73
74- (void)setEnabled:(BOOL)enabled {
75 self.metalView.paused = !enabled;
76}
77
78- (UIViewContentMode)videoContentMode {
79 return self.metalView.contentMode;
80}
81
82- (void)setVideoContentMode:(UIViewContentMode)mode {
83 self.metalView.contentMode = mode;
84}
85
denicija070d5e32017-02-26 11:44:13 -080086#pragma mark - Private
87
88+ (BOOL)isMetalAvailable {
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +020089 return MTLCreateSystemDefaultDevice() != nil;
denicija070d5e32017-02-26 11:44:13 -080090}
91
Kári Tristan Helgason90143242018-07-27 12:34:54 +020092+ (MTKView *)createMetalView:(CGRect)frame {
93 return [[MTKViewClass alloc] initWithFrame:frame];
94}
95
denicijad2088152017-04-28 02:14:54 -070096+ (RTCMTLNV12Renderer *)createNV12Renderer {
97 return [[RTCMTLNV12RendererClass alloc] init];
98}
99
100+ (RTCMTLI420Renderer *)createI420Renderer {
101 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800102}
103
Peter Hanspers1c62b982018-05-03 14:06:04 +0200104+ (RTCMTLRGBRenderer *)createRGBRenderer {
105 return [[RTCMTLRGBRenderer alloc] init];
106}
107
denicija070d5e32017-02-26 11:44:13 -0800108- (void)configure {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200109 NSAssert([RTC_OBJC_TYPE(RTCMTLVideoView) isMetalAvailable],
110 @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800111
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200112 self.metalView = [RTC_OBJC_TYPE(RTCMTLVideoView) createMetalView:self.bounds];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200113 self.metalView.delegate = self;
114 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
115 [self addSubview:self.metalView];
116 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800117}
kthelgason954d9b92017-03-09 03:36:58 -0800118
CZ Theng0ff7c022019-10-22 20:36:40 +0800119- (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
120 [super setMultipleTouchEnabled:multipleTouchEnabled];
121 self.metalView.multipleTouchEnabled = multipleTouchEnabled;
122}
123
Danielac4a14322017-10-31 11:19:38 +0100124- (void)layoutSubviews {
125 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200126
JT Teh5c147252018-04-22 09:28:27 -0700127 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200128 self.metalView.frame = bounds;
129 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
130 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700131 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200132 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700133 }
Danielac4a14322017-10-31 11:19:38 +0100134}
135
denicija070d5e32017-02-26 11:44:13 -0800136#pragma mark - MTKViewDelegate methods
137
138- (void)drawInMTKView:(nonnull MTKView *)view {
139 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200140 RTC_OBJC_TYPE(RTCVideoFrame) *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200141 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200142 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700143 return;
144 }
145
Peter Hanspers3955a502018-11-22 16:29:56 +0100146 if (CGRectIsEmpty(view.bounds)) {
147 return;
148 }
149
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200150 RTCMTLRenderer *renderer;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200151 if ([videoFrame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
152 RTC_OBJC_TYPE(RTCCVPixelBuffer) *buffer = (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)videoFrame.buffer;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200153 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
154 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
155 if (!self.rendererRGB) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200156 self.rendererRGB = [RTC_OBJC_TYPE(RTCMTLVideoView) createRGBRenderer];
Peter Hanspers1c62b982018-05-03 14:06:04 +0200157 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
158 self.rendererRGB = nil;
159 RTCLogError(@"Failed to create RGB renderer");
160 return;
161 }
denicijad2088152017-04-28 02:14:54 -0700162 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200163 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200164 } else {
165 if (!self.rendererNV12) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200166 self.rendererNV12 = [RTC_OBJC_TYPE(RTCMTLVideoView) createNV12Renderer];
Peter Hanspers1c62b982018-05-03 14:06:04 +0200167 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
168 self.rendererNV12 = nil;
169 RTCLogError(@"Failed to create NV12 renderer");
170 return;
171 }
172 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200173 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000174 }
denicijad2088152017-04-28 02:14:54 -0700175 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000176 if (!self.rendererI420) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200177 self.rendererI420 = [RTC_OBJC_TYPE(RTCMTLVideoView) createI420Renderer];
denicijad2088152017-04-28 02:14:54 -0700178 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
179 self.rendererI420 = nil;
180 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200181 return;
denicijad2088152017-04-28 02:14:54 -0700182 }
JT Teh6144fa52018-04-10 00:27:04 +0000183 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200184 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700185 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200186
187 renderer.rotationOverride = self.rotationOverride;
188
189 [renderer drawFrame:videoFrame];
190 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800191}
192
193- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
194}
195
Peter Hanspers7c32c862018-06-14 14:12:45 +0200196#pragma mark -
197
198- (void)setRotationOverride:(NSValue *)rotationOverride {
199 _rotationOverride = rotationOverride;
200
201 self.metalView.drawableSize = [self drawableSize];
202 [self setNeedsLayout];
203}
204
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200205- (RTCVideoRotation)frameRotation {
206 if (self.rotationOverride) {
207 RTCVideoRotation rotation;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200208 if (@available(iOS 11, *)) {
209 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200210 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200211 [self.rotationOverride getValue:&rotation];
212 }
213 return rotation;
214 }
215
216 return self.videoFrame.rotation;
217}
218
219- (CGSize)drawableSize {
220 // Flip width/height if the rotations are not the same.
221 CGSize videoFrameSize = self.videoFrameSize;
222 RTCVideoRotation frameRotation = [self frameRotation];
223
224 BOOL useLandscape =
225 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
226 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
227 (self.videoFrame.rotation == RTCVideoRotation_180);
228
229 if (useLandscape == sizeIsLandscape) {
230 return videoFrameSize;
231 } else {
232 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
233 }
234}
235
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200236#pragma mark - RTC_OBJC_TYPE(RTCVideoRenderer)
denicija070d5e32017-02-26 11:44:13 -0800237
238- (void)setSize:(CGSize)size {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200239 __weak RTC_OBJC_TYPE(RTCMTLVideoView) *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200240 dispatch_async(dispatch_get_main_queue(), ^{
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200241 RTC_OBJC_TYPE(RTCMTLVideoView) *strongSelf = weakSelf;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200242
243 strongSelf.videoFrameSize = size;
244 CGSize drawableSize = [strongSelf drawableSize];
245
246 strongSelf.metalView.drawableSize = drawableSize;
247 [strongSelf setNeedsLayout];
248 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200249 });
denicija070d5e32017-02-26 11:44:13 -0800250}
251
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200252- (void)renderFrame:(nullable RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200253 if (!self.isEnabled) {
254 return;
255 }
256
denicija070d5e32017-02-26 11:44:13 -0800257 if (frame == nil) {
258 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
259 return;
260 }
261 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800262}
263
264@end