Backed out 16 changesets (bug 1666116, bug 1605134) for GTest failures on TestAudioTrackGraph . CLOSED TREE

Backed out changeset dba15089f5d3 (bug 1666116)
Backed out changeset c529b6732b08 (bug 1666116)
Backed out changeset 3225cdc96f82 (bug 1666116)
Backed out changeset 06ced7ee7c2c (bug 1666116)
Backed out changeset f65dbe0c4c64 (bug 1605134)
Backed out changeset dda7d98d3da7 (bug 1605134)
Backed out changeset 6fed7e4730c5 (bug 1605134)
Backed out changeset 9859d35abce6 (bug 1605134)
Backed out changeset e5fb448bbadf (bug 1605134)
Backed out changeset 3ef7199a547a (bug 1605134)
Backed out changeset c24bff49f331 (bug 1605134)
Backed out changeset f8612b562aa7 (bug 1605134)
Backed out changeset 0ba16e1f73ae (bug 1605134)
Backed out changeset 0736167ed294 (bug 1605134)
Backed out changeset 56ead9091c47 (bug 1605134)
Backed out changeset 841eedd33424 (bug 1605134)
This commit is contained in:
Narcis Beleuzu 2020-11-19 22:09:39 +02:00
Родитель 8551a905ba
Коммит 61193c6032
12 изменённых файлов: 242 добавлений и 528 удалений

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

