tor-browser

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

commit 992dd3e291884d283d7543e97f8319282f32fd95
parent 5d32ec5181a6d7aa1896cdf7bf3dab023739d00f
Author: Chun-Min Chang <chun.m.chang@gmail.com>
Date:   Sat, 20 Dec 2025 18:19:43 +0000

Bug 1674892 - Enable connecting AudioStreamTrack and MediaStreamAudioSourceNode in different graphs r=karlt

This patch removes the same-rate restriction between `AudioStreamTrack` and
`MediaStreamAudioSourceNote`. A track running at sample rate X can now feed a
node in a graph running at rate Y.

When the track and node live on different `MediaTrackGraph`s (e.g., due to
different rates), the implementation creates a cross-graph port, which has a
transmitter and receiver pair, to bridge them: The transmitter forwards the
track's audio to its paired receiver, and the receiver supplies data to the
`MediaStreamAudioSourceNode`.

The relay is shared among all consumers targeting the same destination graph and
is managed via a map keyed by graph. The port is created on first use and
destroyed when the last consuming node disconnects.

Differential Revision: https://phabricator.services.mozilla.com/D269914

Diffstat:
Mdom/media/AudioStreamTrack.cpp | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/AudioStreamTrack.h | 20++++++++++++++++++++
Mdom/media/CrossGraphPort.cpp | 12++++++++++++
Mdom/media/CrossGraphPort.h | 4----
Mdom/media/MediaStreamTrack.cpp | 2+-
Mdom/media/MediaStreamTrack.h | 2+-
Mdom/media/MediaTrackGraph.cpp | 1-
Mdom/media/webaudio/MediaStreamAudioSourceNode.cpp | 33+++++++++------------------------
Mdom/media/webaudio/MediaStreamAudioSourceNode.h | 4++--
9 files changed, 154 insertions(+), 33 deletions(-)

