GMPUniquePtr<GMPVideoEncodedFrame> GMPVideoDecoder::CreateFrame(MediaRawData* aSample) { GMPVideoFrame* ftmp = nullptr; GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); if (GMP_FAILED(err)) { return nullptr; } GMPUniquePtr<GMPVideoEncodedFrame> frame( static_cast<GMPVideoEncodedFrame*>(ftmp)); err = frame->CreateEmptyFrame(aSample->Size()); if (GMP_FAILED(err)) { return nullptr; } memcpy(frame->Buffer(), aSample->Data(), frame->Size()); // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to // suit the GMP API. if (mConvertNALUnitLengths) { const int kNALLengthSize = 4; uint8_t* buf = frame->Buffer(); while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) { uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize; *reinterpret_cast<uint32_t *>(buf) = length; buf += length; } } frame->SetBufferType(GMP_BufferLength32); frame->SetEncodedWidth(mConfig.mDisplay.width); frame->SetEncodedHeight(mConfig.mDisplay.height); frame->SetTimeStamp(aSample->mTime.ToMicroseconds()); frame->SetCompleteFrame(true); frame->SetDuration(aSample->mDuration.ToMicroseconds()); frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame); return frame; }
bool GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName, const GMPErr& aStatus) { if (mShutdown) { return true; } nsRefPtr<GMPRecordImpl> record; if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) { // Not fatal. return true; } record->WriteComplete(aStatus); if (GMP_FAILED(aStatus)) { Close(record); } return true; }
bool GMPStorageParent::RecvOpen(const nsCString& aRecordName) { LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')", this, aRecordName.get())); if (mShutdown) { return false; } if (mNodeId.EqualsLiteral("null")) { // Refuse to open storage if the page is opened from local disk, // or shared across origin. LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId", this, aRecordName.get())); Unused << SendOpenComplete(aRecordName, GMPGenericErr); return true; } if (aRecordName.IsEmpty()) { LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty", this, aRecordName.get())); Unused << SendOpenComplete(aRecordName, GMPGenericErr); return true; } if (mStorage->IsOpen(aRecordName)) { LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use", this, aRecordName.get())); Unused << SendOpenComplete(aRecordName, GMPRecordInUse); return true; } auto err = mStorage->Open(aRecordName); MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName)); LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d", this, aRecordName.get(), err)); Unused << SendOpenComplete(aRecordName, err); return true; }
bool GMPStorageChild::RecvReadComplete(const nsCString& aRecordName, const GMPErr& aStatus, const InfallibleTArray<uint8_t>& aBytes) { if (mShutdown) { return true; } nsRefPtr<GMPRecordImpl> record; if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) { // Not fatal. return true; } record->ReadComplete(aStatus, aBytes.Elements(), aBytes.Length()); if (GMP_FAILED(aStatus)) { Close(record); } return true; }
bool GMPStorageChild::RecvRecordNames(InfallibleTArray<nsCString>&& aRecordNames, const GMPErr& aStatus) { RecordIteratorContext ctx; { MonitorAutoLock lock(mMonitor); if (mShutdown || mPendingRecordIterators.empty()) { return true; } ctx = mPendingRecordIterators.front(); mPendingRecordIterators.pop(); } if (GMP_FAILED(aStatus)) { ctx.mFunc(nullptr, ctx.mUserArg, aStatus); } else { ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr); } return true; }
nsresult EMEH264Decoder::GmpInput(MP4Sample* aSample) { MOZ_ASSERT(IsOnGMPThread()); nsAutoPtr<MP4Sample> sample(aSample); if (!mGMP) { mCallback->Error(); return NS_ERROR_FAILURE; } if (sample->crypto.valid) { CDMCaps::AutoLock caps(mProxy->Capabilites()); MOZ_ASSERT(caps.CanDecryptAndDecodeVideo()); const auto& keyid = sample->crypto.key; if (!caps.IsKeyUsable(keyid)) { nsRefPtr<nsIRunnable> task(new DeliverSample(this, sample.forget())); caps.CallWhenKeyUsable(keyid, task, mGMPThread); return NS_OK; } } mLastStreamOffset = sample->byte_offset; GMPVideoFrame* ftmp = nullptr; GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); if (GMP_FAILED(err)) { mCallback->Error(); return NS_ERROR_FAILURE; } gmp::GMPVideoEncodedFrameImpl* frame = static_cast<gmp::GMPVideoEncodedFrameImpl*>(ftmp); err = frame->CreateEmptyFrame(sample->size); if (GMP_FAILED(err)) { mCallback->Error(); return NS_ERROR_FAILURE; } memcpy(frame->Buffer(), sample->data, frame->Size()); frame->SetEncodedWidth(mConfig.display_width); frame->SetEncodedHeight(mConfig.display_height); frame->SetTimeStamp(sample->composition_timestamp); frame->SetCompleteFrame(true); frame->SetDuration(sample->duration); if (sample->crypto.valid) { frame->InitCrypto(sample->crypto); } frame->SetFrameType(sample->is_sync_point ? kGMPKeyFrame : kGMPDeltaFrame); frame->SetBufferType(GMP_BufferLength32); nsTArray<uint8_t> info; // No codec specific per-frame info to pass. nsresult rv = mGMP->Decode(frame, false, info, 0); if (NS_FAILED(rv)) { mCallback->Error(); return rv; } return NS_OK; }
// 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; }
virtual void OpenComplete(GMPErr aStatus) override { if (GMP_FAILED(aStatus) || GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) { Done(mOnFailure, mOnSuccess); } }