518 lines
16 KiB
C++
518 lines
16 KiB
C++
/* -*- 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 <algorithm>
|
|
#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<uint8_t>&& 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<uint8_t>& 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<size_t>(aFrameData.mVPlane().mOffset()) +
|
|
static_cast<size_t>(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<int32_t>(aWidth) + 1) / 2;
|
|
if (!halfWidth.isValid() || aStride_u < halfWidth.value() ||
|
|
aStride_v < halfWidth.value()) {
|
|
return false;
|
|
}
|
|
auto height = CheckedInt<int32_t>(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<int32_t>(aHeight) * aStride_y +
|
|
((CheckedInt<int32_t>(aHeight) + 1) / 2) *
|
|
(CheckedInt<int32_t>(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<uint8_t>(), mShmemBuffer.get<uint8_t>(), 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<const GMPVideoi420FrameImpl&>(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<GMPVideoi420FrameImpl*>(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<uint8_t>();
|
|
}
|
|
if (!mArrayBuffer.IsEmpty()) {
|
|
return mArrayBuffer.Elements();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const uint8_t* GMPVideoi420FrameImpl::Buffer() const {
|
|
if (mShmemBuffer.IsReadable()) {
|
|
return mShmemBuffer.get<uint8_t>();
|
|
}
|
|
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<int32_t>(mShmemBuffer.Size<uint8_t>());
|
|
}
|
|
return static_cast<int32_t>(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
|