/src/mozilla-central/dom/media/platforms/agnostic/AOMDecoder.cpp
Line | Count | Source (jump to first uncovered line) |
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 "AOMDecoder.h" |
8 | | #include "MediaResult.h" |
9 | | #include "TimeUnits.h" |
10 | | #include "aom/aomdx.h" |
11 | | #include "aom/aom_image.h" |
12 | | #include "gfx2DGlue.h" |
13 | | #include "mozilla/PodOperations.h" |
14 | | #include "mozilla/SyncRunnable.h" |
15 | | #include "nsError.h" |
16 | | #include "prsystem.h" |
17 | | #include "ImageContainer.h" |
18 | | |
19 | | #include <algorithm> |
20 | | |
21 | | #undef LOG |
22 | | #define LOG(arg, ...) \ |
23 | 0 | DDMOZ_LOG( \ |
24 | 0 | sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, ##__VA_ARGS__) |
25 | | #define LOG_RESULT(code, message, ...) \ |
26 | 0 | DDMOZ_LOG(sPDMLog, \ |
27 | 0 | mozilla::LogLevel::Debug, \ |
28 | 0 | "::%s: %s (code %d) " message, \ |
29 | 0 | __func__, \ |
30 | 0 | aom_codec_err_to_string(code), \ |
31 | 0 | (int)code, \ |
32 | 0 | ##__VA_ARGS__) |
33 | | #define LOGEX_RESULT(_this, code, message, ...) \ |
34 | 0 | DDMOZ_LOGEX(_this, \ |
35 | 0 | sPDMLog, \ |
36 | 0 | mozilla::LogLevel::Debug, \ |
37 | 0 | "::%s: %s (code %d) " message, \ |
38 | 0 | __func__, \ |
39 | 0 | aom_codec_err_to_string(code), \ |
40 | 0 | (int)code, \ |
41 | 0 | ##__VA_ARGS__) |
42 | | #define LOG_STATIC_RESULT(code, message, ...) \ |
43 | 0 | MOZ_LOG(sPDMLog, \ |
44 | 0 | mozilla::LogLevel::Debug, \ |
45 | 0 | ("AOMDecoder::%s: %s (code %d) " message, \ |
46 | 0 | __func__, \ |
47 | 0 | aom_codec_err_to_string(code), \ |
48 | 0 | (int)code, \ |
49 | 0 | ##__VA_ARGS__)) |
50 | | |
51 | | namespace mozilla { |
52 | | |
53 | | using namespace gfx; |
54 | | using namespace layers; |
55 | | |
56 | | static MediaResult |
57 | | InitContext(AOMDecoder& aAOMDecoder, |
58 | | aom_codec_ctx_t* aCtx, |
59 | | const VideoInfo& aInfo) |
60 | 0 | { |
61 | 0 | aom_codec_iface_t* dx = aom_codec_av1_dx(); |
62 | 0 | if (!dx) { |
63 | 0 | return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
64 | 0 | RESULT_DETAIL("Couldn't get AV1 decoder interface.")); |
65 | 0 | } |
66 | 0 |
|
67 | 0 | int decode_threads = 2; |
68 | 0 | if (aInfo.mDisplay.width >= 2048) { |
69 | 0 | decode_threads = 8; |
70 | 0 | } |
71 | 0 | else if (aInfo.mDisplay.width >= 1024) { |
72 | 0 | decode_threads = 4; |
73 | 0 | } |
74 | 0 | decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors()); |
75 | 0 |
|
76 | 0 | aom_codec_dec_cfg_t config; |
77 | 0 | PodZero(&config); |
78 | 0 | config.threads = decode_threads; |
79 | 0 | config.w = config.h = 0; // set after decode |
80 | 0 | config.allow_lowbitdepth = true; |
81 | 0 |
|
82 | 0 | aom_codec_flags_t flags = 0; |
83 | 0 |
|
84 | 0 | auto res = aom_codec_dec_init(aCtx, dx, &config, flags); |
85 | 0 | if (res != AOM_CODEC_OK) { |
86 | 0 | LOGEX_RESULT( |
87 | 0 | &aAOMDecoder, res, "Codec initialization failed, res=%d", int(res)); |
88 | 0 | return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
89 | 0 | RESULT_DETAIL("AOM error initializing AV1 decoder: %s", |
90 | 0 | aom_codec_err_to_string(res))); |
91 | 0 | } |
92 | 0 | return NS_OK; |
93 | 0 | } |
94 | | |
95 | | AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams) |
96 | | : mImageContainer(aParams.mImageContainer) |
97 | | , mTaskQueue(aParams.mTaskQueue) |
98 | | , mInfo(aParams.VideoConfig()) |
99 | 0 | { |
100 | 0 | PodZero(&mCodec); |
101 | 0 | } |
102 | | |
103 | | AOMDecoder::~AOMDecoder() |
104 | 0 | { |
105 | 0 | } |
106 | | |
107 | | RefPtr<ShutdownPromise> |
108 | | AOMDecoder::Shutdown() |
109 | 0 | { |
110 | 0 | RefPtr<AOMDecoder> self = this; |
111 | 0 | return InvokeAsync(mTaskQueue, __func__, [self]() { |
112 | 0 | auto res = aom_codec_destroy(&self->mCodec); |
113 | 0 | if (res != AOM_CODEC_OK) { |
114 | 0 | LOGEX_RESULT(self.get(), res, "aom_codec_destroy"); |
115 | 0 | } |
116 | 0 | return ShutdownPromise::CreateAndResolve(true, __func__); |
117 | 0 | }); |
118 | 0 | } |
119 | | |
120 | | RefPtr<MediaDataDecoder::InitPromise> |
121 | | AOMDecoder::Init() |
122 | 0 | { |
123 | 0 | MediaResult rv = InitContext(*this, &mCodec, mInfo); |
124 | 0 | if (NS_FAILED(rv)) { |
125 | 0 | return AOMDecoder::InitPromise::CreateAndReject(rv, |
126 | 0 | __func__); |
127 | 0 | } |
128 | 0 | return AOMDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, |
129 | 0 | __func__); |
130 | 0 | } |
131 | | |
132 | | RefPtr<MediaDataDecoder::FlushPromise> |
133 | | AOMDecoder::Flush() |
134 | 0 | { |
135 | 0 | return InvokeAsync(mTaskQueue, __func__, []() { |
136 | 0 | return FlushPromise::CreateAndResolve(true, __func__); |
137 | 0 | }); |
138 | 0 | } |
139 | | |
140 | | // Ported from third_party/aom/tools_common.c. |
141 | | static aom_codec_err_t |
142 | 0 | highbd_img_downshift(aom_image_t *dst, aom_image_t *src, int down_shift) { |
143 | 0 | int plane; |
144 | 0 | if (dst->d_w != src->d_w || dst->d_h != src->d_h) |
145 | 0 | return AOM_CODEC_INVALID_PARAM; |
146 | 0 | if (dst->x_chroma_shift != src->x_chroma_shift) |
147 | 0 | return AOM_CODEC_INVALID_PARAM; |
148 | 0 | if (dst->y_chroma_shift != src->y_chroma_shift) |
149 | 0 | return AOM_CODEC_INVALID_PARAM; |
150 | 0 | if (dst->fmt != (src->fmt & ~AOM_IMG_FMT_HIGHBITDEPTH)) |
151 | 0 | return AOM_CODEC_INVALID_PARAM; |
152 | 0 | if (down_shift < 0) |
153 | 0 | return AOM_CODEC_INVALID_PARAM; |
154 | 0 | switch (dst->fmt) { |
155 | 0 | case AOM_IMG_FMT_I420: |
156 | 0 | case AOM_IMG_FMT_I422: |
157 | 0 | case AOM_IMG_FMT_I444: |
158 | 0 | break; |
159 | 0 | default: |
160 | 0 | return AOM_CODEC_INVALID_PARAM; |
161 | 0 | } |
162 | 0 | switch (src->fmt) { |
163 | 0 | case AOM_IMG_FMT_I42016: |
164 | 0 | case AOM_IMG_FMT_I42216: |
165 | 0 | case AOM_IMG_FMT_I44416: |
166 | 0 | break; |
167 | 0 | default: |
168 | 0 | // We don't support anything that's not 16 bit |
169 | 0 | return AOM_CODEC_UNSUP_BITSTREAM; |
170 | 0 | } |
171 | 0 | for (plane = 0; plane < 3; plane++) { |
172 | 0 | int w = src->d_w; |
173 | 0 | int h = src->d_h; |
174 | 0 | int x, y; |
175 | 0 | if (plane) { |
176 | 0 | w = (w + src->x_chroma_shift) >> src->x_chroma_shift; |
177 | 0 | h = (h + src->y_chroma_shift) >> src->y_chroma_shift; |
178 | 0 | } |
179 | 0 | for (y = 0; y < h; y++) { |
180 | 0 | uint16_t *p_src = |
181 | 0 | (uint16_t *)(src->planes[plane] + y * src->stride[plane]); |
182 | 0 | uint8_t *p_dst = |
183 | 0 | dst->planes[plane] + y * dst->stride[plane]; |
184 | 0 | for (x = 0; x < w; x++) *p_dst++ = (*p_src++ >> down_shift) & 0xFF; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | return AOM_CODEC_OK; |
188 | 0 | } |
189 | | |
190 | | // UniquePtr dtor wrapper for aom_image_t. |
191 | | struct AomImageFree { |
192 | 0 | void operator()(aom_image_t* img) { aom_img_free(img); } |
193 | | }; |
194 | | |
195 | | RefPtr<MediaDataDecoder::DecodePromise> |
196 | | AOMDecoder::ProcessDecode(MediaRawData* aSample) |
197 | 0 | { |
198 | 0 | MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); |
199 | 0 |
|
200 | | #if defined(DEBUG) |
201 | | NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe, |
202 | | "AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync"); |
203 | | #endif |
204 | |
|
205 | 0 | if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr)) { |
206 | 0 | LOG_RESULT(r, "Decode error!"); |
207 | 0 | return DecodePromise::CreateAndReject( |
208 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
209 | 0 | RESULT_DETAIL("AOM error decoding AV1 sample: %s", |
210 | 0 | aom_codec_err_to_string(r))), |
211 | 0 | __func__); |
212 | 0 | } |
213 | 0 |
|
214 | 0 | aom_codec_iter_t iter = nullptr; |
215 | 0 | aom_image_t *img; |
216 | 0 | UniquePtr<aom_image_t, AomImageFree> img8; |
217 | 0 | DecodedData results; |
218 | 0 |
|
219 | 0 | while ((img = aom_codec_get_frame(&mCodec, &iter))) { |
220 | 0 | // Track whether the underlying buffer is 8 or 16 bits per channel. |
221 | 0 | bool highbd = bool(img->fmt & AOM_IMG_FMT_HIGHBITDEPTH); |
222 | 0 | if (highbd) { |
223 | 0 | // Downsample images with more than 8 bits per channel. |
224 | 0 | aom_img_fmt_t fmt8 = static_cast<aom_img_fmt_t>(img->fmt ^ AOM_IMG_FMT_HIGHBITDEPTH); |
225 | 0 | img8.reset(aom_img_alloc(NULL, fmt8, img->d_w, img->d_h, 16)); |
226 | 0 | if (img8 == nullptr) { |
227 | 0 | LOG("Couldn't allocate bitdepth reduction target!"); |
228 | 0 | return DecodePromise::CreateAndReject( |
229 | 0 | MediaResult(NS_ERROR_OUT_OF_MEMORY, |
230 | 0 | RESULT_DETAIL("Couldn't allocate conversion buffer for AV1 frame")), |
231 | 0 | __func__); |
232 | 0 | } |
233 | 0 | if (aom_codec_err_t r = highbd_img_downshift(img8.get(), img, img->bit_depth - 8)) { |
234 | 0 | LOG_RESULT(r, "Image downconversion failed"); |
235 | 0 | return DecodePromise::CreateAndReject( |
236 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
237 | 0 | RESULT_DETAIL("Error converting AV1 frame to 8 bits: %s", |
238 | 0 | aom_codec_err_to_string(r))), |
239 | 0 | __func__); |
240 | 0 | } |
241 | 0 | // img normally points to storage owned by mCodec, so it is not freed. |
242 | 0 | // To copy out the contents of img8 we can overwrite img with an alias. |
243 | 0 | // Since img is assigned at the start of the while loop and img8 is held |
244 | 0 | // outside that loop, the alias won't outlive the storage it points to. |
245 | 0 | img = img8.get(); |
246 | 0 | highbd = false; |
247 | 0 | } |
248 | 0 |
|
249 | 0 | NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 || |
250 | 0 | img->fmt == AOM_IMG_FMT_I42016 || |
251 | 0 | img->fmt == AOM_IMG_FMT_I444 || |
252 | 0 | img->fmt == AOM_IMG_FMT_I44416, |
253 | 0 | "AV1 image format not I420 or I444"); |
254 | 0 |
|
255 | 0 | // Chroma shifts are rounded down as per the decoding examples in the SDK |
256 | 0 | VideoData::YCbCrBuffer b; |
257 | 0 | b.mPlanes[0].mData = img->planes[0]; |
258 | 0 | b.mPlanes[0].mStride = img->stride[0]; |
259 | 0 | b.mPlanes[0].mHeight = img->d_h; |
260 | 0 | b.mPlanes[0].mWidth = img->d_w; |
261 | 0 | b.mPlanes[0].mOffset = 0; |
262 | 0 | b.mPlanes[0].mSkip = highbd ? 1 : 0; |
263 | 0 |
|
264 | 0 | b.mPlanes[1].mData = img->planes[1]; |
265 | 0 | b.mPlanes[1].mStride = img->stride[1]; |
266 | 0 | b.mPlanes[1].mOffset = 0; |
267 | 0 | b.mPlanes[1].mSkip = highbd ? 1 : 0; |
268 | 0 |
|
269 | 0 | b.mPlanes[2].mData = img->planes[2]; |
270 | 0 | b.mPlanes[2].mStride = img->stride[2]; |
271 | 0 | b.mPlanes[2].mOffset = 0; |
272 | 0 | b.mPlanes[2].mSkip = highbd ? 1 : 0; |
273 | 0 |
|
274 | 0 | if (img->fmt == AOM_IMG_FMT_I420 || |
275 | 0 | img->fmt == AOM_IMG_FMT_I42016) { |
276 | 0 | b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift; |
277 | 0 | b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift; |
278 | 0 |
|
279 | 0 | b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift; |
280 | 0 | b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift; |
281 | 0 | } else if (img->fmt == AOM_IMG_FMT_I444) { |
282 | 0 | b.mPlanes[1].mHeight = img->d_h; |
283 | 0 | b.mPlanes[1].mWidth = img->d_w; |
284 | 0 |
|
285 | 0 | b.mPlanes[2].mHeight = img->d_h; |
286 | 0 | b.mPlanes[2].mWidth = img->d_w; |
287 | 0 | } else { |
288 | 0 | LOG("AOM Unknown image format"); |
289 | 0 | return DecodePromise::CreateAndReject( |
290 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
291 | 0 | RESULT_DETAIL("AOM Unknown image format")), |
292 | 0 | __func__); |
293 | 0 | } |
294 | 0 |
|
295 | 0 | RefPtr<VideoData> v; |
296 | 0 | v = VideoData::CreateAndCopyData(mInfo, |
297 | 0 | mImageContainer, |
298 | 0 | aSample->mOffset, |
299 | 0 | aSample->mTime, |
300 | 0 | aSample->mDuration, |
301 | 0 | b, |
302 | 0 | aSample->mKeyframe, |
303 | 0 | aSample->mTimecode, |
304 | 0 | mInfo.ScaledImageRect(img->d_w, |
305 | 0 | img->d_h)); |
306 | 0 |
|
307 | 0 | if (!v) { |
308 | 0 | LOG( |
309 | 0 | "Image allocation error source %ux%u display %ux%u picture %ux%u", |
310 | 0 | img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height, |
311 | 0 | mInfo.mImage.width, mInfo.mImage.height); |
312 | 0 | return DecodePromise::CreateAndReject( |
313 | 0 | MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); |
314 | 0 | } |
315 | 0 | results.AppendElement(std::move(v)); |
316 | 0 | } |
317 | 0 | return DecodePromise::CreateAndResolve(std::move(results), __func__); |
318 | 0 | } |
319 | | |
320 | | RefPtr<MediaDataDecoder::DecodePromise> |
321 | | AOMDecoder::Decode(MediaRawData* aSample) |
322 | 0 | { |
323 | 0 | return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__, |
324 | 0 | &AOMDecoder::ProcessDecode, aSample); |
325 | 0 | } |
326 | | |
327 | | RefPtr<MediaDataDecoder::DecodePromise> |
328 | | AOMDecoder::Drain() |
329 | 0 | { |
330 | 0 | return InvokeAsync(mTaskQueue, __func__, [] { |
331 | 0 | return DecodePromise::CreateAndResolve(DecodedData(), __func__); |
332 | 0 | }); |
333 | 0 | } |
334 | | |
335 | | |
336 | | /* static */ |
337 | | bool |
338 | | AOMDecoder::IsAV1(const nsACString& aMimeType) |
339 | 0 | { |
340 | 0 | return aMimeType.EqualsLiteral("video/av1"); |
341 | 0 | } |
342 | | |
343 | | /* static */ |
344 | | bool |
345 | 0 | AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) { |
346 | 0 | aom_codec_stream_info_t info; |
347 | 0 | PodZero(&info); |
348 | 0 |
|
349 | 0 | auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(), |
350 | 0 | aBuffer.Elements(), |
351 | 0 | aBuffer.Length(), |
352 | 0 | &info); |
353 | 0 | if (res != AOM_CODEC_OK) { |
354 | 0 | LOG_STATIC_RESULT( |
355 | 0 | res, "couldn't get keyframe flag with aom_codec_peek_stream_info"); |
356 | 0 | return false; |
357 | 0 | } |
358 | 0 |
|
359 | 0 | return bool(info.is_kf); |
360 | 0 | } |
361 | | |
362 | | /* static */ |
363 | | gfx::IntSize |
364 | | AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) |
365 | 0 | { |
366 | 0 | aom_codec_stream_info_t info; |
367 | 0 | PodZero(&info); |
368 | 0 |
|
369 | 0 | auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(), |
370 | 0 | aBuffer.Elements(), |
371 | 0 | aBuffer.Length(), |
372 | 0 | &info); |
373 | 0 | if (res != AOM_CODEC_OK) { |
374 | 0 | LOG_STATIC_RESULT( |
375 | 0 | res, "couldn't get frame size with aom_codec_peek_stream_info"); |
376 | 0 | } |
377 | 0 |
|
378 | 0 | return gfx::IntSize(info.w, info.h); |
379 | 0 | } |
380 | | |
381 | | } // namespace mozilla |
382 | | #undef LOG |