Example #1
0
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;
}
Example #2
0
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;
}
Example #4
0
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;
}
Example #6
0
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);
   }
 }