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