EncodedAudioChunk.cpp (9007B)
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/EncodedAudioChunk.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/EncodedAudioChunkBinding.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(EncodedAudioChunk, mParent) 45 NS_IMPL_CYCLE_COLLECTING_ADDREF(EncodedAudioChunk) 46 NS_IMPL_CYCLE_COLLECTING_RELEASE(EncodedAudioChunk) 47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EncodedAudioChunk) 48 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 49 NS_INTERFACE_MAP_ENTRY(nsISupports) 50 NS_INTERFACE_MAP_END 51 52 EncodedAudioChunkData::EncodedAudioChunkData( 53 already_AddRefed<MediaAlignedByteBuffer> aBuffer, 54 const EncodedAudioChunkType& 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 UniquePtr<EncodedAudioChunkData> EncodedAudioChunkData::Clone() const { 67 if (!mBuffer) { 68 LOGE("No buffer in EncodedAudioChunkData %p to clone!", this); 69 return nullptr; 70 } 71 72 // Since EncodedAudioChunkData can be zero-sized, cloning a zero-sized chunk 73 // is allowed. 74 if (mBuffer->Size() == 0) { 75 LOGW("Cloning an empty EncodedAudioChunkData %p", this); 76 } 77 78 auto buffer = 79 MakeRefPtr<MediaAlignedByteBuffer>(mBuffer->Data(), mBuffer->Length()); 80 if (!buffer || buffer->Size() != mBuffer->Size()) { 81 LOGE("OOM to copy EncodedAudioChunkData %p", this); 82 return nullptr; 83 } 84 85 return MakeUnique<EncodedAudioChunkData>(buffer.forget(), mType, mTimestamp, 86 Maybe<uint64_t>(mDuration)); 87 } 88 89 already_AddRefed<MediaRawData> EncodedAudioChunkData::TakeData() { 90 if (!mBuffer || !(*mBuffer)) { 91 LOGE("EncodedAudioChunkData %p has no data!", this); 92 return nullptr; 93 } 94 95 RefPtr<MediaRawData> sample(new MediaRawData(std::move(*mBuffer))); 96 sample->mKeyframe = mType == EncodedAudioChunkType::Key; 97 sample->mTime = TimeUnit::FromMicroseconds(mTimestamp); 98 sample->mTimecode = TimeUnit::FromMicroseconds(mTimestamp); 99 100 if (mDuration) { 101 CheckedInt64 duration(*mDuration); 102 if (!duration.isValid()) { 103 LOGE("EncodedAudioChunkData %p 's duration exceeds TimeUnit's limit", 104 this); 105 return nullptr; 106 } 107 sample->mDuration = TimeUnit::FromMicroseconds(duration.value()); 108 } 109 110 return sample.forget(); 111 } 112 113 nsCString EncodedAudioChunkData::ToString() const { 114 return nsFmtCString( 115 FMT_STRING("EncodedAudioChunkData[bytes: {}, type: {}, ts: {}, dur: {}]"), 116 mBuffer ? mBuffer->Length() : 0, GetEnumString(mType).get(), mTimestamp, 117 mDuration ? std::to_string(*mDuration).c_str() : "none"); 118 } 119 120 EncodedAudioChunk::EncodedAudioChunk( 121 nsIGlobalObject* aParent, already_AddRefed<MediaAlignedByteBuffer> aBuffer, 122 const EncodedAudioChunkType& aType, int64_t aTimestamp, 123 Maybe<uint64_t>&& aDuration) 124 : EncodedAudioChunkData(std::move(aBuffer), aType, aTimestamp, 125 std::move(aDuration)), 126 mParent(aParent) {} 127 128 EncodedAudioChunk::EncodedAudioChunk(nsIGlobalObject* aParent, 129 const EncodedAudioChunkData& aData) 130 : EncodedAudioChunkData(aData), mParent(aParent) {} 131 132 nsIGlobalObject* EncodedAudioChunk::GetParentObject() const { 133 AssertIsOnOwningThread(); 134 135 return mParent.get(); 136 } 137 138 JSObject* EncodedAudioChunk::WrapObject(JSContext* aCx, 139 JS::Handle<JSObject*> aGivenProto) { 140 AssertIsOnOwningThread(); 141 142 return EncodedAudioChunk_Binding::Wrap(aCx, this, aGivenProto); 143 } 144 145 // https://w3c.github.io/webcodecs/#encodedaudiochunk-constructors 146 /* static */ 147 already_AddRefed<EncodedAudioChunk> EncodedAudioChunk::Constructor( 148 const GlobalObject& aGlobal, const EncodedAudioChunkInit& aInit, 149 ErrorResult& aRv) { 150 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 151 if (!global) { 152 aRv.Throw(NS_ERROR_FAILURE); 153 return nullptr; 154 } 155 156 auto buffer = ProcessTypedArrays( 157 aInit.mData, 158 [&](const Span<uint8_t>& aData, 159 JS::AutoCheckCannotGC&&) -> RefPtr<MediaAlignedByteBuffer> { 160 // Make sure it's in uint32_t's range. 161 CheckedUint32 byteLength(aData.Length()); 162 if (!byteLength.isValid()) { 163 aRv.Throw(NS_ERROR_INVALID_ARG); 164 return nullptr; 165 } 166 if (aData.Length() == 0) { 167 LOGW("Buffer for constructing EncodedAudioChunk is empty!"); 168 } 169 RefPtr<MediaAlignedByteBuffer> buf = MakeRefPtr<MediaAlignedByteBuffer>( 170 aData.Elements(), aData.Length()); 171 172 // Instead of checking *buf, size comparision is used to allow 173 // constructing a zero-sized EncodedAudioChunk. 174 if (!buf || buf->Size() != aData.Length()) { 175 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 176 return nullptr; 177 } 178 return buf; 179 }); 180 181 RefPtr<EncodedAudioChunk> chunk(new EncodedAudioChunk( 182 global, buffer.forget(), aInit.mType, aInit.mTimestamp, 183 OptionalToMaybe(aInit.mDuration))); 184 return aRv.Failed() ? nullptr : chunk.forget(); 185 } 186 187 EncodedAudioChunkType EncodedAudioChunk::Type() const { 188 AssertIsOnOwningThread(); 189 190 return mType; 191 } 192 193 int64_t EncodedAudioChunk::Timestamp() const { 194 AssertIsOnOwningThread(); 195 196 return mTimestamp; 197 } 198 199 Nullable<uint64_t> EncodedAudioChunk::GetDuration() const { 200 AssertIsOnOwningThread(); 201 return MaybeToNullable(mDuration); 202 } 203 204 uint32_t EncodedAudioChunk::ByteLength() const { 205 AssertIsOnOwningThread(); 206 MOZ_ASSERT(mBuffer); 207 208 return static_cast<uint32_t>(mBuffer->Length()); 209 } 210 211 // https://w3c.github.io/webcodecs/#dom-encodedaudiochunk-copyto 212 void EncodedAudioChunk::CopyTo(const AllowSharedBufferSource& aDestination, 213 ErrorResult& aRv) { 214 AssertIsOnOwningThread(); 215 216 ProcessTypedArraysFixed(aDestination, [&](const Span<uint8_t>& aData) { 217 if (mBuffer->Size() > aData.size_bytes()) { 218 aRv.ThrowTypeError( 219 "Destination ArrayBuffer smaller than source EncodedAudioChunk"); 220 return; 221 } 222 223 PodCopy(aData.data(), mBuffer->Data(), mBuffer->Size()); 224 }); 225 } 226 227 // https://w3c.github.io/webcodecs/#ref-for-deserialization-steps 228 /* static */ 229 JSObject* EncodedAudioChunk::ReadStructuredClone( 230 JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader, 231 const EncodedAudioChunkData& aData) { 232 JS::Rooted<JS::Value> value(aCx, JS::NullValue()); 233 // To avoid a rooting hazard error from returning a raw JSObject* before 234 // running the RefPtr destructor, RefPtr needs to be destructed before 235 // returning the raw JSObject*, which is why the RefPtr<EncodedAudioChunk> is 236 // created in the scope below. Otherwise, the static analysis infers the 237 // RefPtr cannot be safely destructed while the unrooted return JSObject* is 238 // on the stack. 239 { 240 auto frame = MakeRefPtr<EncodedAudioChunk>(aGlobal, aData); 241 if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) { 242 return nullptr; 243 } 244 } 245 return value.toObjectOrNull(); 246 } 247 248 // https://w3c.github.io/webcodecs/#ref-for-serialization-steps 249 bool EncodedAudioChunk::WriteStructuredClone( 250 JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) const { 251 AssertIsOnOwningThread(); 252 253 // Indexing the chunk and send the index to the receiver. 254 const uint32_t index = 255 static_cast<uint32_t>(aHolder->EncodedAudioChunks().Length()); 256 // The serialization is limited to the same process scope so it's ok to 257 // serialize a reference instead of a copy. 258 aHolder->EncodedAudioChunks().AppendElement(EncodedAudioChunkData(*this)); 259 return !NS_WARN_IF( 260 !JS_WriteUint32Pair(aWriter, SCTAG_DOM_ENCODEDAUDIOCHUNK, index)); 261 } 262 263 #undef LOGW 264 #undef LOGE 265 #undef LOG_INTERNAL 266 267 } // namespace mozilla::dom