Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/gmp/GMPVideoDecoderParent.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "GMPVideoDecoderParent.h"
7
#include "mozilla/Logging.h"
8
#include "mozilla/Unused.h"
9
#include "nsAutoRef.h"
10
#include "nsThreadUtils.h"
11
#include "GMPUtils.h"
12
#include "GMPVideoEncodedFrameImpl.h"
13
#include "GMPVideoi420FrameImpl.h"
14
#include "GMPContentParent.h"
15
#include "GMPMessageUtils.h"
16
#include "mozilla/gmp/GMPTypes.h"
17
#include "nsPrintfCString.h"
18
19
namespace mozilla {
20
21
#ifdef LOG
22
#undef LOG
23
#endif
24
25
extern LogModule* GetGMPLog();
26
27
0
#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg)
28
0
#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
29
0
#define LOGE(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Error, msg)
30
#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
31
32
namespace gmp {
33
34
// States:
35
// Initial: mIsOpen == false
36
//    on InitDecode success -> Open
37
//    on Shutdown -> Dead
38
// Open: mIsOpen == true
39
//    on Close -> Dead
40
//    on ActorDestroy -> Dead
41
//    on Shutdown -> Dead
42
// Dead: mIsOpen == false
43
44
GMPVideoDecoderParent::GMPVideoDecoderParent(GMPContentParent* aPlugin)
45
  : GMPSharedMemManager(aPlugin)
46
  , mIsOpen(false)
47
  , mShuttingDown(false)
48
  , mActorDestroyed(false)
49
  , mIsAwaitingResetComplete(false)
50
  , mIsAwaitingDrainComplete(false)
51
  , mPlugin(aPlugin)
52
  , mCallback(nullptr)
53
  , mVideoHost(this)
54
  , mPluginId(aPlugin->GetPluginId())
55
  , mFrameCount(0)
56
0
{
57
0
  MOZ_ASSERT(mPlugin);
58
0
}
59
60
GMPVideoDecoderParent::~GMPVideoDecoderParent()
61
0
{
62
0
}
63
64
GMPVideoHostImpl&
65
GMPVideoDecoderParent::Host()
66
0
{
67
0
  return mVideoHost;
68
0
}
69
70
// Note: may be called via Terminated()
71
void
72
GMPVideoDecoderParent::Close()
73
0
{
74
0
  LOGD(("GMPVideoDecoderParent[%p]::Close()", this));
75
0
  MOZ_ASSERT(!mPlugin || mPlugin->GMPEventTarget()->IsOnCurrentThread());
76
0
77
0
  // Ensure if we've received a Close while waiting for a ResetComplete
78
0
  // or DrainComplete notification, we'll unblock the caller before processing
79
0
  // the close. This seems unlikely to happen, but better to be careful.
80
0
  UnblockResetAndDrain();
81
0
82
0
  // Consumer is done with us; we can shut down.  No more callbacks should
83
0
  // be made to mCallback.  Note: do this before Shutdown()!
84
0
  mCallback = nullptr;
85
0
  // Let Shutdown mark us as dead so it knows if we had been alive
86
0
87
0
  // In case this is the last reference
88
0
  RefPtr<GMPVideoDecoderParent> kungfudeathgrip(this);
89
0
  Release();
90
0
  Shutdown();
91
0
}
92
93
nsresult
94
GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings,
95
                                  const nsTArray<uint8_t>& aCodecSpecific,
96
                                  GMPVideoDecoderCallbackProxy* aCallback,
97
                                  int32_t aCoreCount)
98
0
{
99
0
  LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this));
100
0
101
0
  if (mActorDestroyed) {
102
0
    NS_WARNING("Trying to use a destroyed GMP video decoder!");
103
0
    return NS_ERROR_FAILURE;
104
0
  }
105
0
  if (mIsOpen) {
106
0
    NS_WARNING("Trying to re-init an in-use GMP video decoder!");
107
0
    return NS_ERROR_FAILURE;
108
0
  }
109
0
110
0
  MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
