Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStarted() r=pehrsons

The aTrack parameter is no longer required because, as of
https://phabricator.services.mozilla.com/D198231, MediaStreamRenderer no
longer needs the promise to be rejected when the track is destroyed.

Instead the wait for the device to start is terminated when no outputs to the
specified device remain.

NotifyWhenDeviceStarted() is removed from some gtests that don't have any
track outputs.  They use WaitFor(cubeb->StreamInitEvent()) to wait for a
device anyway and don't need to know precisely when the graph switches from
the fallback driver to the callback.  If a test does need this precise timing
it can use NotifyWhenDeviceStarted() if MediaTrack::AddAudioOutput() is used.

Differential Revision: https://phabricator.services.mozilla.com/D198232
This commit is contained in:
Karl Tomlinson 2024-01-16 09:14:07 +00:00
Родитель e5f139036a
Коммит 8caf558610
6 изменённых файлов: 81 добавлений и 57 удалений

Просмотреть файл

@ -859,11 +859,12 @@ class HTMLMediaElement::MediaStreamRenderer
MOZ_ASSERT(!mSetAudioDevicePromise.IsEmpty());
mDeviceStartedRequest.Complete();
// The AudioStreamTrack::AddAudioOutput() promise is rejected
// either when the track ends or the graph is force shutdown.
// Rejection is treated in the same way as resolution for
// consistency with the synchronous resolution when
// AddAudioOutput() is called on a track that has already
// ended.
// either when the graph no longer needs the device, in which
// case this handler would have already been disconnected, or
// the graph is force shutdown.
// mSetAudioDevicePromise is resolved regardless of whether
// the AddAudioOutput() promises resolve or reject because
// the underlying device has been changed.
mSetAudioDevicePromise.Resolve(true, __func__);
})
->Track(mDeviceStartedRequest);

Просмотреть файл

@ -90,7 +90,8 @@ CrossGraphPort::~CrossGraphPort() {
RefPtr<GenericPromise> CrossGraphPort::EnsureConnected() {
// The primary graph is already working check the partner (receiver's) graph.
return mReceiver->Graph()->NotifyWhenDeviceStarted(mReceiver.get());
return mReceiver->Graph()->NotifyWhenDeviceStarted(
mReceiver->Graph()->PrimaryOutputDeviceID());
}
/** CrossGraphTransmitter **/

Просмотреть файл

@ -3760,33 +3760,48 @@ void MediaTrackGraphImpl::RemoveTrack(MediaTrack* aTrack) {
}
}
auto MediaTrackGraph::NotifyWhenDeviceStarted(MediaTrack* aTrack)
auto MediaTrackGraphImpl::NotifyWhenDeviceStarted(AudioDeviceID aDeviceID)
-> RefPtr<GraphStartedPromise> {
MOZ_ASSERT(NS_IsMainThread());
size_t index = mOutputDeviceRefCnts.IndexOf(aDeviceID);
if (index == decltype(mOutputDeviceRefCnts)::NoIndex) {
return GraphStartedPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
}
MozPromiseHolder<GraphStartedPromise> h;
RefPtr<GraphStartedPromise> p = h.Ensure(__func__);
aTrack->GraphImpl()->NotifyWhenGraphStarted(aTrack, std::move(h));
if (CrossGraphReceiver* receiver = mOutputDeviceRefCnts[index].mReceiver) {
receiver->GraphImpl()->NotifyWhenPrimaryDeviceStarted(std::move(h));
return p;
}
// aSink corresponds to the primary audio output device of this graph.
NotifyWhenPrimaryDeviceStarted(std::move(h));
return p;
}
void MediaTrackGraphImpl::NotifyWhenGraphStarted(
RefPtr<MediaTrack> aTrack,
void MediaTrackGraphImpl::NotifyWhenPrimaryDeviceStarted(
MozPromiseHolder<GraphStartedPromise>&& aHolder) {
MOZ_ASSERT(NS_IsMainThread());
if (aTrack->IsDestroyed()) {
if (mOutputDeviceRefCnts[0].mRefCnt == 0) {
// There are no track outputs that require the device, so the creator of
// this promise no longer needs to know when the graph is running. Don't
// keep the graph alive with another message.
aHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__);
return;
}
QueueControlOrShutdownMessage(
[self = RefPtr{this}, this, track = std::move(aTrack),
[self = RefPtr{this}, this,
holder = std::move(aHolder)](IsInShutdown aInShutdown) mutable {
if (aInShutdown == IsInShutdown::Yes) {
holder.Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
return;
}
TRACE("MTG::GraphStartedNotificationControlMessage ControlMessage");
TRACE("MTG::NotifyWhenPrimaryDeviceStarted ControlMessage");
// This runs on the graph thread, so when this runs, and the current
// driver is an AudioCallbackDriver, we know the audio hardware is
// started. If not, we are going to switch soon, keep reposting this
@ -3796,18 +3811,17 @@ void MediaTrackGraphImpl::NotifyWhenGraphStarted(
!CurrentDriver()->AsAudioCallbackDriver()->OnFallback()) {
// Avoid Resolve's locking on the graph thread by doing it on main.
Dispatch(NS_NewRunnableFunction(
"MediaTrackGraphImpl::NotifyWhenGraphStarted::Resolver",
"MediaTrackGraphImpl::NotifyWhenPrimaryDeviceStarted::Resolver",
[holder = std::move(holder)]() mutable {
holder.Resolve(true, __func__);
}));
} else {
DispatchToMainThreadStableState(
NewRunnableMethod<
StoreCopyPassByRRef<RefPtr<MediaTrack>>,
StoreCopyPassByRRef<MozPromiseHolder<GraphStartedPromise>>>(
"MediaTrackGraphImpl::NotifyWhenGraphStarted", this,
&MediaTrackGraphImpl::NotifyWhenGraphStarted,
std::move(track), std::move(holder)));
"MediaTrackGraphImpl::NotifyWhenPrimaryDeviceStarted", this,
&MediaTrackGraphImpl::NotifyWhenPrimaryDeviceStarted,
std::move(holder)));
}
});
}