diff --git a/dom/media/AudioStreamTrack.cpp b/dom/media/AudioStreamTrack.cpp @@ -8,6 +8,9 @@ #include "MediaTrackGraph.h" #include "nsContentUtils.h" +extern mozilla::LazyLogModule gMediaStreamTrackLog; +#define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg) + namespace mozilla::dom { RefPtr<GenericPromise> AudioStreamTrack::AddAudioOutput( @@ -36,6 +39,85 @@ void AudioStreamTrack::SetAudioOutputVolume(void* aKey, float aVolume) { mTrack->SetAudioOutputVolume(aKey, aVolume); } +already_AddRefed<MediaInputPort> AudioStreamTrack::AddConsumerPort( + ProcessedMediaTrack* aTrack) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mTrack == Ended()); + + if (!mTrack || !aTrack || aTrack->IsDestroyed()) { + LOG(LogLevel::Warning, + ("AudioStreamTrack %p cannot forward contents: track ended or " + "data/destination track ended/destroyed", + this)); + return nullptr; + } + + MOZ_ASSERT(!mTrack->IsDestroyed()); + if (mTrack->Graph() == aTrack->Graph()) { + return ForwardTrackContentsTo(aTrack); + } + + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p forwarding cross-graph contents from track %p " + "(graph %p) to track %p (graph %p)", + this, mTrack.get(), mTrack->Graph(), aTrack, aTrack->Graph())); + + // Route audio from mTrack through a cross-graph transmitter and receiver to + // aTrack. + MediaTrackGraph* rcvrGraph = aTrack->Graph(); + + // Find existing connection for this graph + for (auto& conn : mCrossGraphs) { + if (conn.mPort->mReceiver->Graph() == rcvrGraph) { + conn.mRefCount++; + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p reusing cross-graph port " + "to graph %p (rate %u), refcount now %zu", + this, rcvrGraph, rcvrGraph->GraphRate(), conn.mRefCount)); + return aTrack->AllocateInputPort(conn.mPort->mReceiver); + } + } + + // Create new connection if none exists + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p creating cross-graph port to graph %p (rate %u)", + this, rcvrGraph, rcvrGraph->GraphRate())); + CrossGraphConnection* conn = mCrossGraphs.AppendElement( + CrossGraphConnection(CrossGraphPort::Connect(RefPtr{this}, rcvrGraph))); + return aTrack->AllocateInputPort(conn->mPort->mReceiver); +} + +void AudioStreamTrack::RemoveConsumerPort(MediaInputPort* aPort) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aPort) { + return; + } + + MediaTrackGraph* receiverGraph = aPort->Graph(); + + // Decrement refcount for this graph's connection and remove if it reaches 0 + for (size_t i = 0; i < mCrossGraphs.Length(); ++i) { + auto& conn = mCrossGraphs[i]; + if (conn.mPort->mReceiver->Graph() == receiverGraph) { + MOZ_ASSERT(conn.mRefCount > 0); + --conn.mRefCount; + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p decrementing cross-graph port refcount to " + "graph %p (rate %u), refcount now %zu", + this, receiverGraph, receiverGraph->GraphRate(), conn.mRefCount)); + if (conn.mRefCount == 0) { + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p removing cross-graph forwarding to graph %p " + "(rate %u)", + this, receiverGraph, receiverGraph->GraphRate())); + mCrossGraphs.UnorderedRemoveElementAt(i); + } + return; + } + } +} + void AudioStreamTrack::GetLabel(nsAString& aLabel, CallerType aCallerType) { MediaStreamTrack::GetLabel(aLabel, aCallerType); } @@ -44,4 +126,31 @@ already_AddRefed<MediaStreamTrack> AudioStreamTrack::Clone() { return MediaStreamTrack::CloneInternal<AudioStreamTrack>(); } +void AudioStreamTrack::SetReadyState(MediaStreamTrackState aState) { + MOZ_ASSERT(NS_IsMainThread()); + + // When transitioning from Live to Ended, mTrack will be destroyed. Since + // mTrack is the source for cross-graph data forwarding, keeping cross-graph + // ports is unnecessary. Clearing them here ensures all related connections + // are properly disconnected and prevents an assertion failure in + // CrossGraphTransmitters::ProcessInput due to a missing source. + // + // This state transition may occur in various situations, such as when the + // track is stopped by a user action, or when mTrack is ended during its + // ProcessInput (because its source has ended), which is then detected by + // MediaTrackGraph and ultimately notifies the ended-signal via MTGListener, + // reaching this point. + if (!mCrossGraphs.IsEmpty() && aState == MediaStreamTrackState::Ended) { + MOZ_ASSERT(!Ended()); + LOG(LogLevel::Verbose, + ("AudioStreamTrack %p ending, destroying %zu cross-graph ports", this, + mCrossGraphs.Length())); + mCrossGraphs.Clear(); + } + + MediaStreamTrack::SetReadyState(aState); +} + } // namespace mozilla::dom + +#undef LOG diff --git a/dom/media/AudioStreamTrack.h b/dom/media/AudioStreamTrack.h @@ -36,10 +36,30 @@ class AudioStreamTrack : public MediaStreamTrack { void RemoveAudioOutput(void* aKey); void SetAudioOutputVolume(void* aKey, float aVolume); + // Use AddConsumerPort instead of ForwardTrackContentsTo when possible, since + // it handles CrossGraphPort creation automatically. Must be balanced with a + // corresponding RemoveConsumerPort call. + already_AddRefed<MediaInputPort> AddConsumerPort(ProcessedMediaTrack* aTrack); + void RemoveConsumerPort(MediaInputPort* aPort); + // WebIDL void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); } void GetLabel(nsAString& aLabel, CallerType aCallerType) override; + + protected: + void SetReadyState(MediaStreamTrackState aState) override; + + private: + // Main thread only + struct CrossGraphConnection { + UniquePtr<CrossGraphPort> mPort; + size_t mRefCount; + + explicit CrossGraphConnection(UniquePtr<CrossGraphPort> aPort) + : mPort(std::move(aPort)), mRefCount(1) {} + }; + nsTArray<CrossGraphConnection> mCrossGraphs; }; } // namespace mozilla::dom diff --git a/dom/media/CrossGraphPort.cpp b/dom/media/CrossGraphPort.cpp @@ -36,11 +36,23 @@ UniquePtr<CrossGraphPort> CrossGraphPort::Connect( RefPtr<MediaInputPort> port = aStreamTrack->ForwardTrackContentsTo(transmitter); + LOG(LogLevel::Verbose, + ("Created CrossGraphPort transmitter %p (rate %u, from AudioStreamTrack " + "%p) and receiver %p (rate %u) between graphs %p and %p", + transmitter.get(), transmitter->mSampleRate, aStreamTrack.get(), + receiver.get(), receiver->mSampleRate, aStreamTrack->Graph(), + aPartnerGraph)); + return WrapUnique(new CrossGraphPort(std::move(port), std::move(transmitter), std::move(receiver))); } CrossGraphPort::~CrossGraphPort() { + LOG(LogLevel::Verbose, + ("Destroying CrossGraphPort transmitter %p (rate %u) and receiver %p " + "(rate %u) between graphs %p and %p", + mTransmitter.get(), mTransmitter->mSampleRate, mReceiver.get(), + mReceiver->mSampleRate, mTransmitter->Graph(), mReceiver->Graph())); mTransmitter->Destroy(); mReceiver->Destroy(); mTransmitterPort->Destroy(); diff --git a/dom/media/CrossGraphPort.h b/dom/media/CrossGraphPort.h @@ -23,10 +23,6 @@ class AudioStreamTrack; namespace mozilla { /** - * CrossGraphTransmitter and CrossGraphPort are currently unused, but intended - * for connecting MediaTracks of different MediaTrackGraphs with different - * sample rates or clock sources for bug 1674892. - * * Create with MediaTrackGraph::CreateCrossGraphTransmitter() */ class CrossGraphTransmitter : public ProcessedMediaTrack { diff --git a/dom/media/MediaStreamTrack.cpp b/dom/media/MediaStreamTrack.cpp @@ -18,7 +18,7 @@ #include "nsServiceManagerUtils.h" #include "systemservices/MediaUtils.h" -static mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack"); +mozilla::LazyLogModule gMediaStreamTrackLog("MediaStreamTrack"); #define LOG(type, msg) MOZ_LOG(gMediaStreamTrackLog, type, msg) using namespace mozilla::media; diff --git a/dom/media/MediaStreamTrack.h b/dom/media/MediaStreamTrack.h @@ -592,7 +592,7 @@ class MediaStreamTrack : public DOMEventTargetHelper, public SupportsWeakPtr { * Forces the ready state to a particular value, for instance when we're * cloning an already ended track. */ - void SetReadyState(MediaStreamTrackState aState); + virtual void SetReadyState(MediaStreamTrackState aState); /** * Notified by the MediaTrackGraph, through our owning MediaStream on the diff --git a/dom/media/MediaTrackGraph.cpp b/dom/media/MediaTrackGraph.cpp @@ -3267,7 +3267,6 @@ MediaTrackGraphImpl* MediaInputPort::GraphImpl() const { } MediaTrackGraph* MediaInputPort::Graph() const { - mGraph->AssertOnGraphThreadOrNotRunning(); return mGraph; } diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp @@ -93,34 +93,21 @@ void MediaStreamAudioSourceNode::Destroy() { MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode() { Destroy(); } -void MediaStreamAudioSourceNode::AttachToTrack( - const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aRv) { +void MediaStreamAudioSourceNode::AttachToTrack(AudioStreamTrack* aTrack) { + MOZ_ASSERT(aTrack); MOZ_ASSERT(!mInputTrack); - MOZ_ASSERT(aTrack->AsAudioStreamTrack()); MOZ_DIAGNOSTIC_ASSERT(!aTrack->Ended()); if (!mTrack) { return; } - if (NS_WARN_IF(Context()->Graph() != aTrack->Graph())) { - nsGlobalWindowInner* pWindow = Context()->GetOwnerWindow(); - Document* document = pWindow ? pWindow->GetExtantDoc() : nullptr; - nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns, - document, nsContentUtils::eDOM_PROPERTIES, - "MediaStreamAudioSourceNodeDifferentRate"); - // This is not a spec-required exception, just a limitation of our - // implementation. - aRv.ThrowNotSupportedError( - "Connecting AudioNodes from AudioContexts with different sample-rate " - "is currently not supported."); - return; - } - mInputTrack = aTrack; ProcessedMediaTrack* outputTrack = static_cast<ProcessedMediaTrack*>(mTrack.get()); - mInputPort = mInputTrack->ForwardTrackContentsTo(outputTrack); + mInputPort = aTrack->AddConsumerPort(outputTrack); + MOZ_DIAGNOSTIC_ASSERT(mInputPort); + PrincipalChanged(mInputTrack); // trigger enabling/disabling of the connector mInputTrack->AddPrincipalChangeObserver(this); MarkActive(); @@ -129,6 +116,7 @@ void MediaStreamAudioSourceNode::AttachToTrack( void MediaStreamAudioSourceNode::DetachFromTrack() { if (mInputTrack) { mInputTrack->RemovePrincipalChangeObserver(this); + mInputTrack->RemoveConsumerPort(mInputPort); mInputTrack = nullptr; } if (mInputPort) { @@ -168,7 +156,7 @@ void MediaStreamAudioSourceNode::AttachToRightTrack( } if (!track->Ended()) { - AttachToTrack(track, aRv); + AttachToTrack(track); } return; } @@ -190,7 +178,7 @@ void MediaStreamAudioSourceNode::NotifyTrackAdded( return; } - AttachToTrack(aTrack, IgnoreErrors()); + AttachToTrack(aTrack->AsAudioStreamTrack()); } void MediaStreamAudioSourceNode::NotifyTrackRemoved( @@ -269,10 +257,7 @@ size_t MediaStreamAudioSourceNode::SizeOfIncludingThis( } void MediaStreamAudioSourceNode::DestroyMediaTrack() { - if (mInputPort) { - mInputPort->Destroy(); - mInputPort = nullptr; - } + DetachFromTrack(); AudioNode::DestroyMediaTrack(); } diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.h b/dom/media/webaudio/MediaStreamAudioSourceNode.h @@ -75,7 +75,7 @@ class MediaStreamAudioSourceNode size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; // Attaches to aTrack so that its audio content will be used as input. - void AttachToTrack(const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aRv); + void AttachToTrack(AudioStreamTrack* aTrack); // Detaches from the currently attached track if there is one. void DetachFromTrack(); @@ -137,7 +137,7 @@ class MediaStreamAudioSourceNode RefPtr<DOMMediaStream> mInputStream; // On construction we set this to the first audio track of mInputStream. - RefPtr<MediaStreamTrack> mInputTrack; + RefPtr<AudioStreamTrack> mInputTrack; RefPtr<TrackListener> mListener; };