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