tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

EncodedVideoChunk.cpp (9564B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/EncodedVideoChunk.h"
      8 
      9 #include <utility>
     10 
     11 #include "MediaData.h"
     12 #include "TimeUnits.h"
     13 #include "mozilla/CheckedInt.h"
     14 #include "mozilla/Logging.h"
     15 #include "mozilla/PodOperations.h"
     16 #include "mozilla/dom/BufferSourceBinding.h"
     17 #include "mozilla/dom/EncodedVideoChunkBinding.h"
     18 #include "mozilla/dom/StructuredCloneHolder.h"
     19 #include "mozilla/dom/StructuredCloneTags.h"
     20 #include "mozilla/dom/WebCodecsUtils.h"
     21 
     22 extern mozilla::LazyLogModule gWebCodecsLog;
     23 using mozilla::media::TimeUnit;
     24 
     25 namespace mozilla::dom {
     26 
     27 #ifdef LOG_INTERNAL
     28 #  undef LOG_INTERNAL
     29 #endif  // LOG_INTERNAL
     30 #define LOG_INTERNAL(level, msg, ...) \
     31  MOZ_LOG(gWebCodecsLog, LogLevel::level, (msg, ##__VA_ARGS__))
     32 
     33 #ifdef LOGW
     34 #  undef LOGW
     35 #endif  // LOGW
     36 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
     37 
     38 #ifdef LOGE
     39 #  undef LOGE
     40 #endif  // LOGE
     41 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
     42 
     43 // Only needed for refcounted objects.
     44 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(EncodedVideoChunk, mParent)
     45 NS_IMPL_CYCLE_COLLECTING_ADDREF(EncodedVideoChunk)
     46 NS_IMPL_CYCLE_COLLECTING_RELEASE(EncodedVideoChunk)
     47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EncodedVideoChunk)
     48  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     49  NS_INTERFACE_MAP_ENTRY(nsISupports)
     50 NS_INTERFACE_MAP_END
     51 
     52 EncodedVideoChunkData::EncodedVideoChunkData(
     53    already_AddRefed<MediaAlignedByteBuffer> aBuffer,
     54    const EncodedVideoChunkType& aType, int64_t aTimestamp,
     55    Maybe<uint64_t>&& aDuration)
     56    : mBuffer(aBuffer),
     57      mType(aType),
     58      mTimestamp(aTimestamp),
     59      mDuration(aDuration) {
     60  MOZ_ASSERT(mBuffer);
     61  MOZ_ASSERT(mBuffer->Length() == mBuffer->Size());
     62  MOZ_ASSERT(mBuffer->Length() <=
     63             static_cast<size_t>(std::numeric_limits<uint32_t>::max()));
     64 }
     65 
     66 EncodedVideoChunkData::~EncodedVideoChunkData() = default;
     67 
     68 UniquePtr<EncodedVideoChunkData> EncodedVideoChunkData::Clone() const {
     69  if (!mBuffer) {
     70    LOGE("No buffer in EncodedVideoChunkData %p to clone!", this);
     71    return nullptr;
     72  }
     73 
     74  // Since EncodedVideoChunkData can be zero-sized, cloning a zero-sized chunk
     75  // is allowed.
     76  if (mBuffer->Size() == 0) {
     77    LOGW("Cloning an empty EncodedVideoChunkData %p", this);
     78  }
     79 
     80  auto buffer =
     81      MakeRefPtr<MediaAlignedByteBuffer>(mBuffer->Data(), mBuffer->Length());
     82  if (!buffer || buffer->Size() != mBuffer->Size()) {
     83    LOGE("OOM to copy EncodedVideoChunkData %p", this);
     84    return nullptr;
     85  }
     86 
     87  return MakeUnique<EncodedVideoChunkData>(buffer.forget(), mType, mTimestamp,
     88                                           Maybe<uint64_t>(mDuration));
     89 }
     90 
     91 already_AddRefed<MediaRawData> EncodedVideoChunkData::TakeData() {
     92  if (!mBuffer || !(*mBuffer)) {
     93    LOGE("EncodedVideoChunkData %p has no data!", this);
     94    return nullptr;
     95  }
     96 
     97  RefPtr<MediaRawData> sample(new MediaRawData(std::move(*mBuffer)));
     98  sample->mKeyframe = mType == EncodedVideoChunkType::Key;
     99  sample->mTime = TimeUnit::FromMicroseconds(mTimestamp);
    100  sample->mTimecode = TimeUnit::FromMicroseconds(mTimestamp);
    101 
    102  if (mDuration) {
    103    CheckedInt64 duration(*mDuration);
    104    if (!duration.isValid()) {
    105      LOGE("EncodedVideoChunkData %p 's duration exceeds TimeUnit's limit",
    106           this);
    107      return nullptr;
    108    }
    109    sample->mDuration = TimeUnit::FromMicroseconds(duration.value());
    110  }
    111 
    112  return sample.forget();
    113 }
    114 
    115 nsCString EncodedVideoChunkData::ToString() const {
    116  return nsFmtCString(
    117      FMT_STRING("EncodedVideoChunkData[bytes: {}, type: {}, ts: {}, dur: {}]"),
    118      mBuffer ? mBuffer->Length() : 0, GetEnumString(mType).get(), mTimestamp,
    119      mDuration ? std::to_string(*mDuration).c_str() : "none");
    120 }
    121 
    122 EncodedVideoChunk::EncodedVideoChunk(
    123    nsIGlobalObject* aParent, already_AddRefed<MediaAlignedByteBuffer> aBuffer,
    124    const EncodedVideoChunkType& aType, int64_t aTimestamp,
    125    Maybe<uint64_t>&& aDuration)
    126    : EncodedVideoChunkData(std::move(aBuffer), aType, aTimestamp,
    127                            std::move(aDuration)),
    128      mParent(aParent) {}
    129 
    130 EncodedVideoChunk::EncodedVideoChunk(nsIGlobalObject* aParent,
    131                                     const EncodedVideoChunkData& aData)
    132    : EncodedVideoChunkData(aData), mParent(aParent) {}
    133 
    134 nsIGlobalObject* EncodedVideoChunk::GetParentObject() const {
    135  AssertIsOnOwningThread();
    136 
    137  return mParent.get();
    138 }
    139 
    140 JSObject* EncodedVideoChunk::WrapObject(JSContext* aCx,
    141                                        JS::Handle<JSObject*> aGivenProto) {
    142  AssertIsOnOwningThread();
    143 
    144  return EncodedVideoChunk_Binding::Wrap(aCx, this, aGivenProto);
    145 }
    146 
    147 // https://w3c.github.io/webcodecs/#encodedvideochunk-constructors
    148 /* static */
    149 already_AddRefed<EncodedVideoChunk> EncodedVideoChunk::Constructor(
    150    const GlobalObject& aGlobal, const EncodedVideoChunkInit& aInit,
    151    ErrorResult& aRv) {
    152  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    153  if (!global) {
    154    aRv.Throw(NS_ERROR_FAILURE);
    155    return nullptr;
    156  }
    157 
    158  auto res = ProcessTypedArrays(
    159      aInit.mData,
    160      [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&)
    161          -> Result<RefPtr<MediaAlignedByteBuffer>, MediaResult> {
    162        // Make sure it's in uint32_t's range.
    163        CheckedUint32 byteLength(aData.Length());
    164        if (!byteLength.isValid()) {
    165          return Err(MediaResult(NS_ERROR_INVALID_ARG,
    166                                 "requested size exceeds uint32_t limit"));
    167        }
    168        if (aData.Length() == 0) {
    169          LOGW("Buffer for constructing EncodedVideoChunk is empty!");
    170        }
    171        RefPtr<MediaAlignedByteBuffer> buf = MakeRefPtr<MediaAlignedByteBuffer>(
    172            aData.Elements(), aData.Length());
    173 
    174        // Instead of checking *buf, size comparision is used to allow
    175        // constructing a zero-sized EncodedVideoChunk.
    176        if (!buf || buf->Size() != aData.Length()) {
    177          return Err(MediaResult(NS_ERROR_OUT_OF_MEMORY,
    178                                 "failed to allocate buffer: OOM or "
    179                                 "exceeds internal size limit"));
    180        }
    181        return buf;
    182      });
    183 
    184  if (res.isErr()) {
    185    MediaResult err = res.unwrapErr();
    186    LOGE("Failed to process buffer for EncodedVideoChunk constructor: %s",
    187         err.Description().get());
    188    aRv.Throw(err.Code());
    189    return nullptr;
    190  }
    191  RefPtr<MediaAlignedByteBuffer> buffer = res.unwrap();
    192  RefPtr<EncodedVideoChunk> chunk(new EncodedVideoChunk(
    193      global, buffer.forget(), aInit.mType, aInit.mTimestamp,
    194      OptionalToMaybe(aInit.mDuration)));
    195  return aRv.Failed() ? nullptr : chunk.forget();
    196 }
    197 
    198 EncodedVideoChunkType EncodedVideoChunk::Type() const {
    199  AssertIsOnOwningThread();
    200 
    201  return mType;
    202 }
    203 
    204 int64_t EncodedVideoChunk::Timestamp() const {
    205  AssertIsOnOwningThread();
    206 
    207  return mTimestamp;
    208 }
    209 
    210 Nullable<uint64_t> EncodedVideoChunk::GetDuration() const {
    211  AssertIsOnOwningThread();
    212  return MaybeToNullable(mDuration);
    213 }
    214 
    215 uint32_t EncodedVideoChunk::ByteLength() const {
    216  AssertIsOnOwningThread();
    217  MOZ_ASSERT(mBuffer);
    218 
    219  return static_cast<uint32_t>(mBuffer->Length());
    220 }
    221 
    222 // https://w3c.github.io/webcodecs/#dom-encodedvideochunk-copyto
    223 void EncodedVideoChunk::CopyTo(const AllowSharedBufferSource& aDestination,
    224                               ErrorResult& aRv) {
    225  AssertIsOnOwningThread();
    226 
    227  ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) {
    228    if (mBuffer->Size() > aData.size_bytes()) {
    229      aRv.ThrowTypeError(
    230          "Destination ArrayBuffer smaller than source EncodedVideoChunk");
    231      return;
    232    }
    233 
    234    PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size());
    235  });
    236 }
    237 
    238 // https://w3c.github.io/webcodecs/#ref-for-deserialization-steps%E2%91%A0
    239 /* static */
    240 JSObject* EncodedVideoChunk::ReadStructuredClone(
    241    JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader,
    242    const EncodedVideoChunkData& aData) {
    243  JS::Rooted<JS::Value> value(aCx, JS::NullValue());
    244  // To avoid a rooting hazard error from returning a raw JSObject* before
    245  // running the RefPtr destructor, RefPtr needs to be destructed before
    246  // returning the raw JSObject*, which is why the RefPtr<EncodedVideoChunk> is
    247  // created in the scope below. Otherwise, the static analysis infers the
    248  // RefPtr cannot be safely destructed while the unrooted return JSObject* is
    249  // on the stack.
    250  {
    251    auto frame = MakeRefPtr<EncodedVideoChunk>(aGlobal, aData);
    252    if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) {
    253      return nullptr;
    254    }
    255  }
    256  return value.toObjectOrNull();
    257 }
    258 
    259 // https://w3c.github.io/webcodecs/#ref-for-serialization-steps%E2%91%A0
    260 bool EncodedVideoChunk::WriteStructuredClone(
    261    JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) const {
    262  AssertIsOnOwningThread();
    263 
    264  // Indexing the chunk and send the index to the receiver.
    265  const uint32_t index =
    266      static_cast<uint32_t>(aHolder->EncodedVideoChunks().Length());
    267  // The serialization is limited to the same process scope so it's ok to
    268  // serialize a reference instead of a copy.
    269  aHolder->EncodedVideoChunks().AppendElement(EncodedVideoChunkData(*this));
    270  return !NS_WARN_IF(
    271      !JS_WriteUint32Pair(aWriter, SCTAG_DOM_ENCODEDVIDEOCHUNK, index));
    272 }
    273 
    274 #undef LOGW
    275 #undef LOGE
    276 #undef LOG_INTERNAL
    277 
    278 }  // namespace mozilla::dom