summaryrefslogtreecommitdiff
path: root/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
blob: 70d2fd8e086434a240e872d4694f20b15f9cd7c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WidevineVideoDecoder.h"

#include "mp4_demuxer/AnnexB.h"
#include "WidevineUtils.h"
#include "WidevineVideoFrame.h"
#include "mozilla/Move.h"

using namespace cdm;

namespace mozilla {

WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
                                           RefPtr<CDMWrapper> aCDMWrapper)
  : mVideoHost(aVideoHost)
  , mCDMWrapper(Move(aCDMWrapper))
  , mExtraData(new MediaByteBuffer())
  , mSentInput(false)
  , mCodecType(kGMPVideoCodecInvalid)
  , mReturnOutputCallDepth(0)
  , mDrainPending(false)
  , mResetInProgress(false)
{
  // Expect to start with a CDM wrapper, will release it in DecodingComplete().
  MOZ_ASSERT(mCDMWrapper);
  Log("WidevineVideoDecoder created this=%p", this);

  // Corresponding Release is in DecodingComplete().
  AddRef();
}

WidevineVideoDecoder::~WidevineVideoDecoder()
{
  Log("WidevineVideoDecoder destroyed this=%p", this);
}

static
VideoDecoderConfig::VideoCodecProfile
ToCDMH264Profile(uint8_t aProfile)
{
  switch (aProfile) {
    case 66: return VideoDecoderConfig::kH264ProfileBaseline;
    case 77: return VideoDecoderConfig::kH264ProfileMain;
    case 88: return VideoDecoderConfig::kH264ProfileExtended;
    case 100: return VideoDecoderConfig::kH264ProfileHigh;
    case 110: return VideoDecoderConfig::kH264ProfileHigh10;
    case 122: return VideoDecoderConfig::kH264ProfileHigh422;
    case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
  }
  return VideoDecoderConfig::kUnknownVideoCodecProfile;
}

void
WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
                                 const uint8_t* aCodecSpecific,
                                 uint32_t aCodecSpecificLength,
                                 GMPVideoDecoderCallback* aCallback,
                                 int32_t aCoreCount)
{
  mCallback = aCallback;
  VideoDecoderConfig config;
  mCodecType = aCodecSettings.mCodecType;
  if (mCodecType == kGMPVideoCodecH264) {
    config.codec = VideoDecoderConfig::kCodecH264;
    const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
    config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
  } else if (mCodecType == kGMPVideoCodecVP8) {
    config.codec = VideoDecoderConfig::kCodecVp8;
    config.profile = VideoDecoderConfig::kProfileNotNeeded;
  } else if (mCodecType == kGMPVideoCodecVP9) {
    config.codec = VideoDecoderConfig::kCodecVp9;
    config.profile = VideoDecoderConfig::kProfileNotNeeded;
  } else {
    mCallback->Error(GMPInvalidArgErr);
    return;
  }
  config.format = kYv12;
  config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
  mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
  config.extra_data = mExtraData->Elements();
  config.extra_data_size = mExtraData->Length();
  Status rv = CDM()->InitializeVideoDecoder(config);
  if (rv != kSuccess) {
    mCallback->Error(ToGMPErr(rv));
    return;
  }
  Log("WidevineVideoDecoder::InitDecode() rv=%d", rv);
  mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
}

