aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/include/pcm_test_device.h5
-rw-r--r--tests/src/mixer_test.cc49
-rw-r--r--tests/src/pcm_in_test.cc114
-rw-r--r--tests/src/pcm_loopback_test.cc219
4 files changed, 385 insertions, 2 deletions
diff --git a/tests/include/pcm_test_device.h b/tests/include/pcm_test_device.h
index 1932737..f6e7807 100644
--- a/tests/include/pcm_test_device.h
+++ b/tests/include/pcm_test_device.h
@@ -40,8 +40,13 @@ namespace testing {
#define TEST_LOOPBACK_PALYBACK_DEVICE 0
#endif
+#ifndef TEST_LOOPBACK_CAPTURE_DEVICE
+#define TEST_LOOPBACK_CAPTURE_DEVICE 1
+#endif
+
constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD;
constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PALYBACK_DEVICE;
+constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE;
} // namespace testing
} // namespace tinyalse
diff --git a/tests/src/mixer_test.cc b/tests/src/mixer_test.cc
index 333c9d7..d4166ab 100644
--- a/tests/src/mixer_test.cc
+++ b/tests/src/mixer_test.cc
@@ -27,7 +27,9 @@
*/
#include "pcm_test_device.h"
+#include <string_view>
#include <string>
+#include <thread>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
@@ -39,6 +41,17 @@
namespace tinyalsa {
namespace testing {
+#ifndef MAX_CARD_INDEX
+#define MAX_CARD_INDEX 2
+#endif
+
+static constexpr unsigned int kMaxCardIndex = MAX_CARD_INDEX;
+
+TEST(MixerTest, OpenAndClose) {
+ ASSERT_EQ(mixer_open(1000), nullptr);
+ mixer_close(nullptr);
+}
+
class MixerTest : public ::testing::TestWithParam<unsigned int> {
protected:
MixerTest() : mixer_object(nullptr) {}
@@ -244,12 +257,44 @@ TEST_P(MixerControlsTest, SetPercent) {
}
}
+TEST_P(MixerControlsTest, Event) {
+ ASSERT_EQ(mixer_subscribe_events(mixer_object, 1), 0);
+ const mixer_ctl *control = nullptr;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string_view name{mixer_ctl_get_name(controls[i])};
+
+ if (name.find("Volume") != std::string_view::npos) {
+ control = controls[i];
+ }
+ }
+
+ if (control == nullptr) {
+ GTEST_SKIP() << "No volume control was found in the controls list.";
+ }
+
+ auto *local_mixer_object = mixer_object;
+ int percent = mixer_ctl_get_percent(control, 0);
+ std::thread thread([local_mixer_object, control, percent] () {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ mixer_ctl_set_percent(const_cast<mixer_ctl *>(control), 0, percent == 100 ? 0 : 100);
+ });
+
+ EXPECT_EQ(mixer_wait_event(mixer_object, 1000), 1);
+
+ EXPECT_EQ(mixer_consume_event(mixer_object), 0);
+
+ thread.join();
+ ASSERT_EQ(mixer_subscribe_events(mixer_object, 0), 0);
+
+ mixer_ctl_set_percent(const_cast<mixer_ctl *>(control), 0, percent);
+}
+
INSTANTIATE_TEST_SUITE_P(
MixerTest,
MixerTest,
::testing::Range<unsigned int>(
0,
- kLoopbackCard + 1
+ kMaxCardIndex + 1
));
INSTANTIATE_TEST_SUITE_P(
@@ -257,7 +302,7 @@ INSTANTIATE_TEST_SUITE_P(
MixerControlsTest,
::testing::Range<unsigned int>(
0,
- kLoopbackCard + 1
+ kMaxCardIndex + 1
));
} // namespace testing
diff --git a/tests/src/pcm_in_test.cc b/tests/src/pcm_in_test.cc
new file mode 100644
index 0000000..719e4ca
--- /dev/null
+++ b/tests/src/pcm_in_test.cc
@@ -0,0 +1,114 @@
+/* pcm_in_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <chrono>
+#include <cstring>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+class PcmInTest : public ::testing::Test {
+ protected:
+ PcmInTest() : pcm_object(nullptr) {}
+ virtual ~PcmInTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kDefaultConfig);
+ ASSERT_NE(pcm_object, nullptr);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ }
+
+ virtual void TearDown() override {
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+ }
+
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr pcm_config kDefaultConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = 0,
+ .stop_threshold = 0,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm* pcm_object;
+};
+
+TEST_F(PcmInTest, GetDealy) {
+ long delay = pcm_get_delay(pcm_object);
+ std::cout << delay << std::endl;
+ ASSERT_GE(delay, 0);
+}
+
+TEST_F(PcmInTest, Readi) {
+ constexpr uint32_t read_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+
+ int read_frames = 0;
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ auto start = std::chrono::steady_clock::now();
+ for (uint32_t i = 0; i < read_count; ++i) {
+ read_frames = pcm_readi(pcm_object, buffer.get(), frames);
+ ASSERT_EQ(read_frames, frames);
+ }
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames * read_count /
+ (kDefaultConfig.rate / 1000));
+
+ std::cout << difference.count() << std::endl;
+ std::cout << expected_elapsed_time_ms.count() << std::endl;
+
+ ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100);
+}
+
+TEST_F(PcmInTest, Writei) {
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ ASSERT_EQ(pcm_writei(pcm_object, buffer.get(), frames), -EINVAL);
+}
+
+} // namespace testing
+} // namespace tinyalse
diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc
new file mode 100644
index 0000000..2571c58
--- /dev/null
+++ b/tests/src/pcm_loopback_test.cc
@@ -0,0 +1,219 @@
+/* pcm_loopback_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <iostream>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+template<int32_t CH, int32_t SR, pcm_format F>
+class SilenceGenerator {
+public:
+ pcm_format GetFormat() {
+ return F;
+ }
+
+ int32_t GetChannels() {
+ return CH;
+ };
+
+ int32_t GetSamplingRate() {
+ return SR;
+ };
+
+ virtual int32_t Read(void *buffer, int32_t size) {
+ std::memset(buffer, 0, size);
+ return size;
+ }
+};
+
+template<pcm_format F>
+struct PcmFormat {
+ using Type = void;
+ static constexpr int32_t kMax = 0;
+ static constexpr int32_t kMin = 0;
+};
+
+template<>
+struct PcmFormat<PCM_FORMAT_S16_LE> {
+ using Type = int16_t;
+ static constexpr Type kMax = std::numeric_limits<Type>::max();
+ static constexpr Type kMin = std::numeric_limits<Type>::min();
+};
+
+// CH: channels
+// SR: sampling rate
+// FQ: sine wave frequency
+// L: max level
+template<int32_t CH, int32_t SR, int32_t FQ, int32_t L, pcm_format F>
+class SineToneGenerator : public SilenceGenerator<CH, SR, F> {
+private:
+ using Type = typename PcmFormat<F>::Type;
+ static constexpr double kPi = M_PI;
+ static constexpr double kStep = FQ * CH * kPi / SR;
+
+ double channels[CH];
+ double gain;
+
+ Type GetSample(double radian) {
+ double sine = std::sin(radian) * gain;
+ if (sine >= 1.0) {
+ return PcmFormat<F>::kMax;
+ } else if (sine <= -1.0) {
+ return PcmFormat<F>::kMin;
+ }
+ return static_cast<Type>(sine * PcmFormat<F>::kMax);
+ }
+
+public:
+ SineToneGenerator() {
+ constexpr double phase = (CH == 1) ? 0 : kPi / 2 / (CH - 1);
+
+ channels[0] = 0.0;
+ for (int32_t i = 1; i < CH; ++i) {
+ channels[i] = channels[i - 1] + phase;
+ }
+
+ gain = std::pow(M_E, std::log(10) * static_cast<double>(L) / 20.0);
+ }
+
+ ~SineToneGenerator() = default;
+
+ int32_t Read(void *buffer, int32_t size) override {
+ Type *pcm_buffer = reinterpret_cast<Type *>(buffer);
+
+ size = (size / (CH * sizeof(Type))) * (CH * sizeof(Type));
+ int32_t samples = size / sizeof(Type);
+ int32_t s = 0;
+
+ while (s < samples) {
+ for (int32_t i = 0; i < CH; ++i) {
+ pcm_buffer[s++] = GetSample(channels[i]);
+ channels[i] += kStep;
+ }
+ }
+ return size;
+ }
+};
+
+template<typename T>
+static double Energy(T *buffer, size_t samples) {
+ double sum = 0.0;
+ for (size_t i = 0; i < samples; i++) {
+ sum += static_cast<double>(buffer[i]) * static_cast<double>(buffer[i]);
+ }
+ return sum;
+}
+
+TEST(PcmLoopbackTest, LoopbackS16le) {
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr pcm_config kInConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = 0,
+ .stop_threshold = 0,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm *pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_in));
+
+ static constexpr pcm_config kOutConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm *pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_out));
+
+ ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
+
+ bool stopping = false;
+ ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out));
+
+ std::thread capture([pcm_in, &stopping] {
+ size_t buffer_size = pcm_frames_to_bytes(pcm_in, kDefaultPeriodSize);
+ unsigned int frames = pcm_bytes_to_frames(pcm_in, buffer_size);
+ auto buffer = std::make_unique<unsigned char[]>(buffer_size);
+ int32_t counter = 0;
+ while (!stopping) {
+ EXPECT_EQ(pcm_readi(pcm_in, buffer.get(), frames), frames);
+ counter++;
+ if (counter >= 5) {
+ double e = Energy(buffer.get(), frames * kInConfig.channels);
+ EXPECT_GT(e, 0.0);
+ }
+ }
+ });
+
+ std::thread playback([pcm_out, &stopping] {
+ SineToneGenerator<2, 48000, 1000, 0, PCM_FORMAT_S16_LE> generator;
+ size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize);
+ unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size);
+ int32_t counter = 0;
+ auto buffer = std::make_unique<unsigned char[]>(buffer_size);
+ while (!stopping) {
+ generator.Read(buffer.get(), buffer_size);
+ EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames);
+ counter++;
+ }
+ });
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ stopping = true;
+ capture.join();
+ playback.join();
+
+ ASSERT_EQ(pcm_unlink(pcm_in), 0);
+ pcm_close(pcm_in);
+ pcm_close(pcm_out);
+}
+
+} // namespace testing
+} // namespace tinyalse