111
0
112
0
  if (!aCallback) {
113
0
    return NS_ERROR_FAILURE;
114
0
  }
115
0
  mCallback = aCallback;
116
0
117
0
  if (!SendInitDecode(aCodecSettings, aCodecSpecific, aCoreCount)) {
118
0
    return NS_ERROR_FAILURE;
119
0
  }
120
0
  mIsOpen = true;
121
0
122
0
  // Async IPC, we don't have access to a return value.
123
0
  return NS_OK;
124
0
}
125
126
nsresult
127
GMPVideoDecoderParent::Decode(GMPUniquePtr<GMPVideoEncodedFrame> aInputFrame,
128
                              bool aMissingFrames,
129
                              const nsTArray<uint8_t>& aCodecSpecificInfo,
130
                              int64_t aRenderTimeMs)
131
0
{
132
0
  LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%" PRId64 " keyframe=%d",
133
0
        this, aInputFrame->TimeStamp(),
134
0
        aInputFrame->FrameType() == kGMPKeyFrame));
135
0
136
0
  if (!mIsOpen) {
137
0
    LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; dead GMPVideoDecoder", this));
138
0
    NS_WARNING("Trying to use an dead GMP video decoder");
139
0
    return NS_ERROR_FAILURE;
140
0
  }
141
0
142
0
  MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
143
0
144
0
  GMPUniquePtr<GMPVideoEncodedFrameImpl> inputFrameImpl(
145
0
    static_cast<GMPVideoEncodedFrameImpl*>(aInputFrame.release()));
146
0
147
0
  // Very rough kill-switch if the plugin stops processing.  If it's merely
148
0
  // hung and continues, we'll come back to life eventually.
149
0
  // 3* is because we're using 3 buffers per frame for i420 data for now.
150
0
  if ((NumInUse(GMPSharedMem::kGMPFrameData) > 3*GMPSharedMem::kGMPBufLimit) ||
151
0
      (NumInUse(GMPSharedMem::kGMPEncodedData) > GMPSharedMem::kGMPBufLimit)) {
152
0
    LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; shmem buffer limit hit frame=%d encoded=%d",
153
0
          this, NumInUse(GMPSharedMem::kGMPFrameData), NumInUse(GMPSharedMem::kGMPEncodedData)));
154
0
    return NS_ERROR_FAILURE;
155
0
  }
156
0
157
0
  GMPVideoEncodedFrameData frameData;
158
0
  inputFrameImpl->RelinquishFrameData(frameData);
159
0
160
0
  if (!SendDecode(frameData,
161
0
                  aMissingFrames,
162
0
                  aCodecSpecificInfo,
163
0
                  aRenderTimeMs)) {
164
0
    LOGE(("GMPVideoDecoderParent[%p]::Decode() ERROR; SendDecode() failure.", this));
165
0
    return NS_ERROR_FAILURE;
166
0
  }
167
0
  mFrameCount++;
168
0
169
0
  // Async IPC, we don't have access to a return value.
170
0
  return NS_OK;
171
0
}
172
173
nsresult
174
GMPVideoDecoderParent::Reset()
175
0
{
176
0
  LOGD(("GMPVideoDecoderParent[%p]::Reset()", this));
177
0
178
0
  if (!mIsOpen) {
179
0
    NS_WARNING("Trying to use an dead GMP video decoder");
180
0
    return NS_ERROR_FAILURE;
181
0
  }
182
0
183
0
  MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
184
0
185
0
  if (!SendReset()) {
186
0
    return NS_ERROR_FAILURE;
187
0
  }
188
0
189
0
  mIsAwaitingResetComplete = true;
190
0
191
0
  RefPtr<GMPVideoDecoderParent> self(this);
192
0
  nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
193
0
    "gmp::GMPVideoDecoderParent::Reset", [self]() -> void {
194
0
      LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out "
195
0
            "waiting for ResetComplete",
196
0
            self.get()));
197
0
      self->mResetCompleteTimeout = nullptr;
198
0
      LogToBrowserConsole(NS_LITERAL_STRING(
199
0
        "GMPVideoDecoderParent timed out waiting for ResetComplete()"));
