blob: e0fd52417713809de9d0253808dfb1df20a1c4c6 [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
Kári Tristan Helgason90143242018-07-27 12:34:54 +020095+ (MTKView *)createMetalView:(CGRect)frame {
96 return [[MTKViewClass alloc] initWithFrame:frame];
97}
98
denicijad2088152017-04-28 02:14:54 -070099+ (RTCMTLNV12Renderer *)createNV12Renderer {
100 return [[RTCMTLNV12RendererClass alloc] init];
101}
102
103+ (RTCMTLI420Renderer *)createI420Renderer {
104 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800105}
106
Peter Hanspers1c62b982018-05-03 14:06:04 +0200107+ (RTCMTLRGBRenderer *)createRGBRenderer {
108 return [[RTCMTLRGBRenderer alloc] init];
109}
110
denicija070d5e32017-02-26 11:44:13 -0800111- (void)configure {
denicijad2088152017-04-28 02:14:54 -0700112 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800113
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200114 self.metalView = [RTCMTLVideoView createMetalView:self.bounds];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200115 self.metalView.delegate = self;
116 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
117 [self addSubview:self.metalView];
118 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800119}
kthelgason954d9b92017-03-09 03:36:58 -0800120
Danielac4a14322017-10-31 11:19:38 +0100121- (void)layoutSubviews {
122 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200123
JT Teh5c147252018-04-22 09:28:27 -0700124 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200125 self.metalView.frame = bounds;
126 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
127 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700128 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200129 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700130 }
Danielac4a14322017-10-31 11:19:38 +0100131}
132
denicija070d5e32017-02-26 11:44:13 -0800133#pragma mark - MTKViewDelegate methods
134
135- (void)drawInMTKView:(nonnull MTKView *)view {
136 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200137 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200138 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200139 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700140 return;
141 }
142
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200143 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200144 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200145 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
146 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
147 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
148 if (!self.rendererRGB) {
149 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
150 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
151 self.rendererRGB = nil;
152 RTCLogError(@"Failed to create RGB renderer");
153 return;
154 }
denicijad2088152017-04-28 02:14:54 -0700155 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200156 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200157 } else {
158 if (!self.rendererNV12) {
159 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
160 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
161 self.rendererNV12 = nil;
162 RTCLogError(@"Failed to create NV12 renderer");
163 return;
164 }
165 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200166 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000167 }
denicijad2088152017-04-28 02:14:54 -0700168 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000169 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700170 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
171 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
172 self.rendererI420 = nil;
173 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200174 return;
denicijad2088152017-04-28 02:14:54 -0700175 }
JT Teh6144fa52018-04-10 00:27:04 +0000176 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200177 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700178 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200179
180 renderer.rotationOverride = self.rotationOverride;
181
182 [renderer drawFrame:videoFrame];
183 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800184}
185
186- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
187}
188
Peter Hanspers7c32c862018-06-14 14:12:45 +0200189#pragma mark -
190
191- (void)setRotationOverride:(NSValue *)rotationOverride {
192 _rotationOverride = rotationOverride;
193
194 self.metalView.drawableSize = [self drawableSize];
195 [self setNeedsLayout];
196}
197
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200198- (RTCVideoRotation)frameRotation {
199 if (self.rotationOverride) {
200 RTCVideoRotation rotation;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200201 if (@available(iOS 11, *)) {
202 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200203 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200204 [self.rotationOverride getValue:&rotation];
205 }
206 return rotation;
207 }
208
209 return self.videoFrame.rotation;
210}
211
212- (CGSize)drawableSize {
213 // Flip width/height if the rotations are not the same.
214 CGSize videoFrameSize = self.videoFrameSize;
215 RTCVideoRotation frameRotation = [self frameRotation];
216
217 BOOL useLandscape =
218 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
219 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
220 (self.videoFrame.rotation == RTCVideoRotation_180);
221
222 if (useLandscape == sizeIsLandscape) {
223 return videoFrameSize;
224 } else {
225 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
226 }
227}
228
denicija070d5e32017-02-26 11:44:13 -0800229#pragma mark - RTCVideoRenderer
230
231- (void)setSize:(CGSize)size {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200232 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200233 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200234 RTCMTLVideoView *strongSelf = weakSelf;
235
236 strongSelf.videoFrameSize = size;
237 CGSize drawableSize = [strongSelf drawableSize];
238
239 strongSelf.metalView.drawableSize = drawableSize;
240 [strongSelf setNeedsLayout];
241 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200242 });
denicija070d5e32017-02-26 11:44:13 -0800243}
244
245- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200246 if (!self.isEnabled) {
247 return;
248 }
249
denicija070d5e32017-02-26 11:44:13 -0800250 if (frame == nil) {
251 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
252 return;
253 }
254 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800255}
256
257@end