Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/webrtc/jsapi/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 15 kB image not shown  

Quelle  RTCRtpScriptTransformer.cpp

  Sprache: C
 

/* 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 https://mozilla.org/MPL/2.0/. */


#include "RTCRtpScriptTransformer.h"

#include <stdint.h>

#include <limits>
#include <memory>
#include <string>
#include <utility>

#include "ErrorList.h"
#include "api/frame_transformer_interface.h"
#include "js/CallArgs.h"
#include "js/RootingAPI.h"
#include "js/Value.h"
#include "libwebrtcglue/FrameTransformerProxy.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/Likely.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PrototypeList.h"
#include "mozilla/dom/RTCEncodedAudioFrame.h"
#include "mozilla/dom/RTCEncodedVideoFrame.h"
#include "mozilla/dom/RTCRtpScriptTransformerBinding.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamControllerBase.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h"
#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/WritableStreamDefaultController.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCycleCollectionTraversalCallback.h"
#include "nsDebug.h"
#include "nsIGlobalObject.h"
#include "nsISupports.h"
#include "nsLiteralString.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
#include "sdp/SdpAttribute.h"  // CheckRidValidity

namespace mozilla::dom {

LazyLogModule gScriptTransformerLog("RTCRtpScriptTransformer");

NS_IMPL_CYCLE_COLLECTION_INHERITED(nsISupportsStreamSource,
                                   UnderlyingSourceAlgorithmsWrapper, mStream,
                                   mThingQueuedPromise, mQueue)
NS_IMPL_ADDREF_INHERITED(nsISupportsStreamSource,
                         UnderlyingSourceAlgorithmsWrapper)
NS_IMPL_RELEASE_INHERITED(nsISupportsStreamSource,
                          UnderlyingSourceAlgorithmsWrapper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsISupportsStreamSource)
NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper)

nsISupportsStreamSource::nsISupportsStreamSource() = default;

nsISupportsStreamSource::~nsISupportsStreamSource() = default;

void nsISupportsStreamSource::Init(ReadableStream* aStream) {
  mStream = aStream;
}

void nsISupportsStreamSource::Enqueue(nsISupports* aThing) {
  if (!mThingQueuedPromise) {
    mQueue.AppendElement(aThing);
    return;
  }

  // Maybe put a limit here? Or at least some sort of logging if this gets
  // unreasonably long?
  AutoJSAPI jsapi;
  if (NS_WARN_IF(!jsapi.Init(mStream->GetParentObject()))) {
    return;
  }

  EnqueueToStream(jsapi.cx(), aThing);
  mThingQueuedPromise->MaybeResolveWithUndefined();
  mThingQueuedPromise = nullptr;
}

already_AddRefed<Promise> nsISupportsStreamSource::PullCallbackImpl(
    JSContext* aCx, ReadableStreamControllerBase& aController,
    ErrorResult& aRv) {
  if (!mQueue.IsEmpty()) {
    EnqueueOneThingFromQueue(aCx);
    return nullptr;
  }

  RefPtr<nsISupportsStreamSource> self(this);
  mThingQueuedPromise = Promise::CreateInfallible(mStream->GetParentObject());
  return do_AddRef(mThingQueuedPromise);
}

void nsISupportsStreamSource::EnqueueToStream(JSContext* aCx,
                                              nsISupports* aThing) {
  JS::Rooted<JS::Value> jsThing(aCx);
  if (NS_WARN_IF(MOZ_UNLIKELY(!ToJSValue(aCx, *aThing, &jsThing)))) {
    // Do we want to add error handling for this?
    return;
  }
  IgnoredErrorResult rv;
  // EnqueueNative is CAN_RUN_SCRIPT. Need a strong-ref temporary.
  auto stream = mStream;
  stream->EnqueueNative(aCx, jsThing, rv);
}

void nsISupportsStreamSource::EnqueueOneThingFromQueue(JSContext* aCx) {
  if (!mQueue.IsEmpty()) {
    RefPtr<nsISupports> thing = mQueue[0];
    mQueue.RemoveElementAt(0);
    EnqueueToStream(aCx, thing);
  }
}

NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableStreamRTCFrameSink,
                                   UnderlyingSinkAlgorithmsWrapper,
                                   mTransformer)
