blob: 6e000ab747eb3777dc866bbeeee4edbab9902a92 [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
12#import "WebRTC/RTCVideoCodecH264.h"
13
14#import <VideoToolbox/VideoToolbox.h>
15
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020016#include "modules/video_coding/include/video_error_codes.h"
17#include "rtc_base/checks.h"
18#include "rtc_base/logging.h"
19#include "rtc_base/timeutils.h"
20#include "sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h"
magjed73c0eb52017-08-07 06:55:28 -070021
22#import "WebRTC/RTCVideoFrame.h"
23#import "WebRTC/RTCVideoFrameBuffer.h"
24#import "helpers.h"
25
26#if defined(WEBRTC_IOS)
27#import "Common/RTCUIApplicationStatusObserver.h"
28#endif
29
30// Struct that we pass to the decoder per frame to decode. We receive it again
31// in the decoder callback.
32struct RTCFrameDecodeParams {
33 RTCFrameDecodeParams(RTCVideoDecoderCallback cb, int64_t ts) : callback(cb), timestamp(ts) {}
34 RTCVideoDecoderCallback callback;
35 int64_t timestamp;
36};
37
38// This is the callback function that VideoToolbox calls when decode is
39// complete.
40void decompressionOutputCallback(void *decoder,
41 void *params,
42 OSStatus status,
43 VTDecodeInfoFlags infoFlags,
44 CVImageBufferRef imageBuffer,
45 CMTime timestamp,
46 CMTime duration) {
47 std::unique_ptr<RTCFrameDecodeParams> decodeParams(
48 reinterpret_cast<RTCFrameDecodeParams *>(params));
49 if (status != noErr) {
50 LOG(LS_ERROR) << "Failed to decode frame. Status: " << status;
51 return;
52 }
53 // TODO(tkchin): Handle CVO properly.
54 RTCCVPixelBuffer *frameBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:imageBuffer];
55 RTCVideoFrame *decodedFrame =
56 [[RTCVideoFrame alloc] initWithBuffer:frameBuffer
57 rotation:RTCVideoRotation_0
58 timeStampNs:CMTimeGetSeconds(timestamp) * rtc::kNumNanosecsPerSec];
59 decodedFrame.timeStamp = decodeParams->timestamp;
60 decodeParams->callback(decodedFrame);
61}
62
63// Decoder.
64@implementation RTCVideoDecoderH264 {
65 CMVideoFormatDescriptionRef _videoFormat;
66 VTDecompressionSessionRef _decompressionSession;
67 RTCVideoDecoderCallback _callback;
68}
69
andersc9a85f072017-09-13 07:31:46 -070070- (instancetype)init {
71 if (self = [super init]) {
72#if defined(WEBRTC_IOS)
73 [RTCUIApplicationStatusObserver prepareForUse];
74#endif
75 }
76
77 return self;
78}
79
magjed73c0eb52017-08-07 06:55:28 -070080- (void)dealloc {
81 [self destroyDecompressionSession];
82 [self setVideoFormat:nullptr];
83}
84
85- (NSInteger)startDecodeWithSettings:(RTCVideoEncoderSettings *)settings
86 numberOfCores:(int)numberOfCores {
87 return WEBRTC_VIDEO_CODEC_OK;
88}
89
90- (NSInteger)decode:(RTCEncodedImage *)inputImage
91 missingFrames:(BOOL)missingFrames
92 fragmentationHeader:(RTCRtpFragmentationHeader *)fragmentationHeader
93 codecSpecificInfo:(__nullable id<RTCCodecSpecificInfo>)info
94 renderTimeMs:(int64_t)renderTimeMs {
95 RTC_DCHECK(inputImage.buffer);
96
97#if defined(WEBRTC_IOS)
98 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) {
99 // Ignore all decode requests when app isn't active. In this state, the
100 // hardware decoder has been invalidated by the OS.
101 // Reset video format so that we won't process frames until the next
102 // keyframe.
103 [self setVideoFormat:nullptr];
104 return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
105 }
106#endif
107 CMVideoFormatDescriptionRef inputFormat = nullptr;
108 if (webrtc::H264AnnexBBufferHasVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
109 inputImage.buffer.length)) {
110 inputFormat = webrtc::CreateVideoFormatDescription((uint8_t *)inputImage.buffer.bytes,
111 inputImage.buffer.length);
112 if (inputFormat) {
113 // Check if the video format has changed, and reinitialize decoder if
114 // needed.
115 if (!CMFormatDescriptionEqual(inputFormat, _videoFormat)) {
116 [self setVideoFormat:inputFormat];
117 [self resetDecompressionSession];
118 }
119 CFRelease(inputFormat);
120 }
121 }
122 if (!_videoFormat) {
123 // We received a frame but we don't have format information so we can't
124 // decode it.
125 // This can happen after backgrounding. We need to wait for the next
126 // sps/pps before we can resume so we request a keyframe by returning an
127 // error.
128 LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required.";
129 return WEBRTC_VIDEO_CODEC_ERROR;
130 }
131 CMSampleBufferRef sampleBuffer = nullptr;
132 if (!webrtc::H264AnnexBBufferToCMSampleBuffer((uint8_t *)inputImage.buffer.bytes,
133 inputImage.buffer.length,
134 _videoFormat,
135 &sampleBuffer)) {
136 return WEBRTC_VIDEO_CODEC_ERROR;
137 }
138 RTC_DCHECK(sampleBuffer);
139 VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
140 std::unique_ptr<RTCFrameDecodeParams> frameDecodeParams;
141 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
142 OSStatus status = VTDecompressionSessionDecodeFrame(
143 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
144#if defined(WEBRTC_IOS)
145 // Re-initialize the decoder if we have an invalid session while the app is
146 // active and retry the decode request.
147 if (status == kVTInvalidSessionErr && [self resetDecompressionSession] == WEBRTC_VIDEO_CODEC_OK) {
148 frameDecodeParams.reset(new RTCFrameDecodeParams(_callback, inputImage.timeStamp));
149 status = VTDecompressionSessionDecodeFrame(
150 _decompressionSession, sampleBuffer, decodeFlags, frameDecodeParams.release(), nullptr);
151 }
152#endif
153 CFRelease(sampleBuffer);
154 if (status != noErr) {
155 LOG(LS_ERROR) << "Failed to decode frame with code: " << status;
156 return WEBRTC_VIDEO_CODEC_ERROR;
157 }
158 return WEBRTC_VIDEO_CODEC_OK;
159}
160
161- (void)setCallback:(RTCVideoDecoderCallback)callback {
162 _callback = callback;
163}
164
165- (NSInteger)releaseDecoder {
166 // Need to invalidate the session so that callbacks no longer occur and it
167 // is safe to null out the callback.
168 [self destroyDecompressionSession];
169 [self setVideoFormat:nullptr];
170 _callback = nullptr;
171 return WEBRTC_VIDEO_CODEC_OK;
172}
173
174#pragma mark - Private
175
176- (int)resetDecompressionSession {
177 [self destroyDecompressionSession];
178
179 // Need to wait for the first SPS to initialize decoder.
180 if (!_videoFormat) {
181 return WEBRTC_VIDEO_CODEC_OK;
182 }
183
184 // Set keys for OpenGL and IOSurface compatibilty, which makes the encoder
185 // create pixel buffers with GPU backed memory. The intent here is to pass
186 // the pixel buffers directly so we avoid a texture upload later during
187 // rendering. This currently is moot because we are converting back to an
188 // I420 frame after decode, but eventually we will be able to plumb
189 // CVPixelBuffers directly to the renderer.
190 // TODO(tkchin): Maybe only set OpenGL/IOSurface keys if we know that that
191 // we can pass CVPixelBuffers as native handles in decoder output.
192 static size_t const attributesSize = 3;
193 CFTypeRef keys[attributesSize] = {
194#if defined(WEBRTC_IOS)
195 kCVPixelBufferOpenGLESCompatibilityKey,
196#elif defined(WEBRTC_MAC)
197 kCVPixelBufferOpenGLCompatibilityKey,
198#endif
199 kCVPixelBufferIOSurfacePropertiesKey,
200 kCVPixelBufferPixelFormatTypeKey
201 };
202 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
203 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
204 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
205 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
206 CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize);
207 if (ioSurfaceValue) {
208 CFRelease(ioSurfaceValue);
209 ioSurfaceValue = nullptr;
210 }
211 if (pixelFormat) {
212 CFRelease(pixelFormat);
213 pixelFormat = nullptr;
214 }
215 VTDecompressionOutputCallbackRecord record = {
216 decompressionOutputCallback, nullptr,
217 };
218 OSStatus status = VTDecompressionSessionCreate(
219 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession);
220 CFRelease(attributes);
221 if (status != noErr) {
222 [self destroyDecompressionSession];
223 return WEBRTC_VIDEO_CODEC_ERROR;
224 }
225 [self configureDecompressionSession];
226
227 return WEBRTC_VIDEO_CODEC_OK;
228}
229
230- (void)configureDecompressionSession {
231 RTC_DCHECK(_decompressionSession);
232#if defined(WEBRTC_IOS)
233 VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
234#endif
235}
236
237- (void)destroyDecompressionSession {
238 if (_decompressionSession) {
239 VTDecompressionSessionInvalidate(_decompressionSession);
240 CFRelease(_decompressionSession);
241 _decompressionSession = nullptr;
242 }
243}
244
245- (void)setVideoFormat:(CMVideoFormatDescriptionRef)videoFormat {
246 if (_videoFormat == videoFormat) {
247 return;
248 }
249 if (_videoFormat) {
250 CFRelease(_videoFormat);
251 }
252 _videoFormat = videoFormat;
253 if (_videoFormat) {
254 CFRetain(_videoFormat);
255 }
256}
257
258- (NSString *)implementationName {
259 return @"VideoToolbox";
260}
261
262@end