@ -103,7 +103,22 @@ class AudioCallbackBufferWrapper {
* instance can be reused.
*/
void BufferFilled() {
MOZ_ASSERT(Available() == 0, "Frames should have been written");
// It's okay to have exactly zero samples here, it can happen we have an
// audio callback driver because of a hint on MTG creation, but the
// AudioOutputStream has not been created yet, or if all the tracks have
// finished but we're still running. Note: it's also ok if we had data in
// the scratch buffer - and we usually do - and all the tracks were ended
// (no mixer callback occured).
// XXX Remove this warning, or find a way to avoid it if the mixer callback
// isn't called.
NS_WARNING_ASSERTION(
Available() == 0 || mSampleWriteOffset == 0,
"Audio Buffer is not full by the end of the callback.");
// Make sure the data returned is always set and not random!
if (Available()) {
PodZero(mBuffer + mSampleWriteOffset,
FramesToSamples(mChannels, Available()));
}
MOZ_ASSERT(mSamples, "Buffer not set.");
mSamples = 0;
mSampleWriteOffset = 0;

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

@ -52,7 +52,7 @@ void GraphDriver::SetState(GraphTime aIterationStart, GraphTime aIterationEnd,
}
#ifdef DEBUG
bool GraphDriver::InIteration() const {
bool GraphDriver::InIteration() {
return OnThread() || Graph()->InDriverIteration(this);
}
#endif
@ -399,7 +399,7 @@ class AudioCallbackDriver::FallbackWrapper : public GraphInterface {
MOZ_CRASH("Unexpected DeviceChanged from fallback SystemClockDriver");
}
#ifdef DEBUG
bool InDriverIteration(const GraphDriver* aDriver) const override {
bool InDriverIteration(GraphDriver* aDriver) override {
return !mOwner->ThreadRunning() && mOwner->InIteration();
}
#endif
@ -867,21 +867,10 @@ long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
if (!mSandboxed && CheckThreadIdChanged()) {
CubebUtils::GetAudioThreadRegistry()->Register(mAudioThreadId);
}
if (mAudioStreamState.compareExchange(AudioStreamState::Pending,
AudioStreamState::Running)) {
LOG(LogLevel::Verbose, ("%p: AudioCallbackDriver %p First audio callback "
"close the Fallback driver",
Graph(), this));
}
FallbackDriverState fallbackState = mFallbackDriverState;
if (MOZ_UNLIKELY(fallbackState == FallbackDriverState::Running)) {
// Wait for the fallback driver to stop. Wake it up so it can stop if it's
// sleeping.
LOG(LogLevel::Verbose,
("%p: AudioCallbackDriver %p Waiting for the Fallback driver to stop",
Graph(), this));
EnsureNextIteration();
PodZero(aOutputBuffer, aFrames * mOutputChannelCount);
return aFrames;
@ -977,9 +966,6 @@ long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
result = IterationResult::CreateStillProcessing();
}
MOZ_ASSERT(mBuffer.Available() == 0,
"The graph should have filled the buffer");
mBuffer.BufferFilled();
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
@ -1073,11 +1059,11 @@ void AudioCallbackDriver::StateCallback(cubeb_state aState) {
LOG(LogLevel::Debug,
("AudioCallbackDriver(%p) State: %s", this, StateToString(aState)));
AudioStreamState streamState = mAudioStreamState;
if (aState != CUBEB_STATE_STARTED) {
// Clear the flag for the not running states: stopped, drained, error.
streamState = mAudioStreamState.exchange(AudioStreamState::None);
}
// Clear the flag for the not running
// states: stopped, drained, error.
AudioStreamState streamState = mAudioStreamState.exchange(
aState == CUBEB_STATE_STARTED ? AudioStreamState::Running
: AudioStreamState::None);
if (aState == CUBEB_STATE_ERROR) {
// About to hand over control of the graph. Do not start a new driver if
@ -1231,11 +1217,6 @@ TimeDuration AudioCallbackDriver::AudioOutputLatency() {
mSampleRate);
}
bool AudioCallbackDriver::OnFallback() const {
MOZ_ASSERT(InIteration());
return mFallbackDriverState == FallbackDriverState::Running;
}
void AudioCallbackDriver::FallbackToSystemClockDriver() {
MOZ_ASSERT(!ThreadRunning());
MOZ_ASSERT(mAudioStreamState == AudioStreamState::None ||

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

@ -199,7 +199,7 @@ struct GraphInterface : public nsISupports {
#ifdef DEBUG
/* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
* and mGraphRunner is currently run by aDriver. */
virtual bool InDriverIteration(const GraphDriver* aDriver) const = 0;
virtual bool InDriverIteration(GraphDriver* aDriver) = 0;
#endif
};
@ -298,19 +298,10 @@ class GraphDriver {
void SetPreviousDriver(GraphDriver* aPreviousDriver);
virtual AudioCallbackDriver* AsAudioCallbackDriver() { return nullptr; }
virtual const AudioCallbackDriver* AsAudioCallbackDriver() const {
return nullptr;
}
virtual OfflineClockDriver* AsOfflineClockDriver() { return nullptr; }
virtual const OfflineClockDriver* AsOfflineClockDriver() const {
return nullptr;
}
virtual SystemClockDriver* AsSystemClockDriver() { return nullptr; }
virtual const SystemClockDriver* AsSystemClockDriver() const {
return nullptr;
}
/**
* Set the state of the driver so it can start at the right point in time,
@ -323,12 +314,12 @@ class GraphDriver {
#ifdef DEBUG
// True if the current thread is currently iterating the MTG.
bool InIteration() const;
bool InIteration();
#endif
// True if the current thread is the GraphDriver's thread.
virtual bool OnThread() const = 0;
virtual bool OnThread() = 0;
// GraphDriver's thread has started and the thread is running.
virtual bool ThreadRunning() const = 0;
virtual bool ThreadRunning() = 0;
double MediaTimeToSeconds(GraphTime aTime) const {
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
@ -440,13 +431,13 @@ class ThreadedDriver : public GraphDriver {
friend class MediaTrackGraphInitThreadRunnable;
uint32_t IterationDuration() override { return MEDIA_GRAPH_TARGET_PERIOD_MS; }
nsIThread* Thread() const { return mThread; }
nsIThread* Thread() { return mThread; }
bool OnThread() const override {
bool OnThread() override {
return !mThread || mThread->EventTarget()->IsOnCurrentThread();
}
bool ThreadRunning() const override { return mThreadRunning; }
bool ThreadRunning() override { return mThreadRunning; }
protected:
/* Waits until it's time to process more data. */
@ -483,7 +474,6 @@ class SystemClockDriver : public ThreadedDriver {
GraphDriver* aPreviousDriver, uint32_t aSampleRate);
virtual ~SystemClockDriver();
SystemClockDriver* AsSystemClockDriver() override { return this; }
const SystemClockDriver* AsSystemClockDriver() const override { return this; }
protected:
/* Return the TimeDuration to wait before the next rendering iteration. */
@ -508,9 +498,6 @@ class OfflineClockDriver : public ThreadedDriver {
GraphTime aSlice);
virtual ~OfflineClockDriver();
OfflineClockDriver* AsOfflineClockDriver() override { return this; }
const OfflineClockDriver* AsOfflineClockDriver() const override {
return this;
}
void RunThread() override;
@ -620,9 +607,6 @@ class AudioCallbackDriver : public GraphDriver,
uint32_t aSampleRate) override;
AudioCallbackDriver* AsAudioCallbackDriver() override { return this; }
const AudioCallbackDriver* AsAudioCallbackDriver() const override {
return this;
}
uint32_t OutputChannelCount() { return mOutputChannelCount; }
@ -635,7 +619,7 @@ class AudioCallbackDriver : public GraphDriver,
return AudioInputType::Unknown;
}
std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); }
std::thread::id ThreadId() { return mAudioThreadIdInCb.load(); }
/* Called when the thread servicing the callback has changed. This can be
* fairly expensive */
@ -644,13 +628,13 @@ class AudioCallbackDriver : public GraphDriver,
* changed. */
bool CheckThreadIdChanged();
bool OnThread() const override {
bool OnThread() override {
return mAudioThreadIdInCb.load() == std::this_thread::get_id();
}
/* Returns true if this audio callback driver has successfully started and not
* yet stopped. If the fallback driver is active, this returns false. */
bool ThreadRunning() const override {
bool ThreadRunning() override {
return mAudioStreamState == AudioStreamState::Running;
}
@ -661,9 +645,6 @@ class AudioCallbackDriver : public GraphDriver,
// Returns the output latency for the current audio output stream.
TimeDuration AudioOutputLatency();
/* Returns true if this driver is currently driven by the fallback driver. */
bool OnFallback() const;
private:
/**
* On certain MacBookPro, the microphone is located near the left speaker.

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

@ -56,13 +56,13 @@ void GraphRunner::Shutdown() {
mThread->Shutdown();
}
auto GraphRunner::OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
auto GraphRunner::OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
AudioMixer* aMixer) -> IterationResult {
TRACE();
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mThreadState == ThreadState::Wait);
mIterationState = Some(IterationState(aStateTime, aIterationEnd, aMixer));
mIterationState = Some(IterationState(aStateEnd, aIterationEnd, aMixer));
#ifdef DEBUG
if (auto audioDriver = mGraph->CurrentDriver()->AsAudioCallbackDriver()) {
@ -113,7 +113,7 @@ NS_IMETHODIMP GraphRunner::Run() {
}
MOZ_DIAGNOSTIC_ASSERT(mIterationState.isSome());
TRACE();
mIterationResult = mGraph->OneIterationImpl(mIterationState->StateTime(),
mIterationResult = mGraph->OneIterationImpl(mIterationState->StateEnd(),
mIterationState->IterationEnd(),
mIterationState->Mixer());
// Signal that mIterationResult was updated
@ -130,12 +130,12 @@ NS_IMETHODIMP GraphRunner::Run() {
return NS_OK;
}
bool GraphRunner::OnThread() const {
bool GraphRunner::OnThread() {
return mThread->EventTarget()->IsOnCurrentThread();
}
#ifdef DEBUG
bool GraphRunner::InDriverIteration(const GraphDriver* aDriver) const {
bool GraphRunner::InDriverIteration(GraphDriver* aDriver) {
if (!OnThread()) {
return false;
}

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

@ -35,7 +35,7 @@ class GraphRunner final : public Runnable {
* Signals one iteration of mGraph. Hands state over to mThread and runs
* the iteration there.
*/
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
IterationResult OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
AudioMixer* aMixer);
/**
@ -46,14 +46,14 @@ class GraphRunner final : public Runnable {
/**
* Returns true if called on mThread.
*/
bool OnThread() const;
bool OnThread();
#ifdef DEBUG
/**
* Returns true if called on mThread, and aDriver was the driver that called
* OneIteration() last.
*/
bool InDriverIteration(const GraphDriver* aDriver) const;
bool InDriverIteration(GraphDriver* aDriver);
#endif
private:
@ -62,18 +62,16 @@ class GraphRunner final : public Runnable {
~GraphRunner();
class IterationState {
GraphTime mStateTime;
GraphTime mStateEnd;
GraphTime mIterationEnd;
AudioMixer* MOZ_NON_OWNING_REF mMixer;
public:
IterationState(GraphTime aStateTime, GraphTime aIterationEnd,
IterationState(GraphTime aStateEnd, GraphTime aIterationEnd,
AudioMixer* aMixer)
: mStateTime(aStateTime),
mIterationEnd(aIterationEnd),
mMixer(aMixer) {}
: mStateEnd(aStateEnd), mIterationEnd(aIterationEnd), mMixer(aMixer) {}
IterationState& operator=(const IterationState& aOther) = default;
GraphTime StateTime() const { return mStateTime; }
GraphTime StateEnd() const { return mStateEnd; }
GraphTime IterationEnd() const { return mIterationEnd; }
AudioMixer* Mixer() const { return mMixer; }
};
@ -92,8 +90,8 @@ class GraphRunner final : public Runnable {
enum class ThreadState {
Wait, // Waiting for a message. This is the initial state.
// A transition from Run back to Wait occurs on the runner thread
// after it processes as far as mIterationState->mStateTime
// A transition from Run back to Wait occurs on the runner
// thread after it processes as far as mIterationState->mStateEnd
// and sets mIterationResult.
Run, // Set on driver thread after each mIterationState update.
Shutdown, // Set when Shutdown() is called on main thread.

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

@ -1365,17 +1365,17 @@ bool MediaTrackGraphImpl::UpdateMainThreadState() {
return false;
}
auto MediaTrackGraphImpl::OneIteration(GraphTime aStateTime,
auto MediaTrackGraphImpl::OneIteration(GraphTime aStateEnd,
GraphTime aIterationEnd,
AudioMixer* aMixer) -> IterationResult {
if (mGraphRunner) {
return mGraphRunner->OneIteration(aStateTime, aIterationEnd, aMixer);
return mGraphRunner->OneIteration(aStateEnd, aIterationEnd, aMixer);
}
return OneIterationImpl(aStateTime, aIterationEnd, aMixer);
return OneIterationImpl(aStateEnd, aIterationEnd, aMixer);
}
auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateEnd,
GraphTime aIterationEnd,
AudioMixer* aMixer)
-> IterationResult {
@ -1406,14 +1406,14 @@ auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
NS_ProcessPendingEvents(nullptr);
}
GraphTime stateTime = std::min(aStateTime, GraphTime(mEndTime));
UpdateGraph(stateTime);
GraphTime stateEnd = std::min(aStateEnd, GraphTime(mEndTime));
UpdateGraph(stateEnd);
mStateComputedTime = stateTime;
mStateComputedTime = stateEnd;
GraphTime oldProcessedTime = mProcessedTime;
Process(aMixer);
MOZ_ASSERT(mProcessedTime == stateTime);
MOZ_ASSERT(mProcessedTime == stateEnd);
UpdateCurrentTimeForTracks(oldProcessedTime);
@ -3095,7 +3095,7 @@ AbstractThread* MediaTrackGraph::AbstractMainThread() {
}
#ifdef DEBUG
bool MediaTrackGraphImpl::InDriverIteration(const GraphDriver* aDriver) const {
bool MediaTrackGraphImpl::InDriverIteration(GraphDriver* aDriver) {
return aDriver->OnThread() ||
(mGraphRunner && mGraphRunner->InDriverIteration(aDriver));
}
@ -3447,8 +3447,7 @@ void MediaTrackGraphImpl::NotifyWhenGraphStarted(
// ControlMessage.
MediaTrackGraphImpl* graphImpl = mMediaTrack->GraphImpl();
if (graphImpl->CurrentDriver()->AsAudioCallbackDriver() &&
graphImpl->CurrentDriver()->ThreadRunning() &&
!graphImpl->CurrentDriver()->AsAudioCallbackDriver()->OnFallback()) {
graphImpl->CurrentDriver()->ThreadRunning()) {
// Avoid Resolve's locking on the graph thread by doing it on main.
graphImpl->Dispatch(NS_NewRunnableFunction(
"MediaTrackGraphImpl::NotifyWhenGraphStarted::Resolver",

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

@ -131,7 +131,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
* and mGraphRunner is currently run by aDriver.
*/
bool InDriverIteration(const GraphDriver* aDriver) const override;
bool InDriverIteration(GraphDriver* aDriver) override;
#endif
/**
@ -222,14 +222,14 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
* OneIterationImpl is called directly. Output from the graph gets mixed into
* aMixer, if it is non-null.
*/
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
IterationResult OneIteration(GraphTime aStateEnd, GraphTime aIterationEnd,
AudioMixer* aMixer) override;
/**
* Returns true if this MediaTrackGraph should keep running
*/
IterationResult OneIterationImpl(GraphTime aStateTime,
GraphTime aIterationEnd, AudioMixer* aMixer);
IterationResult OneIterationImpl(GraphTime aStateEnd, GraphTime aIterationEnd,
AudioMixer* aMixer);
/**
* Called from the driver, when the graph thread is about to stop, to tell

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

@ -15,13 +15,10 @@ MockCubebStream::MockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
cubeb_stream_params* aOutputStreamParams,
cubeb_data_callback aDataCallback,
cubeb_state_callback aStateCallback,
void* aUserPtr, SmartMockCubebStream* aSelf,
TimeDuration aStartDelay)
void* aUserPtr)
: context(aContext),
mHasInput(aInputStreamParams),
mHasOutput(aOutputStreamParams),
mStartDelay(aStartDelay),
mSelf(aSelf),
mDataCallback(aDataCallback),
mStateCallback(aStateCallback),
mUserPtr(aUserPtr),
@ -45,44 +42,26 @@ MockCubebStream::MockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
MockCubebStream::~MockCubebStream() = default;
int MockCubebStream::Start() {
mStateCallback(AsCubebStream(), mUserPtr, CUBEB_STATE_STARTED);
mStreamStop = false;
if (!mStartDelay.IsZero()) {
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
"MockCubebStream::DelayedStart",
[self = RefPtr<SmartMockCubebStream>(mSelf), startDelay = mStartDelay] {
std::this_thread::sleep_for(
std::chrono::microseconds((int64_t)startDelay.ToMicroseconds()));
if (!self->mStreamStop) {
MockCubeb::AsMock(self->context)->StartStream(self);
}
}));
return CUBEB_OK;
}
MockCubeb::AsMock(context)->StartStream(this);
reinterpret_cast<MockCubeb*>(context)->StartStream(this);
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
mStateCallback(stream, mUserPtr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
int MockCubebStream::Stop() {
mStreamStop = true;
mOutputVerificationEvent.Notify(MakeTuple(
mAudioVerifier.PreSilenceSamples(), mAudioVerifier.EstimatedFreq(),
mAudioVerifier.CountDiscontinuities()));
int rv = MockCubeb::AsMock(context)->StopStream(this);
mStreamStop = true;
int rv = reinterpret_cast<MockCubeb*>(context)->StopStream(this);
if (rv == CUBEB_OK) {
mStateCallback(AsCubebStream(), mUserPtr, CUBEB_STATE_STOPPED);
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
mStateCallback(stream, mUserPtr, CUBEB_STATE_STOPPED);
}
return rv;
}
cubeb_stream* MockCubebStream::AsCubebStream() {
return reinterpret_cast<cubeb_stream*>(this);
}
MockCubebStream* MockCubebStream::AsMock(cubeb_stream* aStream) {
return reinterpret_cast<MockCubebStream*>(aStream);
}
cubeb_devid MockCubebStream::GetInputDeviceID() const { return mInputDeviceID; }
cubeb_devid MockCubebStream::GetOutputDeviceID() const {
@ -136,7 +115,7 @@ void MockCubebStream::Process10Ms() {
if (mInputParams.rate) {
mAudioGenerator.GenerateInterleaved(mInputBuffer, nrFrames);
}
cubeb_stream* stream = AsCubebStream();
cubeb_stream* stream = reinterpret_cast<cubeb_stream*>(this);
const long outframes =
mDataCallback(stream, mUserPtr, mHasInput ? mInputBuffer : nullptr,
mHasOutput ? mOutputBuffer : nullptr, nrFrames);
@ -158,9 +137,10 @@ void MockCubebStream::Process10Ms() {
mForceErrorState = false;
// Let the audio thread (this thread!) run to completion before
// being released, by joining and releasing on main.
NS_DispatchBackgroundTask(
NS_NewRunnableFunction(__func__, [cubeb = MockCubeb::AsMock(context),
this] { cubeb->StopStream(this); }));
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
__func__, [cubeb = reinterpret_cast<MockCubeb*>(context), this] {
cubeb->StopStream(this);
}));
mStateCallback(stream, mUserPtr, CUBEB_STATE_ERROR);
mErrorForcedEvent.Notify();
mStreamStop = true;
@ -174,10 +154,6 @@ MockCubeb::~MockCubeb() { MOZ_ASSERT(!mFakeAudioThread); };
cubeb* MockCubeb::AsCubebContext() { return reinterpret_cast<cubeb*>(this); }
MockCubeb* MockCubeb::AsMock(cubeb* aContext) {
return reinterpret_cast<MockCubeb*>(aContext);
}
int MockCubeb::EnumerateDevices(cubeb_device_type aType,
cubeb_device_collection* collection) {
#ifdef ANDROID
@ -311,23 +287,6 @@ void MockCubeb::SetSupportDeviceChangeCallback(bool aSupports) {
mSupportsDeviceCollectionChangedCallback = aSupports;
}
void MockCubeb::SetStreamStartDelay(TimeDuration aDuration) {
mStreamStartDelayUs = aDuration.ToMicroseconds();
}
auto MockCubeb::ForceAudioThread() -> RefPtr<ForcedAudioThreadPromise> {
RefPtr<ForcedAudioThreadPromise> p =
mForcedAudioThreadPromise.Ensure(__func__);
mForcedAudioThread = true;
StartStream(nullptr);
return p;
}
void MockCubeb::UnforceAudioThread() {
mForcedAudioThread = false;
StopStream(nullptr);
}
int MockCubeb::StreamInit(cubeb* aContext, cubeb_stream** aStream,
cubeb_devid aInputDevice,
cubeb_stream_params* aInputStreamParams,
@ -335,28 +294,25 @@ int MockCubeb::StreamInit(cubeb* aContext, cubeb_stream** aStream,
cubeb_stream_params* aOutputStreamParams,
cubeb_data_callback aDataCallback,
cubeb_state_callback aStateCallback, void* aUserPtr) {
auto mockStream = MakeRefPtr<SmartMockCubebStream>(
MockCubebStream* mockStream = new MockCubebStream(
aContext, aInputDevice, aInputStreamParams, aOutputDevice,
aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr,
TimeDuration::FromMicroseconds(mStreamStartDelayUs));
*aStream = mockStream->AsCubebStream();
aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr);
*aStream = reinterpret_cast<cubeb_stream*>(mockStream);
mStreamInitEvent.Notify(mockStream);
// AddRef the stream to keep it alive. StreamDestroy releases it.
Unused << mockStream.forget().take();
return CUBEB_OK;
}
void MockCubeb::StreamDestroy(cubeb_stream* aStream) {
mStreamDestroyEvent.Notify();
RefPtr<SmartMockCubebStream> mockStream =
dont_AddRef(MockCubebStream::AsMock(aStream)->mSelf);
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
delete mockStream;
}
void MockCubeb::GoFaster() { mFastMode = true; }
void MockCubeb::DontGoFaster() { mFastMode = false; }
MediaEventSource<RefPtr<SmartMockCubebStream>>& MockCubeb::StreamInitEvent() {
MediaEventSource<MockCubebStream*>& MockCubeb::StreamInitEvent() {
return mStreamInitEvent;
}
@ -366,13 +322,8 @@ MediaEventSource<void>& MockCubeb::StreamDestroyEvent() {
void MockCubeb::StartStream(MockCubebStream* aStream) {
auto streams = mLiveStreams.Lock();
MOZ_ASSERT_IF(!aStream, mForcedAudioThread);
// Forcing an audio thread must happen before starting streams
MOZ_ASSERT_IF(!aStream, streams->IsEmpty());
if (aStream) {
MOZ_ASSERT(!streams->Contains(aStream->mSelf));
streams->AppendElement(aStream->mSelf);
}
MOZ_ASSERT(!streams->Contains(aStream));
streams->AppendElement(aStream);
if (!mFakeAudioThread) {
mFakeAudioThread = WrapUnique(new std::thread(ThreadFunction_s, this));
}
@ -382,14 +333,13 @@ int MockCubeb::StopStream(MockCubebStream* aStream) {
UniquePtr<std::thread> audioThread;
{
auto streams = mLiveStreams.Lock();
if (aStream) {
if (!streams->Contains(aStream->mSelf)) {
return CUBEB_ERROR;
}
streams->RemoveElement(aStream->mSelf);
if (!streams->Contains(aStream)) {
return CUBEB_ERROR;
}
MOZ_ASSERT(streams->Contains(aStream));
streams->RemoveElement(aStream);
MOZ_ASSERT(mFakeAudioThread);
if (streams->IsEmpty() && !mForcedAudioThread) {
if (streams->IsEmpty()) {
audioThread = std::move(mFakeAudioThread);
}
}
@ -400,17 +350,13 @@ int MockCubeb::StopStream(MockCubebStream* aStream) {
}
void MockCubeb::ThreadFunction() {
if (mForcedAudioThread) {
mForcedAudioThreadPromise.Resolve(MakeRefPtr<AudioThreadAutoUnforcer>(this),
__func__);
}
while (true) {
{
auto streams = mLiveStreams.Lock();
for (auto& stream : *streams) {
stream->Process10Ms();
}
if (streams->IsEmpty() && !mForcedAudioThread) {
if (streams->IsEmpty()) {
break;
}
}

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

@ -10,9 +10,10 @@
#include "AudioVerifier.h"
#include "MediaEventSource.h"
#include "mozilla/DataMutex.h"
#include "mozilla/ThreadSafeWeakPtr.h"
#include "nsTArray.h"
#include "mozilla/DataMutex.h"
#include <thread>
#include <atomic>
#include <chrono>
@ -123,8 +124,6 @@ cubeb_ops const mock_ops = {
cubeb_mock_register_device_collection_changed};
class SmartMockCubebStream;
// Represents the fake cubeb_stream. The context instance is needed to
// provide access on cubeb_ops struct.
class MockCubebStream {
@ -134,17 +133,13 @@ class MockCubebStream {
cubeb_devid aOutputDevice,
cubeb_stream_params* aOutputStreamParams,
cubeb_data_callback aDataCallback,
cubeb_state_callback aStateCallback, void* aUserPtr,
SmartMockCubebStream* aSelf, TimeDuration aStartDelay);
cubeb_state_callback aStateCallback, void* aUserPtr);
~MockCubebStream();
int Start();
int Stop();
cubeb_stream* AsCubebStream();
static MockCubebStream* AsMock(cubeb_stream* aStream);
cubeb_devid GetInputDeviceID() const;
cubeb_devid GetOutputDeviceID() const;
@ -167,8 +162,6 @@ class MockCubebStream {
const bool mHasInput;
const bool mHasOutput;
const TimeDuration mStartDelay;
SmartMockCubebStream* const mSelf;
private:
// Signal to the audio thread that stream is stopped.
@ -201,24 +194,6 @@ class MockCubebStream {
MediaEventProducer<void> mErrorForcedEvent;
};
class SmartMockCubebStream
: public MockCubebStream,
public SupportsThreadSafeWeakPtr<SmartMockCubebStream> {
public:
MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(SmartMockCubebStream)
MOZ_DECLARE_REFCOUNTED_TYPENAME(SmartMockCubebStream)
SmartMockCubebStream(cubeb* aContext, cubeb_devid aInputDevice,
cubeb_stream_params* aInputStreamParams,
cubeb_devid aOutputDevice,
cubeb_stream_params* aOutputStreamParams,
cubeb_data_callback aDataCallback,
cubeb_state_callback aStateCallback, void* aUserPtr,
TimeDuration aStartDelay)
: MockCubebStream(aContext, aInputDevice, aInputStreamParams,
aOutputDevice, aOutputStreamParams, aDataCallback,
aStateCallback, aUserPtr, this, aStartDelay) {}
};
// This class has two facets: it is both a fake cubeb backend that is intended
// to be used for testing, and passed to Gecko code that expects a normal
// backend, but is also controllable by the test code to decide what the backend
@ -230,7 +205,6 @@ class MockCubeb {
// Cubeb backend implementation
// This allows passing this class as a cubeb* instance.
cubeb* AsCubebContext();
static MockCubeb* AsMock(cubeb* aContext);
// Fill in the collection parameter with all devices of aType.
int EnumerateDevices(cubeb_device_type aType,
cubeb_device_collection* collection);
@ -259,35 +233,6 @@ class MockCubeb {
// collection invalidation callback, to be able to test the fallback path.
void SetSupportDeviceChangeCallback(bool aSupports);
// Makes MockCubebStreams starting after this point wait for the given time
// after CUBEB_STATE_STARTED until the first callback.
void SetStreamStartDelay(TimeDuration aDuration);
// Helper class that automatically unforces a forced audio thread on release.
class AudioThreadAutoUnforcer {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioThreadAutoUnforcer)
public:
explicit AudioThreadAutoUnforcer(MockCubeb* aContext)
: mContext(aContext) {}
protected:
virtual ~AudioThreadAutoUnforcer() { mContext->UnforceAudioThread(); }
MockCubeb* mContext;
};
// Creates the audio thread if one is not available. The audio thread remains
// forced until UnforceAudioThread is called. The returned promise is resolved
// when the audio thread is running. With this, a test can ensure starting
// audio streams is deterministically fast across platforms for more accurate
// results.
using ForcedAudioThreadPromise =
MozPromise<RefPtr<AudioThreadAutoUnforcer>, nsresult, false>;
RefPtr<ForcedAudioThreadPromise> ForceAudioThread();
// Allows a forced audio thread to stop.
void UnforceAudioThread();
int StreamInit(cubeb* aContext, cubeb_stream** aStream,
cubeb_devid aInputDevice,
cubeb_stream_params* aInputStreamParams,
@ -301,7 +246,7 @@ class MockCubeb {
void GoFaster();
void DontGoFaster();
MediaEventSource<RefPtr<SmartMockCubebStream>>& StreamInitEvent();
MediaEventSource<MockCubebStream*>& StreamInitEvent();
MediaEventSource<void>& StreamDestroyEvent();
// MockCubeb specific API
@ -335,19 +280,12 @@ class MockCubeb {
// notification via a system callback. If not, Gecko is expected to re-query
// the list every time.
bool mSupportsDeviceCollectionChangedCallback = true;
// The delay to use for stream start, in microseconds.
Atomic<int32_t> mStreamStartDelayUs;
// Whether the audio thread is forced, i.e., whether it remains active even
// with no live streams.
Atomic<bool> mForcedAudioThread{false};
MozPromiseHolder<ForcedAudioThreadPromise> mForcedAudioThreadPromise;
// Our input and output devices.
nsTArray<cubeb_device_info> mInputDevices;
nsTArray<cubeb_device_info> mOutputDevices;
// The streams that are currently running.
DataMutex<nsTArray<RefPtr<SmartMockCubebStream>>> mLiveStreams{
"MockCubeb::mLiveStreams"};
DataMutex<nsTArray<MockCubebStream*>> mLiveStreams{"MockCubeb::mLiveStreams"};
// Thread that simulates the audio thread, shared across MockCubebStreams to
// avoid unintended drift. This is set together with mLiveStreams, under the
// mLiveStreams DataMutex.
@ -357,15 +295,18 @@ class MockCubeb {
// true we sleep(0) between iterations instead of 10ms.
std::atomic<bool> mFastMode{false};
MediaEventProducer<RefPtr<SmartMockCubebStream>> mStreamInitEvent;
MediaEventProducer<MockCubebStream*> mStreamInitEvent;
MediaEventProducer<void> mStreamDestroyEvent;
};
void cubeb_mock_destroy(cubeb* context) { delete MockCubeb::AsMock(context); }
void cubeb_mock_destroy(cubeb* context) {
delete reinterpret_cast<MockCubeb*>(context);
}
int cubeb_mock_enumerate_devices(cubeb* context, cubeb_device_type type,
cubeb_device_collection* out) {
return MockCubeb::AsMock(context)->EnumerateDevices(type, out);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
return mock->EnumerateDevices(type, out);
}
int cubeb_mock_device_collection_destroy(cubeb* context,
@ -377,8 +318,9 @@ int cubeb_mock_device_collection_destroy(cubeb* context,
int cubeb_mock_register_device_collection_changed(
cubeb* context, cubeb_device_type devtype,
cubeb_device_collection_changed_callback callback, void* user_ptr) {
return MockCubeb::AsMock(context)->RegisterDeviceCollectionChangeCallback(
devtype, callback, user_ptr);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
return mock->RegisterDeviceCollectionChangeCallback(devtype, callback,
user_ptr);
}
int cubeb_mock_stream_init(
@ -387,22 +329,25 @@ int cubeb_mock_stream_init(
cubeb_devid output_device, cubeb_stream_params* output_stream_params,
unsigned int latency, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void* user_ptr) {
return MockCubeb::AsMock(context)->StreamInit(
context, stream, input_device, input_stream_params, output_device,
output_stream_params, data_callback, state_callback, user_ptr);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(context);
return mock->StreamInit(context, stream, input_device, input_stream_params,
output_device, output_stream_params, data_callback,
state_callback, user_ptr);
}
int cubeb_mock_stream_start(cubeb_stream* stream) {
return MockCubebStream::AsMock(stream)->Start();
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
return mockStream->Start();
}
int cubeb_mock_stream_stop(cubeb_stream* stream) {
return MockCubebStream::AsMock(stream)->Stop();
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
return mockStream->Stop();
}
void cubeb_mock_stream_destroy(cubeb_stream* stream) {
MockCubebStream* mockStream = MockCubebStream::AsMock(stream);
MockCubeb* mock = MockCubeb::AsMock(mockStream->context);
MockCubebStream* mockStream = reinterpret_cast<MockCubebStream*>(stream);
MockCubeb* mock = reinterpret_cast<MockCubeb*>(mockStream->context);
return mock->StreamDestroy(stream);
}

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

@ -10,22 +10,19 @@
#include "gtest/gtest-printers.h"
#include "gtest/gtest.h"
#include "MediaTrackGraphImpl.h"
#include "mozilla/Attributes.h"
#include "mozilla/UniquePtr.h"
#include "nsTArray.h"
#include "MockCubeb.h"
#include "WaitFor.h"
using namespace mozilla;
using IterationResult = GraphInterface::IterationResult;
using ::testing::NiceMock;
using ::testing::Return;
class MockGraphInterface : public GraphInterface {
NS_DECL_THREADSAFE_ISUPPORTS
explicit MockGraphInterface(TrackRate aSampleRate)
: mSampleRate(aSampleRate) {}
MOCK_METHOD4(NotifyOutputData,
void(AudioDataValue*, size_t, TrackRate, uint32_t));
MOCK_METHOD0(NotifyInputStopped, void());
@ -34,20 +31,7 @@ class MockGraphInterface : public GraphInterface {
MOCK_METHOD0(DeviceChanged, void());
/* OneIteration cannot be mocked because IterationResult is non-memmovable and
* cannot be passed as a parameter, which GMock does internally. */
IterationResult OneIteration(GraphTime aStateComputedTime, GraphTime,
AudioMixer* aMixer) {
GraphDriver* driver = mCurrentDriver;
if (aMixer) {
aMixer->StartMixing();
aMixer->Mix(nullptr,
driver->AsAudioCallbackDriver()->OutputChannelCount(),
aStateComputedTime - mStateComputedTime, mSampleRate);
aMixer->FinishMixing();
}
if (aStateComputedTime != mStateComputedTime) {
++mIterationCount;
}
mStateComputedTime = aStateComputedTime;
IterationResult OneIteration(GraphTime, GraphTime, AudioMixer*) {
if (!mKeepProcessing) {
return IterationResult::CreateStop(
NS_NewRunnableFunction(__func__, [] {}));
@ -57,34 +41,19 @@ class MockGraphInterface : public GraphInterface {
return IterationResult::CreateSwitchDriver(
next, NS_NewRunnableFunction(__func__, [] {}));
}
if (mEnsureNextIteration) {
driver->EnsureNextIteration();
}
return IterationResult::CreateStillProcessing();
}
void SetEnsureNextIteration(bool aEnsure) { mEnsureNextIteration = aEnsure; }
#ifdef DEBUG
bool InDriverIteration(const GraphDriver* aDriver) const override {
bool InDriverIteration(GraphDriver* aDriver) override {
return aDriver->OnThread();
}
#endif
size_t IterationCount() const { return mIterationCount; }
GraphTime StateComputedTime() const { return mStateComputedTime; }
void SetCurrentDriver(GraphDriver* aDriver) { mCurrentDriver = aDriver; }
void StopIterating() { mKeepProcessing = false; }
void SwitchTo(GraphDriver* aDriver) { mNextDriver = aDriver; }
const TrackRate mSampleRate;
protected:
Atomic<size_t> mIterationCount{0};
Atomic<GraphTime> mStateComputedTime{0};
Atomic<GraphDriver*> mCurrentDriver{nullptr};
Atomic<bool> mEnsureNextIteration{false};
Atomic<bool> mKeepProcessing{true};
Atomic<GraphDriver*> mNextDriver{nullptr};
virtual ~MockGraphInterface() = default;
@ -94,22 +63,20 @@ NS_IMPL_ISUPPORTS0(MockGraphInterface)
TEST(TestAudioCallbackDriver, StartStop)
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
const TrackRate rate = 44100;
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
RefPtr<AudioCallbackDriver> driver;
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>();
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
ON_CALL(*graph, NotifyOutputData)
.WillByDefault([&](AudioDataValue*, size_t, TrackRate, uint32_t) {});
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, rate, 2, 0, nullptr,
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, 44100, 2, 0, nullptr,
nullptr, AudioInputType::Unknown);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
graph->SetCurrentDriver(driver);
driver->Start();
// Allow some time to "play" audio.
std::this_thread::sleep_for(std::chrono::milliseconds(200));
@ -122,97 +89,3 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
}
void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
std::cerr << "TestSlowStart with rate " << aRate << std::endl;
MockCubeb* cubeb = new MockCubeb();
const TimeDuration startDelay = TimeDuration::FromSeconds(0.3);
cubeb->SetStreamStartDelay(startDelay);
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
Unused << unforcer;
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
RefPtr<AudioCallbackDriver> driver;
auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(aRate);
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
Maybe<size_t> fallbackIterationCount;
Maybe<int64_t> audioStart;
Maybe<uint32_t> alreadyBuffered;
int64_t inputFrameCount = 0;
int64_t outputFrameCount = 0;
int64_t processedFrameCount = 0;
ON_CALL(*graph, NotifyInputData)
.WillByDefault([&](const AudioDataValue*, size_t aFrames, TrackRate aRate,
uint32_t, uint32_t aAlreadyBuffered) {
if (!audioStart) {
fallbackIterationCount = Some(graph->IterationCount());
audioStart = Some(graph->StateComputedTime());
alreadyBuffered = Some(aAlreadyBuffered);
}
EXPECT_NEAR(inputFrameCount,
static_cast<int64_t>(graph->StateComputedTime() -
*audioStart + *alreadyBuffered),
WEBAUDIO_BLOCK_SIZE)
<< "Input should be behind state time, due to the delayed start. "
"stateComputedTime="
<< graph->StateComputedTime() << ", audioStartTime=" << *audioStart
<< ", alreadyBuffered=" << *alreadyBuffered;
inputFrameCount += aFrames;
});
ON_CALL(*graph, NotifyOutputData)
.WillByDefault([&](AudioDataValue*, size_t aFrames, TrackRate aRate,
uint32_t) { outputFrameCount += aFrames; });
driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, aRate, 2, 2, nullptr,
(void*)1, AudioInputType::Voice);
EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
graph->SetCurrentDriver(driver);
graph->SetEnsureNextIteration(true);
driver->Start();
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
// Wait for at least 100ms of audio data.
WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
processedFrameCount += aFrames;
return processedFrameCount >= aRate / 10;
});
// This will block untill all events have been executed.
graph->StopIterating();
MOZ_KnownLive(driver)->Shutdown();
const uint32_t tenMillis = aRate / 100;
EXPECT_EQ(inputFrameCount, outputFrameCount);
// Margin (50ms) is there to cover (fake) audio callbacks that occur while
// waiting for fallback driver's next iteration to detect that it shall stop.
EXPECT_GE(*audioStart, MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
startDelay.ToSeconds() * aRate - tenMillis * 5))
<< "Fallback driver runs for at least the duration of the start delay";
// Margin (150ms) is generous because on some try machines the
// SystemClockDriver can take upwards of 100ms (should be 10ms) between
// iterations.
EXPECT_LE(*audioStart, MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
startDelay.ToSeconds() * aRate + tenMillis * 15))
<< "Fallback driver does not run significantly longer than the duration "
"of the start delay";
EXPECT_NEAR(graph->StateComputedTime() - *audioStart,
inputFrameCount + *alreadyBuffered, WEBAUDIO_BLOCK_SIZE)
<< "Graph progresses while audio driver runs. stateComputedTime="
<< graph->StateComputedTime() << ", inputFrameCount=" << inputFrameCount;
EXPECT_GE(*fallbackIterationCount, 3U)
<< "Fallback driver runs continuously while starting audio driver";
EXPECT_LE(*fallbackIterationCount,
static_cast<float>(*audioStart) / tenMillis)
<< "Fallback driver does not iterate more frequently than every 10ms";
}
TEST(TestAudioCallbackDriver, SlowStart)
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
TestSlowStart(1000); // 10ms = 10 <<< 128 samples
TestSlowStart(8000); // 10ms = 80 < 128 samples
TestSlowStart(44100); // 10ms = 441 > 128 samples
}

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

@ -15,13 +15,80 @@
#endif // MOZ_WEBRTC
#include "MockCubeb.h"
#include "mozilla/Preferences.h"
#include "WaitFor.h"
#define DRIFT_BUFFERING_PREF "media.clockdrift.buffering"
using namespace mozilla;
namespace {
/**
* Waits for an occurrence of aEvent on the current thread (by blocking it,
* except tasks added to the event loop may run) and returns the event's
* templated value, if it's non-void.
*
* The caller must be wary of eventloop issues, in
* particular cases where we rely on a stable state runnable, but there is never
* a task to trigger stable state. In such cases it is the responsibility of the
* caller to create the needed tasks, as JS would. A noteworthy API that relies
* on stable state is MediaTrackGraph::GetInstance.
*/
template <typename T>
T WaitFor(MediaEventSource<T>& aEvent) {
Maybe<T> value;
MediaEventListener listener = aEvent.Connect(
AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return value.isSome(); });
listener.Disconnect();
return value.value();
}
/**
* Specialization of WaitFor<T> for void.
*/
void WaitFor(MediaEventSource<void>& aEvent) {
bool done = false;
MediaEventListener listener =
aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return done; });
listener.Disconnect();
}
/**
* Variant of WaitFor that blocks the caller until a MozPromise has either been
* resolved or rejected.
*/
template <typename R, typename E, bool Exc>
Result<R, E> WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise) {
Maybe<Result<R, E>> result;
aPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[&](R aResult) { result = Some(Result<R, E>(aResult)); },
[&](E aError) { result = Some(Result<R, E>(aError)); });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return result.isSome(); });
return result.extract();
}
/**
* A variation of WaitFor that takes a callback to be called each time aEvent is
* raised. Blocks the caller until the callback function returns true.
*/
template <typename T, typename CallbackFunction>
void WaitUntil(MediaEventSource<T>& aEvent, const CallbackFunction& aF) {
bool done = false;
MediaEventListener listener =
aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) {
if (!done) {
done = aF(aValue);
}
});
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return done; });
listener.Disconnect();
}
// Short-hand for InvokeAsync on the current thread.
#define Invoke(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)
@ -166,7 +233,7 @@ TEST(TestAudioTrackGraph, SetOutputDeviceID)
DispatchFunction(
[&] { dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO); });
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_EQ(stream->GetOutputDeviceID(), reinterpret_cast<cubeb_devid>(2))
<< "After init confirm the expected output device id";
@ -213,18 +280,14 @@ TEST(TestAudioTrackGraph, ErrorCallback)
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
// Dummy track to make graph rolling. Add it and remove it to remove the
// graph from the global hash table and let it shutdown.
//
// We open an input through this track so that there's something triggering
// EnsureNextIteration on the fallback driver after the callback driver has
// gotten the error.
RefPtr<AudioInputTrack> inputTrack;
RefPtr<AudioInputProcessing> listener;
auto started = Invoke([&] {
Unused << WaitFor(Invoke([&] {
inputTrack = AudioInputTrack::Create(graph);
listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
inputTrack->GraphImpl()->AppendMessage(
@ -232,11 +295,18 @@ TEST(TestAudioTrackGraph, ErrorCallback)
inputTrack->SetInputProcessing(listener);
inputTrack->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(inputTrack, listener));
return graph->NotifyWhenDeviceStarted(inputTrack);
}));
// We open an input through this track so that there's something triggering
// EnsureNextIteration on the fallback driver after the callback driver has
// gotten the error.
auto started = Invoke([&] {
inputTrack->OpenAudioInput((void*)1, listener);
return graph->NotifyWhenDeviceStarted(inputTrack);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
@ -270,21 +340,16 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
{
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
Unused << unforcer;
// Start on a system clock driver, then switch to full-duplex in one go. If we
// did output-then-full-duplex we'd risk a second NotifyWhenDeviceStarted
// resolving early after checking the first audio driver only.
MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
RefPtr<AudioInputTrack> inputTrack;
RefPtr<ProcessedMediaTrack> outputTrack;
RefPtr<MediaInputPort> port;
RefPtr<AudioInputProcessing> listener;
auto p = Invoke([&] {
Unused << WaitFor(Invoke([&] {
inputTrack = AudioInputTrack::Create(graph);
outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
outputTrack->QueueSetAutoend(false);
@ -297,12 +362,16 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
inputTrack->SetInputProcessing(listener);
inputTrack->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(inputTrack, listener));
return graph->NotifyWhenDeviceStarted(inputTrack);
}));
DispatchFunction([&] {
// Device id does not matter. Ignore.
inputTrack->OpenAudioInput((void*)1, listener);
return graph->NotifyWhenDeviceStarted(inputTrack);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
auto p = Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); });
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
Unused << WaitFor(p);
@ -351,9 +420,10 @@ TEST(TestAudioTrackGraph, AudioInputTrack)
// driver has started to complete the switch, *usually* resulting two
// 10ms-iterations of silence; sometimes only one.
EXPECT_LE(preSilenceSamples, 128U + 2 * inputRate / 100 /* 2*10ms */);
// The waveform from AudioGenerator starts at 0, but we don't control its
// ending, so we expect a discontinuity there.
EXPECT_LE(nrDiscontinuities, 1U);
// Waveform may start after the beginning. In this case, there is a gap
// at the beginning and the end which is counted as discontinuity.
EXPECT_GE(nrDiscontinuities, 0U);
EXPECT_LE(nrDiscontinuities, 2U);
}
TEST(TestAudioTrackGraph, ReOpenAudioInput)
@ -387,7 +457,7 @@ TEST(TestAudioTrackGraph, ReOpenAudioInput)
return graph->NotifyWhenDeviceStarted(inputTrack);
});
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(stream->mHasInput);
Unused << WaitFor(p);
@ -604,26 +674,15 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
Unused << unforcer;
/* Primary graph: Create the graph. */
/* Primary graph: Create the graph and a SourceMediaTrack. */
MediaTrackGraph* primary =
MediaTrackGraph::GetInstance(MediaTrackGraph::SYSTEM_THREAD_DRIVER,
MediaTrackGraph::GetInstance(MediaTrackGraph::AUDIO_THREAD_DRIVER,
/*window*/ nullptr, aInputRate, nullptr);
/* Partner graph: Create the graph. */
MediaTrackGraph* partner = MediaTrackGraph::GetInstance(
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr, aOutputRate,
/*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
RefPtr<AudioInputTrack> inputTrack;
RefPtr<AudioInputProcessing> listener;
RefPtr<CrossGraphTransmitter> transmitter;
RefPtr<MediaInputPort> port;
RefPtr<CrossGraphReceiver> receiver;
auto started = Invoke([&] {
/* Primary graph: Create input track and open it */
DispatchFunction([&] {
inputTrack = AudioInputTrack::Create(primary);
listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
inputTrack->GraphImpl()->AppendMessage(
@ -631,9 +690,18 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
inputTrack->SetInputProcessing(listener);
inputTrack->GraphImpl()->AppendMessage(
MakeUnique<StartInputProcessing>(inputTrack, listener));
inputTrack->OpenAudioInput((void*)1, listener);
});
WaitFor(cubeb->StreamInitEvent());
/* Partner graph: Create CrossGraphReceiver */
/* Partner graph: Create graph and the CrossGraphReceiver. */
MediaTrackGraph* partner = MediaTrackGraph::GetInstance(
MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr, aOutputRate,
/*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
RefPtr<CrossGraphReceiver> receiver;
RefPtr<CrossGraphTransmitter> transmitter;
RefPtr<MediaInputPort> port;
DispatchFunction([&] {
receiver = partner->CreateCrossGraphReceiver(primary->GraphRate());
/* Primary graph: Create CrossGraphTransmitter */
@ -644,31 +712,29 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
port = transmitter->AllocateInputPort(inputTrack);
receiver->AddAudioOutput((void*)1);
nsTArray<RefPtr<MediaTrackGraph::GraphStartedPromise>> ps(
{primary->NotifyWhenDeviceStarted(inputTrack),
partner->NotifyWhenDeviceStarted(receiver)});
return MediaTrackGraph::GraphStartedPromise::All(
GetCurrentSerialEventTarget(), ps);
/* Primary graph: Open Audio Input through SourceMediaTrack */
// Device id does not matter. Ignore.
inputTrack->OpenAudioInput((void*)1, listener);
});
RefPtr<SmartMockCubebStream> inputStream;
RefPtr<SmartMockCubebStream> partnerStream;
WaitUntil(cubeb->StreamInitEvent(),
[&](RefPtr<SmartMockCubebStream> aStream) {
if (aStream->mHasInput) {
MOZ_ASSERT(!inputStream);
inputStream = std::move(aStream);
} else {
MOZ_ASSERT(!partnerStream);
partnerStream = std::move(aStream);
}
return inputStream && partnerStream;
});
Unused << WaitFor(started);
MockCubebStream* inputStream = nullptr;
MockCubebStream* partnerStream = nullptr;
// Wait for the streams to be created.
WaitUntil(cubeb->StreamInitEvent(), [&](MockCubebStream* aStream) {
if (aStream->mHasInput) {
inputStream = aStream;
} else {
partnerStream = aStream;
}
return inputStream && partnerStream;
});
partnerStream->SetDriftFactor(aDriftFactor);
// Wait for a second worth of audio data. GoFaster is dispatched through a
// ControlMessage so that it is called in the first audio driver iteration.
// Otherwise the audio driver might be going very fast while the fallback
// system clock driver is still in an iteration.
// Wait for 3s worth of audio data on the receiver stream.
DispatchFunction([&] {
inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
@ -708,9 +774,7 @@ void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
static_cast<uint32_t>(partnerRate * aDriftFactor / 1000 * aBufferMs);
uint32_t margin = partnerRate / 20 /* +/- 50ms */;
EXPECT_NEAR(preSilenceSamples, expectedPreSilence, margin);
// The waveform from AudioGenerator starts at 0, but we don't control its
// ending, so we expect a discontinuity there.
EXPECT_LE(nrDiscontinuities, 1U);
EXPECT_LE(nrDiscontinuities, 2U);
}
TEST(TestAudioTrackGraph, CrossGraphPort)

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

@ -1,88 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* 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 http://mozilla.org/MPL/2.0/. */
#ifndef WAITFOR_H_
#define WAITFOR_H_
#include "MediaEventSource.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
namespace mozilla {
/**
* Waits for an occurrence of aEvent on the current thread (by blocking it,
* except tasks added to the event loop may run) and returns the event's
* templated value, if it's non-void.
*
* The caller must be wary of eventloop issues, in
* particular cases where we rely on a stable state runnable, but there is never
* a task to trigger stable state. In such cases it is the responsibility of the
* caller to create the needed tasks, as JS would. A noteworthy API that relies
* on stable state is MediaTrackGraph::GetInstance.
*/
template <typename T>
T WaitFor(MediaEventSource<T>& aEvent) {
Maybe<T> value;
MediaEventListener listener = aEvent.Connect(
AbstractThread::GetCurrent(), [&](T aValue) { value = Some(aValue); });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return value.isSome(); });
listener.Disconnect();
return value.value();
}
/**
* Specialization of WaitFor<T> for void.
*/
void WaitFor(MediaEventSource<void>& aEvent) {
bool done = false;
MediaEventListener listener =
aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return done; });
listener.Disconnect();
}
/**
* Variant of WaitFor that blocks the caller until a MozPromise has either been
* resolved or rejected.
*/
template <typename R, typename E, bool Exc>
Result<R, E> WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise) {
Maybe<R> success;
Maybe<E> error;
aPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[&](R aResult) { success = Some(aResult); },
[&](E aError) { error = Some(aError); });
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return success.isSome() || error.isSome(); });
if (success.isSome()) {
return success.extract();
}
return Err(error.extract());
}
/**
* A variation of WaitFor that takes a callback to be called each time aEvent is
* raised. Blocks the caller until the callback function returns true.
*/
template <typename T, typename CallbackFunction>
void WaitUntil(MediaEventSource<T>& aEvent, const CallbackFunction& aF) {
bool done = false;
MediaEventListener listener =
aEvent.Connect(AbstractThread::GetCurrent(), [&](T aValue) {
if (!done) {
done = aF(aValue);
}
});
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
[&] { return done; });
listener.Disconnect();
}
} // namespace mozilla
#endif // WAITFOR_H_