mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1495 lines
		
	
	
	
		
			55 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1495 lines
		
	
	
	
		
			55 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* 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/. */
 | 
						|
 | 
						|
#include "GraphDriver.h"
 | 
						|
 | 
						|
#include "AudioNodeEngine.h"
 | 
						|
#include "cubeb/cubeb.h"
 | 
						|
#include "mozilla/dom/AudioContext.h"
 | 
						|
#include "mozilla/dom/AudioDeviceInfo.h"
 | 
						|
#include "mozilla/dom/BaseAudioContextBinding.h"
 | 
						|
#include "mozilla/SchedulerGroup.h"
 | 
						|
#include "mozilla/SharedThreadPool.h"
 | 
						|
#include "mozilla/ClearOnShutdown.h"
 | 
						|
#include "mozilla/Unused.h"
 | 
						|
#include "mozilla/MathAlgorithms.h"
 | 
						|
#include "mozilla/StaticPrefs_media.h"
 | 
						|
#include "CubebDeviceEnumerator.h"
 | 
						|
#include "MediaTrackGraphImpl.h"
 | 
						|
#include "CallbackThreadRegistry.h"
 | 
						|
#include "Tracing.h"
 | 
						|
 | 
						|
#ifdef MOZ_WEBRTC
 | 
						|
#  include "webrtc/MediaEngineWebRTC.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef XP_MACOSX
 | 
						|
#  include <sys/sysctl.h>
 | 
						|
#  include "nsCocoaFeatures.h"
 | 
						|
#endif
 | 
						|
 | 
						|
extern mozilla::LazyLogModule gMediaTrackGraphLog;
 | 
						|
#ifdef LOG
 | 
						|
#  undef LOG
 | 
						|
#endif  // LOG
 | 
						|
#define LOG(type, msg) MOZ_LOG(gMediaTrackGraphLog, type, msg)
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
GraphDriver::GraphDriver(GraphInterface* aGraphInterface,
 | 
						|
                         GraphDriver* aPreviousDriver, uint32_t aSampleRate)
 | 
						|
    : mGraphInterface(aGraphInterface),
 | 
						|
      mSampleRate(aSampleRate),
 | 
						|
      mPreviousDriver(aPreviousDriver) {}
 | 
						|
 | 
						|
void GraphDriver::SetStreamName(const nsACString& aStreamName) {
 | 
						|
  MOZ_ASSERT(InIteration() || (!ThreadRunning() && NS_IsMainThread()));
 | 
						|
  mStreamName = aStreamName;
 | 
						|
  LOG(LogLevel::Debug, ("%p: GraphDriver::SetStreamName driver=%p %s", Graph(),
 | 
						|
                        this, mStreamName.get()));
 | 
						|
}
 | 
						|
 | 
						|
