From 2c63e2e5b060757aca8e1865e8ff9302a0f7e76a Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Tue, 29 Sep 2020 04:39:14 +0000 Subject: [PATCH] Bug 1648309 - P2. Add RemoteArrayOfByteBuffer and ArrayOfRemoteMediaRawData objects. r=padenot,mjf Those two objects can be used to pack multiple array of objects into a minimal amount of shmem. An ArrayOfRemoteByteBuffer will take at most a single shmem and perform a single memory allocation.. Similarly, an ArrayOfMediaRawData will pack multiple MediaRawData in at most 3 shmems (one for each array of bytes a MediaRawData contains). They are designed to work in combination with a ShmemPool which will own each of the allocated Shmem and so can be re-used over and over. Differential Revision: https://phabricator.services.mozilla.com/D91537 --- dom/media/MediaData.cpp | 12 ++ dom/media/MediaData.h | 3 + dom/media/ipc/RemoteMediaData.cpp | 239 ++++++++++++++++++++++++++++++ dom/media/ipc/RemoteMediaData.h | 192 +++++++++++++++++++++++- dom/media/ipc/moz.build | 1 + 5 files changed, 440 insertions(+), 7 deletions(-) create mode 100644 dom/media/ipc/RemoteMediaData.cpp diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index a3c48e394df0..bf0e54497e45 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -452,6 +452,18 @@ MediaRawData::MediaRawData(const uint8_t* aData, size_t aSize, mBuffer(aData, aSize), mAlphaBuffer(aAlphaData, aAlphaSize) {} +MediaRawData::MediaRawData(AlignedByteBuffer&& aData) + : MediaData(Type::RAW_DATA), + mCrypto(mCryptoInternal), + mBuffer(std::move(aData)) {} + +MediaRawData::MediaRawData(AlignedByteBuffer&& aData, + AlignedByteBuffer&& aAlphaData) + : MediaData(Type::RAW_DATA), + mCrypto(mCryptoInternal), + mBuffer(std::move(aData)), + mAlphaBuffer(std::move(aAlphaData)) {} + already_AddRefed MediaRawData::Clone() const { RefPtr s = new MediaRawData; s->mTimecode = mTimecode; diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index 152f8f7a9dbd..db22996829e1 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -610,6 +610,8 @@ class MediaRawData final : public MediaData { MediaRawData(const uint8_t* aData, size_t aSize); MediaRawData(const uint8_t* aData, size_t aSize, const uint8_t* aAlphaData, size_t aAlphaSize); + explicit MediaRawData(AlignedByteBuffer&& aData); + MediaRawData(AlignedByteBuffer&& aData, AlignedByteBuffer&& aAlphaData); // Pointer to data or null if not-yet allocated const uint8_t* Data() const { return mBuffer.Data(); } @@ -658,6 +660,7 @@ class MediaRawData final : public MediaData { private: friend class MediaRawDataWriter; + friend class ArrayOfRemoteMediaRawData; AlignedByteBuffer mBuffer; AlignedByteBuffer mAlphaBuffer; CryptoSample mCryptoInternal; diff --git a/dom/media/ipc/RemoteMediaData.cpp b/dom/media/ipc/RemoteMediaData.cpp new file mode 100644 index 000000000000..1de3b8bf2a2a --- /dev/null +++ b/dom/media/ipc/RemoteMediaData.cpp @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteMediaData.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/MediaIPCUtils.h" +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { + +bool RemoteArrayOfByteBuffer::AllocateShmem( + size_t aSize, std::function& aAllocator) { + ShmemBuffer buffer = aAllocator(aSize); + if (!buffer.Valid()) { + return false; + } + mBuffers.emplace(std::move(buffer.Get())); + return true; +} + +uint8_t* RemoteArrayOfByteBuffer::BuffersStartAddress() const { + MOZ_ASSERT(mBuffers); + return mBuffers->get(); +} + +bool RemoteArrayOfByteBuffer::Check(size_t aOffset, size_t aSizeInBytes) const { + return mBuffers && detail::IsAddValid(aOffset, aSizeInBytes) && + aOffset + aSizeInBytes <= mBuffers->Size(); +} + +void RemoteArrayOfByteBuffer::Write(size_t aOffset, const void* aSourceAddr, + size_t aSizeInBytes) { + if (!aSizeInBytes) { + return; + } + MOZ_DIAGNOSTIC_ASSERT(Check(aOffset, aSizeInBytes), + "Allocated Shmem is too small"); + memcpy(BuffersStartAddress() + aOffset, aSourceAddr, aSizeInBytes); +} + +RemoteArrayOfByteBuffer::RemoteArrayOfByteBuffer() = default; + +RemoteArrayOfByteBuffer::RemoteArrayOfByteBuffer( + const nsTArray>& aArray, + std::function& aAllocator) { + // Determine the total size we will need for this object. + size_t totalSize = 0; + for (const auto& buffer : aArray) { + if (buffer) { + totalSize += buffer->Length(); + } + } + if (totalSize) { + if (!AllocateShmem(totalSize, aAllocator)) { + return; + } + } + size_t offset = 0; + for (const auto& buffer : aArray) { + size_t sizeBuffer = buffer ? buffer->Length() : 0; + if (totalSize && sizeBuffer) { + Write(offset, buffer->Elements(), sizeBuffer); + } + mOffsets.AppendElement(OffsetEntry{offset, sizeBuffer}); + offset += sizeBuffer; + } + mIsValid = true; +} + +RemoteArrayOfByteBuffer& RemoteArrayOfByteBuffer::operator=( + RemoteArrayOfByteBuffer&& aOther) noexcept { + mIsValid = aOther.mIsValid; + mBuffers = std::move(aOther.mBuffers); + mOffsets = std::move(aOther.mOffsets); + aOther.mIsValid = false; + return *this; +} + +RemoteArrayOfByteBuffer::~RemoteArrayOfByteBuffer() = default; + +already_AddRefed RemoteArrayOfByteBuffer::MediaByteBufferAt( + size_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + const OffsetEntry& entry = mOffsets[aIndex]; + if (!mBuffers || !Get<1>(entry)) { + // It's an empty one. + return nullptr; + } + size_t entrySize = Get<1>(entry); + if (!Check(Get<0>(entry), entrySize)) { + // This Shmem is corrupted and can't contain the data we are about to + // retrieve. We return an empty array instead of asserting to allow for + // recovery. + return nullptr; + } + RefPtr buffer = new MediaByteBuffer(entrySize); + buffer->SetLength(entrySize); + memcpy(buffer->Elements(), mBuffers->get() + Get<0>(entry), + entrySize); + return buffer.forget(); +} + +/*static */ void ipc::IPDLParamTraits::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, + const RemoteArrayOfByteBuffer& aVar) { + WriteIPDLParam(aMsg, aActor, aVar.mIsValid); + // We need the following gymnastic as the Shmem transfered over IPC will be + // revoked. We must create a temporary one instead so that it can be recycled + // later back into the original ShmemPool. + if (aVar.mBuffers) { + WriteIPDLParam(aMsg, aActor, Some(ipc::Shmem(*aVar.mBuffers))); + } else { + WriteIPDLParam(aMsg, aActor, Maybe()); + } + WriteIPDLParam(aMsg, aActor, aVar.mOffsets); +} + +/* static */ bool ipc::IPDLParamTraits::Read( + const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, RemoteArrayOfByteBuffer* aVar) { + return ReadIPDLParam(aMsg, aIter, aActor, &aVar->mIsValid) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBuffers) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mOffsets); +} + +bool ArrayOfRemoteMediaRawData::Fill( + const nsTArray>& aData, + std::function&& aAllocator) { + nsTArray dataBuffers(aData.Length()); + nsTArray alphaBuffers(aData.Length()); + nsTArray> extraDataBuffers(aData.Length()); + for (auto&& entry : aData) { + dataBuffers.AppendElement(std::move(entry->mBuffer)); + alphaBuffers.AppendElement(std::move(entry->mAlphaBuffer)); + extraDataBuffers.AppendElement(std::move(entry->mExtraData)); + mSamples.AppendElement(RemoteMediaRawData{ + MediaDataIPDL(entry->mOffset, entry->mTime, entry->mTimecode, + entry->mDuration, entry->mKeyframe), + entry->mEOS, entry->mDiscardPadding, + entry->mOriginalPresentationWindow}); + } + mBuffers = RemoteArrayOfByteBuffer(dataBuffers, aAllocator); + if (!mBuffers.IsValid()) { + return false; + } + mAlphaBuffers = RemoteArrayOfByteBuffer(alphaBuffers, aAllocator); + if (!mAlphaBuffers.IsValid()) { + return false; + } + mExtraDatas = RemoteArrayOfByteBuffer(extraDataBuffers, aAllocator); + return mExtraDatas.IsValid(); +} + +already_AddRefed ArrayOfRemoteMediaRawData::ElementAt( + size_t aIndex) const { + if (!IsValid()) { + return nullptr; + } + MOZ_ASSERT(aIndex < Count()); + MOZ_DIAGNOSTIC_ASSERT(mBuffers.Count() == Count() && + mAlphaBuffers.Count() == Count() && + mExtraDatas.Count() == Count(), + "Something ain't right here"); + const auto& sample = mSamples[aIndex]; + AlignedByteBuffer data = mBuffers.AlignedBufferAt(aIndex); + if (mBuffers.SizeAt(aIndex) && !data) { + // OOM + return nullptr; + } + AlignedByteBuffer alphaData = mAlphaBuffers.AlignedBufferAt(aIndex); + if (mAlphaBuffers.SizeAt(aIndex) && !alphaData) { + // OOM + return nullptr; + } + RefPtr rawData; + if (mAlphaBuffers.SizeAt(aIndex)) { + rawData = new MediaRawData(std::move(data), std::move(alphaData)); + } else { + rawData = new MediaRawData(std::move(data)); + } + rawData->mOffset = sample.mBase.offset(); + rawData->mTime = sample.mBase.time(); + rawData->mTimecode = sample.mBase.timecode(); + rawData->mDuration = sample.mBase.duration(); + rawData->mKeyframe = sample.mBase.keyframe(); + rawData->mEOS = sample.mEOS; + rawData->mDiscardPadding = sample.mDiscardPadding; + rawData->mExtraData = mExtraDatas.MediaByteBufferAt(aIndex); + return rawData.forget(); +} + +/*static */ void ipc::IPDLParamTraits::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, + ArrayOfRemoteMediaRawData* aVar) { + WriteIPDLParam(aMsg, aActor, std::move(aVar->mSamples)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mBuffers)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mAlphaBuffers)); + WriteIPDLParam(aMsg, aActor, std::move(aVar->mExtraDatas)); +} + +/* static */ bool ipc::IPDLParamTraits::Read( + const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::ipc::IProtocol* aActor, RefPtr* aVar) { + auto array = MakeRefPtr(); + if (!ReadIPDLParam(aMsg, aIter, aActor, &array->mSamples) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mBuffers) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mAlphaBuffers) || + !ReadIPDLParam(aMsg, aIter, aActor, &array->mExtraDatas)) { + return false; + } + *aVar = std::move(array); + return true; +} + +/* static */ void +ipc::IPDLParamTraits::Write( + IPC::Message* aMsg, ipc::IProtocol* aActor, const paramType& aVar) { + WriteIPDLParam(aMsg, aActor, aVar.mBase); + WriteIPDLParam(aMsg, aActor, aVar.mEOS); + WriteIPDLParam(aMsg, aActor, aVar.mDiscardPadding); + WriteIPDLParam(aMsg, aActor, aVar.mOriginalPresentationWindow); +} + +/* static */ bool +ipc::IPDLParamTraits::Read( + const IPC::Message* aMsg, PickleIterator* aIter, ipc::IProtocol* aActor, + paramType* aVar) { + MediaDataIPDL mBase; + return ReadIPDLParam(aMsg, aIter, aActor, &aVar->mBase) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mEOS) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mDiscardPadding) && + ReadIPDLParam(aMsg, aIter, aActor, &aVar->mOriginalPresentationWindow); +}; + +} // namespace mozilla diff --git a/dom/media/ipc/RemoteMediaData.h b/dom/media/ipc/RemoteMediaData.h index be002052293b..999b19fa5b23 100644 --- a/dom/media/ipc/RemoteMediaData.h +++ b/dom/media/ipc/RemoteMediaData.h @@ -7,15 +7,26 @@ #ifndef mozilla_dom_media_ipc_RemoteMediaData_h #define mozilla_dom_media_ipc_RemoteMediaData_h +#include + +#include "MediaData.h" #include "PlatformDecoderModule.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/GfxMessageUtils.h" #include "mozilla/PMediaDecoderParams.h" #include "mozilla/RemoteImageHolder.h" +#include "mozilla/ShmemPool.h" #include "mozilla/gfx/Rect.h" namespace mozilla { +class ShmemPool; + +namespace ipc { +class IProtocol; +class Shmem; +} // namespace ipc + //----------------------------------------------------------------------------- // Declaration of the IPDL type |struct RemoteVideoData| // @@ -23,9 +34,7 @@ namespace mozilla { // (see bug 1664362) class RemoteVideoData final { private: - typedef mozilla::MediaDataIPDL MediaDataIPDL; typedef mozilla::gfx::IntSize IntSize; - typedef mozilla::RemoteImageHolder RemoteImageHolder; public: RemoteVideoData() = default; @@ -74,7 +83,7 @@ class ArrayOfRemoteVideoData final { ArrayOfRemoteVideoData(const ArrayOfRemoteVideoData& aOther) { MOZ_CRASH("Should never be used but declared by generated IPDL binding"); } - ArrayOfRemoteVideoData& operator=(ArrayOfRemoteVideoData&& aOther) { + ArrayOfRemoteVideoData& operator=(ArrayOfRemoteVideoData&& aOther) noexcept { if (this != &aOther) { mArray = std::move(aOther.mArray); } @@ -100,6 +109,143 @@ class ArrayOfRemoteVideoData final { nsTArray mArray; }; +/* The class will pack either an array of AlignedBuffer or MediaByteBuffer + * into a single Shmem objects. */ +class RemoteArrayOfByteBuffer { + public: + RemoteArrayOfByteBuffer(); + template + RemoteArrayOfByteBuffer(const nsTArray>& aArray, + std::function& aAllocator) { + // Determine the total size we will need for this object. + size_t totalSize = 0; + for (auto& buffer : aArray) { + totalSize += buffer.Size(); + } + if (totalSize) { + if (!AllocateShmem(totalSize, aAllocator)) { + return; + } + } + size_t offset = 0; + for (auto& buffer : aArray) { + if (totalSize && buffer && buffer.Size()) { + Write(offset, buffer.Data(), buffer.Size()); + } + mOffsets.AppendElement(OffsetEntry{offset, buffer.Size()}); + offset += buffer.Size(); + } + mIsValid = true; + } + + RemoteArrayOfByteBuffer(const nsTArray>& aArray, + std::function& aAllocator); + RemoteArrayOfByteBuffer& operator=(RemoteArrayOfByteBuffer&& aOther) noexcept; + + // Return the packed aIndexth buffer as an AlignedByteBuffer. + // The operation is fallible should an out of memory be encountered. The + // result should be tested accordingly. + template + AlignedBuffer AlignedBufferAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + const OffsetEntry& entry = mOffsets[aIndex]; + size_t entrySize = Get<1>(entry); + if (!mBuffers || !entrySize) { + // It's an empty one. + return AlignedBuffer(); + } + if (!Check(Get<0>(entry), entrySize)) { + // This Shmem is corrupted and can't contain the data we are about to + // retrieve. We return an empty array instead of asserting to allow for + // recovery. + return AlignedBuffer(); + } + if (0 != entrySize % sizeof(Type)) { + // There's an error, that entry can't represent this data. + return AlignedBuffer(); + } + return AlignedBuffer( + reinterpret_cast(BuffersStartAddress() + Get<0>(entry)), + entrySize / sizeof(Type)); + } + + // Return the packed aIndexth buffer as aMediaByteBuffer. + // Will return nullptr if the packed buffer was originally empty. + already_AddRefed MediaByteBufferAt(size_t aIndex) const; + // Return the size of the aIndexth buffer. + size_t SizeAt(size_t aIndex) const { return Get<1>(mOffsets[aIndex]); } + // Return false if an out of memory error was encountered during construction. + bool IsValid() const { return mIsValid; }; + // Return the number of buffers packed into this entity. + size_t Count() const { return mOffsets.Length(); } + virtual ~RemoteArrayOfByteBuffer(); + + private: + friend struct ipc::IPDLParamTraits; + // Allocate shmem, false if an error occurred. + bool AllocateShmem(size_t aSize, + std::function& aAllocator); + // The starting address of the Shmem + uint8_t* BuffersStartAddress() const; + // Check that the allocated Shmem can contain such range. + bool Check(size_t aOffset, size_t aSizeInBytes) const; + void Write(size_t aOffset, const void* aSourceAddr, size_t aSizeInBytes); + // Set to false is the buffer isn't initialized yet or a memory error occurred + // during construction. + bool mIsValid = false; + // The packed data. The Maybe will be empty if all buffers packed were + // orignally empty. + Maybe mBuffers; + // The offset to the start of the individual buffer and its size (all in + // bytes) + typedef Tuple OffsetEntry; + nsTArray mOffsets; +}; + +/* The class will pack an array of MediaRawData using at most three Shmem + * objects. Under the most common scenaria, only two Shmems will be used as + * there are few videos with an alpha channel in the wild. + * We unfortunately can't populate the array at construction nor present an + * interface similar to an actual nsTArray or the ArrayOfRemoteVideoData above + * as currently IPC serialization is always non-fallible. So we must create the + * object first, fill it to determine if we ran out of memory and then send the + * object over IPC. + */ +class ArrayOfRemoteMediaRawData { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ArrayOfRemoteMediaRawData) + public: + // Fill the content, return false if an OOM occurred. + bool Fill(const nsTArray>& aData, + std::function&& aAllocator); + + // Return the aIndexth MediaRawData or nullptr if a memory error occurred. + already_AddRefed ElementAt(size_t aIndex) const; + + // Return the number of MediaRawData stored in this container. + size_t Count() const { return mSamples.Length(); } + bool IsEmpty() const { return Count() == 0; } + bool IsValid() const { + return mBuffers.IsValid() && mAlphaBuffers.IsValid() && + mExtraDatas.IsValid(); + } + + struct RemoteMediaRawData { + MediaDataIPDL mBase; + bool mEOS; + uint32_t mDiscardPadding; + Maybe mOriginalPresentationWindow; + }; + + private: + friend struct ipc::IPDLParamTraits; + virtual ~ArrayOfRemoteMediaRawData() = default; + + nsTArray mSamples; + RemoteArrayOfByteBuffer mBuffers; + RemoteArrayOfByteBuffer mAlphaBuffers; + RemoteArrayOfByteBuffer mExtraDatas; +}; + namespace ipc { template <> @@ -126,15 +272,15 @@ struct IPDLParamTraits { }; template <> -struct IPDLParamTraits { - typedef mozilla::ArrayOfRemoteVideoData paramType; +struct IPDLParamTraits { + typedef ArrayOfRemoteVideoData paramType; static void Write(IPC::Message* aMsg, mozilla::ipc::IProtocol* aActor, paramType* aVar) { WriteIPDLParam(aMsg, aActor, std::move(aVar->mArray)); } static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, - mozilla::ipc::IProtocol* aActor, RefPtr* aVar) { + ipc::IProtocol* aActor, RefPtr* aVar) { nsTArray array; if (!ReadIPDLParam(aMsg, aIter, aActor, &array)) { return false; @@ -145,8 +291,40 @@ struct IPDLParamTraits { } }; +template <> +struct IPDLParamTraits { + typedef RemoteArrayOfByteBuffer paramType; + // We do not want to move the RemoteArrayOfByteBuffer as we want to recycle + // the shmem it contains for another time. + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, paramType* aVar); +}; + +template <> +struct IPDLParamTraits { + typedef ArrayOfRemoteMediaRawData::RemoteMediaRawData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + const paramType& aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, paramType* aVar); +}; + +template <> +struct IPDLParamTraits { + typedef ArrayOfRemoteMediaRawData paramType; + static void Write(IPC::Message* aMsg, ipc::IProtocol* aActor, + paramType* aVar); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + ipc::IProtocol* aActor, RefPtr* aVar); +}; + } // namespace ipc } // namespace mozilla -#endif // mozilla_dom_media_ipc_RemoteMediaData_h \ No newline at end of file +#endif // mozilla_dom_media_ipc_RemoteMediaData_h diff --git a/dom/media/ipc/moz.build b/dom/media/ipc/moz.build index 3cbf214269b9..721fd37bb7cc 100644 --- a/dom/media/ipc/moz.build +++ b/dom/media/ipc/moz.build @@ -49,6 +49,7 @@ SOURCES += [ 'RemoteDecoderModule.cpp', 'RemoteDecoderParent.cpp', 'RemoteImageHolder.cpp', + 'RemoteMediaData.cpp', 'RemoteMediaDataDecoder.cpp', 'RemoteVideoDecoder.cpp', ]