Coverage Report

Created: 2018-09-25 14:53

/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