blob: 667553002c28f1df7aba8f93ae3031b796509468 [file] [log] [blame]
magjed73c0eb52017-08-07 06:55:28 -07001/*
2 * Copyright (c) 2015 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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020012#import "RTCVideoDecoderH264.h"
magjed73c0eb52017-08-07 06:55:28 -070013
14#import <VideoToolbox/VideoToolbox.h>
15
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020016#import "base/RTCVideoFrame.h"
17#import "base/RTCVideoFrameBuffer.h"
18#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
19#import "helpers.h"
20#import "helpers/scoped_cftyperef.h"
21
22#if defined(WEBRTC_IOS)
23#import "helpers/UIDevice+RTCDevice.h"
24#endif
25
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020026#include "modules/video_coding/include/video_error_codes.h"
27#include "rtc_base/checks.h"
28#include "rtc_base/logging.h"
Steve Anton10542f22019-01-11 09:11:00 -080029#include "rtc_base/time_utils.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020030#include "sdk/objc/components/video_codec/nalu_rewriter.h"
magjed73c0eb52017-08-07 06:55:28 -070031
32// Struct that we pass to the decoder per frame to decode. We receive it again
33// in the decoder callback.
34struct RTCFrameDecodeParams {
35 RTCFrameDecodeParams(RTCVideoDecoderCallback cb, int64_t ts) : callback(cb), timestamp(ts) {}
36 RTCVideoDecoderCallback callback;
37 int64_t timestamp;
38};
39
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020040@interface RTC_OBJC_TYPE (RTCVideoDecoderH264)
41() - (void)setError : (OSStatus)error;
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010042@end
43
magjed73c0eb52017-08-07 06:55:28 -070044// This is the callback function that VideoToolbox calls when decode is
45// complete.
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010046void decompressionOutputCallback(void *decoderRef,
magjed73c0eb52017-08-07 06:55:28 -070047 void *params,
48 OSStatus status,
49 VTDecodeInfoFlags infoFlags,
50 CVImageBufferRef imageBuffer,
51 CMTime timestamp,
52 CMTime duration) {
53 std::unique_ptr<RTCFrameDecodeParams> decodeParams(
54 reinterpret_cast<RTCFrameDecodeParams *>(params));
55 if (status != noErr) {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020056 RTC_OBJC_TYPE(RTCVideoDecoderH264) *decoder =
57 (__bridge RTC_OBJC_TYPE(RTCVideoDecoderH264) *)decoderRef;
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010058 [decoder setError:status];
Mirko Bonadei675513b2017-11-09 11:09:25 +010059 RTC_LOG(LS_ERROR) << "Failed to decode frame. Status: " << status;
magjed73c0eb52017-08-07 06:55:28 -070060 return;
61 }
62 // TODO(tkchin): Handle CVO properly.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020063 RTC_OBJC_TYPE(RTCCVPixelBuffer) *frameBuffer =
64 [[RTC_OBJC_TYPE(RTCCVPixelBuffer) alloc] initWithPixelBuffer:imageBuffer];
65 RTC_OBJC_TYPE(RTCVideoFrame) *decodedFrame = [[RTC_OBJC_TYPE(RTCVideoFrame) alloc]
66 initWithBuffer:frameBuffer
67 rotation:RTCVideoRotation_0
68 timeStampNs:CMTimeGetSeconds(timestamp) * rtc::kNumNanosecsPerSec];
magjed73c0eb52017-08-07 06:55:28 -070069 decodedFrame.timeStamp = decodeParams->timestamp;
70 decodeParams->callback(decodedFrame);
71}
72
73// Decoder.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020074@implementation RTC_OBJC_TYPE (RTCVideoDecoderH264) {
magjed73c0eb52017-08-07 06:55:28 -070075 CMVideoFormatDescriptionRef _videoFormat;
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020076 CMMemoryPoolRef _memoryPool;
magjed73c0eb52017-08-07 06:55:28 -070077 VTDecompressionSessionRef _decompressionSession;
78 RTCVideoDecoderCallback _callback;
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010079 OSStatus _error;
magjed73c0eb52017-08-07 06:55:28 -070080}
81
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020082- (instancetype)init {
83 self = [super init];
84 if (self) {
85 _memoryPool = CMMemoryPoolCreate(nil);
86 }
87 return self;
88}
89
magjed73c0eb52017-08-07 06:55:28 -070090- (void)dealloc {
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020091 CMMemoryPoolInvalidate(_memoryPool);
92 CFRelease(_memoryPool);
magjed73c0eb52017-08-07 06:55:28 -070093 [self destroyDecompressionSession];
94 [self setVideoFormat:nullptr];
95}
96
Anders Carlsson2a1bbc32018-04-04 12:49:43 +020097- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores {
98 return WEBRTC_VIDEO_CODEC_OK;
99}
100
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200101- (NSInteger)decode:(RTC_OBJC_TYPE(RTCEncodedImage) *)inputImage
Niels Möllerc199fae2018-04-26 09:54:25 +0200102 missingFrames:(BOOL)missingFrames
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200103 codecSpecificInfo:(nullable id<RTC_OBJC_TYPE(RTCCodecSpecificInfo)>)info
Niels Möllerc199fae2018-04-26 09:54:25 +0200104 renderTimeMs:(int64_t)renderTimeMs {
magjed73c0eb52017-08-07 06:55:28 -0700105 RTC_DCHECK(inputImage.buffer);
106
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +0100107 if (_error != noErr) {
108 RTC_LOG(LS_WARNING) << "Last frame decode failed.";
109 _error = noErr;
110 return WEBRTC_VIDEO_CODEC_ERROR;
111 }
112
Guy Hershenbaum2fcb8342018-02-20 21:33:36 -0800113 rtc::ScopedCFTypeRef<CMVideoFormatDescriptionRef> inputFormat =
114 rtc::ScopedCF(webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
115 inputImage.buffer.length));
116 if (inputFormat) {
117 // Check if the video format has changed, and reinitialize decoder if
118 // needed.
119 if (!CMFormatDescriptionEqual(inputFormat.get(), _videoFormat)) {
120 [self setVideoFormat:inputFormat.get()];
121 int resetDecompressionSessionError = [self resetDecompressionSession];
122 if (resetDecompressionSessionError != WEBRTC_VIDEO_CODEC_OK) {
123 return resetDecompressionSessionError;
magjed73c0eb52017-08-07 06:55:28 -0700124 }
magjed73c0eb52017-08-07 06:55:28 -0700125 }
126 }
127 if (!_videoFormat) {
128 // We received a frame but we don't have format information so we can't
129 // decode it.
130 // This can happen after backgrounding. We need to wait for the next
131 // sps/pps before we can resume so we request a keyframe by returning an
132 // error.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100133 RTC_LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required.";
magjed73c0eb52017-08-07 06:55:28 -0700134 return WEBRTC_VIDEO_CODEC_ERROR;
135 }
136 CMSampleBufferRef sampleBuffer = nullptr;
137 if (!webrtc::H264AnnexBBufferToCMSampleBuffer((uint8_t *)inputImage.buffer.bytes,
138 inputImage.buffer.length,
139 _videoFormat,
Kári Tristan Helgason0d247722018-10-26 10:59:28 +0200140 &sampleBuffer,
141 _memoryPool)) {
magjed73c0eb52017-08-07 06:55:28 -0700142 return WEBRTC_VIDEO_CODEC_ERROR;
143 }
144 RTC_DCHECK(sampleBuffer);
145 VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
146 std::unique_ptr<RTCFrameDecodeParams> frameDecodeParams;
147 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
148 OSStatus status = VTDecompressionSessionDecodeFrame(
149 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
150#if defined(WEBRTC_IOS)
151 // Re-initialize the decoder if we have an invalid session while the app is
Yura Yarosheviche270ff12020-01-13 15:45:58 +0300152 // active or decoder malfunctions and retry the decode request.
153 if ((status == kVTInvalidSessionErr || status == kVTVideoDecoderMalfunctionErr) &&
154 [self resetDecompressionSession] == WEBRTC_VIDEO_CODEC_OK) {
155 RTC_LOG(LS_INFO) << "Failed to decode frame with code: " << status
156 << " retrying decode after decompression session reset";
magjed73c0eb52017-08-07 06:55:28 -0700157 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
158 status = VTDecompressionSessionDecodeFrame(
159 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
160 }
161#endif
162 CFRelease(sampleBuffer);
163 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100164 RTC_LOG(LS_ERROR) << "Failed to decode frame with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700165 return WEBRTC_VIDEO_CODEC_ERROR;
166 }
167 return WEBRTC_VIDEO_CODEC_OK;
168}
169
170- (void)setCallback:(RTCVideoDecoderCallback)callback {
171 _callback = callback;
172}
173
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +0100174- (void)setError:(OSStatus)error {
175 _error = error;
176}
177
magjed73c0eb52017-08-07 06:55:28 -0700178- (NSInteger)releaseDecoder {
179 // Need to invalidate the session so that callbacks no longer occur and it
180 // is safe to null out the callback.
181 [self destroyDecompressionSession];
182 [self setVideoFormat:nullptr];
183 _callback = nullptr;
184 return WEBRTC_VIDEO_CODEC_OK;
185}
186
187#pragma mark - Private
188
189- (int)resetDecompressionSession {
190 [self destroyDecompressionSession];
191
192 // Need to wait for the first SPS to initialize decoder.
193 if (!_videoFormat) {
194 return WEBRTC_VIDEO_CODEC_OK;
195 }
196
197 // Set keys for OpenGL and IOSurface compatibilty, which makes the encoder
198 // create pixel buffers with GPU backed memory. The intent here is to pass
199 // the pixel buffers directly so we avoid a texture upload later during
200 // rendering. This currently is moot because we are converting back to an
201 // I420 frame after decode, but eventually we will be able to plumb
202 // CVPixelBuffers directly to the renderer.
203 // TODO(tkchin): Maybe only set OpenGL/IOSurface keys if we know that that
204 // we can pass CVPixelBuffers as native handles in decoder output.
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400205#if TARGET_OS_SIMULATOR
206 static size_t const attributesSize = 2;
207#else
magjed73c0eb52017-08-07 06:55:28 -0700208 static size_t const attributesSize = 3;
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400209#endif
210
magjed73c0eb52017-08-07 06:55:28 -0700211 CFTypeRef keys[attributesSize] = {
212#if defined(WEBRTC_IOS)
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400213 kCVPixelBufferOpenGLESCompatibilityKey,
magjed73c0eb52017-08-07 06:55:28 -0700214#elif defined(WEBRTC_MAC)
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400215 kCVPixelBufferOpenGLCompatibilityKey,
magjed73c0eb52017-08-07 06:55:28 -0700216#endif
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400217#if !(TARGET_OS_SIMULATOR)
218 kCVPixelBufferIOSurfacePropertiesKey,
219#endif
220 kCVPixelBufferPixelFormatTypeKey};
magjed73c0eb52017-08-07 06:55:28 -0700221 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
222 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
223 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400224#if TARGET_OS_SIMULATOR
225 CFTypeRef values[attributesSize] = {kCFBooleanTrue, pixelFormat};
226#else
magjed73c0eb52017-08-07 06:55:28 -0700227 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
Rohit Krishnanf7cf1332020-08-22 23:08:54 -0400228#endif
229
magjed73c0eb52017-08-07 06:55:28 -0700230 CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize);
231 if (ioSurfaceValue) {
232 CFRelease(ioSurfaceValue);
233 ioSurfaceValue = nullptr;
234 }
235 if (pixelFormat) {
236 CFRelease(pixelFormat);
237 pixelFormat = nullptr;
238 }
239 VTDecompressionOutputCallbackRecord record = {
Kári Tristan Helgasondb6145f2018-02-13 13:58:10 +0100240 decompressionOutputCallback, (__bridge void *)self,
magjed73c0eb52017-08-07 06:55:28 -0700241 };
242 OSStatus status = VTDecompressionSessionCreate(
243 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession);
244 CFRelease(attributes);
245 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300246 RTC_LOG(LS_ERROR) << "Failed to create decompression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700247 [self destroyDecompressionSession];
248 return WEBRTC_VIDEO_CODEC_ERROR;
249 }
250 [self configureDecompressionSession];
251
252 return WEBRTC_VIDEO_CODEC_OK;
253}
254
255- (void)configureDecompressionSession {
256 RTC_DCHECK(_decompressionSession);
257#if defined(WEBRTC_IOS)
258 VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
259#endif
260}
261
262- (void)destroyDecompressionSession {
263 if (_decompressionSession) {
JT Teha6368d12017-09-28 11:00:39 -0700264#if defined(WEBRTC_IOS)
265 if ([UIDevice isIOS11OrLater]) {
266 VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession);
267 }
268#endif
magjed73c0eb52017-08-07 06:55:28 -0700269 VTDecompressionSessionInvalidate(_decompressionSession);
270 CFRelease(_decompressionSession);
271 _decompressionSession = nullptr;
272 }
273}
274
275- (void)setVideoFormat:(CMVideoFormatDescriptionRef)videoFormat {
276 if (_videoFormat == videoFormat) {
277 return;
278 }
279 if (_videoFormat) {
280 CFRelease(_videoFormat);
281 }
282 _videoFormat = videoFormat;
283 if (_videoFormat) {
284 CFRetain(_videoFormat);
285 }
286}
287
288- (NSString *)implementationName {
289 return @"VideoToolbox";
290}
291
292@end