blob: c3342800e0e69679c9e5b5241cae1701d7d3eb51 [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
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010040@interface RTCVideoDecoderH264 ()
41- (void)setError:(OSStatus)error;
42@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) {
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010056 RTCVideoDecoderH264 *decoder = (__bridge RTCVideoDecoderH264 *)decoderRef;
57 [decoder setError:status];
Mirko Bonadei675513b2017-11-09 11:09:25 +010058 RTC_LOG(LS_ERROR) << "Failed to decode frame. Status: " << status;
magjed73c0eb52017-08-07 06:55:28 -070059 return;
60 }
61 // TODO(tkchin): Handle CVO properly.
62 RTCCVPixelBuffer *frameBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:imageBuffer];
63 RTCVideoFrame *decodedFrame =
64 [[RTCVideoFrame alloc] initWithBuffer:frameBuffer
65 rotation:RTCVideoRotation_0
66 timeStampNs:CMTimeGetSeconds(timestamp) * rtc::kNumNanosecsPerSec];
67 decodedFrame.timeStamp = decodeParams->timestamp;
68 decodeParams->callback(decodedFrame);
69}
70
71// Decoder.
72@implementation RTCVideoDecoderH264 {
73 CMVideoFormatDescriptionRef _videoFormat;
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020074 CMMemoryPoolRef _memoryPool;
magjed73c0eb52017-08-07 06:55:28 -070075 VTDecompressionSessionRef _decompressionSession;
76 RTCVideoDecoderCallback _callback;
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010077 OSStatus _error;
magjed73c0eb52017-08-07 06:55:28 -070078}
79
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020080- (instancetype)init {
81 self = [super init];
82 if (self) {
83 _memoryPool = CMMemoryPoolCreate(nil);
84 }
85 return self;
86}
87
magjed73c0eb52017-08-07 06:55:28 -070088- (void)dealloc {
Kári Tristan Helgason0d247722018-10-26 10:59:28 +020089 CMMemoryPoolInvalidate(_memoryPool);
90 CFRelease(_memoryPool);
magjed73c0eb52017-08-07 06:55:28 -070091 [self destroyDecompressionSession];
92 [self setVideoFormat:nullptr];
93}
94
Anders Carlsson2a1bbc32018-04-04 12:49:43 +020095- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores {
96 return WEBRTC_VIDEO_CODEC_OK;
97}
98
Niels Möllerc199fae2018-04-26 09:54:25 +020099- (NSInteger)decode:(RTCEncodedImage *)inputImage
100 missingFrames:(BOOL)missingFrames
101 codecSpecificInfo:(nullable id<RTCCodecSpecificInfo>)info
102 renderTimeMs:(int64_t)renderTimeMs {
magjed73c0eb52017-08-07 06:55:28 -0700103 RTC_DCHECK(inputImage.buffer);
104
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +0100105 if (_error != noErr) {
106 RTC_LOG(LS_WARNING) << "Last frame decode failed.";
107 _error = noErr;
108 return WEBRTC_VIDEO_CODEC_ERROR;
109 }
110
Guy Hershenbaum2fcb8342018-02-20 21:33:36 -0800111 rtc::ScopedCFTypeRef<CMVideoFormatDescriptionRef> inputFormat =
112 rtc::ScopedCF(webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
113 inputImage.buffer.length));
114 if (inputFormat) {
115 // Check if the video format has changed, and reinitialize decoder if
116 // needed.
117 if (!CMFormatDescriptionEqual(inputFormat.get(), _videoFormat)) {
118 [self setVideoFormat:inputFormat.get()];
119 int resetDecompressionSessionError = [self resetDecompressionSession];
120 if (resetDecompressionSessionError != WEBRTC_VIDEO_CODEC_OK) {
121 return resetDecompressionSessionError;
magjed73c0eb52017-08-07 06:55:28 -0700122 }
magjed73c0eb52017-08-07 06:55:28 -0700123 }
124 }
125 if (!_videoFormat) {
126 // We received a frame but we don't have format information so we can't
127 // decode it.
128 // This can happen after backgrounding. We need to wait for the next
129 // sps/pps before we can resume so we request a keyframe by returning an
130 // error.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100131 RTC_LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required.";
magjed73c0eb52017-08-07 06:55:28 -0700132 return WEBRTC_VIDEO_CODEC_ERROR;
133 }
134 CMSampleBufferRef sampleBuffer = nullptr;
135 if (!webrtc::H264AnnexBBufferToCMSampleBuffer((uint8_t *)inputImage.buffer.bytes,
136 inputImage.buffer.length,
137 _videoFormat,
Kári Tristan Helgason0d247722018-10-26 10:59:28 +0200138 &sampleBuffer,
139 _memoryPool)) {
magjed73c0eb52017-08-07 06:55:28 -0700140 return WEBRTC_VIDEO_CODEC_ERROR;
141 }
142 RTC_DCHECK(sampleBuffer);
143 VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
144 std::unique_ptr<RTCFrameDecodeParams> frameDecodeParams;
145 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
146 OSStatus status = VTDecompressionSessionDecodeFrame(
147 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
148#if defined(WEBRTC_IOS)
149 // Re-initialize the decoder if we have an invalid session while the app is
150 // active and retry the decode request.
151 if (status == kVTInvalidSessionErr && [self resetDecompressionSession] == WEBRTC_VIDEO_CODEC_OK) {
152 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
153 status = VTDecompressionSessionDecodeFrame(
154 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
155 }
156#endif
157 CFRelease(sampleBuffer);
158 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100159 RTC_LOG(LS_ERROR) << "Failed to decode frame with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700160 return WEBRTC_VIDEO_CODEC_ERROR;
161 }
162 return WEBRTC_VIDEO_CODEC_OK;
163}
164
165- (void)setCallback:(RTCVideoDecoderCallback)callback {
166 _callback = callback;
167}
168
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +0100169- (void)setError:(OSStatus)error {
170 _error = error;
171}
172
magjed73c0eb52017-08-07 06:55:28 -0700173- (NSInteger)releaseDecoder {
174 // Need to invalidate the session so that callbacks no longer occur and it
175 // is safe to null out the callback.
176 [self destroyDecompressionSession];
177 [self setVideoFormat:nullptr];
178 _callback = nullptr;
179 return WEBRTC_VIDEO_CODEC_OK;
180}
181
182#pragma mark - Private
183
184- (int)resetDecompressionSession {
185 [self destroyDecompressionSession];
186
187 // Need to wait for the first SPS to initialize decoder.
188 if (!_videoFormat) {
189 return WEBRTC_VIDEO_CODEC_OK;
190 }
191
192 // Set keys for OpenGL and IOSurface compatibilty, which makes the encoder
193 // create pixel buffers with GPU backed memory. The intent here is to pass
194 // the pixel buffers directly so we avoid a texture upload later during
195 // rendering. This currently is moot because we are converting back to an
196 // I420 frame after decode, but eventually we will be able to plumb
197 // CVPixelBuffers directly to the renderer.
198 // TODO(tkchin): Maybe only set OpenGL/IOSurface keys if we know that that
199 // we can pass CVPixelBuffers as native handles in decoder output.
200 static size_t const attributesSize = 3;
201 CFTypeRef keys[attributesSize] = {
202#if defined(WEBRTC_IOS)
203 kCVPixelBufferOpenGLESCompatibilityKey,
204#elif defined(WEBRTC_MAC)
205 kCVPixelBufferOpenGLCompatibilityKey,
206#endif
207 kCVPixelBufferIOSurfacePropertiesKey,
208 kCVPixelBufferPixelFormatTypeKey
209 };
210 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
211 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
212 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
213 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
214 CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize);
215 if (ioSurfaceValue) {
216 CFRelease(ioSurfaceValue);
217 ioSurfaceValue = nullptr;
218 }
219 if (pixelFormat) {
220 CFRelease(pixelFormat);
221 pixelFormat = nullptr;
222 }
223 VTDecompressionOutputCallbackRecord record = {
Kári Tristan Helgasondb6145f2018-02-13 13:58:10 +0100224 decompressionOutputCallback, (__bridge void *)self,
magjed73c0eb52017-08-07 06:55:28 -0700225 };
226 OSStatus status = VTDecompressionSessionCreate(
227 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession);
228 CFRelease(attributes);
229 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300230 RTC_LOG(LS_ERROR) << "Failed to create decompression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700231 [self destroyDecompressionSession];
232 return WEBRTC_VIDEO_CODEC_ERROR;
233 }
234 [self configureDecompressionSession];
235
236 return WEBRTC_VIDEO_CODEC_OK;
237}
238
239- (void)configureDecompressionSession {
240 RTC_DCHECK(_decompressionSession);
241#if defined(WEBRTC_IOS)
242 VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
243#endif
244}
245
246- (void)destroyDecompressionSession {
247 if (_decompressionSession) {
JT Teha6368d12017-09-28 11:00:39 -0700248#if defined(WEBRTC_IOS)
249 if ([UIDevice isIOS11OrLater]) {
250 VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession);
251 }
252#endif
magjed73c0eb52017-08-07 06:55:28 -0700253 VTDecompressionSessionInvalidate(_decompressionSession);
254 CFRelease(_decompressionSession);
255 _decompressionSession = nullptr;
256 }
257}
258
259- (void)setVideoFormat:(CMVideoFormatDescriptionRef)videoFormat {
260 if (_videoFormat == videoFormat) {
261 return;
262 }
263 if (_videoFormat) {
264 CFRelease(_videoFormat);
265 }
266 _videoFormat = videoFormat;
267 if (_videoFormat) {
268 CFRetain(_videoFormat);
269 }
270}
271
272- (NSString *)implementationName {
273 return @"VideoToolbox";
274}
275
276@end