200
0
    });
201
0
  CancelResetCompleteTimeout();
202
0
  nsCOMPtr<nsISerialEventTarget> target = mPlugin->GMPEventTarget();
203
0
  mResetCompleteTimeout = SimpleTimer::Create(task, 5000, target);
204
0
205
0
  // Async IPC, we don't have access to a return value.
206
0
  return NS_OK;
207
0
}
208
209
void
210
GMPVideoDecoderParent::CancelResetCompleteTimeout()
211
0
{
212
0
  if (mResetCompleteTimeout) {
213
0
    mResetCompleteTimeout->Cancel();
214
0
    mResetCompleteTimeout = nullptr;
215
0
  }
216
0
}
217
218
nsresult
219
GMPVideoDecoderParent::Drain()
220
0
{
221
0
  LOGD(("GMPVideoDecoderParent[%p]::Drain() frameCount=%d", this, mFrameCount));
222
0
223
0
  if (!mIsOpen) {
224
0
    NS_WARNING("Trying to use an dead GMP video decoder");
225
0
    return NS_ERROR_FAILURE;
226
0
  }
227
0
228
0
  MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
229
0
230
0
  if (!SendDrain()) {
231
0
    return NS_ERROR_FAILURE;
232
0
  }
233
0
234
0
  mIsAwaitingDrainComplete = true;
235
0
236
0
  // Async IPC, we don't have access to a return value.
237
0
  return NS_OK;
238
0
}
239
240
const nsCString&
241
GMPVideoDecoderParent::GetDisplayName() const
242
0
{
243
0
  if (!mIsOpen) {
244
0
    NS_WARNING("Trying to use an dead GMP video decoder");
245
0
  }
246
0
247
0
  MOZ_ASSERT(mPlugin->GMPEventTarget()->IsOnCurrentThread());
248
0
249
0
  return mPlugin->GetDisplayName();
250
0
}
251
252
// Note: Consider keeping ActorDestroy sync'd up when making changes here.
253
nsresult
254
GMPVideoDecoderParent::Shutdown()
255
0
{
256
0
  LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this));
257
0
  MOZ_ASSERT(!mPlugin || mPlugin->GMPEventTarget()->IsOnCurrentThread());
258
0
259
0
  if (mShuttingDown) {
260
0
    return NS_OK;
261
0
  }
262
0
  mShuttingDown = true;
263
0
264
0
  // Ensure if we've received a shutdown while waiting for a ResetComplete
265
0
  // or DrainComplete notification, we'll unblock the caller before processing
266
0
  // the shutdown.
267
0
  UnblockResetAndDrain();
268
0
269
0
  // Notify client we're gone!  Won't occur after Close()
270
0
  if (mCallback) {
271
0
    mCallback->Terminated();
272
0
    mCallback = nullptr;
273
0
  }
274
0
275
0
  mIsOpen = false;
276
0
  if (!mActorDestroyed) {
277
0
    Unused << SendDecodingComplete();
278
0
  }
279
0
280
0
  return NS_OK;
281
0
}
282
283
// Note: Keep this sync'd up with Shutdown
284
void
285
GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy)
286
0
{
287
0
  LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy));
288
0
289
0
  mIsOpen = false;
290
0
  mActorDestroyed = true;
291
0
292
0
  // Ensure if we've received a destroy while waiting for a ResetComplete
293
0
  // or DrainComplete notification, we'll unblock the caller before processing
294
0
  // the error.
295
0
  UnblockResetAndDrain();
296
0
297
0
  if (mCallback) {
298
0
    // May call Close() (and Shutdown()) immediately or with a delay
299
0
    mCallback->Terminated();
300
0
    mCallback = nullptr;
301
0
  }
302
0
  if (mPlugin) {
303
0
    // Ignore any return code. It is OK for this to fail without killing the process.
304
0
    mPlugin->VideoDecoderDestroyed(this);
305
0
    mPlugin = nullptr;
306
0
  }