void
WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
                             bool aMissingFrames,
                             const uint8_t* aCodecSpecificInfo,
                             uint32_t aCodecSpecificInfoLength,
                             int64_t aRenderTimeMs)
{
  // We should not be given new input if a drain has been initiated
  MOZ_ASSERT(!mDrainPending);
  // We may not get the same out of the CDM decoder as we put in, and there
  // may be some latency, i.e. we may need to input (say) 30 frames before
  // we receive output. So we need to store the durations of the frames input,
  // and retrieve them on output.
  mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();

  mSentInput = true;
  InputBuffer sample;

  RefPtr<MediaRawData> raw(
    new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
  if (aInputFrame->Size() && !raw->Data()) {
    // OOM.
    mCallback->Error(GMPAllocErr);
    return;
  }
  raw->mExtraData = mExtraData;
  raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
  if (mCodecType == kGMPVideoCodecH264) {
    // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
    // Chromium uses internally.
    mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
  }

  const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
  nsTArray<SubsampleEntry> subsamples;
  InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples);

  // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
  // at the start of the input. So we need to account for that as clear data
  // in the subsamples.
  if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) {
    subsamples[0].clear_bytes += mAnnexB->Length();
  }

  WidevineVideoFrame frame;
  Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
  Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv);

  // Destroy frame, so that the shmem is now free to be used to return
  // output to the Gecko process.
  aInputFrame->Destroy();
  aInputFrame = nullptr;

  if (rv == kSuccess) {
    if (!ReturnOutput(frame)) {
      Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
      mCallback->Error(GMPDecodeErr);
      return;
    }
    // A reset should only be started at most at level mReturnOutputCallDepth 1,
    // and if it's started it should be finished by that call by the time
    // the it returns, so it should always be false by this point.
    MOZ_ASSERT(!mResetInProgress);
    // Only request more data if we don't have pending samples.
    if (mFrameAllocationQueue.empty()) {
      MOZ_ASSERT(mCDMWrapper);
      mCallback->InputDataExhausted();
    }
  } else if (rv == kNeedMoreData) {
    MOZ_ASSERT(mCDMWrapper);
    mCallback->InputDataExhausted();
  } else {
    mCallback->Error(ToGMPErr(rv));
  }
  // Finish a drain if pending and we have no pending ReturnOutput calls on the stack.
  if (mDrainPending && mReturnOutputCallDepth == 0) {
    Drain();
  }
}

// Util class to assist with counting mReturnOutputCallDepth.
class CounterHelper {
public:
  // RAII, increment counter
  explicit CounterHelper(int32_t& counter)
    : mCounter(counter)
  {
    mCounter++;
  }

  // RAII, decrement counter
  ~CounterHelper()
  {
    mCounter--;
  }

private:
  int32_t& mCounter;
};

// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
// and will destroy it when the helper is destroyed unless the held frame
// if forgotten with ForgetFrame.
class FrameDestroyerHelper {
public:
  explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame)
    : frame(frame)
  {
  }

  // RAII, destroy frame if held.
  ~FrameDestroyerHelper()
  {
    if (frame) {
      frame->Destroy();
    }
    frame = nullptr;
  }

  // Forget the frame without destroying it.
  void ForgetFrame()
  {
    frame = nullptr;
  }

private:
  GMPVideoi420Frame* frame;
};


