RTCRtpScriptTransformer.cpp (15546B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 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 https://mozilla.org/MPL/2.0/. */ 6 7 #include "RTCRtpScriptTransformer.h" 8 9 #include <stdint.h> 10 11 #include <memory> 12 #include <string> 13 #include <utility> 14 15 #include "ErrorList.h" 16 #include "api/frame_transformer_interface.h" 17 #include "js/CallArgs.h" 18 #include "js/RootingAPI.h" 19 #include "js/Value.h" 20 #include "libwebrtcglue/FrameTransformerProxy.h" 21 #include "mozilla/AlreadyAddRefed.h" 22 #include "mozilla/Assertions.h" 23 #include "mozilla/ErrorResult.h" 24 #include "mozilla/HoldDropJSObjects.h" 25 #include "mozilla/Likely.h" 26 #include "mozilla/Logging.h" 27 #include "mozilla/Maybe.h" 28 #include "mozilla/RefPtr.h" 29 #include "mozilla/dom/BindingDeclarations.h" 30 #include "mozilla/dom/BindingUtils.h" 31 #include "mozilla/dom/Promise-inl.h" 32 #include "mozilla/dom/Promise.h" 33 #include "mozilla/dom/PrototypeList.h" 34 #include "mozilla/dom/RTCEncodedAudioFrame.h" 35 #include "mozilla/dom/RTCEncodedVideoFrame.h" 36 #include "mozilla/dom/RTCRtpScriptTransformerBinding.h" 37 #include "mozilla/dom/ReadableStream.h" 38 #include "mozilla/dom/ReadableStreamControllerBase.h" 39 #include "mozilla/dom/ToJSValue.h" 40 #include "mozilla/dom/UnderlyingSinkCallbackHelpers.h" 41 #include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" 42 #include "mozilla/dom/WorkerRef.h" 43 #include "mozilla/dom/WritableStream.h" 44 #include "mozilla/dom/WritableStreamDefaultController.h" 45 #include "nsCOMPtr.h" 46 #include "nsContentUtils.h" 47 #include "nsCycleCollectionParticipant.h" 48 #include "nsCycleCollectionTraversalCallback.h" 49 #include "nsDebug.h" 50 #include "nsIGlobalObject.h" 51 #include "nsISupports.h" 52 #include "nsLiteralString.h" 53 #include "nsString.h" 54 #include "nsStringFwd.h" 55 #include "nsTArray.h" 56 #include "nsWrapperCache.h" 57 #include "sdp/SdpAttribute.h" // CheckRidValidity 58 59 namespace mozilla::dom { 60 61 LazyLogModule gScriptTransformerLog("RTCRtpScriptTransformer"); 62 63 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsISupportsStreamSource, 64 UnderlyingSourceAlgorithmsWrapper, mStream, 65 mThingQueuedPromise, mQueue) 66 NS_IMPL_ADDREF_INHERITED(nsISupportsStreamSource, 67 UnderlyingSourceAlgorithmsWrapper) 68 NS_IMPL_RELEASE_INHERITED(nsISupportsStreamSource, 69 UnderlyingSourceAlgorithmsWrapper) 70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsISupportsStreamSource) 71 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper) 72 73 nsISupportsStreamSource::nsISupportsStreamSource() = default; 74 75 nsISupportsStreamSource::~nsISupportsStreamSource() = default; 76 77 void nsISupportsStreamSource::Init(ReadableStream* aStream) { 78 mStream = aStream; 79 } 80 81 void nsISupportsStreamSource::Enqueue(nsISupports* aThing) { 82 if (!mThingQueuedPromise) { 83 mQueue.AppendElement(aThing); 84 return; 85 } 86 87 // Maybe put a limit here? Or at least some sort of logging if this gets 88 // unreasonably long? 89 AutoJSAPI jsapi; 90 if (NS_WARN_IF(!jsapi.Init(mStream->GetParentObject()))) { 91 return; 92 } 93 94 EnqueueToStream(jsapi.cx(), aThing); 95 mThingQueuedPromise->MaybeResolveWithUndefined(); 96 mThingQueuedPromise = nullptr; 97 } 98 99 already_AddRefed<Promise> nsISupportsStreamSource::PullCallbackImpl( 100 JSContext* aCx, ReadableStreamControllerBase& aController, 101 ErrorResult& aRv) { 102 if (!mQueue.IsEmpty()) { 103 EnqueueOneThingFromQueue(aCx); 104 return nullptr; 105 } 106 107 RefPtr<nsISupportsStreamSource> self(this); 108 mThingQueuedPromise = Promise::CreateInfallible(mStream->GetParentObject()); 109 return do_AddRef(mThingQueuedPromise); 110 } 111 112 void nsISupportsStreamSource::EnqueueToStream(JSContext* aCx, 113 nsISupports* aThing) { 114 JS::Rooted<JS::Value> jsThing(aCx); 115 if (NS_WARN_IF(MOZ_UNLIKELY(!ToJSValue(aCx, *aThing, &jsThing)))) { 116 // Do we want to add error handling for this? 117 return; 118 } 119 IgnoredErrorResult rv; 120 // EnqueueNative is CAN_RUN_SCRIPT. Need a strong-ref temporary. 121 auto stream = mStream; 122 stream->EnqueueNative(aCx, jsThing, rv); 123 } 124 125 void nsISupportsStreamSource::EnqueueOneThingFromQueue(JSContext* aCx) { 126 if (!mQueue.IsEmpty()) { 127 RefPtr<nsISupports> thing = mQueue[0]; 128 mQueue.RemoveElementAt(0); 129 EnqueueToStream(aCx, thing); 130 } 131 } 132 133 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableStreamRTCFrameSink, 134 UnderlyingSinkAlgorithmsWrapper, 135 mTransformer) 136 NS_IMPL_ADDREF_INHERITED(WritableStreamRTCFrameSink, 137 UnderlyingSinkAlgorithmsWrapper) 138 NS_IMPL_RELEASE_INHERITED(WritableStreamRTCFrameSink, 139 UnderlyingSinkAlgorithmsWrapper) 140 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamRTCFrameSink) 141 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsWrapper) 142 143 WritableStreamRTCFrameSink::WritableStreamRTCFrameSink( 144 RTCRtpScriptTransformer* aTransformer) 145 : mTransformer(aTransformer) {} 146 147 WritableStreamRTCFrameSink::~WritableStreamRTCFrameSink() = default; 148 149 already_AddRefed<Promise> WritableStreamRTCFrameSink::WriteCallbackImpl( 150 JSContext* aCx, JS::Handle<JS::Value> aChunk, 151 WritableStreamDefaultController& aController, ErrorResult& aError) { 152 // Spec does not say to do this right now. Might be a spec bug, needs 153 // clarification. 154 // https://github.com/w3c/webrtc-encoded-transform/issues/191 155 if (NS_WARN_IF(!aChunk.isObject())) { 156 aError.ThrowTypeError( 157 "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an object"); 158 return nullptr; 159 } 160 161 // Lame. But, without a webidl base class, this is the only way. 162 RefPtr<RTCEncodedVideoFrame> video; 163 UNWRAP_OBJECT(RTCEncodedVideoFrame, &aChunk.toObject(), video); 164 RefPtr<RTCEncodedAudioFrame> audio; 165 UNWRAP_OBJECT(RTCEncodedAudioFrame, &aChunk.toObject(), audio); 166 167 RefPtr<RTCEncodedFrameBase> frame; 168 if (video) { 169 frame = video; 170 } else if (audio) { 171 frame = audio; 172 } 173 174 if (NS_WARN_IF(!frame)) { 175 aError.ThrowTypeError( 176 "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an " 177 "RTCEncodedAudioFrame or RTCEncodedVideoFrame"); 178 return nullptr; 179 } 180 181 return mTransformer->OnTransformedFrame(frame, aError); 182 } 183 184 // There is not presently an implementation of these for nsTHashMap :( 185 inline void ImplCycleCollectionUnlink( 186 RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap) { 187 for (auto& tableEntry : aMap) { 188 ImplCycleCollectionUnlink(*tableEntry.GetModifiableData()); 189 } 190 aMap.Clear(); 191 } 192 193 inline void ImplCycleCollectionTraverse( 194 nsCycleCollectionTraversalCallback& aCallback, 195 RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap, const char* aName, 196 uint32_t aFlags = 0) { 197 for (auto& tableEntry : aMap) { 198 ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(), 199 aName, aFlags); 200 } 201 } 202 203 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS( 204 RTCRtpScriptTransformer, 205 (mGlobal, mReadableSource, mReadable, mWritable, mWritableSink, 206 mKeyFrameRequestPromises, mGenerateKeyFramePromises), 207 (mOptions)) 208 209 NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpScriptTransformer) 210 NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpScriptTransformer) 211 212 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpScriptTransformer) 213 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 214 NS_INTERFACE_MAP_ENTRY(nsISupports) 215 NS_INTERFACE_MAP_END 216 217 RTCRtpScriptTransformer::RTCRtpScriptTransformer(nsIGlobalObject* aGlobal) 218 : mGlobal(aGlobal), 219 mReadableSource(new nsISupportsStreamSource), 220 mWritableSink(new WritableStreamRTCFrameSink(this)), 221 mOptions(JS::UndefinedHandleValue) { 222 mozilla::HoldJSObjects(this); 223 } 224 225 RTCRtpScriptTransformer::~RTCRtpScriptTransformer() { 226 mozilla::DropJSObjects(this); 227 } 228 229 nsresult RTCRtpScriptTransformer::Init(JSContext* aCx, 230 JS::Handle<JS::Value> aOptions, 231 WorkerPrivate* aWorkerPrivate, 232 FrameTransformerProxy* aProxy) { 233 ErrorResult rv; 234 RefPtr<nsIGlobalObject> global(mGlobal); 235 auto source = mReadableSource; 236 auto sink = mWritableSink; 237 238 // NOTE: We do not transfer these streams from mainthread, as the spec says, 239 // because there's no JS observable reason to. The spec is likely to change 240 // here, because it is overspecifying implementation details. 241 mReadable = ReadableStream::CreateNative(aCx, global, *source, Some(1.0), 242 nullptr, rv); 243 if (rv.Failed()) { 244 return rv.StealNSResult(); 245 } 246 mReadableSource->Init(mReadable); 247 248 // WritableStream::CreateNative takes a nsIGlobalObject&, but 249 // ReadableStream::CreateNative takes a nsIGlobalObject*? 250 mWritable = 251 WritableStream::CreateNative(aCx, *global, *sink, Nothing(), nullptr, rv); 252 if (rv.Failed()) { 253 return rv.StealNSResult(); 254 } 255 256 mOptions = aOptions; 257 mProxy = aProxy; 258 // This will return null if the worker is already shutting down. 259 // A call to ReleaseScriptTransformer will eventually result in a call to 260 // NotifyReleased. 261 mWorkerRef = StrongWorkerRef::Create( 262 aWorkerPrivate, "RTCRtpScriptTransformer", 263 [this, self = RefPtr(this)]() { mProxy->ReleaseScriptTransformer(); }); 264 if (mWorkerRef) { 265 mProxy->SetScriptTransformer(*this); 266 } 267 return NS_OK; 268 } 269 270 void RTCRtpScriptTransformer::NotifyReleased() { 271 RejectPendingPromises(); 272 mWorkerRef = nullptr; 273 mProxy = nullptr; 274 } 275 276 void RTCRtpScriptTransformer::RejectPendingPromises() { 277 for (const auto& promise : mKeyFrameRequestPromises) { 278 ErrorResult rv; 279 rv.ThrowInvalidStateError( 280 "RTCRtpScriptTransformer is not associated with a receiver"); 281 promise->MaybeReject(std::move(rv)); 282 } 283 mKeyFrameRequestPromises.Clear(); 284 285 // GenerateKeyFrame promises are indexed by rid 286 for (auto& ridAndPromises : mGenerateKeyFramePromises) { 287 for (const auto& promise : ridAndPromises.GetData()) { 288 ErrorResult rv; 289 rv.ThrowInvalidStateError( 290 "RTCRtpScriptTransformer is not associated with a sender"); 291 promise->MaybeReject(std::move(rv)); 292 } 293 } 294 mGenerateKeyFramePromises.Clear(); 295 } 296 297 void RTCRtpScriptTransformer::TransformFrame( 298 std::unique_ptr<webrtc::TransformableFrameInterface> aFrame) { 299 if (!mVideo.isSome()) { 300 // First frame. mProxy will know whether it's video or not by now. 301 mVideo = mProxy->IsVideo(); 302 MOZ_ASSERT(mVideo.isSome()); 303 } 304 305 RefPtr<RTCEncodedFrameBase> domFrame; 306 if (*mVideo) { 307 // If this is a send video keyframe, resolve any pending GenerateKeyFrame 308 // promises for its rid. 309 if (aFrame->GetDirection() == 310 webrtc::TransformableFrameInterface::Direction::kSender) { 311 auto* videoFrame = 312 static_cast<webrtc::TransformableVideoFrameInterface*>(aFrame.get()); 313 if (videoFrame->IsKeyFrame()) { 314 MOZ_ASSERT(videoFrame->Rid().has_value()); 315 ResolveGenerateKeyFramePromises( 316 videoFrame->Rid().value_or(std::string()), 317 videoFrame->GetTimestamp()); 318 if (videoFrame->Rid().has_value() && !videoFrame->Rid()->empty() && 319 videoFrame->Metadata().GetSimulcastIdx() == 0) { 320 ResolveGenerateKeyFramePromises("", videoFrame->GetTimestamp()); 321 } 322 } 323 } 324 domFrame = new RTCEncodedVideoFrame(mGlobal, std::move(aFrame), 325 ++mLastEnqueuedFrameCounter, this); 326 } else { 327 domFrame = new RTCEncodedAudioFrame(mGlobal, std::move(aFrame), 328 ++mLastEnqueuedFrameCounter, this); 329 } 330 mReadableSource->Enqueue(domFrame); 331 } 332 333 void RTCRtpScriptTransformer::GetOptions(JSContext* aCx, 334 JS::MutableHandle<JS::Value> aVal, 335 ErrorResult& aError) { 336 if (!ToJSValue(aCx, mOptions, aVal)) { 337 aError.NoteJSContextException(aCx); 338 } 339 } 340 341 already_AddRefed<Promise> RTCRtpScriptTransformer::GenerateKeyFrame( 342 const Optional<nsAString>& aRid) { 343 Maybe<std::string> utf8Rid; 344 if (aRid.WasPassed()) { 345 utf8Rid = Some(NS_ConvertUTF16toUTF8(aRid.Value()).get()); 346 std::string error; 347 if (!SdpRidAttributeList::CheckRidValidity(*utf8Rid, &error)) { 348 ErrorResult rv; 349 nsCString nsError(error.c_str()); 350 rv.ThrowNotAllowedError(nsError); 351 return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv); 352 } 353 } 354 355 nsCString key; 356 if (utf8Rid.isSome()) { 357 key.Assign(utf8Rid->data(), utf8Rid->size()); 358 } 359 360 nsTArray<RefPtr<Promise>>& promises = 361 mGenerateKeyFramePromises.LookupOrInsert(key); 362 if (!promises.Length()) { 363 // No pending keyframe generation request for this rid. Make one. 364 if (mProxy && !mProxy->GenerateKeyFrame(utf8Rid)) { 365 ErrorResult rv; 366 rv.ThrowInvalidStateError( 367 "RTCRtpScriptTransformer is not associated with a video sender"); 368 return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv); 369 } 370 } 371 RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); 372 promises.AppendElement(promise); 373 return promise.forget(); 374 } 375 376 void RTCRtpScriptTransformer::ResolveGenerateKeyFramePromises( 377 const std::string& aRid, uint64_t aTimestamp) { 378 nsCString key(aRid.data(), aRid.size()); 379 nsTArray<RefPtr<Promise>> promises; 380 mGenerateKeyFramePromises.Remove(key, &promises); 381 for (auto& promise : promises) { 382 promise->MaybeResolve(aTimestamp); 383 } 384 } 385 386 void RTCRtpScriptTransformer::GenerateKeyFrameError( 387 const Maybe<std::string>& aRid, const CopyableErrorResult& aResult) { 388 nsCString key; 389 if (aRid.isSome()) { 390 key.Assign(aRid->data(), aRid->size()); 391 } 392 nsTArray<RefPtr<Promise>> promises; 393 mGenerateKeyFramePromises.Remove(key, &promises); 394 for (auto& promise : promises) { 395 CopyableErrorResult rv(aResult); 396 promise->MaybeReject(std::move(rv)); 397 } 398 } 399 400 already_AddRefed<Promise> RTCRtpScriptTransformer::SendKeyFrameRequest() { 401 if (!mKeyFrameRequestPromises.Length()) { 402 if (mProxy && !mProxy->RequestKeyFrame()) { 403 ErrorResult rv; 404 rv.ThrowInvalidStateError( 405 "RTCRtpScriptTransformer is not associated with a video receiver"); 406 return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv); 407 } 408 } 409 RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); 410 mKeyFrameRequestPromises.AppendElement(promise); 411 return promise.forget(); 412 } 413 414 void RTCRtpScriptTransformer::KeyFrameRequestDone() { 415 auto promises = std::move(mKeyFrameRequestPromises); 416 for (const auto& promise : promises) { 417 promise->MaybeResolveWithUndefined(); 418 } 419 } 420 421 JSObject* RTCRtpScriptTransformer::WrapObject( 422 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 423 return RTCRtpScriptTransformer_Binding::Wrap(aCx, this, aGivenProto); 424 } 425 426 already_AddRefed<Promise> RTCRtpScriptTransformer::OnTransformedFrame( 427 RTCEncodedFrameBase* aFrame, ErrorResult& aError) { 428 // Spec says to skip frames that are out of order or have wrong owner 429 if (aFrame->GetCounter() > mLastReceivedFrameCounter && 430 aFrame->CheckOwner(this) && mProxy) { 431 mLastReceivedFrameCounter = aFrame->GetCounter(); 432 // also skip if frame has been detached (transferred away) 433 if (auto frame = aFrame->TakeFrame()) { 434 mProxy->OnTransformedFrame(std::move(frame)); 435 } 436 } 437 438 return Promise::CreateResolvedWithUndefined(GetParentObject(), aError); 439 } 440 441 } // namespace mozilla::dom 442 443 #undef LOGTAG