307
0
  mVideoHost.ActorDestroyed();
308
0
  MaybeDisconnect(aWhy == AbnormalShutdown);
309
0
}
310
311
mozilla::ipc::IPCResult
312
GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame)
313
0
{
314
0
  --mFrameCount;
315
0
  LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%" PRId64 " frameCount=%d",
316
0
    this, aDecodedFrame.mTimestamp(), mFrameCount));
317
0
318
0
  if (!mCallback) {
319
0
    return IPC_FAIL_NO_REASON(this);
320
0
  }
321
0
322
0
  if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) {
323
0
    LOGE(("GMPVideoDecoderParent[%p]::RecvDecoded() "
324
0
          "timestamp=%" PRId64 " decoded frame corrupt, ignoring",
325
0
          this, aDecodedFrame.mTimestamp()));
326
0
    return IPC_FAIL_NO_REASON(this);
327
0
  }
328
0
  auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost);
329
0
330
0
  // Ignore any return code. It is OK for this to fail without killing the process.
331
0
  mCallback->Decoded(f);
332
0
333
0
  return IPC_OK();
334
0
}
335
336
mozilla::ipc::IPCResult
337
GMPVideoDecoderParent::RecvReceivedDecodedReferenceFrame(const uint64_t& aPictureId)
338
0
{
339
0
  if (!mCallback) {
340
0
    return IPC_FAIL_NO_REASON(this);
341
0
  }
342
0
343
0
  // Ignore any return code. It is OK for this to fail without killing the process.
344
0
  mCallback->ReceivedDecodedReferenceFrame(aPictureId);
345
0
346
0
  return IPC_OK();
347
0
}
348
349
mozilla::ipc::IPCResult
350
GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId)
351
0
{
352
0
  if (!mCallback) {
353
0
    return IPC_FAIL_NO_REASON(this);
354
0
  }
355
0
356
0
  // Ignore any return code. It is OK for this to fail without killing the process.
357
0
  mCallback->ReceivedDecodedFrame(aPictureId);
358
0
359
0
  return IPC_OK();
360
0
}
361
362
mozilla::ipc::IPCResult
363
GMPVideoDecoderParent::RecvInputDataExhausted()
364
0
{
365
0
  LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this));
366
0
367
0
  if (!mCallback) {
368
0
    return IPC_FAIL_NO_REASON(this);
369
0
  }
370
0
371
0
  // Ignore any return code. It is OK for this to fail without killing the process.
372
0
  mCallback->InputDataExhausted();
373
0
374
0
  return IPC_OK();
375
0
}
376
377
mozilla::ipc::IPCResult
378
GMPVideoDecoderParent::RecvDrainComplete()
379
0
{
380
0
  LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete() frameCount=%d", this, mFrameCount));
381
0
  nsAutoString msg;
382
0
  msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
383
0
  msg.AppendInt(mFrameCount);
384
0
  LogToBrowserConsole(msg);
385
0
386
0
  if (!mCallback) {
387
0
    // We anticipate shutting down in the middle of a drain in the
388
0
    // `UnblockResetAndDrain` method, which is called when we shutdown, so
389
0
    // everything is sunny.
390
0
    return IPC_OK();
391
0
  }
392
0
393
0
  if (!mIsAwaitingDrainComplete) {
394
0
    return IPC_OK();
395
0
  }
396
0
  mIsAwaitingDrainComplete = false;
397
0
398
0
  // Ignore any return code. It is OK for this to fail without killing the process.
399
0
  mCallback->DrainComplete();
400
0
401
0
  return IPC_OK();
402
0
}
403
404
mozilla::ipc::IPCResult
405
GMPVideoDecoderParent::RecvResetComplete()
406
0
{
407
0
  LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this));
408
0
409
0
  CancelResetCompleteTimeout();
410
0
411
0
  if (!mCallback) {
412
0
    // We anticipate shutting down in the middle of a reset in the
413
0
    // `UnblockResetAndDrain` method, which is called when we shutdown, so
414
0
    // everything is good if we reach here.
415
0
    return IPC_OK();
416
0
  }
