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 "AOMDecoder.h"
8 : #include "MediaResult.h"
9 : #include "TimeUnits.h"
10 : #include "aom/aomdx.h"
11 : #include "gfx2DGlue.h"
12 : #include "mozilla/PodOperations.h"
13 : #include "mozilla/SyncRunnable.h"
14 : #include "nsError.h"
15 : #include "prsystem.h"
16 :
17 : #include <algorithm>
18 :
19 : #undef LOG
20 : #define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
21 : #define LOG_RESULT(code, message, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("AOMDecoder::%s: %s (code %d) " message, __func__, aom_codec_err_to_string(code), (int)code, ##__VA_ARGS__))
22 :
23 : namespace mozilla {
24 :
25 : using namespace gfx;
26 : using namespace layers;
27 :
28 :
29 : static MediaResult
30 0 : InitContext(aom_codec_ctx_t* aCtx,
31 : const VideoInfo& aInfo)
32 : {
33 0 : aom_codec_iface_t* dx = aom_codec_av1_dx();
34 0 : if (!dx) {
35 : return MediaResult(NS_ERROR_FAILURE,
36 0 : RESULT_DETAIL("Couldn't get AV1 decoder interface."));
37 : }
38 :
39 0 : int decode_threads = 2;
40 0 : if (aInfo.mDisplay.width >= 2048) {
41 0 : decode_threads = 8;
42 : }
43 0 : else if (aInfo.mDisplay.width >= 1024) {
44 0 : decode_threads = 4;
45 : }
46 0 : decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
47 :
48 : aom_codec_dec_cfg_t config;
49 0 : PodZero(&config);
50 0 : config.threads = decode_threads;
51 0 : config.w = config.h = 0; // set after decode
52 :
53 0 : aom_codec_flags_t flags = 0;
54 :
55 0 : auto res = aom_codec_dec_init(aCtx, dx, &config, flags);
56 0 : if (res != AOM_CODEC_OK) {
57 0 : LOG_RESULT(res, "Codec initialization failed!");
58 : return MediaResult(NS_ERROR_FAILURE,
59 0 : RESULT_DETAIL("AOM error initializing AV1 decoder: %s",
60 0 : aom_codec_err_to_string(res)));
61 : }
62 0 : return NS_OK;
63 : }
64 :
65 0 : AOMDecoder::AOMDecoder(const CreateDecoderParams& aParams)
66 0 : : mImageContainer(aParams.mImageContainer)
67 0 : , mTaskQueue(aParams.mTaskQueue)
68 0 : , mInfo(aParams.VideoConfig())
69 : {
70 0 : PodZero(&mCodec);
71 0 : }
72 :
73 0 : AOMDecoder::~AOMDecoder()
74 : {
75 0 : }
76 :
77 : RefPtr<ShutdownPromise>
78 0 : AOMDecoder::Shutdown()
79 : {
80 0 : RefPtr<AOMDecoder> self = this;
81 0 : return InvokeAsync(mTaskQueue, __func__, [self, this]() {
82 0 : auto res = aom_codec_destroy(&mCodec);
83 0 : if (res != AOM_CODEC_OK) {
84 0 : LOG_RESULT(res, "aom_codec_destroy");
85 : }
86 0 : return ShutdownPromise::CreateAndResolve(true, __func__);
87 0 : });
88 : }
89 :
90 : RefPtr<MediaDataDecoder::InitPromise>
91 0 : AOMDecoder::Init()
92 : {
93 0 : if (NS_FAILED(InitContext(&mCodec, mInfo))) {
94 : return AOMDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
95 0 : __func__);
96 : }
97 : return AOMDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
98 0 : __func__);
99 : }
100 :
101 : RefPtr<MediaDataDecoder::FlushPromise>
102 0 : AOMDecoder::Flush()
103 : {
104 0 : return InvokeAsync(mTaskQueue, __func__, []() {
105 : return FlushPromise::CreateAndResolve(true, __func__);
106 0 : });
107 : }
108 :
109 : RefPtr<MediaDataDecoder::DecodePromise>
110 0 : AOMDecoder::ProcessDecode(MediaRawData* aSample)
111 : {
112 0 : MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
113 :
114 : #if defined(DEBUG)
115 0 : NS_ASSERTION(IsKeyframe(*aSample) == aSample->mKeyframe,
116 : "AOM Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
117 : #endif
118 :
119 0 : if (aom_codec_err_t r = aom_codec_decode(&mCodec, aSample->Data(), aSample->Size(), nullptr, 0)) {
120 0 : LOG_RESULT(r, "Decode error!");
121 : return DecodePromise::CreateAndReject(
122 0 : MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
123 0 : RESULT_DETAIL("AOM error decoding AV1 sample: %s",
124 : aom_codec_err_to_string(r))),
125 0 : __func__);
126 : }
127 :
128 0 : aom_codec_iter_t iter = nullptr;
129 : aom_image_t *img;
130 0 : DecodedData results;
131 :
132 0 : while ((img = aom_codec_get_frame(&mCodec, &iter))) {
133 0 : NS_ASSERTION(img->fmt == AOM_IMG_FMT_I420 ||
134 : img->fmt == AOM_IMG_FMT_I444,
135 : "WebM image format not I420 or I444");
136 :
137 : // Chroma shifts are rounded down as per the decoding examples in the SDK
138 0 : VideoData::YCbCrBuffer b;
139 0 : b.mPlanes[0].mData = img->planes[0];
140 0 : b.mPlanes[0].mStride = img->stride[0];
141 0 : b.mPlanes[0].mHeight = img->d_h;
142 0 : b.mPlanes[0].mWidth = img->d_w;
143 0 : b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
144 :
145 0 : b.mPlanes[1].mData = img->planes[1];
146 0 : b.mPlanes[1].mStride = img->stride[1];
147 0 : b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
148 :
149 0 : b.mPlanes[2].mData = img->planes[2];
150 0 : b.mPlanes[2].mStride = img->stride[2];
151 0 : b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
152 :
153 0 : if (img->fmt == AOM_IMG_FMT_I420) {
154 0 : b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
155 0 : b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
156 :
157 0 : b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
158 0 : b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
159 0 : } else if (img->fmt == AOM_IMG_FMT_I444) {
160 0 : b.mPlanes[1].mHeight = img->d_h;
161 0 : b.mPlanes[1].mWidth = img->d_w;
162 :
163 0 : b.mPlanes[2].mHeight = img->d_h;
164 0 : b.mPlanes[2].mWidth = img->d_w;
165 : } else {
166 0 : LOG("AOM Unknown image format");
167 : return DecodePromise::CreateAndReject(
168 0 : MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
169 0 : RESULT_DETAIL("AOM Unknown image format")),
170 0 : __func__);
171 : }
172 :
173 0 : RefPtr<VideoData> v;
174 0 : v = VideoData::CreateAndCopyData(mInfo,
175 : mImageContainer,
176 : aSample->mOffset,
177 : aSample->mTime,
178 : aSample->mDuration,
179 : b,
180 0 : aSample->mKeyframe,
181 : aSample->mTimecode,
182 0 : mInfo.ScaledImageRect(img->d_w,
183 0 : img->d_h));
184 :
185 0 : if (!v) {
186 0 : LOG(
187 : "Image allocation error source %ux%u display %ux%u picture %ux%u",
188 : img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
189 : mInfo.mImage.width, mInfo.mImage.height);
190 : return DecodePromise::CreateAndReject(
191 0 : MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
192 : }
193 0 : results.AppendElement(Move(v));
194 : }
195 0 : return DecodePromise::CreateAndResolve(Move(results), __func__);
196 : }
197 :
198 : RefPtr<MediaDataDecoder::DecodePromise>
199 0 : AOMDecoder::Decode(MediaRawData* aSample)
200 : {
201 : return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
202 0 : &AOMDecoder::ProcessDecode, aSample);
203 : }
204 :
205 : RefPtr<MediaDataDecoder::DecodePromise>
206 0 : AOMDecoder::Drain()
207 : {
208 0 : return InvokeAsync(mTaskQueue, __func__, [] {
209 0 : return DecodePromise::CreateAndResolve(DecodedData(), __func__);
210 0 : });
211 : }
212 :
213 :
214 : /* static */
215 : bool
216 0 : AOMDecoder::IsAV1(const nsACString& aMimeType)
217 : {
218 0 : return aMimeType.EqualsLiteral("video/webm; codecs=av1")
219 0 : || aMimeType.EqualsLiteral("video/av1");
220 : }
221 :
222 : /* static */
223 : bool
224 0 : AOMDecoder::IsSupportedCodec(const nsAString& aCodecType)
225 : {
226 : // While AV1 is under development, we describe support
227 : // for a specific aom commit hash so sites can check
228 : // compatibility.
229 0 : auto version = NS_ConvertASCIItoUTF16("av1.experimental.");
230 0 : version.AppendLiteral("aadbb0251996c8ebb8310567bea330ab7ae9abe4");
231 0 : return aCodecType.EqualsLiteral("av1") ||
232 0 : aCodecType.Equals(version);
233 : }
234 :
235 : /* static */
236 : bool
237 0 : AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
238 : aom_codec_stream_info_t info;
239 0 : PodZero(&info);
240 :
241 0 : auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
242 : aBuffer.Elements(),
243 0 : aBuffer.Length(),
244 0 : &info);
245 0 : if (res != AOM_CODEC_OK) {
246 0 : LOG_RESULT(res, "couldn't get keyframe flag with aom_codec_peek_stream_info");
247 0 : return false;
248 : }
249 :
250 0 : return bool(info.is_kf);
251 : }
252 :
253 : /* static */
254 : nsIntSize
255 0 : AOMDecoder::GetFrameSize(Span<const uint8_t> aBuffer) {
256 : aom_codec_stream_info_t info;
257 0 : PodZero(&info);
258 :
259 0 : auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
260 : aBuffer.Elements(),
261 0 : aBuffer.Length(),
262 0 : &info);
263 0 : if (res != AOM_CODEC_OK) {
264 0 : LOG_RESULT(res, "couldn't get frame size with aom_codec_peek_stream_info");
265 : }
266 :
267 0 : return nsIntSize(info.w, info.h);
268 : }
269 :
270 : } // namespace mozilla
271 : #undef LOG
|