// Special handing is needed around ReturnOutput as it spins the IPC message
// queue when creating an empty frame and can end up with reentrant calls into
// the class methods.
bool
WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
{
  MOZ_ASSERT(mReturnOutputCallDepth >= 0);
  CounterHelper counterHelper(mReturnOutputCallDepth);
  mFrameAllocationQueue.push_back(Move(aCDMFrame));
  if (mReturnOutputCallDepth > 1) {
    // In a reentrant call.
    return true;
  }
  while (!mFrameAllocationQueue.empty()) {
    MOZ_ASSERT(mReturnOutputCallDepth == 1);
    // If we're at call level 1 a reset should not have been started. A
    // reset may be received during CreateEmptyFrame below, but we should not
    // be in a reset at this stage -- this would indicate receiving decode
    // messages before completing our reset, which we should not.
    MOZ_ASSERT(!mResetInProgress);
    WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
    mFrameAllocationQueue.pop_front();
    GMPVideoFrame* f = nullptr;
    auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
    if (GMP_FAILED(err) || !f) {
      Log("Failed to create i420 frame!\n");
      return false;
    }
    auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
    FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
    Size size = currentCDMFrame.Size();
    const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
    const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
    const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
    const int32_t halfHeight = size.height / 2;
    // This call can cause a shmem alloc, during this alloc other calls
    // may be made to this class and placed on the stack. ***WARNING***:
    // other IPC calls can happen during this call, resulting in calls
    // being made to the CDM. After this call state can have changed,
    // and should be reevaluated.
    err = gmpFrame->CreateEmptyFrame(size.width,
                                     size.height,
                                     yStride,
                                     uStride,
                                     vStride);
    // Assert possible reentrant calls or resets haven't altered level unexpectedly.
    MOZ_ASSERT(mReturnOutputCallDepth == 1);
    ENSURE_GMP_SUCCESS(err, false);

    // If a reset started we need to dump the current frame and complete the reset.
    if (mResetInProgress) {
      MOZ_ASSERT(mCDMWrapper);
      MOZ_ASSERT(mFrameAllocationQueue.empty());
      CompleteReset();
      return true;
    }

    err = gmpFrame->SetWidth(size.width);
    ENSURE_GMP_SUCCESS(err, false);

    err = gmpFrame->SetHeight(size.height);
    ENSURE_GMP_SUCCESS(err, false);

    Buffer* buffer = currentCDMFrame.FrameBuffer();
    uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
    ENSURE_TRUE(outBuffer != nullptr, false);
    MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
    memcpy(outBuffer,
           buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
           yStride * size.height);

    outBuffer = gmpFrame->Buffer(kGMPUPlane);
    ENSURE_TRUE(outBuffer != nullptr, false);
    MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
    memcpy(outBuffer,
           buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
           uStride * halfHeight);

    outBuffer = gmpFrame->Buffer(kGMPVPlane);
    ENSURE_TRUE(outBuffer != nullptr, false);
    MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
    memcpy(outBuffer,
           buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
           vStride * halfHeight);

    gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());

    auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
    if (d != mFrameDurations.end()) {
      gmpFrame->SetDuration(d->second);
      mFrameDurations.erase(d);
    }

    // Forget frame so it's not deleted, call back taking ownership.
    frameDestroyerHelper.ForgetFrame();
    mCallback->Decoded(gmpFrame);
  }

  return true;
}

void
WidevineVideoDecoder::Reset()
{
  Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
  // We shouldn't reset if a drain is pending.
  MOZ_ASSERT(!mDrainPending);
  mResetInProgress = true;
  if (mSentInput) {
    CDM()->ResetDecoder(kStreamTypeVideo);
  }
  // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
  // ReturnOutput calls unwind and decrement the counter as needed.
  mFrameAllocationQueue.clear();
  mFrameDurations.clear();
  // Only if no ReturnOutput calls are in progress can we complete, otherwise
  // ReturnOutput needs to finalize the reset.
  if (mReturnOutputCallDepth == 0) {
    CompleteReset();
  }
}

void
WidevineVideoDecoder::CompleteReset()
{
  mCallback->ResetComplete();
  mSentInput = false;
  mResetInProgress = false;
}

void
WidevineVideoDecoder::Drain()
{
  Log("WidevineVideoDecoder::Drain()");
  if (mReturnOutputCallDepth > 0) {
    Log("Drain call is reentrant, postponing drain");
    mDrainPending = true;
    return;
  }

  Status rv = kSuccess;
  while (rv == kSuccess) {
    WidevineVideoFrame frame;
    InputBuffer sample;
    Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
    Log("WidevineVideoDecoder::Drain();  DecryptAndDecodeFrame() rv=%d", rv);
    if (frame.Format() == kUnknownVideoFormat) {
      break;
    }
    if (rv == kSuccess) {
      if (!ReturnOutput(frame)) {
        Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
      }
    }
  }
  // Shouldn't be reset while draining.
  MOZ_ASSERT(!mResetInProgress);

  CDM()->ResetDecoder(kStreamTypeVideo);
  mDrainPending = false;
  mCallback->DrainComplete();
}

void
WidevineVideoDecoder::DecodingComplete()
{
  Log("WidevineVideoDecoder::DecodingComplete()");
  if (mCDMWrapper) {
    CDM()->DeinitializeDecoder(kStreamTypeVideo);
    mCDMWrapper = nullptr;
  }
  // Release that corresponds to AddRef() in constructor.
  Release();
}

} // namespace mozilla