TestAudioInputSource.cpp (12951B)
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 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #include "AudioInputSource.h" 8 #include "MockCubeb.h" 9 #include "gmock/gmock.h" 10 #include "gtest/gtest.h" 11 #include "mozilla/Result.h" 12 #include "mozilla/gtest/WaitFor.h" 13 #include "nsContentUtils.h" 14 15 using namespace mozilla; 16 using testing::ContainerEq; 17 18 // Short-hand for DispatchToCurrentThread with a function. 19 #define DispatchFunction(f) \ 20 NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f)) 21 22 // Short-hand for draining the current threads event queue, i.e. processing 23 // those runnables dispatched per above. 24 #define ProcessEventQueue() \ 25 while (NS_ProcessNextEvent(nullptr, false)) { \ 26 } 27 28 class MockEventListener : public AudioInputSource::EventListener { 29 public: 30 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockEventListener, override); 31 MOCK_METHOD1(AudioDeviceChanged, void(AudioInputSource::Id)); 32 MOCK_METHOD2(AudioStateCallback, 33 void(AudioInputSource::Id, 34 AudioInputSource::EventListener::State)); 35 36 private: 37 ~MockEventListener() = default; 38 }; 39 40 TEST(TestAudioInputSource, StartAndStop) 41 { 42 MockCubeb* cubeb = new MockCubeb(); 43 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 44 45 const AudioInputSource::Id sourceId = 1; 46 const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; 47 const uint32_t channels = 2; 48 const PrincipalHandle testPrincipal = 49 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); 50 const TrackRate sourceRate = 44100; 51 const TrackRate targetRate = 48000; 52 53 auto listener = MakeRefPtr<MockEventListener>(); 54 EXPECT_CALL(*listener, 55 AudioStateCallback( 56 sourceId, AudioInputSource::EventListener::State::Started)) 57 .Times(2); 58 EXPECT_CALL(*listener, 59 AudioStateCallback( 60 sourceId, AudioInputSource::EventListener::State::Stopped)) 61 .Times(4); 62 63 RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>( 64 std::move(listener), sourceId, deviceId, channels, true, testPrincipal, 65 sourceRate, targetRate); 66 ASSERT_TRUE(ais); 67 68 // Make sure start and stop works. 69 { 70 DispatchFunction([&] { 71 ais->Init(); 72 ais->Start(); 73 }); 74 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); 75 EXPECT_TRUE(stream->mHasInput); 76 EXPECT_FALSE(stream->mHasOutput); 77 EXPECT_EQ(stream->GetInputDeviceID(), deviceId); 78 EXPECT_EQ(stream->InputChannels(), channels); 79 EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(sourceRate)); 80 81 (void)WaitFor(stream->FramesProcessedEvent()); 82 83 DispatchFunction([&] { ais->Stop(); }); 84 (void)WaitFor(cubeb->StreamDestroyEvent()); 85 } 86 87 // Make sure restart is ok. 88 { 89 DispatchFunction([&] { 90 ais->Init(); 91 ais->Start(); 92 }); 93 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); 94 EXPECT_TRUE(stream->mHasInput); 95 EXPECT_FALSE(stream->mHasOutput); 96 EXPECT_EQ(stream->GetInputDeviceID(), deviceId); 97 EXPECT_EQ(stream->InputChannels(), channels); 98 EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(sourceRate)); 99 100 (void)WaitFor(stream->FramesProcessedEvent()); 101 102 DispatchFunction([&] { ais->Stop(); }); 103 (void)WaitFor(cubeb->StreamDestroyEvent()); 104 } 105 106 ais = nullptr; // Drop the SharedThreadPool here. 107 } 108 109 TEST(TestAudioInputSource, DataOutputBeforeStartAndAfterStop) 110 { 111 MockCubeb* cubeb = new MockCubeb(); 112 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 113 114 const AudioInputSource::Id sourceId = 1; 115 const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; 116 const uint32_t channels = 2; 117 const PrincipalHandle testPrincipal = 118 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); 119 const TrackRate sourceRate = 44100; 120 const TrackRate targetRate = 48000; 121 122 const TrackTime requestFrames = 2 * WEBAUDIO_BLOCK_SIZE; 123 124 auto listener = MakeRefPtr<MockEventListener>(); 125 EXPECT_CALL(*listener, 126 AudioStateCallback( 127 sourceId, AudioInputSource::EventListener::State::Started)); 128 EXPECT_CALL(*listener, 129 AudioStateCallback( 130 sourceId, AudioInputSource::EventListener::State::Stopped)) 131 .Times(2); 132 133 RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>( 134 std::move(listener), sourceId, deviceId, channels, true, testPrincipal, 135 sourceRate, targetRate); 136 ASSERT_TRUE(ais); 137 138 // It's ok to call GetAudioSegment before starting 139 { 140 AudioSegment data = 141 ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same); 142 EXPECT_EQ(data.GetDuration(), requestFrames); 143 EXPECT_TRUE(data.IsNull()); 144 } 145 146 DispatchFunction([&] { 147 ais->Init(); 148 ais->Start(); 149 }); 150 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); 151 EXPECT_TRUE(stream->mHasInput); 152 EXPECT_FALSE(stream->mHasOutput); 153 EXPECT_EQ(stream->InputChannels(), channels); 154 155 stream->SetInputRecordingEnabled(true); 156 157 (void)WaitFor(stream->FramesProcessedEvent()); 158 159 DispatchFunction([&] { ais->Stop(); }); 160 (void)WaitFor(cubeb->StreamDestroyEvent()); 161 162 // Check the data output 163 { 164 nsTArray<AudioDataValue> record = stream->TakeRecordedInput(); 165 size_t frames = record.Length() / channels; 166 AudioSegment deinterleaved; 167 deinterleaved.AppendFromInterleavedBuffer(record.Elements(), frames, 168 channels, testPrincipal); 169 AudioDriftCorrection driftCorrector(sourceRate, targetRate, testPrincipal); 170 AudioSegment expectedSegment = driftCorrector.RequestFrames( 171 deinterleaved, static_cast<uint32_t>(requestFrames)); 172 173 CopyableTArray<AudioDataValue> expected; 174 size_t expectedSamples = 175 expectedSegment.WriteToInterleavedBuffer(expected, channels); 176 177 AudioSegment actualSegment = 178 ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same); 179 EXPECT_EQ(actualSegment.GetDuration(), requestFrames); 180 CopyableTArray<AudioDataValue> actual; 181 size_t actualSamples = 182 actualSegment.WriteToInterleavedBuffer(actual, channels); 183 184 EXPECT_EQ(actualSamples, expectedSamples); 185 EXPECT_EQ(actualSamples / channels, static_cast<size_t>(requestFrames)); 186 EXPECT_THAT(actual, ContainerEq(expected)); 187 } 188 189 ais = nullptr; // Drop the SharedThreadPool here. 190 } 191 192 TEST(TestAudioInputSource, ErrorCallback) 193 { 194 MockCubeb* cubeb = new MockCubeb(); 195 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 196 197 const AudioInputSource::Id sourceId = 1; 198 const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; 199 const uint32_t channels = 2; 200 const PrincipalHandle testPrincipal = 201 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); 202 const TrackRate sourceRate = 44100; 203 const TrackRate targetRate = 48000; 204 205 auto listener = MakeRefPtr<MockEventListener>(); 206 EXPECT_CALL(*listener, 207 AudioStateCallback( 208 sourceId, AudioInputSource::EventListener::State::Started)); 209 EXPECT_CALL(*listener, 210 AudioStateCallback( 211 sourceId, AudioInputSource::EventListener::State::Error)); 212 EXPECT_CALL(*listener, 213 AudioStateCallback( 214 sourceId, AudioInputSource::EventListener::State::Stopped)) 215 .Times(2); 216 217 RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>( 218 std::move(listener), sourceId, deviceId, channels, true, testPrincipal, 219 sourceRate, targetRate); 220 ASSERT_TRUE(ais); 221 222 DispatchFunction([&] { 223 ais->Init(); 224 ais->Start(); 225 }); 226 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); 227 EXPECT_TRUE(stream->mHasInput); 228 EXPECT_FALSE(stream->mHasOutput); 229 EXPECT_EQ(stream->InputChannels(), channels); 230 231 (void)WaitFor(stream->FramesProcessedEvent()); 232 233 DispatchFunction([&] { stream->ForceError(); }); 234 WaitFor(stream->ErrorForcedEvent()); 235 236 DispatchFunction([&] { ais->Stop(); }); 237 (void)WaitFor(cubeb->StreamDestroyEvent()); 238 239 ais = nullptr; // Drop the SharedThreadPool here. 240 } 241 242 TEST(TestAudioInputSource, DeviceChangedCallback) 243 { 244 MockCubeb* cubeb = new MockCubeb(); 245 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 246 247 const AudioInputSource::Id sourceId = 1; 248 const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; 249 const uint32_t channels = 2; 250 const PrincipalHandle testPrincipal = 251 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); 252 const TrackRate sourceRate = 44100; 253 const TrackRate targetRate = 48000; 254 255 auto listener = MakeRefPtr<MockEventListener>(); 256 EXPECT_CALL(*listener, AudioDeviceChanged(sourceId)); 257 EXPECT_CALL(*listener, 258 AudioStateCallback( 259 sourceId, AudioInputSource::EventListener::State::Started)); 260 EXPECT_CALL(*listener, 261 AudioStateCallback( 262 sourceId, AudioInputSource::EventListener::State::Stopped)) 263 .Times(2); 264 265 RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>( 266 std::move(listener), sourceId, deviceId, channels, true, testPrincipal, 267 sourceRate, targetRate); 268 ASSERT_TRUE(ais); 269 270 DispatchFunction([&] { 271 ais->Init(); 272 ais->Start(); 273 }); 274 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); 275 EXPECT_TRUE(stream->mHasInput); 276 EXPECT_FALSE(stream->mHasOutput); 277 EXPECT_EQ(stream->InputChannels(), channels); 278 279 (void)WaitFor(stream->FramesProcessedEvent()); 280 281 DispatchFunction([&] { stream->ForceDeviceChanged(); }); 282 WaitFor(stream->DeviceChangeForcedEvent()); 283 284 DispatchFunction([&] { ais->Stop(); }); 285 (void)WaitFor(cubeb->StreamDestroyEvent()); 286 287 ais = nullptr; // Drop the SharedThreadPool here. 288 } 289 290 TEST(TestAudioInputSource, InputProcessing) 291 { 292 MockCubeb* cubeb = new MockCubeb(); 293 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); 294 295 const AudioInputSource::Id sourceId = 1; 296 const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; 297 const uint32_t channels = 2; 298 const PrincipalHandle testPrincipal = 299 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); 300 const TrackRate sourceRate = 44100; 301 const TrackRate targetRate = 48000; 302 using ProcessingPromise = 303 AudioInputSource::SetRequestedProcessingParamsPromise; 304 305 auto listener = MakeRefPtr<MockEventListener>(); 306 EXPECT_CALL(*listener, 307 AudioStateCallback( 308 sourceId, AudioInputSource::EventListener::State::Started)) 309 .Times(0); 310 EXPECT_CALL(*listener, 311 AudioStateCallback( 312 sourceId, AudioInputSource::EventListener::State::Stopped)) 313 .Times(10); 314 315 RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>( 316 std::move(listener), sourceId, deviceId, channels, true, testPrincipal, 317 sourceRate, targetRate); 318 319 const auto test = 320 [&](cubeb_input_processing_params aRequested, 321 const Result<cubeb_input_processing_params, int>& aExpected) { 322 RefPtr<ProcessingPromise> p; 323 DispatchFunction([&] { 324 ais->Init(); 325 p = ais->SetRequestedProcessingParams(aRequested); 326 }); 327 ProcessEventQueue(); 328 EXPECT_EQ(WaitFor(p), aExpected); 329 330 DispatchFunction([&] { ais->Stop(); }); 331 (void)WaitFor(cubeb->StreamDestroyEvent()); 332 }; 333 334 // Not supported by backend. 335 cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, 336 CUBEB_ERROR_NOT_SUPPORTED); 337 test(CUBEB_INPUT_PROCESSING_PARAM_NONE, Err(CUBEB_ERROR_NOT_SUPPORTED)); 338 339 // Not supported by params. 340 cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, 341 CUBEB_OK); 342 test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, 343 CUBEB_INPUT_PROCESSING_PARAM_NONE); 344 345 constexpr cubeb_input_processing_params allParams = 346 CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION | 347 CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION | 348 CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL | 349 CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION; 350 351 // Successful all. 352 cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK); 353 test(allParams, allParams); 354 355 // Successful partial. 356 test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, 357 CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION); 358 359 // Not supported by stream. 360 // Note this also tests that AudioInputSource resets its configured params 361 // state from the previous successful test. 362 constexpr int propagatedError = 99; 363 cubeb->SetInputProcessingApplyRv(propagatedError); 364 test(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, Err(propagatedError)); 365 366 ais = nullptr; // Drop the SharedThreadPool here. 367 }