Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "VPXDecoder.h"
8 : #include "TimeUnits.h"
9 : #include "gfx2DGlue.h"
10 : #include "mozilla/PodOperations.h"
11 : #include "mozilla/SyncRunnable.h"
12 : #include "nsError.h"
13 : #include "prsystem.h"
14 :
15 : #include <algorithm>
16 :
17 : #undef LOG
18 : #define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("VPXDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
19 :
20 : namespace mozilla {
21 :
22 : using namespace gfx;
23 : using namespace layers;
24 :
25 0 : static VPXDecoder::Codec MimeTypeToCodec(const nsACString& aMimeType)
26 : {
27 0 : if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) {
28 0 : return VPXDecoder::Codec::VP8;
29 0 : } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) {
30 0 : return VPXDecoder::Codec::VP9;
31 0 : } else if (aMimeType.EqualsLiteral("video/vp9")) {
32 0 : return VPXDecoder::Codec::VP9;
33 : }
34 0 : return VPXDecoder::Codec::Unknown;
35 : }
36 :
37 : static nsresult
38 0 : InitContext(vpx_codec_ctx_t* aCtx,
39 : const VideoInfo& aInfo,
40 : const VPXDecoder::Codec aCodec)
41 : {
42 0 : int decode_threads = 2;
43 :
44 0 : vpx_codec_iface_t* dx = nullptr;
45 0 : if (aCodec == VPXDecoder::Codec::VP8) {
46 0 : dx = vpx_codec_vp8_dx();
47 : }
48 0 : else if (aCodec == VPXDecoder::Codec::VP9) {
49 0 : dx = vpx_codec_vp9_dx();
50 0 : if (aInfo.mDisplay.width >= 2048) {
51 0 : decode_threads = 8;
52 : }
53 0 : else if (aInfo.mDisplay.width >= 1024) {
54 0 : decode_threads = 4;
55 : }
56 : }
57 0 : decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
58 :
59 : vpx_codec_dec_cfg_t config;
60 0 : config.threads = decode_threads;
61 0 : config.w = config.h = 0; // set after decode
62 :
63 0 : if (!dx || vpx_codec_dec_init(aCtx, dx, &config, 0)) {
64 0 : return NS_ERROR_FAILURE;
65 : }
66 0 : return NS_OK;
67 : }
68 :
69 0 : VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
70 0 : : mImageContainer(aParams.mImageContainer)
71 : , mImageAllocator(aParams.mKnowsCompositor)
72 0 : , mTaskQueue(aParams.mTaskQueue)
73 0 : , mInfo(aParams.VideoConfig())
74 0 : , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType))
75 : {
76 0 : MOZ_COUNT_CTOR(VPXDecoder);
77 0 : PodZero(&mVPX);
78 0 : PodZero(&mVPXAlpha);
79 0 : }
80 :
81 0 : VPXDecoder::~VPXDecoder()
82 : {
83 0 : MOZ_COUNT_DTOR(VPXDecoder);
84 0 : }
85 :
86 : RefPtr<ShutdownPromise>
87 0 : VPXDecoder::Shutdown()
88 : {
89 0 : RefPtr<VPXDecoder> self = this;
90 0 : return InvokeAsync(mTaskQueue, __func__, [self, this]() {
91 0 : vpx_codec_destroy(&mVPX);
92 0 : vpx_codec_destroy(&mVPXAlpha);
93 0 : return ShutdownPromise::CreateAndResolve(true, __func__);
94 0 : });
95 : }
96 :
97 : RefPtr<MediaDataDecoder::InitPromise>
98 0 : VPXDecoder::Init()
99 : {
100 0 : if (NS_FAILED(InitContext(&mVPX, mInfo, mCodec))) {
101 : return VPXDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
102 0 : __func__);
103 : }
104 0 : if (mInfo.HasAlpha()) {
105 0 : if (NS_FAILED(InitContext(&mVPXAlpha, mInfo, mCodec))) {
106 : return VPXDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
107 0 : __func__);
108 : }
109 : }
110 : return VPXDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
111 0 : __func__);
112 : }
113 :
114 : RefPtr<MediaDataDecoder::FlushPromise>
115 0 : VPXDecoder::Flush()
116 : {
117 0 : return InvokeAsync(mTaskQueue, __func__, []() {
118 : return FlushPromise::CreateAndResolve(true, __func__);
119 0 : });
120 : }
121 :
122 : RefPtr<MediaDataDecoder::DecodePromise>
123 0 : VPXDecoder::ProcessDecode(MediaRawData* aSample)
124 : {
125 0 : MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
126 :
127 : #if defined(DEBUG)
128 0 : NS_ASSERTION(IsKeyframe(*aSample, mCodec) == aSample->mKeyframe,
129 : "VPX Decode Keyframe error sample->mKeyframe and sample data out of sync");
130 : #endif
131 :
132 0 : if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(), aSample->Size(), nullptr, 0)) {
133 0 : LOG("VPX Decode error: %s", vpx_codec_err_to_string(r));
134 : return DecodePromise::CreateAndReject(
135 0 : MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
136 0 : RESULT_DETAIL("VPX error: %s", vpx_codec_err_to_string(r))),
137 0 : __func__);
138 : }
139 :
140 0 : vpx_codec_iter_t iter = nullptr;
141 : vpx_image_t *img;
142 0 : vpx_image_t *img_alpha = nullptr;
143 0 : bool alpha_decoded = false;
144 0 : DecodedData results;
145 :
146 0 : while ((img = vpx_codec_get_frame(&mVPX, &iter))) {
147 0 : NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 ||
148 : img->fmt == VPX_IMG_FMT_I444,
149 : "WebM image format not I420 or I444");
150 0 : NS_ASSERTION(!alpha_decoded,
151 : "Multiple frames per packet that contains alpha");
152 :
153 0 : if (aSample->AlphaSize() > 0) {
154 0 : if (!alpha_decoded){
155 0 : MediaResult rv = DecodeAlpha(&img_alpha, aSample);
156 0 : if (NS_FAILED(rv)) {
157 0 : return DecodePromise::CreateAndReject(rv, __func__);
158 : }
159 0 : alpha_decoded = true;
160 : }
161 : }
162 : // Chroma shifts are rounded down as per the decoding examples in the SDK
163 0 : VideoData::YCbCrBuffer b;
164 0 : b.mPlanes[0].mData = img->planes[0];
165 0 : b.mPlanes[0].mStride = img->stride[0];
166 0 : b.mPlanes[0].mHeight = img->d_h;
167 0 : b.mPlanes[0].mWidth = img->d_w;
168 0 : b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
169 :
170 0 : b.mPlanes[1].mData = img->planes[1];
171 0 : b.mPlanes[1].mStride = img->stride[1];
172 0 : b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
173 :
174 0 : b.mPlanes[2].mData = img->planes[2];
175 0 : b.mPlanes[2].mStride = img->stride[2];
176 0 : b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
177 :
178 0 : if (img->fmt == VPX_IMG_FMT_I420) {
179 0 : b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
180 0 : b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
181 :
182 0 : b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
183 0 : b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
184 0 : } else if (img->fmt == VPX_IMG_FMT_I444) {
185 0 : b.mPlanes[1].mHeight = img->d_h;
186 0 : b.mPlanes[1].mWidth = img->d_w;
187 :
188 0 : b.mPlanes[2].mHeight = img->d_h;
189 0 : b.mPlanes[2].mWidth = img->d_w;
190 : } else {
191 0 : LOG("VPX Unknown image format");
192 : return DecodePromise::CreateAndReject(
193 0 : MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
194 0 : RESULT_DETAIL("VPX Unknown image format")),
195 0 : __func__);
196 : }
197 :
198 0 : RefPtr<VideoData> v;
199 0 : if (!img_alpha) {
200 0 : v = VideoData::CreateAndCopyData(mInfo,
201 : mImageContainer,
202 : aSample->mOffset,
203 : aSample->mTime,
204 : aSample->mDuration,
205 : b,
206 0 : aSample->mKeyframe,
207 : aSample->mTimecode,
208 0 : mInfo.ScaledImageRect(img->d_w,
209 0 : img->d_h),
210 0 : mImageAllocator);
211 : } else {
212 : VideoData::YCbCrBuffer::Plane alpha_plane;
213 0 : alpha_plane.mData = img_alpha->planes[0];
214 0 : alpha_plane.mStride = img_alpha->stride[0];
215 0 : alpha_plane.mHeight = img_alpha->d_h;
216 0 : alpha_plane.mWidth = img_alpha->d_w;
217 0 : alpha_plane.mOffset = alpha_plane.mSkip = 0;
218 0 : v = VideoData::CreateAndCopyData(mInfo,
219 : mImageContainer,
220 : aSample->mOffset,
221 : aSample->mTime,
222 : aSample->mDuration,
223 : b,
224 : alpha_plane,
225 0 : aSample->mKeyframe,
226 : aSample->mTimecode,
227 0 : mInfo.ScaledImageRect(img->d_w,
228 0 : img->d_h));
229 :
230 : }
231 :
232 0 : if (!v) {
233 0 : LOG(
234 : "Image allocation error source %ux%u display %ux%u picture %ux%u",
235 : img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
236 : mInfo.mImage.width, mInfo.mImage.height);
237 : return DecodePromise::CreateAndReject(
238 0 : MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
239 : }
240 0 : results.AppendElement(Move(v));
241 : }
242 0 : return DecodePromise::CreateAndResolve(Move(results), __func__);
243 : }
244 :
245 : RefPtr<MediaDataDecoder::DecodePromise>
246 0 : VPXDecoder::Decode(MediaRawData* aSample)
247 : {
248 : return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
249 0 : &VPXDecoder::ProcessDecode, aSample);
250 : }
251 :
252 : RefPtr<MediaDataDecoder::DecodePromise>
253 0 : VPXDecoder::Drain()
254 : {
255 0 : return InvokeAsync(mTaskQueue, __func__, [] {
256 0 : return DecodePromise::CreateAndResolve(DecodedData(), __func__);
257 0 : });
258 : }
259 :
260 : MediaResult
261 0 : VPXDecoder::DecodeAlpha(vpx_image_t** aImgAlpha, const MediaRawData* aSample)
262 : {
263 0 : vpx_codec_err_t r = vpx_codec_decode(&mVPXAlpha,
264 : aSample->AlphaData(),
265 0 : aSample->AlphaSize(),
266 : nullptr,
267 0 : 0);
268 0 : if (r) {
269 0 : LOG("VPX decode alpha error: %s", vpx_codec_err_to_string(r));
270 : return MediaResult(
271 : NS_ERROR_DOM_MEDIA_DECODE_ERR,
272 0 : RESULT_DETAIL("VPX decode alpha error: %s", vpx_codec_err_to_string(r)));
273 : }
274 :
275 0 : vpx_codec_iter_t iter = nullptr;
276 :
277 0 : *aImgAlpha = vpx_codec_get_frame(&mVPXAlpha, &iter);
278 0 : NS_ASSERTION((*aImgAlpha)->fmt == VPX_IMG_FMT_I420 ||
279 : (*aImgAlpha)->fmt == VPX_IMG_FMT_I444,
280 : "WebM image format not I420 or I444");
281 :
282 0 : return NS_OK;
283 : }
284 :
285 : /* static */
286 : bool
287 0 : VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask)
288 : {
289 0 : return ((aCodecMask & VPXDecoder::VP8)
290 0 : && aMimeType.EqualsLiteral("video/webm; codecs=vp8"))
291 0 : || ((aCodecMask & VPXDecoder::VP9)
292 0 : && aMimeType.EqualsLiteral("video/webm; codecs=vp9"))
293 0 : || ((aCodecMask & VPXDecoder::VP9)
294 0 : && aMimeType.EqualsLiteral("video/vp9"));
295 : }
296 :
297 : /* static */
298 : bool
299 0 : VPXDecoder::IsVP8(const nsACString& aMimeType)
300 : {
301 0 : return IsVPX(aMimeType, VPXDecoder::VP8);
302 : }
303 :
304 : /* static */
305 : bool
306 0 : VPXDecoder::IsVP9(const nsACString& aMimeType)
307 : {
308 0 : return IsVPX(aMimeType, VPXDecoder::VP9);
309 : }
310 :
311 : /* static */
312 : bool
313 0 : VPXDecoder::IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec)
314 : {
315 : vpx_codec_stream_info_t si;
316 0 : PodZero(&si);
317 0 : si.sz = sizeof(si);
318 :
319 0 : if (aCodec == Codec::VP8) {
320 0 : vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
321 0 : return bool(si.is_kf);
322 0 : } else if (aCodec == Codec::VP9) {
323 0 : vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
324 0 : return bool(si.is_kf);
325 : }
326 :
327 0 : return false;
328 : }
329 :
330 : /* static */
331 : nsIntSize
332 0 : VPXDecoder::GetFrameSize(Span<const uint8_t> aBuffer, Codec aCodec)
333 : {
334 : vpx_codec_stream_info_t si;
335 0 : PodZero(&si);
336 0 : si.sz = sizeof(si);
337 :
338 0 : if (aCodec == Codec::VP8) {
339 0 : vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
340 0 : } else if (aCodec == Codec::VP9) {
341 0 : vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
342 : }
343 :
344 0 : return nsIntSize(si.w, si.h);
345 : }
346 : } // namespace mozilla
347 : #undef LOG
|