• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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