NS_IMPL_ADDREF_INHERITED(WritableStreamRTCFrameSink,
                         UnderlyingSinkAlgorithmsWrapper)
NS_IMPL_RELEASE_INHERITED(WritableStreamRTCFrameSink,
                          UnderlyingSinkAlgorithmsWrapper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamRTCFrameSink)
NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsWrapper)

WritableStreamRTCFrameSink::WritableStreamRTCFrameSink(
    RTCRtpScriptTransformer* aTransformer)
    : mTransformer(aTransformer) {}

WritableStreamRTCFrameSink::~WritableStreamRTCFrameSink() = default;

already_AddRefed<Promise> WritableStreamRTCFrameSink::WriteCallbackImpl(
    JSContext* aCx, JS::Handle<JS::Value> aChunk,
    WritableStreamDefaultController& aController, ErrorResult& aError) {
  // Spec does not say to do this right now. Might be a spec bug, needs
  // clarification.
  // https://github.com/w3c/webrtc-encoded-transform/issues/191
  if (NS_WARN_IF(!aChunk.isObject())) {
    aError.ThrowTypeError(
        "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an object");
    return nullptr;
  }

  // Lame. But, without a webidl base class, this is the only way.
  RefPtr<RTCEncodedVideoFrame> video;
  UNWRAP_OBJECT(RTCEncodedVideoFrame, &aChunk.toObject(), video);
  RefPtr<RTCEncodedAudioFrame> audio;
  UNWRAP_OBJECT(RTCEncodedAudioFrame, &aChunk.toObject(), audio);

  RefPtr<RTCEncodedFrameBase> frame;
  if (video) {
    frame = video;
  } else if (audio) {
    frame = audio;
  }

  if (NS_WARN_IF(!frame)) {
    aError.ThrowTypeError(
        "Wrong type for RTCRtpScriptTransformer.[[writeable]]: Not an "
        "RTCEncodedAudioFrame or RTCEncodedVideoFrame");
    return nullptr;
  }

  return mTransformer->OnTransformedFrame(frame, aError);
}

// There is not presently an implementation of these for nsTHashMap :(
inline void ImplCycleCollectionUnlink(
    RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap) {
  for (auto& tableEntry : aMap) {
    ImplCycleCollectionUnlink(*tableEntry.GetModifiableData());
  }
  aMap.Clear();
}

inline void ImplCycleCollectionTraverse(
    nsCycleCollectionTraversalCallback& aCallback,
    RTCRtpScriptTransformer::GenerateKeyFramePromises& aMap, const char* aName,
    uint32_t aFlags = 0) {
  for (auto& tableEntry : aMap) {
    ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(),
                                aName, aFlags);
  }
}

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
    RTCRtpScriptTransformer,
    (mGlobal, mReadableSource, mReadable, mWritable, mWritableSink,
     mKeyFrameRequestPromises, mGenerateKeyFramePromises),
    (mOptions))

NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpScriptTransformer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpScriptTransformer)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpScriptTransformer)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

RTCRtpScriptTransformer::RTCRtpScriptTransformer(nsIGlobalObject* aGlobal)
    : mGlobal(aGlobal),
      mReadableSource(new nsISupportsStreamSource),
      mWritableSink(new WritableStreamRTCFrameSink(this)),
      mOptions(JS::UndefinedHandleValue) {
  mozilla::HoldJSObjects(this);
}

RTCRtpScriptTransformer::~RTCRtpScriptTransformer() {
  mozilla::DropJSObjects(this);
}

