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