OLD | NEW |
| (Empty) |
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 #include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/decoder.h" | |
13 | |
14 #include <memory> | |
15 | |
16 #if defined(WEBRTC_IOS) | |
17 #include "Common/RTCUIApplication.h" | |
18 #endif | |
19 #include "libyuv/convert.h" | |
20 #include "webrtc/api/video/video_frame.h" | |
21 #include "webrtc/base/checks.h" | |
22 #include "webrtc/base/logging.h" | |
23 #include "webrtc/common_video/include/video_frame.h" | |
24 #include "webrtc/sdk/objc/Framework/Classes/Video/corevideo_frame_buffer.h" | |
25 #include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h" | |
26 | |
27 namespace webrtc { | |
28 namespace { | |
29 | |
30 static const int64_t kMsPerSec = 1000; | |
31 | |
32 // Convenience function for creating a dictionary. | |
33 inline CFDictionaryRef CreateCFDictionary(CFTypeRef* keys, | |
34 CFTypeRef* values, | |
35 size_t size) { | |
36 return CFDictionaryCreate(nullptr, keys, values, size, | |
37 &kCFTypeDictionaryKeyCallBacks, | |
38 &kCFTypeDictionaryValueCallBacks); | |
39 } | |
40 | |
41 // Struct that we pass to the decoder per frame to decode. We receive it again | |
42 // in the decoder callback. | |
43 struct FrameDecodeParams { | |
44 FrameDecodeParams(DecodedImageCallback* cb, int64_t ts) | |
45 : callback(cb), timestamp(ts) {} | |
46 DecodedImageCallback* callback; | |
47 int64_t timestamp; | |
48 }; | |
49 | |
50 // This is the callback function that VideoToolbox calls when decode is | |
51 // complete. | |
52 void VTDecompressionOutputCallback(void* decoder, | |
53 void* params, | |
54 OSStatus status, | |
55 VTDecodeInfoFlags info_flags, | |
56 CVImageBufferRef image_buffer, | |
57 CMTime timestamp, | |
58 CMTime duration) { | |
59 std::unique_ptr<FrameDecodeParams> decode_params( | |
60 reinterpret_cast<FrameDecodeParams*>(params)); | |
61 if (status != noErr) { | |
62 LOG(LS_ERROR) << "Failed to decode frame. Status: " << status; | |
63 return; | |
64 } | |
65 // TODO(tkchin): Handle CVO properly. | |
66 rtc::scoped_refptr<VideoFrameBuffer> buffer = | |
67 new rtc::RefCountedObject<CoreVideoFrameBuffer>(image_buffer); | |
68 VideoFrame decoded_frame(buffer, decode_params->timestamp, | |
69 CMTimeGetSeconds(timestamp) * kMsPerSec, | |
70 kVideoRotation_0); | |
71 decode_params->callback->Decoded(decoded_frame); | |
72 } | |
73 | |
74 } // namespace | |
75 | |
76 H264VideoToolboxDecoder::H264VideoToolboxDecoder() | |
77 : callback_(nullptr), | |
78 video_format_(nullptr), | |
79 decompression_session_(nullptr) {} | |
80 | |
81 H264VideoToolboxDecoder::~H264VideoToolboxDecoder() { | |
82 DestroyDecompressionSession(); | |
83 SetVideoFormat(nullptr); | |
84 } | |
85 | |
86 int H264VideoToolboxDecoder::InitDecode(const VideoCodec* video_codec, | |
87 int number_of_cores) { | |
88 return WEBRTC_VIDEO_CODEC_OK; | |
89 } | |
90 | |
91 int H264VideoToolboxDecoder::Decode( | |
92 const EncodedImage& input_image, | |
93 bool missing_frames, | |
94 const RTPFragmentationHeader* fragmentation, | |
95 const CodecSpecificInfo* codec_specific_info, | |
96 int64_t render_time_ms) { | |
97 RTC_DCHECK(input_image._buffer); | |
98 | |
99 #if defined(WEBRTC_IOS) | |
100 if (!RTCIsUIApplicationActive()) { | |
101 // Ignore all decode requests when app isn't active. In this state, the | |
102 // hardware decoder has been invalidated by the OS. | |
103 // Reset video format so that we won't process frames until the next | |
104 // keyframe. | |
105 SetVideoFormat(nullptr); | |
106 return WEBRTC_VIDEO_CODEC_NO_OUTPUT; | |
107 } | |
108 #endif | |
109 CMVideoFormatDescriptionRef input_format = nullptr; | |
110 if (H264AnnexBBufferHasVideoFormatDescription(input_image._buffer, | |
111 input_image._length)) { | |
112 input_format = CreateVideoFormatDescription(input_image._buffer, | |
113 input_image._length); | |
114 if (input_format) { | |
115 // Check if the video format has changed, and reinitialize decoder if | |
116 // needed. | |
117 if (!CMFormatDescriptionEqual(input_format, video_format_)) { | |
118 SetVideoFormat(input_format); | |
119 ResetDecompressionSession(); | |
120 } | |
121 CFRelease(input_format); | |
122 } | |
123 } | |
124 if (!video_format_) { | |
125 // We received a frame but we don't have format information so we can't | |
126 // decode it. | |
127 // This can happen after backgrounding. We need to wait for the next | |
128 // sps/pps before we can resume so we request a keyframe by returning an | |
129 // error. | |
130 LOG(LS_WARNING) << "Missing video format. Frame with sps/pps required."; | |
131 return WEBRTC_VIDEO_CODEC_ERROR; | |
132 } | |
133 CMSampleBufferRef sample_buffer = nullptr; | |
134 if (!H264AnnexBBufferToCMSampleBuffer(input_image._buffer, | |
135 input_image._length, video_format_, | |
136 &sample_buffer)) { | |
137 return WEBRTC_VIDEO_CODEC_ERROR; | |
138 } | |
139 RTC_DCHECK(sample_buffer); | |
140 VTDecodeFrameFlags decode_flags = | |
141 kVTDecodeFrame_EnableAsynchronousDecompression; | |
142 std::unique_ptr<FrameDecodeParams> frame_decode_params; | |
143 frame_decode_params.reset( | |
144 new FrameDecodeParams(callback_, input_image._timeStamp)); | |
145 OSStatus status = VTDecompressionSessionDecodeFrame( | |
146 decompression_session_, sample_buffer, decode_flags, | |
147 frame_decode_params.release(), nullptr); | |
148 #if defined(WEBRTC_IOS) | |
149 // Re-initialize the decoder if we have an invalid session while the app is | |
150 // active and retry the decode request. | |
151 if (status == kVTInvalidSessionErr && | |
152 ResetDecompressionSession() == WEBRTC_VIDEO_CODEC_OK) { | |
153 frame_decode_params.reset( | |
154 new FrameDecodeParams(callback_, input_image._timeStamp)); | |
155 status = VTDecompressionSessionDecodeFrame( | |
156 decompression_session_, sample_buffer, decode_flags, | |
157 frame_decode_params.release(), nullptr); | |
158 } | |
159 #endif | |
160 CFRelease(sample_buffer); | |
161 if (status != noErr) { | |
162 LOG(LS_ERROR) << "Failed to decode frame with code: " << status; | |
163 return WEBRTC_VIDEO_CODEC_ERROR; | |
164 } | |
165 return WEBRTC_VIDEO_CODEC_OK; | |
166 } | |
167 | |
168 int H264VideoToolboxDecoder::RegisterDecodeCompleteCallback( | |
169 DecodedImageCallback* callback) { | |
170 RTC_DCHECK(!callback_); | |
171 callback_ = callback; | |
172 return WEBRTC_VIDEO_CODEC_OK; | |
173 } | |
174 | |
175 int H264VideoToolboxDecoder::Release() { | |
176 // Need to invalidate the session so that callbacks no longer occur and it | |
177 // is safe to null out the callback. | |
178 DestroyDecompressionSession(); | |
179 SetVideoFormat(nullptr); | |
180 callback_ = nullptr; | |
181 return WEBRTC_VIDEO_CODEC_OK; | |
182 } | |
183 | |
184 int H264VideoToolboxDecoder::ResetDecompressionSession() { | |
185 DestroyDecompressionSession(); | |
186 | |
187 // Need to wait for the first SPS to initialize decoder. | |
188 if (!video_format_) { | |
189 return WEBRTC_VIDEO_CODEC_OK; | |
190 } | |
191 | |
192 // Set keys for OpenGL and IOSurface compatibilty, which makes the encoder | |
193 // create pixel buffers with GPU backed memory. The intent here is to pass | |
194 // the pixel buffers directly so we avoid a texture upload later during | |
195 // rendering. This currently is moot because we are converting back to an | |
196 // I420 frame after decode, but eventually we will be able to plumb | |
197 // CVPixelBuffers directly to the renderer. | |
198 // TODO(tkchin): Maybe only set OpenGL/IOSurface keys if we know that that | |
199 // we can pass CVPixelBuffers as native handles in decoder output. | |
200 static size_t const attributes_size = 3; | |
201 CFTypeRef keys[attributes_size] = { | |
202 #if defined(WEBRTC_IOS) | |
203 kCVPixelBufferOpenGLESCompatibilityKey, | |
204 #elif defined(WEBRTC_MAC) | |
205 kCVPixelBufferOpenGLCompatibilityKey, | |
206 #endif | |
207 kCVPixelBufferIOSurfacePropertiesKey, | |
208 kCVPixelBufferPixelFormatTypeKey | |
209 }; | |
210 CFDictionaryRef io_surface_value = CreateCFDictionary(nullptr, nullptr, 0); | |
211 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; | |
212 CFNumberRef pixel_format = | |
213 CFNumberCreate(nullptr, kCFNumberLongType, &nv12type); | |
214 CFTypeRef values[attributes_size] = {kCFBooleanTrue, io_surface_value, | |
215 pixel_format}; | |
216 CFDictionaryRef attributes = | |
217 CreateCFDictionary(keys, values, attributes_size); | |
218 if (io_surface_value) { | |
219 CFRelease(io_surface_value); | |
220 io_surface_value = nullptr; | |
221 } | |
222 if (pixel_format) { | |
223 CFRelease(pixel_format); | |
224 pixel_format = nullptr; | |
225 } | |
226 VTDecompressionOutputCallbackRecord record = { | |
227 VTDecompressionOutputCallback, this, | |
228 }; | |
229 OSStatus status = | |
230 VTDecompressionSessionCreate(nullptr, video_format_, nullptr, attributes, | |
231 &record, &decompression_session_); | |
232 CFRelease(attributes); | |
233 if (status != noErr) { | |
234 DestroyDecompressionSession(); | |
235 return WEBRTC_VIDEO_CODEC_ERROR; | |
236 } | |
237 ConfigureDecompressionSession(); | |
238 | |
239 return WEBRTC_VIDEO_CODEC_OK; | |
240 } | |
241 | |
242 void H264VideoToolboxDecoder::ConfigureDecompressionSession() { | |
243 RTC_DCHECK(decompression_session_); | |
244 #if defined(WEBRTC_IOS) | |
245 VTSessionSetProperty(decompression_session_, | |
246 kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue); | |
247 #endif | |
248 } | |
249 | |
250 void H264VideoToolboxDecoder::DestroyDecompressionSession() { | |
251 if (decompression_session_) { | |
252 VTDecompressionSessionInvalidate(decompression_session_); | |
253 CFRelease(decompression_session_); | |
254 decompression_session_ = nullptr; | |
255 } | |
256 } | |
257 | |
258 void H264VideoToolboxDecoder::SetVideoFormat( | |
259 CMVideoFormatDescriptionRef video_format) { | |
260 if (video_format_ == video_format) { | |
261 return; | |
262 } | |
263 if (video_format_) { | |
264 CFRelease(video_format_); | |
265 } | |
266 video_format_ = video_format; | |
267 if (video_format_) { | |
268 CFRetain(video_format_); | |
269 } | |
270 } | |
271 | |
272 const char* H264VideoToolboxDecoder::ImplementationName() const { | |
273 return "VideoToolbox"; | |
274 } | |
275 | |
276 } // namespace webrtc | |
OLD | NEW |