Просмотреть файл

@ -1123,15 +1123,17 @@ class MediaTrackGraph {
void AddTrack(MediaTrack* aTrack);
/* From the main thread, ask the MTG to resolve the returned promise when
* the device has started.
* The promise is rejected with NS_ERROR_NOT_AVAILABLE if aTrack
* is destroyed, or NS_ERROR_ILLEGAL_DURING_SHUTDOWN if the graph is shut
* down, before the promise could be resolved.
* (Audio is initially processed in the FallbackDriver's thread while the
* device is starting up.)
* the device specified has started.
* A null aDeviceID indicates the default audio output device.
* The promise is rejected with NS_ERROR_INVALID_ARG if aSink does not
* correspond to any output devices used by the graph, or
* NS_ERROR_NOT_AVAILABLE if outputs to the device are removed or
* NS_ERROR_ILLEGAL_DURING_SHUTDOWN if the graph is force shut down
* before the promise could be resolved.
*/
using GraphStartedPromise = GenericPromise;
RefPtr<GraphStartedPromise> NotifyWhenDeviceStarted(MediaTrack* aTrack);
virtual RefPtr<GraphStartedPromise> NotifyWhenDeviceStarted(
CubebUtils::AudioDeviceID aDeviceID) = 0;
/* From the main thread, suspend, resume or close an AudioContext. Calls
* are not counted. Even Resume calls can be more frequent than Suspend

Просмотреть файл

@ -382,12 +382,27 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
*/
void RunMessageAfterProcessing(UniquePtr<ControlMessageInterface> aMessage);
/* From the main thread, ask the MTG to resolve the returned promise when
* the device specified has started.
* A null aDeviceID indicates the default audio output device.
* The promise is rejected with NS_ERROR_INVALID_ARG if aSink does not
* correspond to any output devices used by the graph, or
* NS_ERROR_NOT_AVAILABLE if outputs to the device are removed or
* NS_ERROR_ILLEGAL_DURING_SHUTDOWN if the graph is force shut down
* before the promise could be resolved.
*/
using GraphStartedPromise = GenericPromise;
RefPtr<GraphStartedPromise> NotifyWhenDeviceStarted(
CubebUtils::AudioDeviceID aDeviceID) override;
/**
* Resolve the GraphStartedPromise when the driver has started processing on
* the audio thread after the device has started.
* (Audio is initially processed in the FallbackDriver's thread while the
* device is starting up.)
*/
void NotifyWhenGraphStarted(RefPtr<MediaTrack> aTrack,
MozPromiseHolder<GraphStartedPromise>&& aHolder);
void NotifyWhenPrimaryDeviceStarted(
MozPromiseHolder<GraphStartedPromise>&& aHolder);
/**
* Apply an AudioContext operation (suspend/resume/close), on the graph

Просмотреть файл

@ -273,7 +273,7 @@ TEST(TestAudioTrackGraph, NotifyDeviceStarted)
// graph from the global hash table and let it shutdown.
dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO);
return graph->NotifyWhenDeviceStarted(dummySource);
return graph->NotifyWhenDeviceStarted(nullptr);
}));
{
@ -303,16 +303,13 @@ TEST(TestAudioTrackGraph, NonNativeInputTrackStartAndStop)
// Add a NonNativeInputTrack to graph, making graph create an output-only
// AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
RefPtr<NonNativeInputTrack> track;
auto started = Invoke([&] {
DispatchFunction([&] {
track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
PRINCIPAL_HANDLE_NONE);
graph->AddTrack(track);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_FALSE(driverStream->mHasInput);
EXPECT_TRUE(driverStream->mHasOutput);
@ -477,16 +474,13 @@ TEST(TestAudioTrackGraph, NonNativeInputTrackErrorCallback)
// Add a NonNativeInputTrack to graph, making graph create an output-only
// AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
RefPtr<NonNativeInputTrack> track;
auto started = Invoke([&] {
DispatchFunction([&] {
track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
PRINCIPAL_HANDLE_NONE);
graph->AddTrack(track);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_FALSE(driverStream->mHasInput);
EXPECT_TRUE(driverStream->mHasOutput);
@ -629,7 +623,7 @@ TEST(TestAudioTrackGraph, DeviceChangedCallback)
EXPECT_TRUE(track1->ConnectToNativeDevice());
EXPECT_FALSE(track1->ConnectToNonNativeDevice());
auto started =
Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(track1); });
Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
EXPECT_TRUE(stream1->mHasOutput);
@ -861,7 +855,7 @@ TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged)
EXPECT_TRUE(track1->ConnectToNativeDevice());
EXPECT_FALSE(track1->ConnectToNonNativeDevice());
auto started =
Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(track1); });
Invoke([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
nativeStream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nativeStream->mHasInput);
EXPECT_TRUE(nativeStream->mHasOutput);
@ -1071,7 +1065,8 @@ TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started = Invoke([&] { return graph->NotifyWhenDeviceStarted(track1); });
auto started =
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
@ -1190,7 +1185,7 @@ TEST(TestAudioTrackGraph, ErrorCallback)
PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(processingTrack->DeviceId().value(), deviceId);
processingTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
return graph->NotifyWhenDeviceStarted(processingTrack);
return graph->NotifyWhenDeviceStarted(nullptr);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
@ -1260,7 +1255,7 @@ TEST(TestAudioTrackGraph, AudioProcessingTrack)
// Device id does not matter. Ignore.
processingTrack->ConnectDeviceInput(deviceId, listener,
PRINCIPAL_HANDLE_NONE);
return graph->NotifyWhenDeviceStarted(processingTrack);
return graph->NotifyWhenDeviceStarted(nullptr);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
@ -1347,7 +1342,7 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
MakeUnique<StartInputProcessing>(processingTrack, listener));
processingTrack->ConnectDeviceInput(deviceId, listener,
PRINCIPAL_HANDLE_NONE);
return graph->NotifyWhenDeviceStarted(processingTrack);
return graph->NotifyWhenDeviceStarted(nullptr);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
@ -1381,7 +1376,7 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_FALSE(stream->mHasInput);
Unused << WaitFor(
Invoke([&] { return graph->NotifyWhenDeviceStarted(processingTrack); }));
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
// Output-only. Wait for another second before unmuting.
DispatchFunction([&] {
@ -1406,7 +1401,7 @@ TEST(TestAudioTrackGraph, ReConnectDeviceInput)
stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
Unused << WaitFor(
Invoke([&] { return graph->NotifyWhenDeviceStarted(processingTrack); }));
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
// Full-duplex. Wait for another second before finishing.
DispatchFunction([&] {
@ -1506,7 +1501,7 @@ TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
PRINCIPAL_HANDLE_NONE);
processingTrack->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(processingTrack, listener));
return graph->NotifyWhenDeviceStarted(processingTrack);
return graph->NotifyWhenDeviceStarted(nullptr);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
@ -1607,7 +1602,8 @@ TEST(TestAudioTrackGraph, SetRequestedInputChannelCount)
track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started = Invoke([&] { return graph->NotifyWhenDeviceStarted(track1); });
auto started =
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);
@ -1841,7 +1837,7 @@ TEST(TestAudioTrackGraph, RestartAudioIfProcessingMaxChannelCountChanged)
EXPECT_EQ(track1->DeviceId().value(), nativeDevice);
auto started =
Invoke([&] { return graph->NotifyWhenDeviceStarted(track1); });
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
nativeStream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nativeStream->mHasInput);
@ -2033,7 +2029,7 @@ TEST(TestAudioTrackGraph, SetInputChannelCountBeforeAudioCallbackDriver)
EXPECT_EQ(stream->InputChannels(), 1U);
Unused << WaitFor(
Invoke([&] { return graph->NotifyWhenDeviceStarted(track); }));
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));
// Clean up.
DispatchFunction([&] {
@ -2059,7 +2055,7 @@ TEST(TestAudioTrackGraph, StartAudioDeviceBeforeStartingAudioProcessing)
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
RefPtr<AudioProcessingTrack> track;
RefPtr<AudioInputProcessing> listener;
auto started = Invoke([&] {
DispatchFunction([&] {
track = AudioProcessingTrack::Create(graph);
listener = new AudioInputProcessing(2);
track->GraphImpl()->AppendMessage(
@ -2067,12 +2063,9 @@ TEST(TestAudioTrackGraph, StartAudioDeviceBeforeStartingAudioProcessing)
track->SetInputProcessing(listener);
// Start audio device without starting audio processing.
track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_TRUE(stream->mHasInput);
EXPECT_TRUE(stream->mHasOutput);
@ -2128,7 +2121,7 @@ TEST(TestAudioTrackGraph, StopAudioProcessingBeforeStoppingAudioDevice)
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
RefPtr<AudioProcessingTrack> track;
RefPtr<AudioInputProcessing> listener;
auto started = Invoke([&] {
DispatchFunction([&] {
track = AudioProcessingTrack::Create(graph);
listener = new AudioInputProcessing(2);
track->GraphImpl()->AppendMessage(
@ -2137,12 +2130,9 @@ TEST(TestAudioTrackGraph, StopAudioProcessingBeforeStoppingAudioDevice)
track->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(track, listener));
track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_TRUE(stream->mHasInput);
EXPECT_TRUE(stream->mHasOutput);
@ -2277,7 +2267,8 @@ TEST(TestAudioTrackGraph, SwitchNativeAudioProcessingTrack)
track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
EXPECT_EQ(track1->DeviceId().value(), device1);
auto started = Invoke([&] { return graph->NotifyWhenDeviceStarted(track1); });
auto started =
Invoke([&] { return graph->NotifyWhenDeviceStarted(nullptr); });
RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream1->mHasInput);