void GraphDriver::SetState(const nsACString& aStreamName,
 | 
						|
                           GraphTime aIterationEnd,
 | 
						|
                           GraphTime aStateComputedTime) {
 | 
						|
  MOZ_ASSERT(InIteration() || !ThreadRunning());
 | 
						|
 | 
						|
  mStreamName = aStreamName;
 | 
						|
  mIterationEnd = aIterationEnd;
 | 
						|
  mStateComputedTime = aStateComputedTime;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
bool GraphDriver::InIteration() const {
 | 
						|
  return OnThread() || Graph()->InDriverIteration(this);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
GraphDriver* GraphDriver::PreviousDriver() {
 | 
						|
  MOZ_ASSERT(InIteration() || !ThreadRunning());
 | 
						|
  return mPreviousDriver;
 | 
						|
}
 | 
						|
 | 
						|
void GraphDriver::SetPreviousDriver(GraphDriver* aPreviousDriver) {
 | 
						|
  MOZ_ASSERT(InIteration() || !ThreadRunning());
 | 
						|
  mPreviousDriver = aPreviousDriver;
 | 
						|
}
 | 
						|
 | 
						|
ThreadedDriver::ThreadedDriver(GraphInterface* aGraphInterface,
 | 
						|
                               GraphDriver* aPreviousDriver,
 | 
						|
                               uint32_t aSampleRate)
 | 
						|
    : GraphDriver(aGraphInterface, aPreviousDriver, aSampleRate),
 | 
						|
      mThreadRunning(false) {}
 | 
						|
 | 
						|
class MediaTrackGraphShutdownThreadRunnable : public Runnable {
 | 
						|
 public:
 | 
						|
  explicit MediaTrackGraphShutdownThreadRunnable(
 | 
						|
      already_AddRefed<nsIThread> aThread)
 | 
						|
      : Runnable("MediaTrackGraphShutdownThreadRunnable"), mThread(aThread) {}
 | 
						|
  NS_IMETHOD Run() override {
 | 
						|
    TRACE("MediaTrackGraphShutdownThreadRunnable");
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    MOZ_ASSERT(mThread);
 | 
						|
 | 
						|
    mThread->AsyncShutdown();
 | 
						|
    mThread = nullptr;
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  nsCOMPtr<nsIThread> mThread;
 | 
						|
};
 | 
						|
 | 
						|
ThreadedDriver::~ThreadedDriver() {
 | 
						|
  if (mThread) {
 | 
						|
    nsCOMPtr<nsIRunnable> event =
 | 
						|
        new MediaTrackGraphShutdownThreadRunnable(mThread.forget());
 | 
						|
    SchedulerGroup::Dispatch(event.forget());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class MediaTrackGraphInitThreadRunnable : public Runnable {
 | 
						|
 public:
 | 
						|
  explicit MediaTrackGraphInitThreadRunnable(ThreadedDriver* aDriver)
 | 
						|
      : Runnable("MediaTrackGraphInitThreadRunnable"), mDriver(aDriver) {}
 | 
						|
  NS_IMETHOD Run() override {
 | 
						|
    TRACE("MediaTrackGraphInitThreadRunnable");
 | 
						|
    MOZ_ASSERT(!mDriver->ThreadRunning());
 | 
						|
    LOG(LogLevel::Debug, ("Starting a new system driver for graph %p",
 | 
						|
                          mDriver->mGraphInterface.get()));
 | 
						|
 | 
						|
    if (GraphDriver* previousDriver = mDriver->PreviousDriver()) {
 | 
						|
      LOG(LogLevel::Debug,
 | 
						|
          ("%p releasing an AudioCallbackDriver(%p), for graph %p",
 | 
						|
           mDriver.get(), previousDriver, mDriver->Graph()));
 | 
						|
      MOZ_ASSERT(!mDriver->AsAudioCallbackDriver());
 | 
						|
      AudioCallbackDriver* audioCallbackDriver =
 | 
						|
          previousDriver->AsAudioCallbackDriver();
 | 
						|
      MOZ_ALWAYS_SUCCEEDS(audioCallbackDriver->mCubebOperationThread->Dispatch(
 | 
						|
          NS_NewRunnableFunction(
 | 
						|
              "ThreadedDriver previousDriver::Stop()",
 | 
						|
              [audioCallbackDriver = RefPtr{audioCallbackDriver}] {
 | 
						|
                audioCallbackDriver->Stop();
 | 
						|
              })));
 | 
						|
      mDriver->SetPreviousDriver(nullptr);
 | 
						|
    }
 | 
						|
 | 
						|
    mDriver->RunThread();
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  RefPtr<ThreadedDriver> mDriver;
 | 
						|
};
 | 
						|
 | 
						|
void ThreadedDriver::Start() {
 | 
						|
  MOZ_ASSERT(!ThreadRunning());
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("Starting thread for a SystemClockDriver  %p", mGraphInterface.get()));
 | 
						|
  Unused << NS_WARN_IF(mThread);
 | 
						|
  MOZ_ASSERT(!mThread);  // Ensure we haven't already started it
 | 
						|
 | 
						|
  nsCOMPtr<nsIRunnable> event = new MediaTrackGraphInitThreadRunnable(this);
 | 
						|
  // Note: mThread may be null during event->Run() if we pass to NewNamedThread!
 | 
						|
  // See AudioInitTask
 | 
						|
  nsresult rv = NS_NewNamedThread("MediaTrackGrph", getter_AddRefs(mThread));
 | 
						|
  if (NS_SUCCEEDED(rv)) {
 | 
						|
    mThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ThreadedDriver::Shutdown() {
 | 
						|
  NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
 | 
						|
  // mGraph's thread is not running so it's OK to do whatever here
 | 
						|
  LOG(LogLevel::Debug, ("Stopping threads for MediaTrackGraph %p", this));
 | 
						|
 | 
						|
  if (mThread) {
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("%p: Stopping ThreadedDriver's %p thread", Graph(), this));
 | 
						|
    mThread->AsyncShutdown();
 | 
						|
    mThread = nullptr;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
SystemClockDriver::SystemClockDriver(GraphInterface* aGraphInterface,
 | 
						|
                                     GraphDriver* aPreviousDriver,
 | 
						|
                                     uint32_t aSampleRate)
 | 
						|
    : ThreadedDriver(aGraphInterface, aPreviousDriver, aSampleRate),
 | 
						|
      mInitialTimeStamp(TimeStamp::Now()),
 | 
						|
      mCurrentTimeStamp(TimeStamp::Now()),
 | 
						|
      mLastTimeStamp(TimeStamp::Now()) {}
 | 
						|
 | 
						|
SystemClockDriver::~SystemClockDriver() = default;
 | 
						|
 | 
						|
void ThreadedDriver::RunThread() {
 | 
						|
  mThreadRunning = true;
 | 
						|
  while (true) {
 | 
						|
    auto iterationStart = mIterationEnd;
 | 
						|
    mIterationEnd += GetIntervalForIteration();
 | 
						|
 | 
						|
    if (mStateComputedTime < mIterationEnd) {
 | 
						|
      LOG(LogLevel::Warning, ("%p: Global underrun detected", Graph()));
 | 
						|
      mIterationEnd = mStateComputedTime;
 | 
						|
    }
 | 
						|
 | 
						|
    if (iterationStart >= mIterationEnd) {
 | 
						|
      NS_ASSERTION(iterationStart == mIterationEnd, "Time can't go backwards!");
 | 
						|
      // This could happen due to low clock resolution, maybe?
 | 
						|
      LOG(LogLevel::Debug, ("%p: Time did not advance", Graph()));
 | 
						|
    }
 | 
						|
 | 
						|
    GraphTime nextStateComputedTime =
 | 
						|
        MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
 | 
						|
            mIterationEnd + MillisecondsToMediaTime(AUDIO_TARGET_MS));
 | 
						|
    if (nextStateComputedTime < mStateComputedTime) {
 | 
						|
      // A previous driver may have been processing further ahead of
 | 
						|
      // iterationEnd.
 | 
						|
      LOG(LogLevel::Warning,
 | 
						|
          ("%p: Prevent state from going backwards. interval[%ld; %ld] "
 | 
						|
           "state[%ld; "
 | 
						|
           "%ld]",
 | 
						|
           Graph(), (long)iterationStart, (long)mIterationEnd,
 | 
						|
           (long)mStateComputedTime, (long)nextStateComputedTime));
 | 
						|
      nextStateComputedTime = mStateComputedTime;
 | 
						|
    }
 | 
						|
    LOG(LogLevel::Verbose,
 | 
						|
        ("%p: interval[%ld; %ld] state[%ld; %ld]", Graph(),
 | 
						|
         (long)iterationStart, (long)mIterationEnd, (long)mStateComputedTime,
 | 
						|
         (long)nextStateComputedTime));
 | 
						|
 | 
						|
    mStateComputedTime = nextStateComputedTime;
 | 
						|
    IterationResult result =
 | 
						|
        Graph()->OneIteration(mStateComputedTime, mIterationEnd, nullptr);
 | 
						|
 | 
						|
    if (result.IsStop()) {
 | 
						|
      // Signal that we're done stopping.
 | 
						|
      result.Stopped();
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    WaitForNextIteration();
 | 
						|
    if (GraphDriver* nextDriver = result.NextDriver()) {
 | 
						|
      LOG(LogLevel::Debug, ("%p: Switching to AudioCallbackDriver", Graph()));
 | 
						|
      result.Switched();
 | 
						|
      nextDriver->SetState(mStreamName, mIterationEnd, mStateComputedTime);
 | 
						|
      nextDriver->Start();
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    MOZ_ASSERT(result.IsStillProcessing());
 | 
						|
  }
 | 
						|
  mThreadRunning = false;
 | 
						|
}
 | 
						|
 | 
						|
MediaTime SystemClockDriver::GetIntervalForIteration() {
 | 
						|
  TimeStamp now = TimeStamp::Now();
 | 
						|
  MediaTime interval =
 | 
						|
      SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds());
 | 
						|
  mCurrentTimeStamp = now;
 | 
						|
 | 
						|
  MOZ_LOG(gMediaTrackGraphLog, LogLevel::Verbose,
 | 
						|
          ("%p: Updating current time to %f (real %f, StateComputedTime() %f)",
 | 
						|
           Graph(), MediaTimeToSeconds(mIterationEnd + interval),
 | 
						|
           (now - mInitialTimeStamp).ToSeconds(),
 | 
						|
           MediaTimeToSeconds(mStateComputedTime)));
 | 
						|
 | 
						|
  return interval;
 | 
						|
}
 | 
						|
 | 
						|
void ThreadedDriver::EnsureNextIteration() {
 | 
						|
  mWaitHelper.EnsureNextIteration();
 | 
						|
}
 | 
						|
 | 
						|
void ThreadedDriver::WaitForNextIteration() {
 | 
						|
  MOZ_ASSERT(mThread);
 | 
						|
  MOZ_ASSERT(OnThread());
 | 
						|
  mWaitHelper.WaitForNextIterationAtLeast(WaitInterval());
 | 
						|
}
 | 
						|
 | 
						|
TimeDuration SystemClockDriver::WaitInterval() {
 | 
						|
  MOZ_ASSERT(mThread);
 | 
						|
  MOZ_ASSERT(OnThread());
 | 
						|
  TimeStamp now = TimeStamp::Now();
 | 
						|
  int64_t timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS -
 | 
						|
                      int64_t((now - mCurrentTimeStamp).ToMilliseconds());
 | 
						|
  // Make sure timeoutMS doesn't overflow 32 bits by waking up at
 | 
						|
  // least once a minute, if we need to wake up at all
 | 
						|
  timeoutMS = std::max<int64_t>(0, std::min<int64_t>(timeoutMS, 60 * 1000));
 | 
						|
  LOG(LogLevel::Verbose,
 | 
						|
      ("%p: Waiting for next iteration; at %f, timeout=%f", Graph(),
 | 
						|
       (now - mInitialTimeStamp).ToSeconds(), timeoutMS / 1000.0));
 | 
						|
 | 
						|
  return TimeDuration::FromMilliseconds(timeoutMS);
 | 
						|
}
 | 
						|
 | 
						|
OfflineClockDriver::OfflineClockDriver(GraphInterface* aGraphInterface,
 | 
						|
                                       uint32_t aSampleRate, GraphTime aSlice)
 | 
						|
    : ThreadedDriver(aGraphInterface, nullptr, aSampleRate), mSlice(aSlice) {}
 | 
						|
 | 
						|
OfflineClockDriver::~OfflineClockDriver() = default;
 | 
						|
 | 
						|
void OfflineClockDriver::RunThread() {
 | 
						|
  nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(mThread);
 | 
						|
  nsCOMPtr<nsIThreadObserver> observer = do_QueryInterface(Graph());
 | 
						|
  threadInternal->SetObserver(observer);
 | 
						|
 | 
						|
  ThreadedDriver::RunThread();
 | 
						|
}
 | 
						|
 | 
						|
MediaTime OfflineClockDriver::GetIntervalForIteration() {
 | 
						|
  return MillisecondsToMediaTime(mSlice);
 | 
						|
}
 | 
						|
 | 
						|
/* Helper to proxy the GraphInterface methods used by a running
 | 
						|
 * mFallbackDriver. */
 | 
						|
class AudioCallbackDriver::FallbackWrapper : public GraphInterface {
 | 
						|
 public:
 | 
						|
  FallbackWrapper(RefPtr<GraphInterface> aGraph,
 | 
						|
                  RefPtr<AudioCallbackDriver> aOwner, uint32_t aSampleRate,
 | 
						|
                  const nsACString& aStreamName, GraphTime aIterationEnd,
 | 
						|
                  GraphTime aStateComputedTime)
 | 
						|
      : mGraph(std::move(aGraph)),
 | 
						|
        mOwner(std::move(aOwner)),
 | 
						|
        mFallbackDriver(
 | 
						|
            MakeRefPtr<SystemClockDriver>(this, nullptr, aSampleRate)) {
 | 
						|
    mFallbackDriver->SetState(aStreamName, aIterationEnd, aStateComputedTime);
 | 
						|
  }
 | 
						|
 | 
						|
  NS_DECL_THREADSAFE_ISUPPORTS
 | 
						|
 | 
						|
  /* Proxied SystemClockDriver methods */
 | 
						|
  void Start() { mFallbackDriver->Start(); }
 | 
						|
  MOZ_CAN_RUN_SCRIPT void Shutdown() {
 | 
						|
    RefPtr<SystemClockDriver> driver = mFallbackDriver;
 | 
						|
    driver->Shutdown();
 | 
						|
  }
 | 
						|
  void SetStreamName(const nsACString& aStreamName) {
 | 
						|
    mFallbackDriver->SetStreamName(aStreamName);
 | 
						|
  }
 | 
						|
  void EnsureNextIteration() { mFallbackDriver->EnsureNextIteration(); }
 | 
						|
#ifdef DEBUG
 | 
						|
  bool InIteration() { return mFallbackDriver->InIteration(); }
 | 
						|
#endif
 | 
						|
  bool OnThread() { return mFallbackDriver->OnThread(); }
 | 
						|
 | 
						|
  /* GraphInterface methods */
 | 
						|
  void NotifyInputStopped() override {
 | 
						|
    MOZ_CRASH("Unexpected NotifyInputStopped from fallback SystemClockDriver");
 | 
						|
  }
 | 
						|
  void NotifyInputData(const AudioDataValue* aBuffer, size_t aFrames,
 | 
						|
                       TrackRate aRate, uint32_t aChannels,
 | 
						|
                       uint32_t aAlreadyBuffered) override {
 | 
						|
    MOZ_CRASH("Unexpected NotifyInputData from fallback SystemClockDriver");
 | 
						|
  }
 | 
						|
  void NotifySetRequestedInputProcessingParamsResult(
 | 
						|
      AudioCallbackDriver* aDriver,
 | 
						|
      cubeb_input_processing_params aRequestedParams,
 | 
						|
      Result<cubeb_input_processing_params, int>&& aResult) override {
 | 
						|
    MOZ_CRASH(
 | 
						|
        "Unexpected processing params result from fallback SystemClockDriver");
 | 
						|
  }
 | 
						|
  void DeviceChanged() override {
 | 
						|
    MOZ_CRASH("Unexpected DeviceChanged from fallback SystemClockDriver");
 | 
						|
  }
 | 
						|
#ifdef DEBUG
 | 
						|
  bool InDriverIteration(const GraphDriver* aDriver) const override {
 | 
						|
    return mGraph->InDriverIteration(mOwner) && mOwner->OnFallback();
 | 
						|
  }
 | 
						|
#endif
 | 
						|
  IterationResult OneIteration(GraphTime aStateComputedEnd,
 | 
						|
                               GraphTime aIterationEnd,
 | 
						|
                               MixerCallbackReceiver* aMixerReceiver) override {
 | 
						|
    MOZ_ASSERT(!aMixerReceiver);
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
    AutoInCallback aic(mOwner);
 | 
						|
#endif
 | 
						|
 | 
						|
    IterationResult result =
 | 
						|
        mGraph->OneIteration(aStateComputedEnd, aIterationEnd, aMixerReceiver);
 | 
						|
 | 
						|
    AudioStreamState audioState = mOwner->mAudioStreamState;
 | 
						|
 | 
						|
    MOZ_ASSERT(audioState != AudioStreamState::Stopping,
 | 
						|
               "The audio driver can only enter stopping if it iterated the "
 | 
						|
               "graph, which it can only do if there's no fallback driver");
 | 
						|
 | 
						|
    // After a devicechange event from the audio driver, wait for a five
 | 
						|
    // millisecond grace period before handing control to the audio driver. We
 | 
						|
    // do this because cubeb leaves no guarantee on audio callbacks coming in
 | 
						|
    // after a device change event.
 | 
						|
    if (audioState == AudioStreamState::ChangingDevice &&
 | 
						|
        mOwner->mChangingDeviceStartTime + TimeDuration::FromMilliseconds(5) <
 | 
						|
            TimeStamp::Now()) {
 | 
						|
      mOwner->mChangingDeviceStartTime = TimeStamp();
 | 
						|
      if (mOwner->mAudioStreamState.compareExchange(
 | 
						|
              AudioStreamState::ChangingDevice, AudioStreamState::Starting)) {
 | 
						|
        audioState = AudioStreamState::Starting;
 | 
						|
        LOG(LogLevel::Debug, ("%p: Fallback driver has started. Waiting for "
 | 
						|
                              "audio driver to start.",
 | 
						|
                              mOwner.get()));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (audioState != AudioStreamState::Running && result.IsStillProcessing()) {
 | 
						|
      mOwner->MaybeStartAudioStream();
 | 
						|
      return result;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(result.IsStillProcessing() || result.IsStop() ||
 | 
						|
               result.IsSwitchDriver());
 | 
						|
 | 
						|
    // Proxy the release of the fallback driver to a background thread, so it
 | 
						|
    // doesn't perform unexpected suicide.
 | 
						|
    IterationResult stopFallback =
 | 
						|
        IterationResult::CreateStop(NS_NewRunnableFunction(
 | 
						|
            "AudioCallbackDriver::FallbackDriverStopped",
 | 
						|
            [self = RefPtr<FallbackWrapper>(this), this, aIterationEnd,
 | 
						|
             aStateComputedEnd, result = std::move(result)]() mutable {
 | 
						|
              FallbackDriverState fallbackState =
 | 
						|
                  result.IsStillProcessing() ? FallbackDriverState::None
 | 
						|
                                             : FallbackDriverState::Stopped;
 | 
						|
              mOwner->FallbackDriverStopped(aIterationEnd, aStateComputedEnd,
 | 
						|
                                            fallbackState);
 | 
						|
 | 
						|
              if (fallbackState == FallbackDriverState::Stopped) {
 | 
						|
#ifdef DEBUG
 | 
						|
                // The AudioCallbackDriver may not iterate the graph, but we'll
 | 
						|
                // call into it so we need to be regarded as "in iteration".
 | 
						|
                AutoInCallback aic(mOwner);
 | 
						|
#endif
 | 
						|
                if (GraphDriver* nextDriver = result.NextDriver()) {
 | 
						|
                  LOG(LogLevel::Debug,
 | 
						|
                      ("%p: Switching from fallback to other driver.",
 | 
						|
                       mOwner.get()));
 | 
						|
                  result.Switched();
 | 
						|
                  nextDriver->SetState(mOwner->mStreamName, aIterationEnd,
 | 
						|
                                       aStateComputedEnd);
 | 
						|
                  nextDriver->Start();
 | 
						|
                } else if (result.IsStop()) {
 | 
						|
                  LOG(LogLevel::Debug,
 | 
						|
                      ("%p: Stopping fallback driver.", mOwner.get()));
 | 
						|
                  result.Stopped();
 | 
						|
                }
 | 
						|
              }
 | 
						|
              mOwner = nullptr;
 | 
						|
              NS_DispatchBackgroundTask(NS_NewRunnableFunction(
 | 
						|
                  "AudioCallbackDriver::FallbackDriverStopped::Release",
 | 
						|
                  [fallback = std::move(self->mFallbackDriver)] {}));
 | 
						|
            }));
 | 
						|
 | 
						|
    return stopFallback;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual ~FallbackWrapper() = default;
 | 
						|
 | 
						|
  const RefPtr<GraphInterface> mGraph;
 | 
						|
  // Valid until mFallbackDriver has finished its last iteration.
 | 
						|
  RefPtr<AudioCallbackDriver> mOwner;
 | 
						|
  RefPtr<SystemClockDriver> mFallbackDriver;
 | 
						|
};
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS0(AudioCallbackDriver::FallbackWrapper)
 | 
						|
 | 
						|
/* static */
 | 
						|
already_AddRefed<TaskQueue> AudioCallbackDriver::CreateTaskQueue() {
 | 
						|
  return TaskQueue::Create(CubebUtils::GetCubebOperationThread(),
 | 
						|
                           "AudioCallbackDriver cubeb task queue")
 | 
						|
      .forget();
 | 
						|
}
 | 
						|
 | 
						|
AudioCallbackDriver::AudioCallbackDriver(
 | 
						|
    GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
 | 
						|
    uint32_t aSampleRate, uint32_t aOutputChannelCount,
 | 
						|
    uint32_t aInputChannelCount, CubebUtils::AudioDeviceID aOutputDeviceID,
 | 
						|
    CubebUtils::AudioDeviceID aInputDeviceID, AudioInputType aAudioInputType,
 | 
						|
    cubeb_input_processing_params aRequestedInputProcessingParams)
 | 
						|
    : GraphDriver(aGraphInterface, aPreviousDriver, aSampleRate),
 | 
						|
      mOutputChannelCount(aOutputChannelCount),
 | 
						|
      mInputChannelCount(aInputChannelCount),
 | 
						|
      mOutputDeviceID(aOutputDeviceID),
 | 
						|
      mInputDeviceID(aInputDeviceID),
 | 
						|
      mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS),
 | 
						|
      mCubebOperationThread(CreateTaskQueue()),
 | 
						|
      mRequestedInputProcessingParams(aRequestedInputProcessingParams),
 | 
						|
      mAudioThreadId(ProfilerThreadId{}),
 | 
						|
      mAudioThreadIdInCb(std::thread::id()),
 | 
						|
      mFallback("AudioCallbackDriver::mFallback"),
 | 
						|
      mSandboxed(CubebUtils::SandboxEnabled()) {
 | 
						|
  LOG(LogLevel::Debug, ("%p: AudioCallbackDriver %p ctor - input: device %p, "
 | 
						|
                        "channel %d, output: device %p, channel %d",
 | 
						|
                        Graph(), this, mInputDeviceID, mInputChannelCount,
 | 
						|
                        mOutputDeviceID, mOutputChannelCount));
 | 
						|
 | 
						|
  NS_WARNING_ASSERTION(mOutputChannelCount != 0,
 | 
						|
                       "Invalid output channel count");
 | 
						|
 | 
						|
  if (aAudioInputType == AudioInputType::Voice &&
 | 
						|
      StaticPrefs::
 | 
						|
          media_getusermedia_microphone_prefer_voice_stream_with_processing_enabled()) {
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("%p: AudioCallbackDriver %p ctor - using VOICE and requesting input "
 | 
						|
         "processing params %s.",
 | 
						|
         Graph(), this,
 | 
						|
         CubebUtils::ProcessingParamsToString(aRequestedInputProcessingParams)
 | 
						|
             .get()));
 | 
						|
    mInputDevicePreference = CUBEB_DEVICE_PREF_VOICE;
 | 
						|
    CubebUtils::SetInCommunication(true);
 | 
						|
  } else {
 | 
						|
    mInputDevicePreference = CUBEB_DEVICE_PREF_ALL;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
AudioCallbackDriver::~AudioCallbackDriver() {
 | 
						|
  if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE) {
 | 
						|
    CubebUtils::SetInCommunication(false);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool IsMacbookOrMacbookAir() {
 | 
						|
#ifdef XP_MACOSX
 | 
						|
  size_t len = 0;
 | 
						|
  sysctlbyname("hw.model", NULL, &len, NULL, 0);
 | 
						|
  if (len) {
 | 
						|
    UniquePtr<char[]> model(new char[len]);
 | 
						|
    // This string can be
 | 
						|
    // MacBook%d,%d for a normal MacBook
 | 
						|
    // MacBookAir%d,%d for a Macbook Air
 | 
						|
    sysctlbyname("hw.model", model.get(), &len, NULL, 0);
 | 
						|
    char* substring = strstr(model.get(), "MacBook");
 | 
						|
    if (substring) {
 | 
						|
      const size_t offset = strlen("MacBook");
 | 
						|
      if (!strncmp(model.get() + offset, "Air", 3) ||
 | 
						|
          isdigit(model[offset + 1])) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::Init(const nsCString& aStreamName) {
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: AudioCallbackDriver::Init driver=%p", Graph(), this));
 | 
						|
  TRACE("AudioCallbackDriver::Init");
 | 
						|
  MOZ_ASSERT(OnCubebOperationThread());
 | 
						|
  MOZ_ASSERT(mAudioStreamState == AudioStreamState::Pending);
 | 
						|
  if (mFallbackDriverState == FallbackDriverState::Stopped) {
 | 
						|
    // The graph has already stopped us.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  RefPtr<CubebUtils::CubebHandle> handle = CubebUtils::GetCubeb();
 | 
						|
  if (!handle) {
 | 
						|
    NS_WARNING("Could not get cubeb context.");
 | 
						|
    LOG(LogLevel::Warning, ("%s: Could not get cubeb context", __func__));
 | 
						|
    mAudioStreamState = AudioStreamState::None;
 | 
						|
    if (TryStartingFallbackDriver().isOk()) {
 | 
						|
      CubebUtils::ReportCubebStreamInitFailure(true);
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  cubeb_stream_params output;
 | 
						|
  cubeb_stream_params input;
 | 
						|
  bool firstStream = CubebUtils::GetFirstStream();
 | 
						|
 | 
						|
  MOZ_ASSERT(!NS_IsMainThread(),
 | 
						|
             "This is blocking and should never run on the main thread.");
 | 
						|
 | 
						|
  output.rate = mSampleRate;
 | 
						|
  output.format = CUBEB_SAMPLE_FLOAT32NE;
 | 
						|
 | 
						|
  if (!mOutputChannelCount) {
 | 
						|
    LOG(LogLevel::Warning, ("Output number of channels is 0."));
 | 
						|
    mAudioStreamState = AudioStreamState::None;
 | 
						|
    if (TryStartingFallbackDriver().isOk()) {
 | 
						|
      CubebUtils::ReportCubebStreamInitFailure(firstStream);
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  CubebUtils::AudioDeviceID forcedOutputDeviceId = nullptr;
 | 
						|
 | 
						|
  char* forcedOutputDeviceName = CubebUtils::GetForcedOutputDevice();
 | 
						|
  if (forcedOutputDeviceName) {
 | 
						|
    RefPtr<CubebDeviceEnumerator> enumerator = Enumerator::GetInstance();
 | 
						|
    RefPtr<AudioDeviceInfo> device = enumerator->DeviceInfoFromName(
 | 
						|
        NS_ConvertUTF8toUTF16(forcedOutputDeviceName), EnumeratorSide::OUTPUT);
 | 
						|
    if (device && device->DeviceID()) {
 | 
						|
      forcedOutputDeviceId = device->DeviceID();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  mBuffer = AudioCallbackBufferWrapper<AudioDataValue>(mOutputChannelCount);
 | 
						|
  mScratchBuffer =
 | 
						|
      SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2>(mOutputChannelCount);
 | 
						|
 | 
						|
  output.channels = mOutputChannelCount;
 | 
						|
  AudioConfig::ChannelLayout::ChannelMap channelMap =
 | 
						|
      AudioConfig::ChannelLayout(mOutputChannelCount).Map();
 | 
						|
 | 
						|
  output.layout = static_cast<uint32_t>(channelMap);
 | 
						|
  output.prefs = CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT);
 | 
						|
  if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE &&
 | 
						|
      CubebUtils::RouteOutputAsVoice()) {
 | 
						|
    output.prefs |= static_cast<cubeb_stream_prefs>(CUBEB_STREAM_PREF_VOICE);
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t latencyFrames = CubebUtils::GetCubebMTGLatencyInFrames(&output);
 | 
						|
 | 
						|
  LOG(LogLevel::Debug, ("Minimum latency in frames: %d", latencyFrames));
 | 
						|
 | 
						|
  // Macbook and MacBook air don't have enough CPU to run very low latency
 | 
						|
  // MediaTrackGraphs, cap the minimal latency to 512 frames int this case.
 | 
						|
  if (IsMacbookOrMacbookAir()) {
 | 
						|
    latencyFrames = std::max((uint32_t)512, latencyFrames);
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("Macbook or macbook air, new latency: %d", latencyFrames));
 | 
						|
  }
 | 
						|
 | 
						|
  // Buffer sizes lower than 10ms are nowadays common. It's not very useful
 | 
						|
  // when doing voice, because all the WebRTC code that does audio input
 | 
						|
  // processing deals in 10ms chunks of audio. Take the first power of two
 | 
						|
  // above 10ms at the current rate in this case. It's probably 512, for common
 | 
						|
  // rates.
 | 
						|
  if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE) {
 | 
						|
    if (latencyFrames < mSampleRate / 100) {
 | 
						|
      latencyFrames = mozilla::RoundUpPow2(mSampleRate / 100);
 | 
						|
      LOG(LogLevel::Debug,
 | 
						|
          ("AudioProcessing enabled, new latency %d", latencyFrames));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // It's not useful for the graph to run with a block size lower than the Web
 | 
						|
  // Audio API block size, but increasingly devices report that they can do
 | 
						|
  // audio latencies lower than that.
 | 
						|
  if (latencyFrames < WEBAUDIO_BLOCK_SIZE) {
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("Latency clamped to %d from %d", WEBAUDIO_BLOCK_SIZE, latencyFrames));
 | 
						|
    latencyFrames = WEBAUDIO_BLOCK_SIZE;
 | 
						|
  }
 | 
						|
  LOG(LogLevel::Debug, ("Effective latency in frames: %d", latencyFrames));
 | 
						|
 | 
						|
  input = output;
 | 
						|
  input.channels = mInputChannelCount;
 | 
						|
  input.layout = CUBEB_LAYOUT_UNDEFINED;
 | 
						|
  input.prefs = CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_INPUT);
 | 
						|
  if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE) {
 | 
						|
    input.prefs |= static_cast<cubeb_stream_prefs>(CUBEB_STREAM_PREF_VOICE);
 | 
						|
  }
 | 
						|
 | 
						|
  cubeb_stream* stream = nullptr;
 | 
						|
  const char* streamName =
 | 
						|
      aStreamName.IsEmpty() ? "AudioCallbackDriver" : aStreamName.get();
 | 
						|
  bool inputWanted = mInputChannelCount > 0;
 | 
						|
  CubebUtils::AudioDeviceID outputId = mOutputDeviceID;
 | 
						|
  CubebUtils::AudioDeviceID inputId = mInputDeviceID;
 | 
						|
 | 
						|
  if (CubebUtils::CubebStreamInit(
 | 
						|
          handle->Context(), &stream, streamName, inputId,
 | 
						|
          inputWanted ? &input : nullptr,
 | 
						|
          forcedOutputDeviceId ? forcedOutputDeviceId : outputId, &output,
 | 
						|
          latencyFrames, DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
 | 
						|
    mCubeb = handle;
 | 
						|
    mAudioStream.own(stream);
 | 
						|
    DebugOnly<int> rv =
 | 
						|
        cubeb_stream_set_volume(mAudioStream, CubebUtils::GetVolumeScale());
 | 
						|
    NS_WARNING_ASSERTION(
 | 
						|
        rv == CUBEB_OK,
 | 
						|
        "Could not set the audio stream volume in GraphDriver.cpp");
 | 
						|
    CubebUtils::ReportCubebBackendUsed();
 | 
						|
  } else {
 | 
						|
    NS_WARNING(
 | 
						|
        "Could not create a cubeb stream for MediaTrackGraph, falling "
 | 
						|
        "back to a SystemClockDriver");
 | 
						|
    mAudioStreamState = AudioStreamState::None;
 | 
						|
    // Only report failures when we're not coming from a driver that was
 | 
						|
    // created itself as a fallback driver because of a previous audio driver
 | 
						|
    // failure.
 | 
						|
    if (TryStartingFallbackDriver().isOk()) {
 | 
						|
      CubebUtils::ReportCubebStreamInitFailure(firstStream);
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef XP_MACOSX
 | 
						|
  PanOutputIfNeeded(inputWanted);
 | 
						|
#endif
 | 
						|
 | 
						|
  if (inputWanted && InputDevicePreference() == AudioInputType::Voice) {
 | 
						|
    SetInputProcessingParams(mRequestedInputProcessingParams);
 | 
						|
  }
 | 
						|
 | 
						|
  cubeb_stream_register_device_changed_callback(
 | 
						|
      mAudioStream, AudioCallbackDriver::DeviceChangedCallback_s);
 | 
						|
 | 
						|
  // No-op if MOZ_DUMP_AUDIO is not defined as an environment variable. This
 | 
						|
  // is intended for diagnosing issues, and only works if the content sandbox is
 | 
						|
  // disabled.
 | 
						|
  mInputStreamFile.Open("GraphDriverInput", input.channels, input.rate);
 | 
						|
  mOutputStreamFile.Open("GraphDriverOutput", output.channels, output.rate);
 | 
						|
 | 
						|
  if (NS_WARN_IF(!StartStream())) {
 | 
						|
    LOG(LogLevel::Warning,
 | 
						|
        ("%p: AudioCallbackDriver couldn't start a cubeb stream.", Graph()));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  LOG(LogLevel::Debug, ("%p: AudioCallbackDriver started.", Graph()));
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::SetCubebStreamName(const nsCString& aStreamName) {
 | 
						|
  MOZ_ASSERT(OnCubebOperationThread());
 | 
						|
  MOZ_ASSERT(mAudioStream);
 | 
						|
  cubeb_stream_set_name(mAudioStream, aStreamName.get());
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::Start() {
 | 
						|
  MOZ_ASSERT(!IsStarted());
 | 
						|
  MOZ_ASSERT(mAudioStreamState == AudioStreamState::None);
 | 
						|
  MOZ_ASSERT_IF(PreviousDriver(), PreviousDriver()->InIteration());
 | 
						|
  mAudioStreamState = AudioStreamState::Pending;
 | 
						|
 | 
						|
  // Starting an audio driver could take a while. We start a system driver in
 | 
						|
  // the meantime so that the graph is kept running.
 | 
						|
  (void)TryStartingFallbackDriver();
 | 
						|
 | 
						|
  if (mPreviousDriver) {
 | 
						|
    if (AudioCallbackDriver* previousAudioCallback =
 | 
						|
            mPreviousDriver->AsAudioCallbackDriver()) {
 | 
						|
      LOG(LogLevel::Debug, ("Releasing audio driver off main thread."));
 | 
						|
      MOZ_ALWAYS_SUCCEEDS(
 | 
						|
          previousAudioCallback->mCubebOperationThread->Dispatch(
 | 
						|
              NS_NewRunnableFunction(
 | 
						|
                  "AudioCallbackDriver previousDriver::Stop()",
 | 
						|
                  [previousDriver = RefPtr{previousAudioCallback}] {
 | 
						|
                    previousDriver->Stop();
 | 
						|
                  })));
 | 
						|
    } else {
 | 
						|
      LOG(LogLevel::Debug,
 | 
						|
          ("Dropping driver reference for SystemClockDriver."));
 | 
						|
      MOZ_ASSERT(mPreviousDriver->AsSystemClockDriver());
 | 
						|
    }
 | 
						|
    mPreviousDriver = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  LOG(LogLevel::Debug, ("Starting new audio driver off main thread, "
 | 
						|
                        "to ensure it runs after previous shutdown."));
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(mCubebOperationThread->Dispatch(
 | 
						|
      NS_NewRunnableFunction("AudioCallbackDriver Init()",
 | 
						|
                             [self = RefPtr{this}, streamName = mStreamName] {
 | 
						|
                               self->Init(streamName);
 | 
						|
                             })));
 | 
						|
}
 | 
						|
 | 
						|
bool AudioCallbackDriver::StartStream() {
 | 
						|
  TRACE("AudioCallbackDriver::StartStream");
 | 
						|
  MOZ_ASSERT(!IsStarted() && OnCubebOperationThread());
 | 
						|
  // Set STARTING before cubeb_stream_start, since starting the cubeb stream
 | 
						|
  // can result in a callback (that may read mAudioStreamState) before
 | 
						|
  // mAudioStreamState would otherwise be set.
 | 
						|
  mAudioStreamState = AudioStreamState::Starting;
 | 
						|
  if (cubeb_stream_start(mAudioStream) != CUBEB_OK) {
 | 
						|
    NS_WARNING("Could not start cubeb stream for MTG.");
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::Stop() {
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: AudioCallbackDriver::Stop driver=%p", Graph(), this));
 | 
						|
  TRACE("AudioCallbackDriver::Stop");
 | 
						|
  MOZ_ASSERT(OnCubebOperationThread());
 | 
						|
  cubeb_stream_register_device_changed_callback(mAudioStream, nullptr);
 | 
						|
  if (cubeb_stream_stop(mAudioStream) != CUBEB_OK) {
 | 
						|
    NS_WARNING("Could not stop cubeb stream for MTG.");
 | 
						|
  } else {
 | 
						|
    mAudioStreamState = AudioStreamState::None;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::Shutdown() {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  RefPtr<FallbackWrapper> fallback;
 | 
						|
  {
 | 
						|
    auto fallbackLock = mFallback.Lock();
 | 
						|
    fallback = fallbackLock.ref();
 | 
						|
    fallbackLock.ref() = nullptr;
 | 
						|
  }
 | 
						|
  if (fallback) {
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("%p: Releasing fallback driver %p.", Graph(), fallback.get()));
 | 
						|
    fallback->Shutdown();
 | 
						|
  }
 | 
						|
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: Releasing audio driver off main thread (GraphDriver::Shutdown).",
 | 
						|
       Graph()));
 | 
						|
 | 
						|
  nsLiteralCString reason("AudioCallbackDriver::Shutdown");
 | 
						|
  NS_DispatchAndSpinEventLoopUntilComplete(
 | 
						|
      reason, mCubebOperationThread,
 | 
						|
      NS_NewRunnableFunction(reason.get(),
 | 
						|
                             [self = RefPtr{this}] { self->Stop(); }));
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::SetStreamName(const nsACString& aStreamName) {
 | 
						|
  MOZ_ASSERT(InIteration() || !ThreadRunning());
 | 
						|
  if (aStreamName == mStreamName) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  // Record the stream name, which will be passed onto the next driver, if
 | 
						|
  // any, either from this driver or the fallback driver.
 | 
						|
  GraphDriver::SetStreamName(aStreamName);
 | 
						|
  {
 | 
						|
    auto fallbackLock = mFallback.Lock();
 | 
						|
    FallbackWrapper* fallback = fallbackLock.ref().get();
 | 
						|
    if (fallback) {
 | 
						|
      MOZ_ASSERT(fallback->InIteration());
 | 
						|
      fallback->SetStreamName(aStreamName);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  AudioStreamState streamState = mAudioStreamState;
 | 
						|
  if (streamState != AudioStreamState::None &&
 | 
						|
      streamState != AudioStreamState::Stopping) {
 | 
						|
    MOZ_ALWAYS_SUCCEEDS(mCubebOperationThread->Dispatch(
 | 
						|
        NS_NewRunnableFunction("AudioCallbackDriver SetStreamName()",
 | 
						|
                               [self = RefPtr{this}, streamName = mStreamName] {
 | 
						|
                                 self->SetCubebStreamName(streamName);
 | 
						|
                               })));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
long AudioCallbackDriver::DataCallback_s(cubeb_stream* aStream, void* aUser,
 | 
						|
                                         const void* aInputBuffer,
 | 
						|
                                         void* aOutputBuffer, long aFrames) {
 | 
						|
  AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
 | 
						|
  return driver->DataCallback(static_cast<const AudioDataValue*>(aInputBuffer),
 | 
						|
                              static_cast<AudioDataValue*>(aOutputBuffer),
 | 
						|
                              aFrames);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
void AudioCallbackDriver::StateCallback_s(cubeb_stream* aStream, void* aUser,
 | 
						|
                                          cubeb_state aState) {
 | 
						|
  AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
 | 
						|
  driver->StateCallback(aState);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
void AudioCallbackDriver::DeviceChangedCallback_s(void* aUser) {
 | 
						|
  AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
 | 
						|
  driver->DeviceChangedCallback();
 | 
						|
}
 | 
						|
 | 
						|
AudioCallbackDriver::AutoInCallback::AutoInCallback(
 | 
						|
    AudioCallbackDriver* aDriver)
 | 
						|
    : mDriver(aDriver) {
 | 
						|
  MOZ_ASSERT(mDriver->mAudioThreadIdInCb == std::thread::id());
 | 
						|
  mDriver->mAudioThreadIdInCb = std::this_thread::get_id();
 | 
						|
}
 | 
						|
 | 
						|
AudioCallbackDriver::AutoInCallback::~AutoInCallback() {
 | 
						|
  MOZ_ASSERT(mDriver->mAudioThreadIdInCb == std::this_thread::get_id());
 | 
						|
  mDriver->mAudioThreadIdInCb = std::thread::id();
 | 
						|
}
 | 
						|
 | 
						|
bool AudioCallbackDriver::CheckThreadIdChanged() {
 | 
						|
  ProfilerThreadId id = profiler_current_thread_id();
 | 
						|
  if (id != mAudioThreadId) {
 | 
						|
    mAudioThreadId = id;
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
 | 
						|
                                       AudioDataValue* aOutputBuffer,
 | 
						|
                                       long aFrames) {
 | 
						|
  if (!mSandboxed && CheckThreadIdChanged()) {
 | 
						|
    CallbackThreadRegistry::Get()->Register(mAudioThreadId,
 | 
						|
                                            "NativeAudioCallback");
 | 
						|
  }
 | 
						|
 | 
						|
  if (mAudioStreamState.compareExchange(AudioStreamState::Starting,
 | 
						|
                                        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::Stopped)) {
 | 
						|
    // We're supposed to stop.
 | 
						|
    PodZero(aOutputBuffer, aFrames * mOutputChannelCount);
 | 
						|
    if (!mSandboxed) {
 | 
						|
      CallbackThreadRegistry::Get()->Unregister(mAudioThreadId);
 | 
						|
    }
 | 
						|
    return aFrames - 1;
 | 
						|
  }
 | 
						|
 | 
						|
  AudioStreamState audioStreamState = mAudioStreamState;
 | 
						|
  if (MOZ_UNLIKELY(audioStreamState == AudioStreamState::ChangingDevice ||
 | 
						|
                   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;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(audioStreamState == AudioStreamState::Running);
 | 
						|
  TRACE_AUDIO_CALLBACK_BUDGET("AudioCallbackDriver real-time budget", aFrames,
 | 
						|
                              mSampleRate);
 | 
						|
  TRACE("AudioCallbackDriver::DataCallback");
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  AutoInCallback aic(this);
 | 
						|
#endif
 | 
						|
 | 
						|
  uint32_t durationMS = aFrames * 1000 / mSampleRate;
 | 
						|
 | 
						|
  // For now, simply average the duration with the previous
 | 
						|
  // duration so there is some damping against sudden changes.
 | 
						|
  if (!mIterationDurationMS) {
 | 
						|
    mIterationDurationMS = durationMS;
 | 
						|
  } else {
 | 
						|
    mIterationDurationMS = (mIterationDurationMS * 3) + durationMS;
 | 
						|
    mIterationDurationMS /= 4;
 | 
						|
  }
 | 
						|
 | 
						|
  mBuffer.SetBuffer(aOutputBuffer, aFrames);
 | 
						|
  // fill part or all with leftover data from last iteration (since we
 | 
						|
  // align to Audio blocks)
 | 
						|
  uint32_t alreadyBuffered = mScratchBuffer.Empty(mBuffer);
 | 
						|
 | 
						|
  // State computed time is decided by the audio callback's buffer length. We
 | 
						|
  // compute the iteration start and end from there, trying to keep the amount
 | 
						|
  // of buffering in the graph constant.
 | 
						|
  GraphTime nextStateComputedTime =
 | 
						|
      MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(mStateComputedTime +
 | 
						|
                                                    mBuffer.Available());
 | 
						|
 | 
						|
  auto iterationStart = mIterationEnd;
 | 
						|
  // inGraph is the number of audio frames there is between the state time and
 | 
						|
  // the current time, i.e. the maximum theoretical length of the interval we
 | 
						|
  // could use as [iterationStart; mIterationEnd].
 | 
						|
  GraphTime inGraph = mStateComputedTime - iterationStart;
 | 
						|
  // We want the interval [iterationStart; mIterationEnd] to be before the
 | 
						|
  // interval [mStateComputedTime; nextStateComputedTime]. We also want
 | 
						|
  // the distance between these intervals to be roughly equivalent each time, to
 | 
						|
  // ensure there is no clock drift between current time and state time. Since
 | 
						|
  // we can't act on the state time because we have to fill the audio buffer, we
 | 
						|
  // reclock the current time against the state time, here.
 | 
						|
  mIterationEnd = iterationStart + 0.8 * inGraph;
 | 
						|
 | 
						|
  LOG(LogLevel::Verbose,
 | 
						|
      ("%p: interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) "
 | 
						|
       "(duration ticks: %ld)",
 | 
						|
       Graph(), (long)iterationStart, (long)mIterationEnd,
 | 
						|
       (long)mStateComputedTime, (long)nextStateComputedTime, (long)aFrames,
 | 
						|
       (uint32_t)durationMS,
 | 
						|
       (long)(nextStateComputedTime - mStateComputedTime)));
 | 
						|
 | 
						|
  if (mStateComputedTime < mIterationEnd) {
 | 
						|
    LOG(LogLevel::Error, ("%p: Media graph global underrun detected", Graph()));
 | 
						|
    MOZ_ASSERT_UNREACHABLE("We should not underrun in full duplex");
 | 
						|
    mIterationEnd = mStateComputedTime;
 | 
						|
  }
 | 
						|
 | 
						|
  // Process mic data if any/needed
 | 
						|
  if (aInputBuffer && mInputChannelCount > 0) {
 | 
						|
    Graph()->NotifyInputData(aInputBuffer, static_cast<size_t>(aFrames),
 | 
						|
                             mSampleRate, mInputChannelCount, alreadyBuffered);
 | 
						|
  }
 | 
						|
 | 
						|
  IterationResult result =
 | 
						|
      Graph()->OneIteration(nextStateComputedTime, mIterationEnd, this);
 | 
						|
 | 
						|
  mStateComputedTime = nextStateComputedTime;
 | 
						|
 | 
						|
  MOZ_ASSERT(mBuffer.Available() == 0,
 | 
						|
             "The graph should have filled the buffer");
 | 
						|
 | 
						|
  mBuffer.BufferFilled();
 | 
						|
 | 
						|
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
 | 
						|
  // Prevent returning NaN to the OS mixer, and propagating NaN into the reverse
 | 
						|
  // stream of the AEC.
 | 
						|
  NaNToZeroInPlace(aOutputBuffer, aFrames * mOutputChannelCount);
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef XP_MACOSX
 | 
						|
  // This only happens when the output is on a macbookpro's external speaker,
 | 
						|
  // that are stereo, but let's just be safe.
 | 
						|
  if (mNeedsPanning && mOutputChannelCount == 2) {
 | 
						|
    // hard pan to the right
 | 
						|
    for (uint32_t i = 0; i < aFrames * 2; i += 2) {
 | 
						|
      aOutputBuffer[i + 1] += aOutputBuffer[i];
 | 
						|
      aOutputBuffer[i] = 0.0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  // No-op if MOZ_DUMP_AUDIO is not defined as an environment variable
 | 
						|
  if (aInputBuffer) {
 | 
						|
    mInputStreamFile.Write(static_cast<const AudioDataValue*>(aInputBuffer),
 | 
						|
                           aFrames * mInputChannelCount);
 | 
						|
  }
 | 
						|
  mOutputStreamFile.Write(static_cast<const AudioDataValue*>(aOutputBuffer),
 | 
						|
                          aFrames * mOutputChannelCount);
 | 
						|
 | 
						|
  if (result.IsStop()) {
 | 
						|
    if (mInputDeviceID) {
 | 
						|
      mGraphInterface->NotifyInputStopped();
 | 
						|
    }
 | 
						|
    // Signal that we have stopped.
 | 
						|
    result.Stopped();
 | 
						|
    // Update the flag before handing over the graph and going to drain.
 | 
						|
    mAudioStreamState = AudioStreamState::Stopping;
 | 
						|
    if (!mSandboxed) {
 | 
						|
      CallbackThreadRegistry::Get()->Unregister(mAudioThreadId);
 | 
						|
    }
 | 
						|
    return aFrames - 1;
 | 
						|
  }
 | 
						|
 | 
						|
  if (GraphDriver* nextDriver = result.NextDriver()) {
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("%p: Switching to %s driver.", Graph(),
 | 
						|
         nextDriver->AsAudioCallbackDriver() ? "audio" : "system"));
 | 
						|
    if (mInputDeviceID) {
 | 
						|
      mGraphInterface->NotifyInputStopped();
 | 
						|
    }
 | 
						|
    result.Switched();
 | 
						|
    mAudioStreamState = AudioStreamState::Stopping;
 | 
						|
    nextDriver->SetState(mStreamName, mIterationEnd, mStateComputedTime);
 | 
						|
    nextDriver->Start();
 | 
						|
    if (!mSandboxed) {
 | 
						|
      CallbackThreadRegistry::Get()->Unregister(mAudioThreadId);
 | 
						|
    }
 | 
						|
    // Returning less than aFrames starts the draining and eventually stops the
 | 
						|
    // audio thread. This function will never get called again.
 | 
						|
    return aFrames - 1;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(result.IsStillProcessing());
 | 
						|
  return aFrames;
 | 
						|
}
 | 
						|
 | 
						|
static const char* StateToString(cubeb_state aState) {
 | 
						|
  switch (aState) {
 | 
						|
    case CUBEB_STATE_STARTED:
 | 
						|
      return "STARTED";
 | 
						|
    case CUBEB_STATE_STOPPED:
 | 
						|
      return "STOPPED";
 | 
						|
    case CUBEB_STATE_DRAINED:
 | 
						|
      return "DRAINED";
 | 
						|
    case CUBEB_STATE_ERROR:
 | 
						|
      return "ERROR";
 | 
						|
    default:
 | 
						|
      MOZ_CRASH("Unexpected state!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::StateCallback(cubeb_state aState) {
 | 
						|
  MOZ_ASSERT(!InIteration());
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("AudioCallbackDriver(%p) State: %s", this, StateToString(aState)));
 | 
						|
 | 
						|
  if (aState == CUBEB_STATE_STARTED || aState == CUBEB_STATE_STOPPED) {
 | 
						|
    // Nothing to do for STARTED.
 | 
						|
    //
 | 
						|
    // For STOPPED, don't reset mAudioStreamState until after
 | 
						|
    // cubeb_stream_stop() returns, as wasapi_stream_stop() dispatches
 | 
						|
    // CUBEB_STATE_STOPPED before ensuring that data callbacks have finished.
 | 
						|
    // https://searchfox.org/mozilla-central/rev/f9beb753a84aa297713d1565dcd0c5e3c66e4174/media/libcubeb/src/cubeb_wasapi.cpp#3009,3012
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AudioStreamState streamState = mAudioStreamState;
 | 
						|
  if (streamState < AudioStreamState::Starting) {
 | 
						|
    // mAudioStream has already entered STOPPED, DRAINED, or ERROR.
 | 
						|
    // Don't reset a Pending state indicating that a task to destroy
 | 
						|
    // mAudioStream and init a new cubeb_stream has already been triggered.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Reset for DRAINED or ERROR.
 | 
						|
  streamState = mAudioStreamState.exchange(AudioStreamState::None);
 | 
						|
 | 
						|
  if (aState == CUBEB_STATE_ERROR) {
 | 
						|
    // About to hand over control of the graph.  Do not start a new driver if
 | 
						|
    // StateCallback() receives an error for this stream while the main thread
 | 
						|
    // or another driver has control of the graph.
 | 
						|
    if (streamState == AudioStreamState::Starting ||
 | 
						|
        streamState == AudioStreamState::ChangingDevice ||
 | 
						|
        streamState == AudioStreamState::Running) {
 | 
						|
      if (mFallbackDriverState.compareExchange(FallbackDriverState::None,
 | 
						|
                                               FallbackDriverState::Running)) {
 | 
						|
        // Only switch to fallback if it's not already running. It could be
 | 
						|
        // running with the callback driver having started but not seen a single
 | 
						|
        // callback yet. I.e., handover from fallback to callback is not done.
 | 
						|
        if (mInputDeviceID) {
 | 
						|
#ifdef DEBUG
 | 
						|
          // No audio callback after an error. We're calling into the graph here
 | 
						|
          // so we need to be regarded as "in iteration".
 | 
						|
          AutoInCallback aic(this);
 | 
						|
#endif
 | 
						|
          mGraphInterface->NotifyInputStopped();
 | 
						|
        }
 | 
						|
        FallbackToSystemClockDriver();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::MixerCallback(AudioChunk* aMixedBuffer,
 | 
						|
                                        uint32_t aSampleRate) {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  uint32_t toWrite = mBuffer.Available();
 | 
						|
 | 
						|
  TrackTime frameCount = aMixedBuffer->mDuration;
 | 
						|
  if (!mBuffer.Available() && frameCount > 0) {
 | 
						|
    NS_WARNING("DataCallback buffer full, expect frame drops.");
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(mBuffer.Available() <= frameCount);
 | 
						|
 | 
						|
  mBuffer.WriteFrames(*aMixedBuffer, mBuffer.Available());
 | 
						|
  MOZ_ASSERT(mBuffer.Available() == 0,
 | 
						|
             "Missing frames to fill audio callback's buffer.");
 | 
						|
  if (toWrite == frameCount) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  aMixedBuffer->SliceTo(toWrite, frameCount);
 | 
						|
  DebugOnly<uint32_t> written = mScratchBuffer.Fill(*aMixedBuffer);
 | 
						|
  NS_WARNING_ASSERTION(written == frameCount - toWrite, "Dropping frames.");
 | 
						|
};
 | 
						|
 | 
						|
void AudioCallbackDriver::PanOutputIfNeeded(bool aMicrophoneActive) {
 | 
						|
#ifdef XP_MACOSX
 | 
						|
  TRACE("AudioCallbackDriver::PanOutputIfNeeded");
 | 
						|
  cubeb_device* out = nullptr;
 | 
						|
  int rv;
 | 
						|
  char name[128];
 | 
						|
  size_t length = sizeof(name);
 | 
						|
 | 
						|
  rv = sysctlbyname("hw.model", name, &length, NULL, 0);
 | 
						|
  if (rv) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  int major, minor;
 | 
						|
  for (uint32_t i = 0; i < length; i++) {
 | 
						|
    // skip the model name
 | 
						|
    if (isalpha(name[i])) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    sscanf(name + i, "%d,%d", &major, &minor);
 | 
						|
    break;
 | 
						|
  }
 | 
						|
 | 
						|
  enum MacbookModel { MacBook, MacBookPro, MacBookAir, NotAMacbook };
 | 
						|
 | 
						|
  MacbookModel model;
 | 
						|
 | 
						|
  if (!strncmp(name, "MacBookPro", length)) {
 | 
						|
    model = MacBookPro;
 | 
						|
  } else if (strncmp(name, "MacBookAir", length)) {
 | 
						|
    model = MacBookAir;
 | 
						|
  } else if (strncmp(name, "MacBook", length)) {
 | 
						|
    model = MacBook;
 | 
						|
  } else {
 | 
						|
    model = NotAMacbook;
 | 
						|
  }
 | 
						|
  // For macbook pro before 2016 model (change of chassis), hard pan the audio
 | 
						|
  // to the right if the speakers are in use to avoid feedback.
 | 
						|
  if (model == MacBookPro && major <= 12) {
 | 
						|
    if (cubeb_stream_get_current_device(mAudioStream, &out) == CUBEB_OK) {
 | 
						|
      MOZ_ASSERT(out);
 | 
						|
      // Check if we are currently outputing sound on external speakers.
 | 
						|
      if (out->output_name && !strcmp(out->output_name, "ispk")) {
 | 
						|
        // Pan everything to the right speaker.
 | 
						|
        LOG(LogLevel::Debug, ("Using the built-in speakers, with%s audio input",
 | 
						|
                              aMicrophoneActive ? "" : "out"));
 | 
						|
        mNeedsPanning = aMicrophoneActive;
 | 
						|
      } else {
 | 
						|
        LOG(LogLevel::Debug, ("Using an external output device"));
 | 
						|
        mNeedsPanning = false;
 | 
						|
      }
 | 
						|
      cubeb_stream_device_destroy(mAudioStream, out);
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::DeviceChangedCallback() {
 | 
						|
  MOZ_ASSERT(!InIteration());
 | 
						|
  // Set this before the atomic write.
 | 
						|
  mChangingDeviceStartTime = TimeStamp::Now();
 | 
						|
 | 
						|
  if (mAudioStreamState.compareExchange(AudioStreamState::Running,
 | 
						|
                                        AudioStreamState::ChangingDevice)) {
 | 
						|
    // Change to ChangingDevice only if we're running, i.e. there has been a
 | 
						|
    // data callback and no state callback saying otherwise.
 | 
						|
    // - If the audio stream is not running, it has either been stopped or it is
 | 
						|
    //   starting. In the latter case we assume there will be no data callback
 | 
						|
    //   coming until after the device change is done.
 | 
						|
    // - If the audio stream is running here, there is no guarantee from the
 | 
						|
    //   cubeb mac backend that no more data callback will occur before the
 | 
						|
    //   device change takes place. They will however stop *soon*, and we hope
 | 
						|
    //   they stop before the first callback from the fallback driver. If the
 | 
						|
    //   fallback driver callback occurs before the last data callback before
 | 
						|
    //   the device switch, the worst case is that a long period of time
 | 
						|
    //   (seconds) may pass without the graph getting iterated at all.
 | 
						|
    Result<bool, FallbackDriverState> res = TryStartingFallbackDriver();
 | 
						|
 | 
						|
    LOG(LogLevel::Info,
 | 
						|
        ("%p: AudioCallbackDriver %p underlying default device is changing. "
 | 
						|
         "Fallback %s.",
 | 
						|
         Graph(), this,
 | 
						|
         res.isOk() ? "started"
 | 
						|
                    : (res.inspectErr() == FallbackDriverState::Running
 | 
						|
                           ? "already running"
 | 
						|
                           : "has been stopped")));
 | 
						|
 | 
						|
    if (res.isErr() && res.inspectErr() == FallbackDriverState::Stopped) {
 | 
						|
      mChangingDeviceStartTime = TimeStamp();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Tell the audio engine the device has changed, it might want to reset some
 | 
						|
  // state.
 | 
						|
  Graph()->DeviceChanged();
 | 
						|
#ifdef XP_MACOSX
 | 
						|
  RefPtr<AudioCallbackDriver> self(this);
 | 
						|
  bool hasInput = mInputChannelCount;
 | 
						|
  NS_DispatchBackgroundTask(NS_NewRunnableFunction(
 | 
						|
      "PanOutputIfNeeded", [self{std::move(self)}, hasInput]() {
 | 
						|
        self->PanOutputIfNeeded(hasInput);
 | 
						|
      }));
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
uint32_t AudioCallbackDriver::IterationDuration() {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  // The real fix would be to have an API in cubeb to give us the number. Short
 | 
						|
  // of that, we approximate it here. bug 1019507
 | 
						|
  return mIterationDurationMS;
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::EnsureNextIteration() {
 | 
						|
  if (mFallbackDriverState == FallbackDriverState::Running) {
 | 
						|
    auto fallback = mFallback.Lock();
 | 
						|
    if (fallback.ref()) {
 | 
						|
      fallback.ref()->EnsureNextIteration();
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TimeDuration AudioCallbackDriver::AudioOutputLatency() {
 | 
						|
  TRACE("AudioCallbackDriver::AudioOutputLatency");
 | 
						|
  uint32_t latencyFrames;
 | 
						|
  int rv = cubeb_stream_get_latency(mAudioStream, &latencyFrames);
 | 
						|
  if (rv || mSampleRate == 0) {
 | 
						|
    return TimeDuration::FromSeconds(0.0);
 | 
						|
  }
 | 
						|
 | 
						|
  return TimeDuration::FromSeconds(static_cast<double>(latencyFrames) /
 | 
						|
                                   mSampleRate);
 | 
						|
}
 | 
						|
 | 
						|
bool AudioCallbackDriver::HasFallback() const {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  return mFallbackDriverState != FallbackDriverState::None;
 | 
						|
}
 | 
						|
 | 
						|
bool AudioCallbackDriver::OnFallback() const {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  return mFallbackDriverState == FallbackDriverState::Running;
 | 
						|
}
 | 
						|
 | 
						|
Result<bool, AudioCallbackDriver::FallbackDriverState>
 | 
						|
AudioCallbackDriver::TryStartingFallbackDriver() {
 | 
						|
  FallbackDriverState oldState =
 | 
						|
      mFallbackDriverState.exchange(FallbackDriverState::Running);
 | 
						|
  switch (oldState) {
 | 
						|
    case FallbackDriverState::None:
 | 
						|
      // None -> Running: we can start the fallback.
 | 
						|
      FallbackToSystemClockDriver();
 | 
						|
      return true;
 | 
						|
    case FallbackDriverState::Stopped:
 | 
						|
      // Stopped -> Running: Invalid edge, the graph has told us to stop.
 | 
						|
      // Restore the state.
 | 
						|
      mFallbackDriverState = oldState;
 | 
						|
      [[fallthrough]];
 | 
						|
    case FallbackDriverState::Running:
 | 
						|
      // Nothing to do, return the state.
 | 
						|
      return Err(oldState);
 | 
						|
  }
 | 
						|
  MOZ_CRASH("Unexpected fallback state");
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::FallbackToSystemClockDriver() {
 | 
						|
  MOZ_ASSERT(mFallbackDriverState == FallbackDriverState::Running);
 | 
						|
  DebugOnly<AudioStreamState> audioStreamState =
 | 
						|
      static_cast<AudioStreamState>(mAudioStreamState);
 | 
						|
  MOZ_ASSERT(audioStreamState == AudioStreamState::None ||
 | 
						|
             audioStreamState == AudioStreamState::Pending ||
 | 
						|
             audioStreamState == AudioStreamState::ChangingDevice);
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: AudioCallbackDriver %p Falling back to SystemClockDriver.", Graph(),
 | 
						|
       this));
 | 
						|
  mNextReInitBackoffStep =
 | 
						|
      TimeDuration::FromMilliseconds(AUDIO_INITIAL_FALLBACK_BACKOFF_STEP_MS);
 | 
						|
  mNextReInitAttempt = TimeStamp::Now() + mNextReInitBackoffStep;
 | 
						|
  auto fallback =
 | 
						|
      MakeRefPtr<FallbackWrapper>(Graph(), this, mSampleRate, mStreamName,
 | 
						|
                                  mIterationEnd, mStateComputedTime);
 | 
						|
  {
 | 
						|
    auto driver = mFallback.Lock();
 | 
						|
    MOZ_RELEASE_ASSERT(!driver.ref());
 | 
						|
    driver.ref() = fallback;
 | 
						|
  }
 | 
						|
  fallback->Start();
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::FallbackDriverStopped(GraphTime aIterationEnd,
 | 
						|
                                                GraphTime aStateComputedTime,
 | 
						|
                                                FallbackDriverState aState) {
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: AudioCallbackDriver %p Fallback driver has stopped.", Graph(),
 | 
						|
       this));
 | 
						|
  mIterationEnd = aIterationEnd;
 | 
						|
  mStateComputedTime = aStateComputedTime;
 | 
						|
  mNextReInitAttempt = TimeStamp();
 | 
						|
  mNextReInitBackoffStep = TimeDuration();
 | 
						|
  {
 | 
						|
    auto fallback = mFallback.Lock();
 | 
						|
    MOZ_ASSERT(fallback.ref()->OnThread());
 | 
						|
    fallback.ref() = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(aState == FallbackDriverState::None ||
 | 
						|
             aState == FallbackDriverState::Stopped);
 | 
						|
  mFallbackDriverState = aState;
 | 
						|
  AudioStreamState audioState = mAudioStreamState;
 | 
						|
  LOG(LogLevel::Debug,
 | 
						|
      ("%p: AudioCallbackDriver %p Fallback driver stopped.%s%s", Graph(), this,
 | 
						|
       aState == FallbackDriverState::Stopped ? " Draining." : "",
 | 
						|
       aState == FallbackDriverState::None &&
 | 
						|
               audioState == AudioStreamState::ChangingDevice
 | 
						|
           ? " Starting another due to device change."
 | 
						|
           : ""));
 | 
						|
 | 
						|
  if (aState == FallbackDriverState::None) {
 | 
						|
    MOZ_ASSERT(audioState == AudioStreamState::Running ||
 | 
						|
               audioState == AudioStreamState::ChangingDevice);
 | 
						|
    if (audioState == AudioStreamState::ChangingDevice) {
 | 
						|
      MOZ_ALWAYS_OK(TryStartingFallbackDriver());
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::MaybeStartAudioStream() {
 | 
						|
  AudioStreamState streamState = mAudioStreamState;
 | 
						|
  if (streamState != AudioStreamState::None) {
 | 
						|
    LOG(LogLevel::Verbose,
 | 
						|
        ("%p: AudioCallbackDriver %p Cannot re-init.", Graph(), this));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  TimeStamp now = TimeStamp::Now();
 | 
						|
  if (now < mNextReInitAttempt) {
 | 
						|
    LOG(LogLevel::Verbose,
 | 
						|
        ("%p: AudioCallbackDriver %p Not time to re-init yet. %.3fs left.",
 | 
						|
         Graph(), this, (mNextReInitAttempt - now).ToSeconds()));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  LOG(LogLevel::Debug, ("%p: AudioCallbackDriver %p Attempting to re-init "
 | 
						|
                        "audio stream from fallback driver.",
 | 
						|
                        Graph(), this));
 | 
						|
  mNextReInitBackoffStep =
 | 
						|
      std::min(mNextReInitBackoffStep * 2,
 | 
						|
               TimeDuration::FromMilliseconds(
 | 
						|
                   StaticPrefs::media_audio_device_retry_ms()));
 | 
						|
  mNextReInitAttempt = now + mNextReInitBackoffStep;
 | 
						|
  Start();
 | 
						|
}
 | 
						|
 | 
						|
cubeb_input_processing_params
 | 
						|
AudioCallbackDriver::RequestedInputProcessingParams() const {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  return mRequestedInputProcessingParams;
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::SetRequestedInputProcessingParams(
 | 
						|
    cubeb_input_processing_params aParams) {
 | 
						|
  MOZ_ASSERT(InIteration());
 | 
						|
  if (mRequestedInputProcessingParams == aParams) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  LOG(LogLevel::Info,
 | 
						|
      ("AudioCallbackDriver %p, Input processing params %s requested.", this,
 | 
						|
       CubebUtils::ProcessingParamsToString(aParams).get()));
 | 
						|
  mRequestedInputProcessingParams = aParams;
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(mCubebOperationThread->Dispatch(
 | 
						|
      NS_NewRunnableFunction(__func__, [this, self = RefPtr(this), aParams] {
 | 
						|
        SetInputProcessingParams(aParams);
 | 
						|
      })));
 | 
						|
}
 | 
						|
 | 
						|
void AudioCallbackDriver::SetInputProcessingParams(
 | 
						|
    cubeb_input_processing_params aParams) {
 | 
						|
  MOZ_ASSERT(OnCubebOperationThread());
 | 
						|
  auto requested = aParams;
 | 
						|
  auto result = ([&]() -> Maybe<Result<cubeb_input_processing_params, int>> {
 | 
						|
    // This function decides how to handle the request.
 | 
						|
    // Returning Nothing() does nothing, because either
 | 
						|
    //   1) there is no update since the previous state, or
 | 
						|
    //   2) handling is deferred to a later time.
 | 
						|
    // Returning Some() result will forward that result to
 | 
						|
    // AudioDataListener::OnInputProcessingParamsResult on the callback
 | 
						|
    // thread.
 | 
						|
    if (!mAudioStream) {
 | 
						|
      // No Init yet.
 | 
						|
      LOG(LogLevel::Debug, ("AudioCallbackDriver %p, has no cubeb stream to "
 | 
						|
                            "set processing params on!",
 | 
						|
                            this));
 | 
						|
      return Nothing();
 | 
						|
    }
 | 
						|
    if (mAudioStreamState == AudioStreamState::None) {
 | 
						|
      // Driver (and cubeb stream) was stopped.
 | 
						|
      return Nothing();
 | 
						|
    }
 | 
						|
    cubeb_input_processing_params supported;
 | 
						|
    auto handle = CubebUtils::GetCubeb();
 | 
						|
    int r = cubeb_get_supported_input_processing_params(handle->Context(),
 | 
						|
                                                        &supported);
 | 
						|
    if (r != CUBEB_OK) {
 | 
						|
      LOG(LogLevel::Debug,
 | 
						|
          ("AudioCallbackDriver %p, no supported processing params", this));
 | 
						|
      return Some(Err(CUBEB_ERROR_NOT_SUPPORTED));
 | 
						|
    }
 | 
						|
    aParams &= supported;
 | 
						|
    LOG(LogLevel::Debug,
 | 
						|
        ("AudioCallbackDriver %p, requested processing params %s reduced to %s "
 | 
						|
         "by supported params %s",
 | 
						|
         this, CubebUtils::ProcessingParamsToString(requested).get(),
 | 
						|
         CubebUtils::ProcessingParamsToString(aParams).get(),
 | 
						|
         CubebUtils::ProcessingParamsToString(supported).get()));
 | 
						|
    if (aParams == mConfiguredInputProcessingParams) {
 | 
						|
      LOG(LogLevel::Debug,
 | 
						|
          ("AudioCallbackDriver %p, no change in processing params %s. Not "
 | 
						|
           "attempting reconfiguration.",
 | 
						|
           this, CubebUtils::ProcessingParamsToString(aParams).get()));
 | 
						|
      return Some(aParams);
 | 
						|
    }
 | 
						|
    mConfiguredInputProcessingParams = aParams;
 | 
						|
    r = cubeb_stream_set_input_processing_params(mAudioStream, aParams);
 | 
						|
    if (r == CUBEB_OK) {
 | 
						|
      LOG(LogLevel::Info,
 | 
						|
          ("AudioCallbackDriver %p, input processing params set to %s", this,
 | 
						|
           CubebUtils::ProcessingParamsToString(aParams).get()));
 | 
						|
      return Some(aParams);
 | 
						|
    }
 | 
						|
    LOG(LogLevel::Info,
 | 
						|
        ("AudioCallbackDriver %p, failed setting input processing params to "
 | 
						|
         "%s. r=%d",
 | 
						|
         this, CubebUtils::ProcessingParamsToString(aParams).get(), r));
 | 
						|
    return Some(Err(r));
 | 
						|
  })();
 | 
						|
  if (!result) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
 | 
						|
      NS_NewRunnableFunction(__func__, [this, self = RefPtr(this), requested,
 | 
						|
                                        result = result.extract()]() mutable {
 | 
						|
        LOG(LogLevel::Debug,
 | 
						|
            ("AudioCallbackDriver %p, Notifying of input processing params %s. "
 | 
						|
             "r=%d",
 | 
						|
             this,
 | 
						|
             CubebUtils::ProcessingParamsToString(
 | 
						|
                 result.unwrapOr(CUBEB_INPUT_PROCESSING_PARAM_NONE))
 | 
						|
                 .get(),
 | 
						|
             result.isErr() ? result.inspectErr() : CUBEB_OK));
 | 
						|
        mGraphInterface->NotifySetRequestedInputProcessingParamsResult(
 | 
						|
            this, requested, std::move(result));
 | 
						|
      })));
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
// avoid redefined macro in unified build
 | 
						|
#undef LOG
 |