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