summaryrefslogtreecommitdiffstats
path: root/dom/media/gtest/TestAudioInputSource.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/media/gtest/TestAudioInputSource.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/gtest/TestAudioInputSource.cpp')
-rw-r--r--dom/media/gtest/TestAudioInputSource.cpp275
1 files changed, 275 insertions, 0 deletions
diff --git a/dom/media/gtest/TestAudioInputSource.cpp b/dom/media/gtest/TestAudioInputSource.cpp
new file mode 100644
index 0000000000..c5b820065b
--- /dev/null
+++ b/dom/media/gtest/TestAudioInputSource.cpp
@@ -0,0 +1,275 @@
+/* -*- 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<MockEventListener>();
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Started))
+ .Times(2);
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Stopped))
+ .Times(4);
+
+ RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>(
+ std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
+ sourceRate, targetRate, bufferingMs);
+ ASSERT_TRUE(ais);
+
+ // Make sure start and stop works.
+ {
+ DispatchFunction([&] { ais->Start(); });
+ RefPtr<SmartMockCubebStream> 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<uint32_t>(sourceRate));
+
+ Unused << WaitFor(stream->FramesProcessedEvent());
+
+ DispatchFunction([&] { ais->Stop(); });
+ Unused << WaitFor(cubeb->StreamDestroyEvent());
+ }
+
+ // Make sure restart is ok.
+ {
+ DispatchFunction([&] { ais->Start(); });
+ RefPtr<SmartMockCubebStream> 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<uint32_t>(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<MockEventListener>();
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Started));
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Stopped))
+ .Times(2);
+
+ RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>(
+ 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<SmartMockCubebStream> 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<AudioDataValue> 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<uint32_t>(requestFrames));
+
+ nsTArray<AudioDataValue> expected;
+ size_t expectedSamples =
+ expectedSegment.WriteToInterleavedBuffer(expected, channels);
+
+ AudioSegment actualSegment =
+ ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same);
+ EXPECT_EQ(actualSegment.GetDuration(), requestFrames);
+ nsTArray<AudioDataValue> actual;
+ size_t actualSamples =
+ actualSegment.WriteToInterleavedBuffer(actual, channels);
+
+ EXPECT_EQ(actualSamples, expectedSamples);
+ EXPECT_EQ(actualSamples / channels, static_cast<size_t>(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<MockEventListener>();
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Started));
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Error));
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Stopped));
+
+ RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>(
+ std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
+ sourceRate, targetRate, bufferingMs);
+ ASSERT_TRUE(ais);
+
+ DispatchFunction([&] { ais->Start(); });
+ RefPtr<SmartMockCubebStream> 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<MockEventListener>();
+ EXPECT_CALL(*listener, AudioDeviceChanged(sourceId));
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Started));
+ EXPECT_CALL(*listener,
+ AudioStateCallback(
+ sourceId, AudioInputSource::EventListener::State::Stopped))
+ .Times(2);
+
+ RefPtr<AudioInputSource> ais = MakeRefPtr<AudioInputSource>(
+ std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
+ sourceRate, targetRate, bufferingMs);
+ ASSERT_TRUE(ais);
+
+ DispatchFunction([&] { ais->Start(); });
+ RefPtr<SmartMockCubebStream> 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.
+}