/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=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 https://mozilla.org/MPL/2.0/. */ #include "AudioInputSource.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "MockCubeb.h" #include "WaitFor.h" #include "nsContentUtils.h" using namespace mozilla; namespace { #define DispatchFunction(f) \ NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f)) } // namespace class MockEventListener : public AudioInputSource::EventListener { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockEventListener, override); MOCK_METHOD1(AudioDeviceChanged, void(AudioInputSource::Id)); MOCK_METHOD2(AudioStateCallback, void(AudioInputSource::Id, AudioInputSource::EventListener::State)); private: ~MockEventListener() = default; }; TEST(TestAudioInputSource, StartAndStop) { MockCubeb* cubeb = new MockCubeb(); CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); const AudioInputSource::Id sourceId = 1; const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; const uint32_t channels = 2; const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); const TrackRate sourceRate = 44100; const TrackRate targetRate = 48000; const uint32_t bufferingMs = 50; // ms auto listener = MakeRefPtr(); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Started)) .Times(2); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Stopped)) .Times(2); RefPtr ais = MakeRefPtr( std::move(listener), sourceId, deviceId, channels, true, testPrincipal, sourceRate, targetRate, bufferingMs); ASSERT_TRUE(ais); // Make sure start and stop works. { DispatchFunction([&] { ais->Start(); }); RefPtr stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_TRUE(stream->mHasInput); EXPECT_FALSE(stream->mHasOutput); EXPECT_EQ(stream->GetInputDeviceID(), deviceId); EXPECT_EQ(stream->InputChannels(), channels); EXPECT_EQ(stream->InputSampleRate(), static_cast(sourceRate)); Unused << WaitFor(stream->FramesProcessedEvent()); DispatchFunction([&] { ais->Stop(); }); Unused << WaitFor(cubeb->StreamDestroyEvent()); } // Make sure restart is ok. { DispatchFunction([&] { ais->Start(); }); RefPtr stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_TRUE(stream->mHasInput); EXPECT_FALSE(stream->mHasOutput); EXPECT_EQ(stream->GetInputDeviceID(), deviceId); EXPECT_EQ(stream->InputChannels(), channels); EXPECT_EQ(stream->InputSampleRate(), static_cast(sourceRate)); Unused << WaitFor(stream->FramesProcessedEvent()); DispatchFunction([&] { ais->Stop(); }); Unused << WaitFor(cubeb->StreamDestroyEvent()); } ais = nullptr; // Drop the SharedThreadPool here. } TEST(TestAudioInputSource, DataOutputBeforeStartAndAfterStop) { MockCubeb* cubeb = new MockCubeb(); CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); const AudioInputSource::Id sourceId = 1; const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; const uint32_t channels = 2; const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); const TrackRate sourceRate = 44100; const TrackRate targetRate = 48000; const uint32_t bufferingMs = 50; // ms const TrackTime requestFrames = 2 * WEBAUDIO_BLOCK_SIZE; auto listener = MakeRefPtr(); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Started)); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Stopped)); RefPtr ais = MakeRefPtr( std::move(listener), sourceId, deviceId, channels, true, testPrincipal, sourceRate, targetRate, bufferingMs); ASSERT_TRUE(ais); // It's ok to call GetAudioSegment before starting { AudioSegment data = ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same); EXPECT_EQ(data.GetDuration(), requestFrames); EXPECT_TRUE(data.IsNull()); } DispatchFunction([&] { ais->Start(); }); RefPtr stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_TRUE(stream->mHasInput); EXPECT_FALSE(stream->mHasOutput); EXPECT_EQ(stream->InputChannels(), channels); stream->SetInputRecordingEnabled(true); Unused << WaitFor(stream->FramesProcessedEvent()); DispatchFunction([&] { ais->Stop(); }); Unused << WaitFor(cubeb->StreamDestroyEvent()); // Check the data output { nsTArray record = stream->TakeRecordedInput(); size_t frames = record.Length() / channels; AudioSegment deinterleaved; deinterleaved.AppendFromInterleavedBuffer(record.Elements(), frames, channels, testPrincipal); AudioDriftCorrection driftCorrector(sourceRate, targetRate, bufferingMs, testPrincipal); AudioSegment expectedSegment = driftCorrector.RequestFrames( deinterleaved, static_cast(requestFrames)); nsTArray expected; size_t expectedSamples = expectedSegment.WriteToInterleavedBuffer(expected, channels); AudioSegment actualSegment = ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same); EXPECT_EQ(actualSegment.GetDuration(), requestFrames); nsTArray actual; size_t actualSamples = actualSegment.WriteToInterleavedBuffer(actual, channels); EXPECT_EQ(actualSamples, expectedSamples); EXPECT_EQ(actualSamples / channels, static_cast(requestFrames)); EXPECT_EQ(actual, expected); } ais = nullptr; // Drop the SharedThreadPool here. } TEST(TestAudioInputSource, ErrorCallback) { MockCubeb* cubeb = new MockCubeb(); CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); const AudioInputSource::Id sourceId = 1; const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; const uint32_t channels = 2; const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); const TrackRate sourceRate = 44100; const TrackRate targetRate = 48000; const uint32_t bufferingMs = 50; // ms auto listener = MakeRefPtr(); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Started)); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Error)); RefPtr ais = MakeRefPtr( std::move(listener), sourceId, deviceId, channels, true, testPrincipal, sourceRate, targetRate, bufferingMs); ASSERT_TRUE(ais); DispatchFunction([&] { ais->Start(); }); RefPtr stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_TRUE(stream->mHasInput); EXPECT_FALSE(stream->mHasOutput); EXPECT_EQ(stream->InputChannels(), channels); Unused << WaitFor(stream->FramesProcessedEvent()); DispatchFunction([&] { stream->ForceError(); }); WaitFor(stream->ErrorForcedEvent()); // Make sure the stream has been stopped by the error-state's backgroud thread // task, to avoid getting a stopped state callback by `ais->Stop` below. WaitFor(stream->ErrorStoppedEvent()); DispatchFunction([&] { ais->Stop(); }); Unused << WaitFor(cubeb->StreamDestroyEvent()); ais = nullptr; // Drop the SharedThreadPool here. } TEST(TestAudioInputSource, DeviceChangedCallback) { MockCubeb* cubeb = new MockCubeb(); CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); const AudioInputSource::Id sourceId = 1; const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1; const uint32_t channels = 2; const PrincipalHandle testPrincipal = MakePrincipalHandle(nsContentUtils::GetSystemPrincipal()); const TrackRate sourceRate = 44100; const TrackRate targetRate = 48000; const uint32_t bufferingMs = 50; // ms auto listener = MakeRefPtr(); EXPECT_CALL(*listener, AudioDeviceChanged(sourceId)); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Started)); EXPECT_CALL(*listener, AudioStateCallback( sourceId, AudioInputSource::EventListener::State::Stopped)); RefPtr ais = MakeRefPtr( std::move(listener), sourceId, deviceId, channels, true, testPrincipal, sourceRate, targetRate, bufferingMs); ASSERT_TRUE(ais); DispatchFunction([&] { ais->Start(); }); RefPtr stream = WaitFor(cubeb->StreamInitEvent()); EXPECT_TRUE(stream->mHasInput); EXPECT_FALSE(stream->mHasOutput); EXPECT_EQ(stream->InputChannels(), channels); Unused << WaitFor(stream->FramesProcessedEvent()); DispatchFunction([&] { stream->ForceDeviceChanged(); }); WaitFor(stream->DeviceChangeForcedEvent()); DispatchFunction([&] { ais->Stop(); }); Unused << WaitFor(cubeb->StreamDestroyEvent()); ais = nullptr; // Drop the SharedThreadPool here. }