tor-browser

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

MockCubeb.cpp (28107B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "MockCubeb.h"
      7 
      8 namespace mozilla {
      9 
     10 using KeepProcessing = MockCubebStream::KeepProcessing;
     11 
     12 void PrintDevice(cubeb_device_info aInfo) {
     13  printf(
     14      "id: %zu\n"
     15      "device_id: %s\n"
     16      "friendly_name: %s\n"
     17      "group_id: %s\n"
     18      "vendor_name: %s\n"
     19      "type: %d\n"
     20      "state: %d\n"
     21      "preferred: %d\n"
     22      "format: %d\n"
     23      "default_format: %d\n"
     24      "max_channels: %d\n"
     25      "default_rate: %d\n"
     26      "max_rate: %d\n"
     27      "min_rate: %d\n"
     28      "latency_lo: %d\n"
     29      "latency_hi: %d\n",
     30      reinterpret_cast<uintptr_t>(aInfo.devid), aInfo.device_id,
     31      aInfo.friendly_name, aInfo.group_id, aInfo.vendor_name, aInfo.type,
     32      aInfo.state, aInfo.preferred, aInfo.format, aInfo.default_format,
     33      aInfo.max_channels, aInfo.default_rate, aInfo.max_rate, aInfo.min_rate,
     34      aInfo.latency_lo, aInfo.latency_hi);
     35 }
     36 
     37 void PrintDevice(AudioDeviceInfo* aInfo) {
     38  cubeb_devid id;
     39  nsString name;
     40  nsString groupid;
     41  nsString vendor;
     42  uint16_t type;
     43  uint16_t state;
     44  uint16_t preferred;
     45  uint16_t supportedFormat;
     46  uint16_t defaultFormat;
     47  uint32_t maxChannels;
     48  uint32_t defaultRate;
     49  uint32_t maxRate;
     50  uint32_t minRate;
     51  uint32_t maxLatency;
     52  uint32_t minLatency;
     53 
     54  id = aInfo->DeviceID();
     55  aInfo->GetName(name);
     56  aInfo->GetGroupId(groupid);
     57  aInfo->GetVendor(vendor);
     58  aInfo->GetType(&type);
     59  aInfo->GetState(&state);
     60  aInfo->GetPreferred(&preferred);
     61  aInfo->GetSupportedFormat(&supportedFormat);
     62  aInfo->GetDefaultFormat(&defaultFormat);
     63  aInfo->GetMaxChannels(&maxChannels);
     64  aInfo->GetDefaultRate(&defaultRate);
     65  aInfo->GetMaxRate(&maxRate);
     66  aInfo->GetMinRate(&minRate);
     67  aInfo->GetMinLatency(&minLatency);
     68  aInfo->GetMaxLatency(&maxLatency);
     69 
     70  printf(
     71      "device id: %zu\n"
     72      "friendly_name: %s\n"
     73      "group_id: %s\n"
     74      "vendor_name: %s\n"
     75      "type: %d\n"
     76      "state: %d\n"
     77      "preferred: %d\n"
     78      "format: %d\n"
     79      "default_format: %d\n"
     80      "max_channels: %d\n"
     81      "default_rate: %d\n"
     82      "max_rate: %d\n"
     83      "min_rate: %d\n"
     84      "latency_lo: %d\n"
     85      "latency_hi: %d\n",
     86      reinterpret_cast<uintptr_t>(id), NS_LossyConvertUTF16toASCII(name).get(),
     87      NS_LossyConvertUTF16toASCII(groupid).get(),
     88      NS_LossyConvertUTF16toASCII(vendor).get(), type, state, preferred,
     89      supportedFormat, defaultFormat, maxChannels, defaultRate, maxRate,
     90      minRate, minLatency, maxLatency);
     91 }
     92 
     93 cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType,
     94                                 const char* name) {
     95  // A fake input device
     96  cubeb_device_info device;
     97  device.devid = aId;
     98  device.device_id = "nice name";
     99  device.friendly_name = name;
    100  device.group_id = "the physical device";
    101  device.vendor_name = "mozilla";
    102  device.type = aType;
    103  device.state = CUBEB_DEVICE_STATE_ENABLED;
    104  device.preferred = CUBEB_DEVICE_PREF_NONE;
    105  device.format = CUBEB_DEVICE_FMT_F32NE;
    106  device.default_format = CUBEB_DEVICE_FMT_F32NE;
    107  device.max_channels = 2;
    108  device.default_rate = 44100;
    109  device.max_rate = 44100;
    110  device.min_rate = 16000;
    111  device.latency_lo = 256;
    112  device.latency_hi = 1024;
    113 
    114  return device;
    115 }
    116 
    117 cubeb_device_info DeviceTemplate(cubeb_devid aId, cubeb_device_type aType) {
    118  return DeviceTemplate(aId, aType, "nice name");
    119 }
    120 
    121 void AddDevices(MockCubeb* mock, uint32_t device_count,
    122                cubeb_device_type deviceType) {
    123  mock->ClearDevices(deviceType);
    124  // Add a few input devices (almost all the same but it does not really
    125  // matter as long as they have distinct IDs and only one is the default
    126  // devices)
    127  for (uintptr_t i = 0; i < device_count; i++) {
    128    cubeb_device_info device =
    129        DeviceTemplate(reinterpret_cast<void*>(i + 1), deviceType);
    130    // Make it so that the last device is the default input device.
    131    if (i == device_count - 1) {
    132      device.preferred = CUBEB_DEVICE_PREF_ALL;
    133    }
    134    mock->AddDevice(device);
    135  }
    136 }
    137 
    138 void cubeb_mock_destroy(cubeb* context) {
    139  MockCubeb::AsMock(context)->Destroy();
    140 }
    141 
    142 MockCubebStream::MockCubebStream(
    143    cubeb* aContext, char const* aStreamName, cubeb_devid aInputDevice,
    144    cubeb_stream_params* aInputStreamParams, cubeb_devid aOutputDevice,
    145    cubeb_stream_params* aOutputStreamParams, cubeb_data_callback aDataCallback,
    146    cubeb_state_callback aStateCallback, void* aUserPtr,
    147    SmartMockCubebStream* aSelf, RunningMode aRunningMode, bool aFrozenStart)
    148    : context(aContext),
    149      mUserPtr(aUserPtr),
    150      mRunningMode(aRunningMode),
    151      mHasInput(aInputStreamParams),
    152      mHasOutput(aOutputStreamParams),
    153      mSelf(aSelf),
    154      mFrozenStartMonitor("MockCubebStream::mFrozenStartMonitor"),
    155      mFrozenStart(aFrozenStart),
    156      mDataCallback(aDataCallback),
    157      mStateCallback(aStateCallback),
    158      mName(aStreamName),
    159      mInputDeviceID(aInputDevice),
    160      mOutputDeviceID(aOutputDevice),
    161      mAudioGenerator(aInputStreamParams ? aInputStreamParams->channels
    162                                         : MAX_INPUT_CHANNELS,
    163                      aInputStreamParams ? aInputStreamParams->rate
    164                                         : aOutputStreamParams->rate,
    165                      100 /* aFrequency */),
    166      mAudioVerifier(aInputStreamParams ? aInputStreamParams->rate
    167                                        : aOutputStreamParams->rate,
    168                     100 /* aFrequency */) {
    169  MOZ_RELEASE_ASSERT(mAudioGenerator.ChannelCount() <= MAX_INPUT_CHANNELS,
    170                     "mInputBuffer has no enough space to hold generated data");
    171  if (mFrozenStart) {
    172    MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    173  }
    174  if (aInputStreamParams) {
    175    mInputParams = *aInputStreamParams;
    176  }
    177  if (aOutputStreamParams) {
    178    mOutputParams = *aOutputStreamParams;
    179    MOZ_RELEASE_ASSERT(SampleRate() == mOutputParams.rate);
    180  }
    181 }
    182 
    183 MockCubebStream::~MockCubebStream() = default;
    184 
    185 int MockCubebStream::Start() {
    186  {
    187    MutexAutoLock l(mMutex);
    188    NotifyState(CUBEB_STATE_STARTED);
    189  }
    190  {
    191    MonitorAutoLock lock(mFrozenStartMonitor);
    192    if (mFrozenStart) {
    193      // We need to grab mFrozenStartMonitor before returning to avoid races in
    194      // the calling code -- it controls when to mFrozenStartMonitor.Notify().
    195      // TempData helps facilitate this by holding what's needed to block the
    196      // calling thread until the background thread has grabbed the lock.
    197      struct TempData {
    198        NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TempData)
    199        static_assert(HasThreadSafeRefCnt::value,
    200                      "Silence a -Wunused-local-typedef warning");
    201        Monitor mMonitor{"MockCubebStream::Start::TempData::mMonitor"};
    202        bool mFinished = false;
    203 
    204       private:
    205        ~TempData() = default;
    206      };
    207      auto temp = MakeRefPtr<TempData>();
    208      MonitorAutoLock lock(temp->mMonitor);
    209      NS_DispatchBackgroundTask(NS_NewRunnableFunction(
    210          "MockCubebStream::WaitForThawBeforeStart",
    211          [temp, this, self = RefPtr<SmartMockCubebStream>(mSelf)]() mutable {
    212            {
    213              // Unblock MockCubebStream::Start now that we have locked the
    214              // frozen start monitor.
    215              MonitorAutoLock tempLock(temp->mMonitor);
    216              temp->mFinished = true;
    217              temp->mMonitor.Notify();
    218              temp = nullptr;
    219            }
    220            {
    221              MonitorAutoLock lock(mFrozenStartMonitor);
    222              while (mFrozenStart) {
    223                mFrozenStartMonitor.Wait();
    224              }
    225            }
    226            if (MutexAutoLock l(mMutex);
    227                !mState || *mState != CUBEB_STATE_STARTED) {
    228              return;
    229            }
    230            MockCubeb::AsMock(context)->StartStream(mSelf);
    231          }));
    232      while (!temp->mFinished) {
    233        temp->mMonitor.Wait();
    234      }
    235      return CUBEB_OK;
    236    }
    237  }
    238  MockCubeb::AsMock(context)->StartStream(this);
    239  return CUBEB_OK;
    240 }
    241 
    242 int MockCubebStream::Stop() {
    243  MockCubeb::AsMock(context)->StopStream(this);
    244  MutexAutoLock l(mMutex);
    245  mOutputVerificationEvent.Notify(std::make_tuple(
    246      mAudioVerifier.PreSilenceSamples(), mAudioVerifier.EstimatedFreq(),
    247      mAudioVerifier.CountDiscontinuities()));
    248  NotifyState(CUBEB_STATE_STOPPED);
    249  return CUBEB_OK;
    250 }
    251 
    252 uint64_t MockCubebStream::Position() {
    253  MutexAutoLock l(mMutex);
    254  return mPosition;
    255 }
    256 
    257 void MockCubebStream::Destroy() {
    258  // Stop() even if cubeb_stream_stop() has already been called, as with
    259  // audioipc.  https://bugzilla.mozilla.org/show_bug.cgi?id=1801190#c1
    260  // This provides an extra STOPPED state callback as with audioipc.
    261  // It also ensures that this stream is removed from MockCubeb::mLiveStreams.
    262  Stop();
    263  {
    264    MutexAutoLock l(mMutex);
    265    mDestroyed = true;
    266  }
    267  MockCubeb::AsMock(context)->StreamDestroy(this);
    268 }
    269 
    270 int MockCubebStream::SetName(char const* aName) {
    271  MutexAutoLock l(mMutex);
    272  mName = aName;
    273  mNameSetEvent.Notify(mName);
    274  return CUBEB_OK;
    275 }
    276 
    277 int MockCubebStream::RegisterDeviceChangedCallback(
    278    cubeb_device_changed_callback aDeviceChangedCallback) {
    279  MutexAutoLock l(mMutex);
    280  if (mDeviceChangedCallback && aDeviceChangedCallback) {
    281    return CUBEB_ERROR_INVALID_PARAMETER;
    282  }
    283  mDeviceChangedCallback = aDeviceChangedCallback;
    284  return CUBEB_OK;
    285 }
    286 
    287 int MockCubebStream::SetInputProcessingParams(
    288    cubeb_input_processing_params aParams) {
    289  MockCubeb* mock = MockCubeb::AsMock(context);
    290  auto res = mock->SupportedInputProcessingParams();
    291  if (res.isErr()) {
    292    return CUBEB_ERROR_NOT_SUPPORTED;
    293  }
    294  cubeb_input_processing_params supported = res.unwrap();
    295  if ((supported & aParams) != aParams) {
    296    return CUBEB_ERROR_INVALID_PARAMETER;
    297  }
    298  return mock->InputProcessingApplyRv();
    299 }
    300 
    301 cubeb_stream* MockCubebStream::AsCubebStream() {
    302  MutexAutoLock l(mMutex);
    303  return AsCubebStreamLocked();
    304 }
    305 
    306 cubeb_stream* MockCubebStream::AsCubebStreamLocked() {
    307  MOZ_RELEASE_ASSERT(!mDestroyed);
    308  mMutex.AssertCurrentThreadOwns();
    309  return reinterpret_cast<cubeb_stream*>(this);
    310 }
    311 
    312 MockCubebStream* MockCubebStream::AsMock(cubeb_stream* aStream) {
    313  auto* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
    314  MutexAutoLock l(mockStream->mMutex);
    315  return AsMockLocked(aStream);
    316 }
    317 
    318 MockCubebStream* MockCubebStream::AsMockLocked(cubeb_stream* aStream) {
    319  auto* mockStream = reinterpret_cast<MockCubebStream*>(aStream);
    320  mockStream->mMutex.AssertCurrentThreadOwns();
    321  MOZ_RELEASE_ASSERT(!mockStream->mDestroyed);
    322  return mockStream;
    323 }
    324 
    325 cubeb_devid MockCubebStream::GetInputDeviceID() const { return mInputDeviceID; }
    326 
    327 cubeb_devid MockCubebStream::GetOutputDeviceID() const {
    328  return mOutputDeviceID;
    329 }
    330 
    331 uint32_t MockCubebStream::InputChannels() const {
    332  MutexAutoLock l(mMutex);
    333  return InputChannelsLocked();
    334 }
    335 
    336 uint32_t MockCubebStream::InputChannelsLocked() const {
    337  mMutex.AssertCurrentThreadOwns();
    338  return mAudioGenerator.ChannelCount();
    339 }
    340 
    341 uint32_t MockCubebStream::OutputChannels() const {
    342  MutexAutoLock l(mMutex);
    343  return OutputChannelsLocked();
    344 }
    345 
    346 uint32_t MockCubebStream::OutputChannelsLocked() const {
    347  mMutex.AssertCurrentThreadOwns();
    348  return mOutputParams.channels;
    349 }
    350 
    351 uint32_t MockCubebStream::SampleRate() const {
    352  MutexAutoLock l(mMutex);
    353  return SampleRateLocked();
    354 }
    355 
    356 uint32_t MockCubebStream::SampleRateLocked() const {
    357  mMutex.AssertCurrentThreadOwns();
    358  return mAudioGenerator.mSampleRate;
    359 }
    360 
    361 uint32_t MockCubebStream::InputFrequency() const {
    362  MutexAutoLock l(mMutex);
    363  return InputFrequencyLocked();
    364 }
    365 
    366 uint32_t MockCubebStream::InputFrequencyLocked() const {
    367  mMutex.AssertCurrentThreadOwns();
    368  return mAudioGenerator.mFrequency;
    369 }
    370 
    371 Maybe<cubeb_state> MockCubebStream::State() const {
    372  MutexAutoLock l(mMutex);
    373  return mState;
    374 }
    375 
    376 nsTArray<AudioDataValue>&& MockCubebStream::TakeRecordedOutput() {
    377  MutexAutoLock l(mMutex);
    378  return std::move(mRecordedOutput);
    379 }
    380 
    381 nsTArray<AudioDataValue>&& MockCubebStream::TakeRecordedInput() {
    382  MutexAutoLock l(mMutex);
    383  return std::move(mRecordedInput);
    384 }
    385 
    386 void MockCubebStream::SetDriftFactor(float aDriftFactor) {
    387  MOZ_RELEASE_ASSERT(mRunningMode == MockCubeb::RunningMode::Automatic);
    388  MutexAutoLock l(mMutex);
    389  mDriftFactor = aDriftFactor;
    390 }
    391 
    392 void MockCubebStream::ForceError() {
    393  MutexAutoLock l(mMutex);
    394  mForceErrorState = true;
    395 }
    396 
    397 void MockCubebStream::ForceDeviceChanged() {
    398  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    399  MutexAutoLock l(mMutex);
    400  mForceDeviceChanged = true;
    401 };
    402 
    403 void MockCubebStream::NotifyDeviceChangedNow() {
    404  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Manual);
    405  NotifyDeviceChanged();
    406 }
    407 
    408 void MockCubebStream::Thaw() {
    409  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    410  MonitorAutoLock l(mFrozenStartMonitor);
    411  mFrozenStart = false;
    412  mFrozenStartMonitor.Notify();
    413 }
    414 
    415 void MockCubebStream::SetOutputRecordingEnabled(bool aEnabled) {
    416  MutexAutoLock l(mMutex);
    417  mOutputRecordingEnabled = aEnabled;
    418 }
    419 
    420 void MockCubebStream::SetInputRecordingEnabled(bool aEnabled) {
    421  MutexAutoLock l(mMutex);
    422  mInputRecordingEnabled = aEnabled;
    423 }
    424 
    425 MediaEventSource<nsCString>& MockCubebStream::NameSetEvent() {
    426  return mNameSetEvent;
    427 }
    428 
    429 MediaEventSource<cubeb_state>& MockCubebStream::StateEvent() {
    430  return mStateEvent;
    431 }
    432 
    433 MediaEventSource<uint32_t>& MockCubebStream::FramesProcessedEvent() {
    434  return mFramesProcessedEvent;
    435 }
    436 
    437 MediaEventSource<uint32_t>& MockCubebStream::FramesVerifiedEvent() {
    438  return mFramesVerifiedEvent;
    439 }
    440 
    441 MediaEventSource<std::tuple<uint64_t, float, uint32_t>>&
    442 MockCubebStream::OutputVerificationEvent() {
    443  return mOutputVerificationEvent;
    444 }
    445 
    446 MediaEventSource<void>& MockCubebStream::ErrorForcedEvent() {
    447  return mErrorForcedEvent;
    448 }
    449 
    450 MediaEventSource<void>& MockCubebStream::DeviceChangeForcedEvent() {
    451  return mDeviceChangedForcedEvent;
    452 }
    453 
    454 KeepProcessing MockCubebStream::ManualDataCallback(long aNrFrames) {
    455  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Manual);
    456  MOZ_RELEASE_ASSERT(aNrFrames <= kMaxNrFrames);
    457  MutexAutoLock l(mMutex);
    458  return Process(aNrFrames);
    459 }
    460 
    461 KeepProcessing MockCubebStream::Process(long aNrFrames) {
    462  mMutex.AssertCurrentThreadOwns();
    463  if (!mState || *mState != CUBEB_STATE_STARTED) {
    464    return KeepProcessing::InvalidState;
    465  }
    466  if (mInputParams.rate) {
    467    mAudioGenerator.GenerateInterleaved(mInputBuffer, aNrFrames);
    468  }
    469  cubeb_stream* stream = AsCubebStreamLocked();
    470  const long outframes =
    471      mDataCallback(stream, mUserPtr, mHasInput ? mInputBuffer : nullptr,
    472                    mHasOutput ? mOutputBuffer : nullptr, aNrFrames);
    473 
    474  if (outframes > 0) {
    475    if (mInputRecordingEnabled && mHasInput) {
    476      mRecordedInput.AppendElements(mInputBuffer,
    477                                    outframes * InputChannelsLocked());
    478    }
    479    if (mOutputRecordingEnabled && mHasOutput) {
    480      mRecordedOutput.AppendElements(mOutputBuffer,
    481 
    482                                     outframes * OutputChannelsLocked());
    483    }
    484    mAudioVerifier.AppendDataInterleaved(mOutputBuffer, outframes,
    485                                         MAX_OUTPUT_CHANNELS);
    486    mPosition += outframes;
    487 
    488    mFramesProcessedEvent.Notify(outframes);
    489    if (mAudioVerifier.PreSilenceEnded()) {
    490      mFramesVerifiedEvent.Notify(outframes);
    491    }
    492  }
    493 
    494  if (outframes < aNrFrames) {
    495    NotifyState(CUBEB_STATE_DRAINED);
    496    return KeepProcessing::No;
    497  }
    498  if (mForceErrorState) {
    499    mForceErrorState = false;
    500    NotifyState(CUBEB_STATE_ERROR);
    501    mErrorForcedEvent.Notify();
    502    return KeepProcessing::No;
    503  }
    504  if (mForceDeviceChanged) {
    505    mForceDeviceChanged = false;
    506    // The device-changed callback is not necessary to be run in the
    507    // audio-callback thread. It's up to the platform APIs. We don't have any
    508    // control over them. Fire the device-changed callback in another thread to
    509    // simulate this.
    510    NS_DispatchBackgroundTask(NS_NewRunnableFunction(
    511        __func__, [this, self = RefPtr(mSelf)] { NotifyDeviceChanged(); }));
    512  }
    513  return KeepProcessing::Yes;
    514 }
    515 
    516 KeepProcessing MockCubebStream::Process10Ms() {
    517  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    518  MutexAutoLock l(mMutex);
    519  uint32_t rate = SampleRateLocked();
    520  const long nrFrames =
    521      static_cast<long>(static_cast<float>(rate * 10) * mDriftFactor) /
    522      PR_MSEC_PER_SEC;
    523  return Process(nrFrames);
    524 }
    525 
    526 void MockCubebStream::NotifyState(cubeb_state aState) {
    527  mMutex.AssertCurrentThreadOwns();
    528  mState = Some(aState);
    529  mStateCallback(AsCubebStreamLocked(), mUserPtr, aState);
    530  mStateEvent.Notify(aState);
    531 }
    532 
    533 void MockCubebStream::NotifyDeviceChanged() {
    534  MutexAutoLock l(mMutex);
    535  mDeviceChangedCallback(this->mUserPtr);
    536  mDeviceChangedForcedEvent.Notify();
    537 }
    538 
    539 MockCubeb::MockCubeb() : MockCubeb(MockCubeb::RunningMode::Automatic) {}
    540 
    541 MockCubeb::MockCubeb(RunningMode aRunningMode)
    542    : ops(&mock_ops), mRunningMode(aRunningMode) {
    543  // Silence a -Wunused-private-field warning in clang.
    544  // Note [[maybe_unused]] could silence this but then gcc warns about
    545  // error: 'unused' attribute ignored [-Werror=attributes].
    546  (void)ops;
    547 }
    548 
    549 MockCubeb::~MockCubeb() { MOZ_RELEASE_ASSERT(!mFakeAudioThreadRunning); }
    550 
    551 void MockCubeb::Destroy() {
    552  MOZ_RELEASE_ASSERT(mHasCubebContext);
    553  {
    554    auto streams = mLiveStreams.Lock();
    555    MOZ_RELEASE_ASSERT(streams->IsEmpty());
    556  }
    557  mDestroyed = true;
    558  Release();
    559 }
    560 
    561 cubeb* MockCubeb::AsCubebContext() {
    562  MOZ_RELEASE_ASSERT(!mDestroyed);
    563  if (mHasCubebContext.compareExchange(false, true)) {
    564    AddRef();
    565  }
    566  return reinterpret_cast<cubeb*>(this);
    567 }
    568 
    569 MockCubeb* MockCubeb::AsMock(cubeb* aContext) {
    570  auto* mockCubeb = reinterpret_cast<MockCubeb*>(aContext);
    571  MOZ_RELEASE_ASSERT(!mockCubeb->mDestroyed);
    572  return mockCubeb;
    573 }
    574 
    575 int MockCubeb::EnumerateDevices(cubeb_device_type aType,
    576                                cubeb_device_collection* aCollection) {
    577 #ifdef ANDROID
    578  MOZ_ASSERT_UNREACHABLE("This is not to be called on Android.");
    579 #endif
    580  size_t count = 0;
    581  if (aType & CUBEB_DEVICE_TYPE_INPUT) {
    582    count += mInputDevices.Length();
    583  }
    584  if (aType & CUBEB_DEVICE_TYPE_OUTPUT) {
    585    count += mOutputDevices.Length();
    586  }
    587  aCollection->device = new cubeb_device_info[count];
    588  aCollection->count = count;
    589 
    590  uint32_t collection_index = 0;
    591  if (aType & CUBEB_DEVICE_TYPE_INPUT) {
    592    for (auto& device : mInputDevices) {
    593      aCollection->device[collection_index] = device;
    594      collection_index++;
    595    }
    596  }
    597  if (aType & CUBEB_DEVICE_TYPE_OUTPUT) {
    598    for (auto& device : mOutputDevices) {
    599      aCollection->device[collection_index] = device;
    600      collection_index++;
    601    }
    602  }
    603 
    604  return CUBEB_OK;
    605 }
    606 
    607 int MockCubeb::DestroyDeviceCollection(cubeb_device_collection* aCollection) {
    608  delete[] aCollection->device;
    609  aCollection->count = 0;
    610  return CUBEB_OK;
    611 }
    612 
    613 int MockCubeb::RegisterDeviceCollectionChangeCallback(
    614    cubeb_device_type aDevType,
    615    cubeb_device_collection_changed_callback aCallback, void* aUserPtr) {
    616  if (!mSupportsDeviceCollectionChangedCallback) {
    617    return CUBEB_ERROR;
    618  }
    619 
    620  if (aDevType & CUBEB_DEVICE_TYPE_INPUT) {
    621    mInputDeviceCollectionChangeCallback = aCallback;
    622    mInputDeviceCollectionChangeUserPtr = aUserPtr;
    623  }
    624  if (aDevType & CUBEB_DEVICE_TYPE_OUTPUT) {
    625    mOutputDeviceCollectionChangeCallback = aCallback;
    626    mOutputDeviceCollectionChangeUserPtr = aUserPtr;
    627  }
    628 
    629  return CUBEB_OK;
    630 }
    631 
    632 Result<cubeb_input_processing_params, int>
    633 MockCubeb::SupportedInputProcessingParams() const {
    634  const auto& [params, rv] = mSupportedInputProcessingParams;
    635  if (rv != CUBEB_OK) {
    636    return Err(rv);
    637  }
    638  return params;
    639 }
    640 
    641 void MockCubeb::SetSupportedInputProcessingParams(
    642    cubeb_input_processing_params aParams, int aRv) {
    643  mSupportedInputProcessingParams = std::make_pair(aParams, aRv);
    644 }
    645 
    646 void MockCubeb::SetInputProcessingApplyRv(int aRv) {
    647  mInputProcessingParamsApplyRv = aRv;
    648 }
    649 
    650 int MockCubeb::InputProcessingApplyRv() const {
    651  return mInputProcessingParamsApplyRv;
    652 }
    653 
    654 void MockCubeb::AddDevice(cubeb_device_info aDevice) {
    655  if (aDevice.type == CUBEB_DEVICE_TYPE_INPUT) {
    656    mInputDevices.AppendElement(aDevice);
    657  } else if (aDevice.type == CUBEB_DEVICE_TYPE_OUTPUT) {
    658    mOutputDevices.AppendElement(aDevice);
    659  } else {
    660    MOZ_CRASH("bad device type when adding a device in mock cubeb backend");
    661  }
    662 
    663  bool isInput = aDevice.type & CUBEB_DEVICE_TYPE_INPUT;
    664  if (isInput && mInputDeviceCollectionChangeCallback) {
    665    mInputDeviceCollectionChangeCallback(AsCubebContext(),
    666                                         mInputDeviceCollectionChangeUserPtr);
    667  }
    668  if (!isInput && mOutputDeviceCollectionChangeCallback) {
    669    mOutputDeviceCollectionChangeCallback(AsCubebContext(),
    670                                          mOutputDeviceCollectionChangeUserPtr);
    671  }
    672 }
    673 
    674 bool MockCubeb::RemoveDevice(cubeb_devid aId) {
    675  bool foundInput = false;
    676  bool foundOutput = false;
    677  mInputDevices.RemoveElementsBy(
    678      [aId, &foundInput](cubeb_device_info& aDeviceInfo) {
    679        bool foundThisTime = aDeviceInfo.devid == aId;
    680        foundInput |= foundThisTime;
    681        return foundThisTime;
    682      });
    683  mOutputDevices.RemoveElementsBy(
    684      [aId, &foundOutput](cubeb_device_info& aDeviceInfo) {
    685        bool foundThisTime = aDeviceInfo.devid == aId;
    686        foundOutput |= foundThisTime;
    687        return foundThisTime;
    688      });
    689 
    690  if (foundInput && mInputDeviceCollectionChangeCallback) {
    691    mInputDeviceCollectionChangeCallback(AsCubebContext(),
    692                                         mInputDeviceCollectionChangeUserPtr);
    693  }
    694  if (foundOutput && mOutputDeviceCollectionChangeCallback) {
    695    mOutputDeviceCollectionChangeCallback(AsCubebContext(),
    696                                          mOutputDeviceCollectionChangeUserPtr);
    697  }
    698  // If the device removed was a default device, set another device as the
    699  // default, if there are still devices available.
    700  bool foundDefault = false;
    701  for (uint32_t i = 0; i < mInputDevices.Length(); i++) {
    702    foundDefault |= mInputDevices[i].preferred != CUBEB_DEVICE_PREF_NONE;
    703  }
    704 
    705  if (!foundDefault) {
    706    if (!mInputDevices.IsEmpty()) {
    707      mInputDevices[mInputDevices.Length() - 1].preferred =
    708          CUBEB_DEVICE_PREF_ALL;
    709    }
    710  }
    711 
    712  foundDefault = false;
    713  for (uint32_t i = 0; i < mOutputDevices.Length(); i++) {
    714    foundDefault |= mOutputDevices[i].preferred != CUBEB_DEVICE_PREF_NONE;
    715  }
    716 
    717  if (!foundDefault) {
    718    if (!mOutputDevices.IsEmpty()) {
    719      mOutputDevices[mOutputDevices.Length() - 1].preferred =
    720          CUBEB_DEVICE_PREF_ALL;
    721    }
    722  }
    723 
    724  return foundInput | foundOutput;
    725 }
    726 
    727 void MockCubeb::ClearDevices(cubeb_device_type aType) {
    728  if (aType != CUBEB_DEVICE_TYPE_OUTPUT) {
    729    mInputDevices.Clear();
    730  }
    731  if (aType != CUBEB_DEVICE_TYPE_INPUT) {
    732    mOutputDevices.Clear();
    733  }
    734 }
    735 
    736 void MockCubeb::SetPreferredDevice(cubeb_devid aId, cubeb_device_type aType) {
    737  nsTArray<cubeb_device_info>& devs =
    738      aType == CUBEB_DEVICE_TYPE_INPUT ? mInputDevices : mOutputDevices;
    739  bool found{};
    740  for (const auto& dev : devs) {
    741    if (dev.devid == aId) {
    742      found = true;
    743      break;
    744    }
    745  }
    746  if (!found) {
    747    return;
    748  }
    749  for (auto& dev : devs) {
    750    dev.preferred =
    751        dev.devid == aId ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
    752  }
    753  auto& callback = aType == CUBEB_DEVICE_TYPE_INPUT
    754                       ? mInputDeviceCollectionChangeCallback
    755                       : mOutputDeviceCollectionChangeCallback;
    756  void* user_ptr = aType == CUBEB_DEVICE_TYPE_INPUT
    757                       ? mInputDeviceCollectionChangeUserPtr
    758                       : mOutputDeviceCollectionChangeUserPtr;
    759  if (found && callback) {
    760    callback(AsCubebContext(), user_ptr);
    761  }
    762 }
    763 
    764 void MockCubeb::SetSupportDeviceChangeCallback(bool aSupports) {
    765  mSupportsDeviceCollectionChangedCallback = aSupports;
    766 }
    767 
    768 void MockCubeb::ForceStreamInitError() { mStreamInitErrorState = true; }
    769 
    770 void MockCubeb::SetStreamStartFreezeEnabled(bool aEnabled) {
    771  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    772  mStreamStartFreezeEnabled = aEnabled;
    773 }
    774 
    775 auto MockCubeb::ForceAudioThread() -> RefPtr<ForcedAudioThreadPromise> {
    776  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    777  RefPtr<ForcedAudioThreadPromise> p =
    778      mForcedAudioThreadPromise.Ensure(__func__);
    779  mForcedAudioThread = true;
    780  StartStream(nullptr);
    781  return p;
    782 }
    783 
    784 void MockCubeb::UnforceAudioThread() {
    785  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    786  mForcedAudioThread = false;
    787 }
    788 
    789 int MockCubeb::StreamInit(cubeb* aContext, cubeb_stream** aStream,
    790                          char const* aStreamName, cubeb_devid aInputDevice,
    791                          cubeb_stream_params* aInputStreamParams,
    792                          cubeb_devid aOutputDevice,
    793                          cubeb_stream_params* aOutputStreamParams,
    794                          cubeb_data_callback aDataCallback,
    795                          cubeb_state_callback aStateCallback, void* aUserPtr) {
    796  if (mStreamInitErrorState.compareExchange(true, false)) {
    797    mStreamInitEvent.Notify(nullptr);
    798    return CUBEB_ERROR_DEVICE_UNAVAILABLE;
    799  }
    800 
    801  auto mockStream = MakeRefPtr<SmartMockCubebStream>(
    802      aContext, aStreamName, aInputDevice, aInputStreamParams, aOutputDevice,
    803      aOutputStreamParams, aDataCallback, aStateCallback, aUserPtr,
    804      mRunningMode, mStreamStartFreezeEnabled);
    805  *aStream = mockStream->AsCubebStream();
    806  mStreamInitEvent.Notify(mockStream);
    807  // AddRef the stream to keep it alive. StreamDestroy releases it.
    808  NS_ADDREF(mockStream.get());
    809  return CUBEB_OK;
    810 }
    811 
    812 void MockCubeb::StreamDestroy(MockCubebStream* aStream) {
    813  RefPtr<SmartMockCubebStream> mockStream = dont_AddRef(aStream->mSelf);
    814  mStreamDestroyEvent.Notify(mockStream);
    815 }
    816 
    817 void MockCubeb::GoFaster() {
    818  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    819  mFastMode = true;
    820 }
    821 
    822 void MockCubeb::DontGoFaster() {
    823  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    824  mFastMode = false;
    825 }
    826 
    827 MediaEventSource<RefPtr<SmartMockCubebStream>>& MockCubeb::StreamInitEvent() {
    828  return mStreamInitEvent;
    829 }
    830 
    831 MediaEventSource<RefPtr<SmartMockCubebStream>>&
    832 MockCubeb::StreamDestroyEvent() {
    833  return mStreamDestroyEvent;
    834 }
    835 
    836 void MockCubeb::StartStream(MockCubebStream* aStream) {
    837  if (aStream) {
    838    aStream->mMutex.AssertNotCurrentThreadOwns();
    839  }
    840  auto streams = mLiveStreams.Lock();
    841  if (aStream) {
    842    MOZ_RELEASE_ASSERT(!streams->Contains(aStream->mSelf));
    843    streams->AppendElement(aStream->mSelf);
    844  } else {
    845    MOZ_RELEASE_ASSERT(mForcedAudioThread);
    846    // Forcing an audio thread must happen before starting streams
    847    MOZ_RELEASE_ASSERT(streams->IsEmpty());
    848  }
    849  if (!mFakeAudioThreadRunning && mRunningMode == RunningMode::Automatic) {
    850    AddRef();  // released when the thread exits
    851    std::thread(ThreadFunction_s, this).detach();
    852    mFakeAudioThreadRunning = true;
    853  }
    854 }
    855 
    856 void MockCubeb::StopStream(MockCubebStream* aStream) {
    857  aStream->mMutex.AssertNotCurrentThreadOwns();
    858  {
    859    auto streams = mLiveStreams.Lock();
    860    if (!streams->Contains(aStream->mSelf)) {
    861      return;
    862    }
    863    streams->RemoveElement(aStream->mSelf);
    864  }
    865 }
    866 
    867 void MockCubeb::ThreadFunction() {
    868  MOZ_RELEASE_ASSERT(mRunningMode == RunningMode::Automatic);
    869  if (mForcedAudioThread) {
    870    mForcedAudioThreadPromise.Resolve(MakeRefPtr<AudioThreadAutoUnforcer>(this),
    871                                      __func__);
    872  }
    873  while (true) {
    874    {
    875      auto streams = mLiveStreams.Lock();
    876      for (auto& stream : *streams) {
    877        auto keepProcessing = stream->Process10Ms();
    878        if (keepProcessing == KeepProcessing::No) {
    879          stream = nullptr;
    880        }
    881      }
    882      streams->RemoveElementsBy([](const auto& stream) { return !stream; });
    883      MOZ_RELEASE_ASSERT(mFakeAudioThreadRunning);
    884      if (streams->IsEmpty() && !mForcedAudioThread) {
    885        mFakeAudioThreadRunning = false;
    886        break;
    887      }
    888    }
    889    std::this_thread::sleep_for(
    890        std::chrono::microseconds(mFastMode ? 0 : 10 * PR_USEC_PER_MSEC));
    891  }
    892  Release();
    893 }
    894 
    895 }  // namespace mozilla