417
0
418
0
  if (!mIsAwaitingResetComplete) {
419
0
    return IPC_OK();
420
0
  }
421
0
  mIsAwaitingResetComplete = false;
422
0
  mFrameCount = 0;
423
0
424
0
  // Ignore any return code. It is OK for this to fail without killing the process.
425
0
  mCallback->ResetComplete();
426
0
427
0
  return IPC_OK();
428
0
}
429
430
mozilla::ipc::IPCResult
431
GMPVideoDecoderParent::RecvError(const GMPErr& aError)
432
0
{
433
0
  LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError));
434
0
435
0
  if (!mCallback) {
436
0
    return IPC_FAIL_NO_REASON(this);
437
0
  }
438
0
439
0
  // Ensure if we've received an error while waiting for a ResetComplete
440
0
  // or DrainComplete notification, we'll unblock the caller before processing
441
0
  // the error.
442
0
  UnblockResetAndDrain();
443
0
444
0
  // Ignore any return code. It is OK for this to fail without killing the process.
445
0
  mCallback->Error(aError);
446
0
447
0
  return IPC_OK();
448
0
}
449
450
mozilla::ipc::IPCResult
451
GMPVideoDecoderParent::RecvShutdown()
452
0
{
453
0
  LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this));
454
0
455
0
  Shutdown();
456
0
  return IPC_OK();
457
0
}
458
459
mozilla::ipc::IPCResult
460
GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer)
461
0
{
462
0
  if (aEncodedBuffer.IsWritable()) {
463
0
    mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData,
464
0
                                               aEncodedBuffer);
465
0
  }
466
0
  return IPC_OK();
467
0
}
468
469
mozilla::ipc::IPCResult
470
GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize,
471
                                       Shmem* aMem)
472
0
{
473
0
  ipc::Shmem mem;
474
0
475
0
  if (!mVideoHost.SharedMemMgr()->MgrAllocShmem(GMPSharedMem::kGMPFrameData,
476
0
                                                aFrameBufferSize,
477
0
                                                ipc::SharedMemory::TYPE_BASIC, &mem))
478
0
  {
479
0
    LOGE(("%s: Failed to get a shared mem buffer for Child! size %u",
480
0
         __FUNCTION__, aFrameBufferSize));
481
0
    return IPC_FAIL_NO_REASON(this);
482
0
  }
483
0
  *aMem = mem;
484
0
  mem = ipc::Shmem();
485
0
  return IPC_OK();
486
0
}
487
488
mozilla::ipc::IPCResult
489
GMPVideoDecoderParent::Recv__delete__()
490
0
{
491
0
  LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this));
492
0
493
0
  if (mPlugin) {
494
0
    // Ignore any return code. It is OK for this to fail without killing the process.
495
0
    mPlugin->VideoDecoderDestroyed(this);
496
0
    mPlugin = nullptr;
497
0
  }
498
0
499
0
  return IPC_OK();
500
0
}
501
502
void
503
GMPVideoDecoderParent::UnblockResetAndDrain()
504
0
{
505
0
  LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain() "
506
0
        "awaitingResetComplete=%d awaitingDrainComplete=%d",
507
0
       this, mIsAwaitingResetComplete, mIsAwaitingDrainComplete));
508
0
509
0
  if (!mCallback) {
510
0
    MOZ_ASSERT(!mIsAwaitingResetComplete);
511
0
    MOZ_ASSERT(!mIsAwaitingDrainComplete);
512
0
    return;
513
0
  }
514
0
  if (mIsAwaitingResetComplete) {
515
0
    mIsAwaitingResetComplete = false;
516
0
    mCallback->ResetComplete();
517
0
  }
518
0
  if (mIsAwaitingDrainComplete) {
519
0
    mIsAwaitingDrainComplete = false;
520
0
    mCallback->DrainComplete();
521
0
  }
522
0
  CancelResetCompleteTimeout();
523
0
}
524
525
} // namespace gmp
526
} // namespace mozilla