blob: 7ad1d79d3e3f6d63be46dfe0861161811a9aa543 [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
denicija070d5e32017-02-26 11:44:13 -080032@interface RTCMTLVideoView () <MTKViewDelegate>
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020033@property(nonatomic) RTCMTLI420Renderer *rendererI420;
34@property(nonatomic) RTCMTLNV12Renderer *rendererNV12;
35@property(nonatomic) RTCMTLRGBRenderer *rendererRGB;
36@property(nonatomic) MTKView *metalView;
37@property(atomic) RTCVideoFrame *videoFrame;
38@property(nonatomic) CGSize videoFrameSize;
39@property(nonatomic) int64_t lastFrameTimeNs;
denicija070d5e32017-02-26 11:44:13 -080040@end
41
Peter Hanspers5daaf7d2018-06-01 10:34:37 +020042@implementation 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 {
kthelgasona2fb30c2017-03-09 03:34:27 -080089#if defined(RTC_SUPPORTS_METAL)
Kári Tristan Helgasonbe806812018-04-18 17:04:02 +020090 return MTLCreateSystemDefaultDevice() != nil;
kthelgasona2fb30c2017-03-09 03:34:27 -080091#else
denicija070d5e32017-02-26 11:44:13 -080092 return NO;
93#endif
94}
95
Kári Tristan Helgason90143242018-07-27 12:34:54 +020096+ (MTKView *)createMetalView:(CGRect)frame {
97 return [[MTKViewClass alloc] initWithFrame:frame];
98}
99
denicijad2088152017-04-28 02:14:54 -0700100+ (RTCMTLNV12Renderer *)createNV12Renderer {
101 return [[RTCMTLNV12RendererClass alloc] init];
102}
103
104+ (RTCMTLI420Renderer *)createI420Renderer {
105 return [[RTCMTLI420RendererClass alloc] init];
kthelgason954d9b92017-03-09 03:36:58 -0800106}
107
Peter Hanspers1c62b982018-05-03 14:06:04 +0200108+ (RTCMTLRGBRenderer *)createRGBRenderer {
109 return [[RTCMTLRGBRenderer alloc] init];
110}
111
denicija070d5e32017-02-26 11:44:13 -0800112- (void)configure {
denicijad2088152017-04-28 02:14:54 -0700113 NSAssert([RTCMTLVideoView isMetalAvailable], @"Metal not availiable on this device");
kthelgason954d9b92017-03-09 03:36:58 -0800114
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200115 self.metalView = [RTCMTLVideoView createMetalView:self.bounds];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200116 self.metalView.delegate = self;
117 self.metalView.contentMode = UIViewContentModeScaleAspectFill;
118 [self addSubview:self.metalView];
119 self.videoFrameSize = CGSizeZero;
denicija070d5e32017-02-26 11:44:13 -0800120}
kthelgason954d9b92017-03-09 03:36:58 -0800121
Danielac4a14322017-10-31 11:19:38 +0100122- (void)layoutSubviews {
123 [super layoutSubviews];
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200124
JT Teh5c147252018-04-22 09:28:27 -0700125 CGRect bounds = self.bounds;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200126 self.metalView.frame = bounds;
127 if (!CGSizeEqualToSize(self.videoFrameSize, CGSizeZero)) {
128 self.metalView.drawableSize = [self drawableSize];
JT Teh5c147252018-04-22 09:28:27 -0700129 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200130 self.metalView.drawableSize = bounds.size;
JT Teh5c147252018-04-22 09:28:27 -0700131 }
Danielac4a14322017-10-31 11:19:38 +0100132}
133
denicija070d5e32017-02-26 11:44:13 -0800134#pragma mark - MTKViewDelegate methods
135
136- (void)drawInMTKView:(nonnull MTKView *)view {
137 NSAssert(view == self.metalView, @"Receiving draw callbacks from foreign instance.");
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200138 RTCVideoFrame *videoFrame = self.videoFrame;
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200139 // Skip rendering if we've already rendered this frame.
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200140 if (!videoFrame || videoFrame.timeStampNs == self.lastFrameTimeNs) {
denicijad2088152017-04-28 02:14:54 -0700141 return;
142 }
143
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200144 RTCMTLRenderer *renderer;
Kári Tristan Helgasone49452d2018-04-10 10:38:43 +0200145 if ([videoFrame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
Peter Hanspers1c62b982018-05-03 14:06:04 +0200146 RTCCVPixelBuffer *buffer = (RTCCVPixelBuffer*)videoFrame.buffer;
147 const OSType pixelFormat = CVPixelBufferGetPixelFormatType(buffer.pixelBuffer);
148 if (pixelFormat == kCVPixelFormatType_32BGRA || pixelFormat == kCVPixelFormatType_32ARGB) {
149 if (!self.rendererRGB) {
150 self.rendererRGB = [RTCMTLVideoView createRGBRenderer];
151 if (![self.rendererRGB addRenderingDestination:self.metalView]) {
152 self.rendererRGB = nil;
153 RTCLogError(@"Failed to create RGB renderer");
154 return;
155 }
denicijad2088152017-04-28 02:14:54 -0700156 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200157 renderer = self.rendererRGB;
Peter Hanspers1c62b982018-05-03 14:06:04 +0200158 } else {
159 if (!self.rendererNV12) {
160 self.rendererNV12 = [RTCMTLVideoView createNV12Renderer];
161 if (![self.rendererNV12 addRenderingDestination:self.metalView]) {
162 self.rendererNV12 = nil;
163 RTCLogError(@"Failed to create NV12 renderer");
164 return;
165 }
166 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200167 renderer = self.rendererNV12;
JT Teh6144fa52018-04-10 00:27:04 +0000168 }
denicijad2088152017-04-28 02:14:54 -0700169 } else {
JT Teh6144fa52018-04-10 00:27:04 +0000170 if (!self.rendererI420) {
denicijad2088152017-04-28 02:14:54 -0700171 self.rendererI420 = [RTCMTLVideoView createI420Renderer];
172 if (![self.rendererI420 addRenderingDestination:self.metalView]) {
173 self.rendererI420 = nil;
174 RTCLogError(@"Failed to create I420 renderer");
Kári Tristan Helgason4f7b6402018-04-16 11:39:53 +0200175 return;
denicijad2088152017-04-28 02:14:54 -0700176 }
JT Teh6144fa52018-04-10 00:27:04 +0000177 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200178 renderer = self.rendererI420;
denicijad2088152017-04-28 02:14:54 -0700179 }
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200180
181 renderer.rotationOverride = self.rotationOverride;
182
183 [renderer drawFrame:videoFrame];
184 self.lastFrameTimeNs = videoFrame.timeStampNs;
denicija070d5e32017-02-26 11:44:13 -0800185}
186
187- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
188}
189
Peter Hanspers7c32c862018-06-14 14:12:45 +0200190#pragma mark -
191
192- (void)setRotationOverride:(NSValue *)rotationOverride {
193 _rotationOverride = rotationOverride;
194
195 self.metalView.drawableSize = [self drawableSize];
196 [self setNeedsLayout];
197}
198
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200199- (RTCVideoRotation)frameRotation {
200 if (self.rotationOverride) {
201 RTCVideoRotation rotation;
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200202 if (@available(iOS 11, *)) {
203 [self.rotationOverride getValue:&rotation size:sizeof(rotation)];
Kári Tristan Helgason90143242018-07-27 12:34:54 +0200204 } else {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200205 [self.rotationOverride getValue:&rotation];
206 }
207 return rotation;
208 }
209
210 return self.videoFrame.rotation;
211}
212
213- (CGSize)drawableSize {
214 // Flip width/height if the rotations are not the same.
215 CGSize videoFrameSize = self.videoFrameSize;
216 RTCVideoRotation frameRotation = [self frameRotation];
217
218 BOOL useLandscape =
219 (frameRotation == RTCVideoRotation_0) || (frameRotation == RTCVideoRotation_180);
220 BOOL sizeIsLandscape = (self.videoFrame.rotation == RTCVideoRotation_0) ||
221 (self.videoFrame.rotation == RTCVideoRotation_180);
222
223 if (useLandscape == sizeIsLandscape) {
224 return videoFrameSize;
225 } else {
226 return CGSizeMake(videoFrameSize.height, videoFrameSize.width);
227 }
228}
229
denicija070d5e32017-02-26 11:44:13 -0800230#pragma mark - RTCVideoRenderer
231
232- (void)setSize:(CGSize)size {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200233 __weak RTCMTLVideoView *weakSelf = self;
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200234 dispatch_async(dispatch_get_main_queue(), ^{
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200235 RTCMTLVideoView *strongSelf = weakSelf;
236
237 strongSelf.videoFrameSize = size;
238 CGSize drawableSize = [strongSelf drawableSize];
239
240 strongSelf.metalView.drawableSize = drawableSize;
241 [strongSelf setNeedsLayout];
242 [strongSelf.delegate videoView:self didChangeVideoSize:size];
Kári Tristan Helgason06d094f2018-04-19 17:38:05 +0200243 });
denicija070d5e32017-02-26 11:44:13 -0800244}
245
246- (void)renderFrame:(nullable RTCVideoFrame *)frame {
Peter Hanspers5daaf7d2018-06-01 10:34:37 +0200247 if (!self.isEnabled) {
248 return;
249 }
250
denicija070d5e32017-02-26 11:44:13 -0800251 if (frame == nil) {
252 RTCLogInfo(@"Incoming frame is nil. Exiting render callback.");
253 return;
254 }
255 self.videoFrame = frame;
denicija070d5e32017-02-26 11:44:13 -0800256}
257
258@end