TestAudioSinkWrapper.cpp (5924B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "AudioSink.h" 8 #include "AudioSinkWrapper.h" 9 #include "CubebUtils.h" 10 #include "MockCubeb.h" 11 #include "TimeUnits.h" 12 #include "gmock/gmock.h" 13 #include "gtest/gtest-printers.h" 14 #include "gtest/gtest.h" 15 #include "mozilla/SpinEventLoopUntil.h" 16 #include "mozilla/gtest/WaitFor.h" 17 #include "nsThreadManager.h" 18 #include "nsThreadUtils.h" 19 20 using namespace mozilla; 21 22 // This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is 23 // not settled twice when sync and async AudioSink initializations race. 24 TEST(TestAudioSinkWrapper, AsyncInitFailureWithSyncInitSuccess) 25 { 26 MockCubeb* cubeb = new MockCubeb(); 27 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 28 29 MediaQueue<AudioData> audioQueue; 30 MediaInfo info; 31 info.EnableAudio(); 32 auto audioSinkCreator = [&]() { 33 return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(), 34 audioQueue, info.mAudio, 35 /*resistFingerprinting*/ false)}; 36 }; 37 const double initialVolume = 0.0; // so that there is initially no AudioSink 38 RefPtr wrapper = new AudioSinkWrapper( 39 AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator), 40 initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true, 41 /*sinkDevice*/ nullptr); 42 43 wrapper->Start(media::TimeUnit::Zero(), info); 44 // The first AudioSink init occurs on a background thread. Listen for this, 45 // but don't process any events on the current thread so that the 46 // AudioSinkWrapper does not yet handle the result of AudioSink 47 // initialization. 48 RefPtr backgroundQueue = 49 nsThreadManager::get().CreateBackgroundTaskQueue(__func__); 50 Monitor monitor(__func__); 51 bool initDone = false; 52 MediaEventListener initListener = cubeb->StreamInitEvent().Connect( 53 backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) { 54 EXPECT_EQ(aStream, nullptr); 55 MonitorAutoLock lock(monitor); 56 initDone = true; 57 lock.Notify(); 58 }); 59 cubeb->ForceStreamInitError(); 60 wrapper->SetVolume(0.5); // triggers async sink init, which fails 61 { 62 // Wait for the async init to complete. 63 MonitorAutoLock lock(monitor); 64 while (!initDone) { 65 lock.Wait(); 66 } 67 } 68 initListener.Disconnect(); 69 wrapper->SetPlaying(false); 70 // The second AudioSink init is synchronous. 71 nsIThread* currentThread = NS_GetCurrentThread(); 72 RefPtr<SmartMockCubebStream> stream; 73 initListener = cubeb->StreamInitEvent().Connect( 74 currentThread, [&](RefPtr<SmartMockCubebStream> aStream) { 75 stream = std::move(aStream); 76 }); 77 wrapper->SetPlaying(true); // sync sink init, which succeeds 78 // Let AudioSinkWrapper handle the (first) AudioSink initialization failure 79 // and allow `stream` to be set. 80 NS_ProcessPendingEvents(currentThread); 81 initListener.Disconnect(); 82 cubeb_state state = CUBEB_STATE_STARTED; 83 MediaEventListener stateListener = stream->StateEvent().Connect( 84 currentThread, [&](cubeb_state aState) { state = aState; }); 85 // Run AudioSinkWrapper::OnAudioEnded(). 86 // This test passes if there is no crash. Bug 1845811. 87 audioQueue.Finish(); 88 SpinEventLoopUntil("stream state change"_ns, 89 [&] { return state != CUBEB_STATE_STARTED; }); 90 stateListener.Disconnect(); 91 EXPECT_EQ(state, CUBEB_STATE_DRAINED); 92 wrapper->Stop(); 93 wrapper->Shutdown(); 94 } 95 96 // This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is 97 // not settled twice when the audio ends during async AudioSink initialization. 98 TEST(TestAudioSinkWrapper, AsyncInitWithEndOfAudio) 99 { 100 MockCubeb* cubeb = new MockCubeb(); 101 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 102 103 MediaQueue<AudioData> audioQueue; 104 MediaInfo info; 105 info.EnableAudio(); 106 auto audioSinkCreator = [&]() { 107 return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(), 108 audioQueue, info.mAudio, 109 /*resistFingerprinting*/ false)}; 110 }; 111 const double initialVolume = 0.0; // so that there is initially no AudioSink 112 RefPtr wrapper = new AudioSinkWrapper( 113 AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator), 114 initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true, 115 /*sinkDevice*/ nullptr); 116 117 wrapper->Start(media::TimeUnit::Zero(), info); 118 // The first AudioSink init occurs on a background thread. Listen for this, 119 // but don't process any events on the current thread so that the 120 // AudioSinkWrapper does not yet use the initialized AudioSink. 121 RefPtr backgroundQueue = 122 nsThreadManager::get().CreateBackgroundTaskQueue(__func__); 123 Monitor monitor(__func__); 124 RefPtr<SmartMockCubebStream> stream; 125 MediaEventListener initListener = cubeb->StreamInitEvent().Connect( 126 backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) { 127 EXPECT_NE(aStream, nullptr); 128 MonitorAutoLock lock(monitor); 129 stream = std::move(aStream); 130 lock.Notify(); 131 }); 132 wrapper->SetVolume(0.5); // triggers async sink init 133 { 134 // Wait for the async init to complete. 135 MonitorAutoLock lock(monitor); 136 while (!stream) { 137 lock.Wait(); 138 } 139 } 140 initListener.Disconnect(); 141 // Finish the audio before AudioSinkWrapper considers using the initialized 142 // AudioSink. 143 audioQueue.Finish(); 144 // Wait for AudioSinkWrapper to destroy the initialized stream. 145 // This test passes if there is no crash. Bug 1846854. 146 WaitFor(cubeb->StreamDestroyEvent()); 147 wrapper->Stop(); 148 wrapper->Shutdown(); 149 }