blob: 52edefe0535b7a9be6ff5e972afc9d6589c7ae3b [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.
205 static size_t const attributesSize = 3;
206 CFTypeRef keys[attributesSize] = {
207#if defined(WEBRTC_IOS)
208 kCVPixelBufferOpenGLESCompatibilityKey,
209#elif defined(WEBRTC_MAC)
210 kCVPixelBufferOpenGLCompatibilityKey,
211#endif
212 kCVPixelBufferIOSurfacePropertiesKey,
213 kCVPixelBufferPixelFormatTypeKey
214 };
215 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
216 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
217 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
218 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
219 CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize);
220 if (ioSurfaceValue) {
221 CFRelease(ioSurfaceValue);
222 ioSurfaceValue = nullptr;
223 }
224 if (pixelFormat) {
225 CFRelease(pixelFormat);
226 pixelFormat = nullptr;
227 }
228 VTDecompressionOutputCallbackRecord record = {
Kári Tristan Helgasondb6145f2018-02-13 13:58:10 +0100229 decompressionOutputCallback, (__bridge void *)self,
magjed73c0eb52017-08-07 06:55:28 -0700230 };
231 OSStatus status = VTDecompressionSessionCreate(
232 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession);
233 CFRelease(attributes);
234 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300235 RTC_LOG(LS_ERROR) << "Failed to create decompression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700236 [self destroyDecompressionSession];
237 return WEBRTC_VIDEO_CODEC_ERROR;
238 }
239 [self configureDecompressionSession];
240
241 return WEBRTC_VIDEO_CODEC_OK;
242}
243
244- (void)configureDecompressionSession {
245 RTC_DCHECK(_decompressionSession);
246#if defined(WEBRTC_IOS)
247 VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
248#endif
249}
250
251- (void)destroyDecompressionSession {
252 if (_decompressionSession) {
JT Teha6368d12017-09-28 11:00:39 -0700253#if defined(WEBRTC_IOS)
254 if ([UIDevice isIOS11OrLater]) {
255 VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession);
256 }
257#endif
magjed73c0eb52017-08-07 06:55:28 -0700258 VTDecompressionSessionInvalidate(_decompressionSession);
259 CFRelease(_decompressionSession);
260 _decompressionSession = nullptr;
261 }
262}
263
264- (void)setVideoFormat:(CMVideoFormatDescriptionRef)videoFormat {
265 if (_videoFormat == videoFormat) {
266 return;
267 }
268 if (_videoFormat) {
269 CFRelease(_videoFormat);
270 }
271 _videoFormat = videoFormat;
272 if (_videoFormat) {
273 CFRetain(_videoFormat);
274 }
275}
276
277- (NSString *)implementationName {
278 return @"VideoToolbox";
279}
280
281@end