1/* 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 "RTCVideoDecoderH264.h" 13 14#import <VideoToolbox/VideoToolbox.h> 15 16#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 26#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/time_utils.h" 30#include "sdk/objc/components/video_codec/nalu_rewriter.h" 31 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 40@interface RTC_OBJC_TYPE (RTCVideoDecoderH264) 41() - (void)setError : (OSStatus)error; 42@end 43 44// This is the callback function that VideoToolbox calls when decode is 45// complete. 46void decompressionOutputCallback(void *decoderRef, 47 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) { 56 RTC_OBJC_TYPE(RTCVideoDecoderH264) *decoder = 57 (__bridge RTC_OBJC_TYPE(RTCVideoDecoderH264) *)decoderRef; 58 [decoder setError:status]; 59 RTC_LOG(LS_ERROR) << "Failed to decode frame. Status: " << status; 60 return; 61 } 62 // TODO(tkchin): Handle CVO properly. 63 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]; 69 decodedFrame.timeStamp = decodeParams->timestamp; 70 decodeParams->callback(decodedFrame); 71} 72 73// Decoder. 74@implementation RTC_OBJC_TYPE (RTCVideoDecoderH264) { 75 CMVideoFormatDescriptionRef _videoFormat; 76 CMMemoryPoolRef _memoryPool; 77 VTDecompressionSessionRef _decompressionSession; 78 RTCVideoDecoderCallback _callback; 79 OSStatus _error; 80} 81 82- (instancetype)init { 83 self = [super init]; 84 if (self) { 85 _memoryPool = CMMemoryPoolCreate(nil); 86 } 87 return self; 88} 89 90- (void)dealloc { 91 CMMemoryPoolInvalidate(_memoryPool); 92 CFRelease(_memoryPool); 93 [self destroyDecompressionSession]; 94 [self setVideoFormat:nullptr]; 95} 96 97- (NSInteger)startDecodeWithNumberOfCores:(int)numberOfCores { 98 return WEBRTC_VIDEO_CODEC_OK; 99} 100 101- (NSInteger)decode:(RTC_OBJC_TYPE(RTCEncodedImage) *)inputImage 102 missingFrames:(BOOL)missingFrames 103 codecSpecificInfo:(nullable id<RTC_OBJC_TYPE(RTCCodecSpecificInfo)>)info 104 renderTimeMs:(int64_t)renderTimeMs { 105 RTC_DCHECK(inputImage.buffer); 106 107 if (_error != noErr) { 108 RTC_LOG(LS_WARNING) << "Last frame decode failed."; 109 _error = noErr; 110 return WEBRTC_VIDEO_CODEC_ERROR; 111 } 112 113 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; 124 } 125 } 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. 133 RTC_LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required."; 134 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, 140 &sampleBuffer, 141 _memoryPool)) { 142 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 152 // 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"; 157 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) { 164 RTC_LOG(LS_ERROR) << "Failed to decode frame with code: " << status; 165 return WEBRTC_VIDEO_CODEC_ERROR; 166 } 167 return WEBRTC_VIDEO_CODEC_OK; 168} 169 170- (void)setCallback:(RTCVideoDecoderCallback)callback { 171 _callback = callback; 172} 173 174- (void)setError:(OSStatus)error { 175 _error = error; 176} 177 178- (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 = { 229 decompressionOutputCallback, (__bridge void *)self, 230 }; 231 OSStatus status = VTDecompressionSessionCreate( 232 nullptr, _videoFormat, nullptr, attributes, &record, &_decompressionSession); 233 CFRelease(attributes); 234 if (status != noErr) { 235 RTC_LOG(LS_ERROR) << "Failed to create decompression session: " << status; 236 [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) { 253#if defined(WEBRTC_IOS) 254 if ([UIDevice isIOS11OrLater]) { 255 VTDecompressionSessionWaitForAsynchronousFrames(_decompressionSession); 256 } 257#endif 258 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 282