/* -*- 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 "GMPVideoi420FrameImpl.h" #include #include "mozilla/gmp/GMPTypes.h" #include "mozilla/CheckedInt.h" #include "GMPVideoHost.h" #include "GMPSharedMemManager.h" namespace mozilla::gmp { GMPVideoi420FrameImpl::GMPFramePlane::GMPFramePlane( const GMPPlaneData& aPlaneData) : mOffset(aPlaneData.mOffset()), mSize(aPlaneData.mSize()), mStride(aPlaneData.mStride()) {} void GMPVideoi420FrameImpl::GMPFramePlane::InitPlaneData( GMPPlaneData& aPlaneData) { aPlaneData.mOffset() = mOffset; aPlaneData.mSize() = mSize; aPlaneData.mStride() = mStride; } void GMPVideoi420FrameImpl::GMPFramePlane::Copy(uint8_t* aDst, int32_t aDstOffset, const uint8_t* aSrc, int32_t aSize, int32_t aStride) { mOffset = aDstOffset; mSize = aSize; mStride = aStride; if (aDst && aSrc && aSize > 0) { memcpy(aDst + aDstOffset, aSrc, aSize); } } GMPVideoi420FrameImpl::GMPVideoi420FrameImpl(GMPVideoHostImpl* aHost) : mHost(aHost), mWidth(0), mHeight(0), mTimestamp(0ll), mDuration(0ll) { MOZ_ASSERT(aHost); aHost->DecodedFrameCreated(this); } GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( const GMPVideoi420FrameData& aFrameData, ipc::Shmem&& aShmemBuffer, GMPVideoHostImpl* aHost) : mHost(aHost), mShmemBuffer(std::move(aShmemBuffer)), mYPlane(aFrameData.mYPlane()), mUPlane(aFrameData.mUPlane()), mVPlane(aFrameData.mVPlane()), mWidth(aFrameData.mWidth()), mHeight(aFrameData.mHeight()), mTimestamp(aFrameData.mTimestamp()), mUpdatedTimestamp(aFrameData.mUpdatedTimestamp()), mDuration(aFrameData.mDuration()) { MOZ_ASSERT(aHost); aHost->DecodedFrameCreated(this); } GMPVideoi420FrameImpl::GMPVideoi420FrameImpl( const GMPVideoi420FrameData& aFrameData, nsTArray&& aArrayBuffer, GMPVideoHostImpl* aHost) : mHost(aHost), mArrayBuffer(std::move(aArrayBuffer)), mYPlane(aFrameData.mYPlane()), mUPlane(aFrameData.mUPlane()), mVPlane(aFrameData.mVPlane()), mWidth(aFrameData.mWidth()), mHeight(aFrameData.mHeight()), mTimestamp(aFrameData.mTimestamp()), mUpdatedTimestamp(aFrameData.mUpdatedTimestamp()), mDuration(aFrameData.mDuration()) { MOZ_ASSERT(aHost); aHost->DecodedFrameCreated(this); } GMPVideoi420FrameImpl::~GMPVideoi420FrameImpl() { DestroyBuffer(); if (mHost) { mHost->DecodedFrameDestroyed(this); } } void GMPVideoi420FrameImpl::DoneWithAPI() { DestroyBuffer(); // Do this after destroying the buffer because destruction // involves deallocation, which requires a host. mHost = nullptr; } void GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData) { mYPlane.InitPlaneData(aFrameData.mYPlane()); mUPlane.InitPlaneData(aFrameData.mUPlane()); mVPlane.InitPlaneData(aFrameData.mVPlane()); aFrameData.mWidth() = mWidth; aFrameData.mHeight() = mHeight; aFrameData.mTimestamp() = mTimestamp; aFrameData.mUpdatedTimestamp() = mUpdatedTimestamp; aFrameData.mDuration() = mDuration; } bool GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData, ipc::Shmem& aShmemBuffer) { if (!mShmemBuffer.IsReadable()) { return false; } aShmemBuffer = mShmemBuffer; // This method is called right before Shmem is sent to another process. // We need to effectively zero out our member copy so that we don't // try to delete memory we don't own later. mShmemBuffer = ipc::Shmem(); InitFrameData(aFrameData); return true; } bool GMPVideoi420FrameImpl::InitFrameData(GMPVideoi420FrameData& aFrameData, nsTArray& aArrayBuffer) { if (mShmemBuffer.IsReadable()) { return false; } aArrayBuffer = std::move(mArrayBuffer); InitFrameData(aFrameData); return true; } GMPVideoFrameFormat GMPVideoi420FrameImpl::GetFrameFormat() { return kGMPI420VideoFrame; } void GMPVideoi420FrameImpl::Destroy() { delete this; } /* static */ bool GMPVideoi420FrameImpl::CheckFrameData( const GMPVideoi420FrameData& aFrameData, size_t aBufferSize) { // We may be passed the "wrong" shmem (one smaller than the actual size). // This implies a bug or serious error on the child size. Ignore this frame // if so. Note: Size() greater than expected is also an error, but with no // negative consequences int32_t half_width = (aFrameData.mWidth() + 1) / 2; if ((aFrameData.mYPlane().mStride() <= 0) || (aFrameData.mYPlane().mSize() <= 0) || (aFrameData.mYPlane().mOffset() < 0) || (aFrameData.mUPlane().mStride() <= 0) || (aFrameData.mUPlane().mSize() <= 0) || (aFrameData.mUPlane().mOffset() < aFrameData.mYPlane().mOffset() + aFrameData.mYPlane().mSize()) || (aFrameData.mVPlane().mStride() <= 0) || (aFrameData.mVPlane().mSize() <= 0) || (aFrameData.mVPlane().mOffset() < aFrameData.mUPlane().mOffset() + aFrameData.mUPlane().mSize()) || (aBufferSize < static_cast(aFrameData.mVPlane().mOffset()) + static_cast(aFrameData.mVPlane().mSize())) || (aFrameData.mYPlane().mStride() < aFrameData.mWidth()) || (aFrameData.mUPlane().mStride() < half_width) || (aFrameData.mVPlane().mStride() < half_width) || (aFrameData.mYPlane().mSize() < aFrameData.mYPlane().mStride() * aFrameData.mHeight()) || (aFrameData.mUPlane().mSize() < aFrameData.mUPlane().mStride() * ((aFrameData.mHeight() + 1) / 2)) || (aFrameData.mVPlane().mSize() < aFrameData.mVPlane().mStride() * ((aFrameData.mHeight() + 1) / 2))) { return false; } return true; } bool GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight, int32_t aStride_y, int32_t aStride_u, int32_t aStride_v, int32_t aSize_y, int32_t aSize_u, int32_t aSize_v) { if (aWidth < 1 || aHeight < 1 || aStride_y < aWidth || aSize_y < 1 || aSize_u < 1 || aSize_v < 1) { return false; } auto halfWidth = (CheckedInt(aWidth) + 1) / 2; if (!halfWidth.isValid() || aStride_u < halfWidth.value() || aStride_v < halfWidth.value()) { return false; } auto height = CheckedInt(aHeight); auto halfHeight = (height + 1) / 2; auto minSizeY = height * aStride_y; auto minSizeU = halfHeight * aStride_u; auto minSizeV = halfHeight * aStride_v; auto totalSize = minSizeY + minSizeU + minSizeV; if (!minSizeY.isValid() || !minSizeU.isValid() || !minSizeV.isValid() || !totalSize.isValid() || minSizeY.value() > aSize_y || minSizeU.value() > aSize_u || minSizeV.value() > aSize_v) { return false; } return true; } bool GMPVideoi420FrameImpl::CheckDimensions(int32_t aWidth, int32_t aHeight, int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) { int32_t half_width = (aWidth + 1) / 2; if (aWidth < 1 || aHeight < 1 || aStride_y < aWidth || aStride_u < half_width || aStride_v < half_width || !(CheckedInt(aHeight) * aStride_y + ((CheckedInt(aHeight) + 1) / 2) * (CheckedInt(aStride_u) + aStride_v)) .isValid()) { return false; } return true; } const GMPVideoi420FrameImpl::GMPFramePlane* GMPVideoi420FrameImpl::GetPlane( GMPPlaneType aType) const { switch (aType) { case kGMPYPlane: return &mYPlane; case kGMPUPlane: return &mUPlane; case kGMPVPlane: return &mVPlane; default: MOZ_CRASH("Unknown plane type!"); } return nullptr; } GMPVideoi420FrameImpl::GMPFramePlane* GMPVideoi420FrameImpl::GetPlane( GMPPlaneType aType) { switch (aType) { case kGMPYPlane: return &mYPlane; case kGMPUPlane: return &mUPlane; case kGMPVPlane: return &mVPlane; default: MOZ_CRASH("Unknown plane type!"); } return nullptr; } GMPErr GMPVideoi420FrameImpl::MaybeResize(int32_t aNewSize) { if (aNewSize <= AllocatedSize()) { return GMPNoErr; } if (!mHost) { return GMPGenericErr; } if (!mArrayBuffer.IsEmpty()) { if (!mArrayBuffer.SetLength(aNewSize, fallible)) { return GMPAllocErr; } return GMPNoErr; } ipc::Shmem new_mem; if (!mHost->SharedMemMgr()->MgrTakeShmem(GMPSharedMemClass::Decoded, aNewSize, &new_mem) && !mArrayBuffer.SetLength(aNewSize, fallible)) { return GMPAllocErr; } if (mShmemBuffer.IsReadable()) { if (new_mem.IsWritable()) { memcpy(new_mem.get(), mShmemBuffer.get(), aNewSize); } mHost->SharedMemMgr()->MgrGiveShmem(GMPSharedMemClass::Decoded, std::move(mShmemBuffer)); } mShmemBuffer = new_mem; return GMPNoErr; } void GMPVideoi420FrameImpl::DestroyBuffer() { if (mHost && mShmemBuffer.IsWritable()) { mHost->SharedMemMgr()->MgrGiveShmem(GMPSharedMemClass::Decoded, std::move(mShmemBuffer)); } mShmemBuffer = ipc::Shmem(); mArrayBuffer.Clear(); } GMPErr GMPVideoi420FrameImpl::CreateEmptyFrame(int32_t aWidth, int32_t aHeight, int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) { if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v)) { return GMPGenericErr; } int32_t size_y = aStride_y * aHeight; int32_t half_height = (aHeight + 1) / 2; int32_t size_u = aStride_u * half_height; int32_t size_v = aStride_v * half_height; int32_t bufferSize = size_y + size_u + size_v; GMPErr err = MaybeResize(bufferSize); if (err != GMPNoErr) { return err; } mYPlane.mOffset = 0; mYPlane.mSize = size_y; mYPlane.mStride = aStride_y; mUPlane.mOffset = size_y; mUPlane.mSize = size_u; mUPlane.mStride = aStride_u; mVPlane.mOffset = size_y + size_u; mVPlane.mSize = size_v; mVPlane.mStride = aStride_v; mWidth = aWidth; mHeight = aHeight; mTimestamp = 0ll; mUpdatedTimestamp.reset(); mDuration = 0ll; return GMPNoErr; } GMPErr GMPVideoi420FrameImpl::CreateFrame( int32_t aSize_y, const uint8_t* aBuffer_y, int32_t aSize_u, const uint8_t* aBuffer_u, int32_t aSize_v, const uint8_t* aBuffer_v, int32_t aWidth, int32_t aHeight, int32_t aStride_y, int32_t aStride_u, int32_t aStride_v) { MOZ_ASSERT(aBuffer_y); MOZ_ASSERT(aBuffer_u); MOZ_ASSERT(aBuffer_v); if (!CheckDimensions(aWidth, aHeight, aStride_y, aStride_u, aStride_v, aSize_y, aSize_u, aSize_v)) { return GMPGenericErr; } int32_t bufferSize = aSize_y + aSize_u + aSize_v; GMPErr err = MaybeResize(bufferSize); if (err != GMPNoErr) { return err; } uint8_t* bufferPtr = Buffer(); mYPlane.Copy(bufferPtr, 0, aBuffer_y, aSize_y, aStride_y); mUPlane.Copy(bufferPtr, aSize_y, aBuffer_u, aSize_u, aStride_u); mVPlane.Copy(bufferPtr, aSize_y + aSize_u, aBuffer_v, aSize_v, aStride_v); mWidth = aWidth; mHeight = aHeight; return GMPNoErr; } GMPErr GMPVideoi420FrameImpl::CopyFrame(const GMPVideoi420Frame& aFrame) { auto& f = static_cast(aFrame); int32_t bufferSize = f.mYPlane.mSize + f.mUPlane.mSize + f.mVPlane.mSize; if (bufferSize != AllocatedSize()) { return GMPGenericErr; } GMPErr err = MaybeResize(bufferSize); if (err != GMPNoErr) { return err; } mYPlane = f.mYPlane; mUPlane = f.mUPlane; mVPlane = f.mVPlane; mWidth = f.mWidth; mHeight = f.mHeight; mTimestamp = f.mTimestamp; mUpdatedTimestamp = f.mUpdatedTimestamp; mDuration = f.mDuration; memcpy(Buffer(), f.Buffer(), bufferSize); return GMPNoErr; } void GMPVideoi420FrameImpl::SwapFrame(GMPVideoi420Frame* aFrame) { auto f = static_cast(aFrame); mArrayBuffer.SwapElements(f->mArrayBuffer); std::swap(mShmemBuffer, f->mShmemBuffer); std::swap(mYPlane, f->mYPlane); std::swap(mUPlane, f->mUPlane); std::swap(mVPlane, f->mVPlane); std::swap(mWidth, f->mWidth); std::swap(mHeight, f->mHeight); std::swap(mTimestamp, f->mTimestamp); std::swap(mUpdatedTimestamp, f->mUpdatedTimestamp); std::swap(mDuration, f->mDuration); } uint8_t* GMPVideoi420FrameImpl::Buffer() { if (mShmemBuffer.IsWritable()) { return mShmemBuffer.get(); } if (!mArrayBuffer.IsEmpty()) { return mArrayBuffer.Elements(); } return nullptr; } const uint8_t* GMPVideoi420FrameImpl::Buffer() const { if (mShmemBuffer.IsReadable()) { return mShmemBuffer.get(); } if (!mArrayBuffer.IsEmpty()) { return mArrayBuffer.Elements(); } return nullptr; } uint8_t* GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) { if (auto* p = GetPlane(aType)) { if (auto* buffer = Buffer()) { return buffer + p->mOffset; } } return nullptr; } const uint8_t* GMPVideoi420FrameImpl::Buffer(GMPPlaneType aType) const { if (const auto* p = GetPlane(aType)) { if (const auto* buffer = Buffer()) { return buffer + p->mOffset; } } return nullptr; } int32_t GMPVideoi420FrameImpl::AllocatedSize() const { if (mShmemBuffer.IsWritable()) { return static_cast(mShmemBuffer.Size()); } return static_cast(mArrayBuffer.Length()); } int32_t GMPVideoi420FrameImpl::AllocatedSize(GMPPlaneType aType) const { if (const auto* p = GetPlane(aType)) { return p->mSize; } return -1; } int32_t GMPVideoi420FrameImpl::Stride(GMPPlaneType aType) const { if (const auto* p = GetPlane(aType)) { return p->mStride; } return -1; } GMPErr GMPVideoi420FrameImpl::SetWidth(int32_t aWidth) { if (!CheckDimensions(aWidth, mHeight, mYPlane.mStride, mUPlane.mStride, mVPlane.mStride)) { return GMPGenericErr; } mWidth = aWidth; return GMPNoErr; } GMPErr GMPVideoi420FrameImpl::SetHeight(int32_t aHeight) { if (!CheckDimensions(mWidth, aHeight, mYPlane.mStride, mUPlane.mStride, mVPlane.mStride)) { return GMPGenericErr; } mHeight = aHeight; return GMPNoErr; } int32_t GMPVideoi420FrameImpl::Width() const { return mWidth; } int32_t GMPVideoi420FrameImpl::Height() const { return mHeight; } void GMPVideoi420FrameImpl::SetTimestamp(uint64_t aTimestamp) { mTimestamp = aTimestamp; } uint64_t GMPVideoi420FrameImpl::Timestamp() const { return mTimestamp; } void GMPVideoi420FrameImpl::SetUpdatedTimestamp(uint64_t aTimestamp) { mUpdatedTimestamp = Some(aTimestamp); } uint64_t GMPVideoi420FrameImpl::UpdatedTimestamp() const { return mUpdatedTimestamp ? *mUpdatedTimestamp : mTimestamp; } void GMPVideoi420FrameImpl::SetDuration(uint64_t aDuration) { mDuration = aDuration; } uint64_t GMPVideoi420FrameImpl::Duration() const { return mDuration; } bool GMPVideoi420FrameImpl::IsZeroSize() const { return (mYPlane.mSize == 0 && mUPlane.mSize == 0 && mVPlane.mSize == 0); } void GMPVideoi420FrameImpl::ResetSize() { mYPlane.mSize = 0; mUPlane.mSize = 0; mVPlane.mSize = 0; } } // namespace mozilla::gmp