nsresult RTCRtpScriptTransformer::Init(JSContext* aCx,
                                       JS::Handle<JS::Value> aOptions,
                                       WorkerPrivate* aWorkerPrivate,
                                       FrameTransformerProxy* aProxy) {
  ErrorResult rv;
  RefPtr<nsIGlobalObject> global(mGlobal);
  auto source = mReadableSource;
  auto sink = mWritableSink;

  // NOTE: We do not transfer these streams from mainthread, as the spec says,
  // because there's no JS observable reason to. The spec is likely to change
  // here, because it is overspecifying implementation details.
  mReadable = ReadableStream::CreateNative(aCx, global, *source, Some(1.0),
                                           nullptr, rv);
  if (rv.Failed()) {
    return rv.StealNSResult();
  }
  mReadableSource->Init(mReadable);

  // WritableStream::CreateNative takes a nsIGlobalObject&, but
  // ReadableStream::CreateNative takes a nsIGlobalObject*?
  mWritable =
      WritableStream::CreateNative(aCx, *global, *sink, Nothing(), nullptr, rv);
  if (rv.Failed()) {
    return rv.StealNSResult();
  }

  mOptions = aOptions;
  mProxy = aProxy;
  // This will return null if the worker is already shutting down.
  // A call to ReleaseScriptTransformer will eventually result in a call to
  // NotifyReleased.
  mWorkerRef = StrongWorkerRef::Create(
      aWorkerPrivate, "RTCRtpScriptTransformer",
      [this, self = RefPtr(this)]() { mProxy->ReleaseScriptTransformer(); });
  if (mWorkerRef) {
    mProxy->SetScriptTransformer(*this);
  }
  return NS_OK;
}

void RTCRtpScriptTransformer::NotifyReleased() {
  RejectPendingPromises();
  mWorkerRef = nullptr;
  mProxy = nullptr;
}

void RTCRtpScriptTransformer::RejectPendingPromises() {
  for (const auto& promise : mKeyFrameRequestPromises) {
    ErrorResult rv;
    rv.ThrowInvalidStateError(
        "RTCRtpScriptTransformer is not associated with a receiver");
    promise->MaybeReject(std::move(rv));
  }
  mKeyFrameRequestPromises.Clear();

  // GenerateKeyFrame promises are indexed by rid
  for (auto& ridAndPromises : mGenerateKeyFramePromises) {
    for (const auto& promise : ridAndPromises.GetData()) {
      ErrorResult rv;
      rv.ThrowInvalidStateError(
          "RTCRtpScriptTransformer is not associated with a sender");
      promise->MaybeReject(std::move(rv));
    }
  }
  mGenerateKeyFramePromises.Clear();
}

void RTCRtpScriptTransformer::TransformFrame(
    std::unique_ptr<webrtc::TransformableFrameInterface> aFrame) {
  if (!mVideo.isSome()) {
    // First frame. mProxy will know whether it's video or not by now.
    mVideo = mProxy->IsVideo();
    MOZ_ASSERT(mVideo.isSome());
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);
    mTimestampMaker.emplace(RTCStatsTimestampMaker::Create(*workerPrivate));
  }

  RefPtr<RTCEncodedFrameBase> domFrame;
  if (*mVideo) {
    // If this is a send video keyframe, resolve any pending GenerateKeyFrame
    // promises for its rid.
    if (aFrame->GetDirection() ==
        webrtc::TransformableFrameInterface::Direction::kSender) {
      auto* videoFrame =
          static_cast<webrtc::TransformableVideoFrameInterface*>(aFrame.get());
      if (videoFrame->IsKeyFrame()) {
        MOZ_ASSERT(videoFrame->Rid().has_value());
        ResolveGenerateKeyFramePromises(
            videoFrame->Rid().value_or(std::string()),
            videoFrame->GetTimestamp());
        if (videoFrame->Rid().has_value() && !videoFrame->Rid()->empty() &&
            videoFrame->Metadata().GetSimulcastIdx() == 0) {
          ResolveGenerateKeyFramePromises("", videoFrame->GetTimestamp());
        }
      }
    }
    domFrame = new RTCEncodedVideoFrame(mGlobal, std::move(aFrame),
                                        ++mLastEnqueuedFrameCounter, this,
                                        mTimestampMaker);
  } else {
    domFrame = new RTCEncodedAudioFrame(mGlobal, std::move(aFrame),
                                        ++mLastEnqueuedFrameCounter, this,
                                        mTimestampMaker);
  }
  mReadableSource->Enqueue(domFrame);
}

void RTCRtpScriptTransformer::GetOptions(JSContext* aCx,
                                         JS::MutableHandle<JS::Value> aVal,
                                         ErrorResult& aError) {
  if (!ToJSValue(aCx, mOptions, aVal)) {
    aError.NoteJSContextException(aCx);
  }
}

