blob: a6edbc8474a7a1a1a585cd6b76f729f1de3d66dd [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"
29#include "rtc_base/timeutils.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;
74 VTDecompressionSessionRef _decompressionSession;
75 RTCVideoDecoderCallback _callback;
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010076 OSStatus _error;
magjed73c0eb52017-08-07 06:55:28 -070077}
78
magjed73c0eb52017-08-07 06:55:28 -070079- (void)dealloc {
80 [self destroyDecompressionSession];
81 [self setVideoFormat:nullptr];
82}
83
Anders Carlsson2a1bbc32018-04-04 12:49:43 +020084- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores {
85 return WEBRTC_VIDEO_CODEC_OK;
86}
87
magjed73c0eb52017-08-07 06:55:28 -070088- (NSInteger)startDecodeWithSettings:(RTCVideoEncoderSettings *)settings
89 numberOfCores:(int)numberOfCores {
90 return WEBRTC_VIDEO_CODEC_OK;
91}
92
Niels Möllerc199fae2018-04-26 09:54:25 +020093- (NSInteger)decode:(RTCEncodedImage *)inputImage
94 missingFrames:(BOOL)missingFrames
95 codecSpecificInfo:(nullable id<RTCCodecSpecificInfo>)info
96 renderTimeMs:(int64_t)renderTimeMs {
magjed73c0eb52017-08-07 06:55:28 -070097 RTC_DCHECK(inputImage.buffer);
98
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +010099 if (_error != noErr) {
100 RTC_LOG(LS_WARNING) << "Last frame decode failed.";
101 _error = noErr;
102 return WEBRTC_VIDEO_CODEC_ERROR;
103 }
104
Guy Hershenbaum2fcb8342018-02-20 21:33:36 -0800105 rtc::ScopedCFTypeRef<CMVideoFormatDescriptionRef> inputFormat =
106 rtc::ScopedCF(webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
107 inputImage.buffer.length));
108 if (inputFormat) {
109 // Check if the video format has changed, and reinitialize decoder if
110 // needed.
111 if (!CMFormatDescriptionEqual(inputFormat.get(), _videoFormat)) {
112 [self setVideoFormat:inputFormat.get()];
113 int resetDecompressionSessionError = [self resetDecompressionSession];
114 if (resetDecompressionSessionError != WEBRTC_VIDEO_CODEC_OK) {
115 return resetDecompressionSessionError;
magjed73c0eb52017-08-07 06:55:28 -0700116 }
magjed73c0eb52017-08-07 06:55:28 -0700117 }
118 }
119 if (!_videoFormat) {
120 // We received a frame but we don't have format information so we can't
121 // decode it.
122 // This can happen after backgrounding. We need to wait for the next
123 // sps/pps before we can resume so we request a keyframe by returning an
124 // error.
Mirko Bonadei675513b2017-11-09 11:09:25 +0100125 RTC_LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required.";
magjed73c0eb52017-08-07 06:55:28 -0700126 return WEBRTC_VIDEO_CODEC_ERROR;
127 }
128 CMSampleBufferRef sampleBuffer = nullptr;
129 if (!webrtc::H264AnnexBBufferToCMSampleBuffer((uint8_t *)inputImage.buffer.bytes,
130 inputImage.buffer.length,
131 _videoFormat,
132 &sampleBuffer)) {
133 return WEBRTC_VIDEO_CODEC_ERROR;
134 }
135 RTC_DCHECK(sampleBuffer);
136 VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
137 std::unique_ptr<RTCFrameDecodeParams> frameDecodeParams;
138 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
139 OSStatus status = VTDecompressionSessionDecodeFrame(
140 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
141#if defined(WEBRTC_IOS)
142 // Re-initialize the decoder if we have an invalid session while the app is
143 // active and retry the decode request.
144 if (status == kVTInvalidSessionErr && [self resetDecompressionSession] == WEBRTC_VIDEO_CODEC_OK) {
145 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
146 status = VTDecompressionSessionDecodeFrame(
147 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
148 }
149#endif
150 CFRelease(sampleBuffer);
151 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100152 RTC_LOG(LS_ERROR) << "Failed to decode frame with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700153 return WEBRTC_VIDEO_CODEC_ERROR;
154 }
155 return WEBRTC_VIDEO_CODEC_OK;
156}
157
158- (void)setCallback:(RTCVideoDecoderCallback)callback {
159 _callback = callback;
160}
161
Kári Tristan Helgason86de7e82017-12-01 13:48:48 +0100162- (void)setError:(OSStatus)error {
163 _error = error;
164}
165
magjed73c0eb52017-08-07 06:55:28 -0700166- (NSInteger)releaseDecoder {
167 // Need to invalidate the session so that callbacks no longer occur and it
168 // is safe to null out the callback.
169 [self destroyDecompressionSession];
170 [self setVideoFormat:nullptr];
171 _callback = nullptr;
172 return WEBRTC_VIDEO_CODEC_OK;
173}
174
175#pragma mark - Private
176
177- (int)resetDecompressionSession {
178 [self destroyDecompressionSession];
179
180 // Need to wait for the first SPS to initialize decoder.
181 if (!_videoFormat) {
182 return WEBRTC_VIDEO_CODEC_OK;
183 }
184
185 // Set keys for OpenGL and IOSurface compatibilty, which makes the encoder
186 // create pixel buffers with GPU backed memory. The intent here is to pass
187 // the pixel buffers directly so we avoid a texture upload later during
188 // rendering. This currently is moot because we are converting back to an
189 // I420 frame after decode, but eventually we will be able to plumb
190 // CVPixelBuffers directly to the renderer.
191 // TODO(tkchin): Maybe only set OpenGL/IOSurface keys if we know that that
192 // we can pass CVPixelBuffers as native handles in decoder output.
193 static size_t const attributesSize = 3;
194 CFTypeRef keys[attributesSize] = {
195#if defined(WEBRTC_IOS)
196 kCVPixelBufferOpenGLESCompatibilityKey,
197#elif defined(WEBRTC_MAC)
198 kCVPixelBufferOpenGLCompatibilityKey,
199#endif
200 kCVPixelBufferIOSurfacePropertiesKey,
201 kCVPixelBufferPixelFormatTypeKey
202 };
203 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
204 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
205 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
206 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
207 CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize);
208 if (ioSurfaceValue) {
209 CFRelease(ioSurfaceValue);
210 ioSurfaceValue = nullptr;
211 }
212 if (pixelFormat) {
213 CFRelease(pixelFormat);
214 pixelFormat = nullptr;
215 }
216 VTDecompressionOutputCallbackRecord record = {
Kári Tristan Helgasondb6145f2018-02-13 13:58:10 +0100217 decompressionOutputCallback, (__bridge void *)self,
magjed73c0eb52017-08-07 06:55:28 -0700218 };
219 OSStatus status = VTDecompressionSessionCreate(
220 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession);
221 CFRelease(attributes);
222 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300223 RTC_LOG(LS_ERROR) << "Failed to create decompression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700224 [self destroyDecompressionSession];
225 return WEBRTC_VIDEO_CODEC_ERROR;
226 }
227 [self configureDecompressionSession];
228
229 return WEBRTC_VIDEO_CODEC_OK;
230}
231
232- (void)configureDecompressionSession {
233 RTC_DCHECK(_decompressionSession);
234#if defined(WEBRTC_IOS)
235 VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
236#endif
237}
238
239- (void)destroyDecompressionSession {
240 if (_decompressionSession) {
JT Teha6368d12017-09-28 11:00:39 -0700241#if defined(WEBRTC_IOS)
242 if ([UIDevice isIOS11OrLater]) {
243 VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession);
244 }
245#endif
magjed73c0eb52017-08-07 06:55:28 -0700246 VTDecompressionSessionInvalidate(_decompressionSession);
247 CFRelease(_decompressionSession);
248 _decompressionSession = nullptr;
249 }
250}
251
252- (void)setVideoFormat:(CMVideoFormatDescriptionRef)videoFormat {
253 if (_videoFormat == videoFormat) {
254 return;
255 }
256 if (_videoFormat) {
257 CFRelease(_videoFormat);
258 }
259 _videoFormat = videoFormat;
260 if (_videoFormat) {
261 CFRetain(_videoFormat);
262 }
263}
264
265- (NSString *)implementationName {
266 return @"VideoToolbox";
267}
268
269@end