already_AddRefed<Promise> RTCRtpScriptTransformer::GenerateKeyFrame(
    const Optional<nsAString>& aRid) {
  Maybe<std::string> utf8Rid;
  if (aRid.WasPassed()) {
    utf8Rid = Some(NS_ConvertUTF16toUTF8(aRid.Value()).get());
    std::string error;
    if (!SdpRidAttributeList::CheckRidValidity(*utf8Rid, &error)) {
      ErrorResult rv;
      nsCString nsError(error.c_str());
      rv.ThrowNotAllowedError(nsError);
      return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
    }
  }

  nsCString key;
  if (utf8Rid.isSome()) {
    key.Assign(utf8Rid->data(), utf8Rid->size());
  }

  nsTArray<RefPtr<Promise>>& promises =
      mGenerateKeyFramePromises.LookupOrInsert(key);
  if (!promises.Length()) {
    // No pending keyframe generation request for this rid. Make one.
    if (mProxy && !mProxy->GenerateKeyFrame(utf8Rid)) {
      ErrorResult rv;
      rv.ThrowInvalidStateError(
          "RTCRtpScriptTransformer is not associated with a video sender");
      return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
    }
  }
  RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
  promises.AppendElement(promise);
  return promise.forget();
}

void RTCRtpScriptTransformer::ResolveGenerateKeyFramePromises(
    const std::string& aRid, uint64_t aTimestamp) {
  nsCString key(aRid.data(), aRid.size());
  nsTArray<RefPtr<Promise>> promises;
  mGenerateKeyFramePromises.Remove(key, &promises);
  for (auto& promise : promises) {
    promise->MaybeResolve(aTimestamp);
  }
}

void RTCRtpScriptTransformer::GenerateKeyFrameError(
    const Maybe<std::string>& aRid, const CopyableErrorResult& aResult) {
  nsCString key;
  if (aRid.isSome()) {
    key.Assign(aRid->data(), aRid->size());
  }
  nsTArray<RefPtr<Promise>> promises;
  mGenerateKeyFramePromises.Remove(key, &promises);
  for (auto& promise : promises) {
    CopyableErrorResult rv(aResult);
    promise->MaybeReject(std::move(rv));
  }
}

already_AddRefed<Promise> RTCRtpScriptTransformer::SendKeyFrameRequest() {
  if (!mKeyFrameRequestPromises.Length()) {
    if (mProxy && !mProxy->RequestKeyFrame()) {
      ErrorResult rv;
      rv.ThrowInvalidStateError(
          "RTCRtpScriptTransformer is not associated with a video receiver");
      return Promise::CreateRejectedWithErrorResult(GetParentObject(), rv);
    }
  }
  RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
  mKeyFrameRequestPromises.AppendElement(promise);
  return promise.forget();
}

void RTCRtpScriptTransformer::KeyFrameRequestDone() {
  auto promises = std::move(mKeyFrameRequestPromises);
  for (const auto& promise : promises) {
    promise->MaybeResolveWithUndefined();
  }
}

JSObject* RTCRtpScriptTransformer::WrapObject(
    JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
  return RTCRtpScriptTransformer_Binding::Wrap(aCx, this, aGivenProto);
}

already_AddRefed<Promise> RTCRtpScriptTransformer::OnTransformedFrame(
    RTCEncodedFrameBase* aFrame, ErrorResult& aError) {
  // Spec says to skip frames that are out of order or have wrong owner
  // We also skip frames that are unreasonably large
  if (aFrame->GetCounter() > mLastReceivedFrameCounter &&
      aFrame->CheckOwner(this) && mProxy &&
      aFrame->Size() <= std::numeric_limits<int>::max() / 4) {
    mLastReceivedFrameCounter = aFrame->GetCounter();
    // also skip if frame has been detached (transferred away)
    if (auto frame = aFrame->TakeFrame()) {
      mProxy->OnTransformedFrame(std::move(frame));
    }
  }

  return Promise::CreateResolvedWithUndefined(GetParentObject(), aError);
}

}  // namespace mozilla::dom

#undef LOGTAG

Messung V0.5 in Prozent
C=89 H=95 G=91

¤ Dauer der Verarbeitung: 0.5 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.