aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordvdli <70133153+dvdli@users.noreply.github.com>2020-12-04 09:54:26 +0800
committerGitHub <noreply@github.com>2020-12-04 09:54:26 +0800
commit649c6908706677fab68e3845d5225ede4f52b4fe (patch)
tree8e902ae95c75c7a83428db4f16f4224c1ce2442b
parent40867609e738919872c88b7716b17c30d96211a4 (diff)
parent69d41f144ad82d652f148c303a168cd07d222d41 (diff)
Merge pull request #191 from dvdli/tinyalsa-tests
Tests for libtinyalsa
-rw-r--r--.gitignore1
-rw-r--r--BUILD57
-rw-r--r--README.md38
-rw-r--r--WORKSPACE7
-rw-r--r--tests/include/pcm_test_device.h54
-rw-r--r--tests/src/mixer_test.cc316
-rw-r--r--tests/src/pcm_in_test.cc114
-rw-r--r--tests/src/pcm_loopback_test.cc230
-rw-r--r--tests/src/pcm_out_test.cc216
-rw-r--r--tests/src/pcm_params_test.cc222
-rw-r--r--tests/src/pcm_test.cc103
11 files changed, 1358 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index befe4ba..8773c34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@
/utils/tinymix
/utils/tinypcminfo
+/bazel*
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..dba4ab5
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,57 @@
+# BUILD
+#
+# 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.
+#
+
+cc_library(
+ name = "tinyalsa",
+ srcs = glob(["src/*.c"]),
+ includes = ["include"],
+ hdrs = glob([
+ "include/**/*.h",
+ "src/*.h",
+ ]),
+ visibility = ["//visibility:public"],
+)
+
+cc_test(
+ name = "tinyalsa_tests",
+ srcs = glob([
+ "tests/src/*.cc",
+ "tests/include/*.h",
+ ]),
+ includes = ["tests/include"],
+ deps = [
+ "//:tinyalsa",
+ "@googletest//:gtest_main"
+ ],
+ linkopts = [
+ "-ldl",
+ ],
+ copts = [
+ "-std=c++17",
+ ],
+)
diff --git a/README.md b/README.md
index 5e8fdb6..ed4203a 100644
--- a/README.md
+++ b/README.md
@@ -63,3 +63,41 @@ man libtinyalsa-pcm
man libtinyalsa-mixer
```
+### Test
+
+To test libtinyalsa, please follow the instructions,
+
+#### Setup Bazel build environment
+
+Visit [here](https://docs.bazel.build/versions/3.7.0/install.html) to get more info to setup Bazel environment.
+
+#### Insert loopback devices
+
+The test program does pcm_* operations on loopback devices. You have to insert loopback devices after your system boots up.
+
+```
+sudo modprobe snd-aloop
+sudo chmod 777 /dev/snd/*
+```
+
+#### Run test program
+
+```
+bazel test //:tinyalsa_tests --test_output=all
+```
+
+The default playback device is hw:2,0 and the default capture device is hw:2,1. If your loopback devices are not hw:2,0 and hw:2,1, you can specify the loopback device.
+
+```
+bazel test //:tinyalsa_tests --test_output=all \
+ --copt=-DTEST_LOOPBACK_CARD=[loopback card] \
+ --copt=-DTEST_LOOPBACK_PLAYBACK_DEVICE=[loopback playback device] \
+ --copt=-DTEST_LOOPBACK_CAPTURE_DEVICE=[loopback capture device]
+```
+
+#### Generate coverage report
+
+```
+bazel coverage //:tinyalsa_tests --combined_report=lcov --test_output=all
+genhtml bazel-out/_coverage/_coverage_report.dat -o tinyalsa_tests_coverage
+```
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..02b57bf
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,7 @@
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
+git_repository(
+ name = "googletest",
+ remote = "https://github.com/google/googletest",
+ branch = "master",
+)
diff --git a/tests/include/pcm_test_device.h b/tests/include/pcm_test_device.h
new file mode 100644
index 0000000..7ced192
--- /dev/null
+++ b/tests/include/pcm_test_device.h
@@ -0,0 +1,54 @@
+/* pcm_test.h
+**
+** 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.
+*/
+
+#ifndef TINYALSA_TESTS_PCM_TEST_H_
+#define TINYALSA_TESTS_PCM_TEST_H_
+
+namespace tinyalsa {
+namespace testing {
+
+#ifndef TEST_LOOPBACK_CARD
+#define TEST_LOOPBACK_CARD 2
+#endif
+
+#ifndef TEST_LOOPBACK_PLAYBACK_DEVICE
+#define TEST_LOOPBACK_PLAYBACK_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_PLAYBACK_DEVICE;
+constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE;
+
+} // namespace testing
+} // namespace tinyalsa
+
+#endif
diff --git a/tests/src/mixer_test.cc b/tests/src/mixer_test.cc
new file mode 100644
index 0000000..717269c
--- /dev/null
+++ b/tests/src/mixer_test.cc
@@ -0,0 +1,316 @@
+/* mixer_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 <string_view>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/mixer.h"
+
+namespace tinyalsa {
+namespace testing {
+
+#ifndef MAX_CARD_INDEX
+#define MAX_CARD_INDEX 2
+#endif
+
+static constexpr unsigned int kMaxCardIndex = MAX_CARD_INDEX;
+
+static constexpr int k100Percent = 100;
+static constexpr int k0Percent = 0;
+
+TEST(MixerTest, OpenAndClose) {
+ ASSERT_EQ(mixer_open(1000), nullptr);
+ mixer_close(nullptr);
+}
+
+class MixerTest : public ::testing::TestWithParam<unsigned int> {
+ protected:
+ MixerTest() : mixer_object(nullptr) {}
+ virtual ~MixerTest() = default;
+
+ virtual void SetUp() override {
+ unsigned int card = GetParam();
+ mixer_object = mixer_open(card);
+ ASSERT_NE(mixer_object, nullptr);
+ }
+
+ virtual void TearDown() override {
+ mixer_close(mixer_object);
+ }
+
+ mixer *mixer_object;
+};
+
+TEST_P(MixerTest, AddNewControls) {
+ ASSERT_EQ(mixer_add_new_ctls(mixer_object), 0);
+}
+
+TEST_P(MixerTest, GetName) {
+ const char *name = mixer_get_name(mixer_object);
+ std::cout << name << std::endl;
+ ASSERT_STRNE(name, "");
+}
+
+TEST_P(MixerTest, GetNumberOfControls) {
+ unsigned int nums = mixer_get_num_ctls(mixer_object);
+ std::cout << nums << std::endl;
+ ASSERT_GT(nums, 0);
+}
+
+class MixerControlsTest : public MixerTest {
+ protected:
+ MixerControlsTest() : number_of_controls(0), controls(nullptr) {}
+ virtual ~MixerControlsTest() = default;
+
+ virtual void SetUp() override {
+ MixerTest::SetUp();
+
+ number_of_controls = mixer_get_num_ctls(mixer_object);
+ ASSERT_GT(number_of_controls, 0);
+
+ controls = std::make_unique<const mixer_ctl *[]>(number_of_controls);
+ ASSERT_NE(controls, nullptr);
+
+ for (unsigned int i = 0; i < number_of_controls; i++) {
+ controls[i] = mixer_get_ctl_const(mixer_object, i);
+ ASSERT_EQ(mixer_ctl_get_id(controls[i]), i);
+ ASSERT_STRNE(mixer_ctl_get_name(controls[i]), "");
+ ASSERT_NE(controls[i], nullptr);
+ }
+ }
+
+ virtual void TearDown() override {
+ controls = nullptr;
+ MixerTest::TearDown();
+ }
+
+ unsigned int number_of_controls;
+ std::unique_ptr<const mixer_ctl *[]> controls;
+};
+
+TEST_P(MixerControlsTest, GetNumberOfControlsByName) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const char *name = mixer_ctl_get_name(controls[i]);
+ ASSERT_GE(mixer_get_num_ctls_by_name(mixer_object, name), 1);
+ }
+
+ std::string name{mixer_ctl_get_name(controls[0])};
+ name += "1";
+ ASSERT_EQ(mixer_get_num_ctls_by_name(mixer_object, name.c_str()), 0);
+}
+
+TEST_P(MixerControlsTest, GetControlById) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ ASSERT_EQ(mixer_get_ctl(mixer_object, i), controls[i]);
+ }
+
+ ASSERT_EQ(mixer_get_ctl(mixer_object, number_of_controls), nullptr);
+}
+
+TEST_P(MixerControlsTest, GetControlByName) {
+ std::unordered_set<std::string> visited_names_set;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string name{mixer_ctl_get_name(controls[i])};
+ if (visited_names_set.find(name) == visited_names_set.end()) {
+ ASSERT_EQ(mixer_get_ctl_by_name(mixer_object, name.c_str()), controls[i]);
+ visited_names_set.insert(name);
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, GetControlByNameAndIndex) {
+ std::unordered_map<std::string, int32_t> visited_names_and_count_map;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string name{mixer_ctl_get_name(controls[i])};
+ if (visited_names_and_count_map.find(name) == visited_names_and_count_map.end()) {
+ visited_names_and_count_map[name] = 0;
+ }
+ ASSERT_EQ(
+ mixer_get_ctl_by_name_and_index(mixer_object,
+ name.c_str(),
+ visited_names_and_count_map[name]),
+ controls[i]);
+ visited_names_and_count_map[name] = visited_names_and_count_map[name] + 1;
+ }
+}
+
+static inline bool IsValidTypeString(std::string& type) {
+ return type == "BOOL" || type == "INT" || type == "ENUM" || type == "BYTE" ||
+ type == "IEC958" || type == "INT64";
+}
+
+TEST_P(MixerControlsTest, GetControlTypeString) {
+ ASSERT_STREQ(mixer_ctl_get_type_string(nullptr), "");
+
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string type{mixer_ctl_get_type_string(controls[i])};
+ ASSERT_TRUE(IsValidTypeString(type));
+ }
+}
+
+TEST_P(MixerControlsTest, GetNumberOfValues) {
+ ASSERT_EQ(mixer_ctl_get_num_values(nullptr), 0);
+}
+
+TEST_P(MixerControlsTest, GetNumberOfEnumsAndEnumString) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_ENUM) {
+ unsigned int number_of_enums = mixer_ctl_get_num_enums(control);
+ ASSERT_GT(number_of_enums, 0);
+ for (unsigned int enum_id = 0; enum_id < number_of_enums; ++enum_id) {
+ const char *enum_name = mixer_ctl_get_enum_string(
+ const_cast<mixer_ctl *>(control),
+ enum_id);
+ ASSERT_STRNE(enum_name, "");
+ }
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, UpdateControl) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ mixer_ctl_update(const_cast<mixer_ctl *>(controls[i]));
+ }
+}
+
+TEST_P(MixerControlsTest, GetPercent) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_INT) {
+ unsigned int number_of_values = mixer_ctl_get_num_values(controls[i]);
+ std::unique_ptr<long []> values = std::make_unique<long []>(number_of_values);
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ for (unsigned int value_id = 0; value_id < number_of_values; ++value_id) {
+ int max = mixer_ctl_get_range_max(control);
+ int min = mixer_ctl_get_range_min(control);
+ int percent = mixer_ctl_get_percent(control, value_id);
+ ASSERT_GE(percent, k0Percent);
+ ASSERT_LE(percent, k100Percent);
+ int range = max - min;
+ ASSERT_EQ(percent, (values[value_id] - min) * k100Percent / range);
+ }
+ } else {
+ ASSERT_EQ(mixer_ctl_get_percent(control, 0), -EINVAL);
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, SetPercent) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_INT) {
+ unsigned int number_of_values = mixer_ctl_get_num_values(controls[i]);
+ std::unique_ptr<long []> values = std::make_unique<long []>(number_of_values);
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ for (unsigned int value_id = 0; value_id < number_of_values; ++value_id) {
+ int max = mixer_ctl_get_range_max(control);
+ int min = mixer_ctl_get_range_min(control);
+ int value = values[value_id];
+ int percent = mixer_ctl_get_percent(control, value_id);
+ if (mixer_ctl_set_percent(
+ const_cast<mixer_ctl *>(control), value_id, k100Percent) == 0) {
+ // note: some controls are able to be written, but their values might not be
+ // changed.
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ int new_value = values[value_id];
+ ASSERT_TRUE(new_value == value || new_value == max);
+ }
+ if (mixer_ctl_set_percent(
+ const_cast<mixer_ctl *>(control), value_id, k0Percent) == 0) {
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ int new_value = values[value_id];
+ ASSERT_TRUE(new_value == value || new_value == min);
+ }
+ mixer_ctl_set_percent(const_cast<mixer_ctl *>(control), value_id, percent);
+ }
+ } else {
+ ASSERT_EQ(mixer_ctl_get_percent(control, 0), -EINVAL);
+ }
+ }
+}
+
+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 == k100Percent ? k0Percent : k100Percent);
+ });
+
+ 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,
+ kMaxCardIndex + 1
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ MixerControlsTest,
+ MixerControlsTest,
+ ::testing::Range<unsigned int>(
+ 0,
+ kMaxCardIndex + 1
+ ));
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_in_test.cc b/tests/src/pcm_in_test.cc
new file mode 100644
index 0000000..e912abb
--- /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, GetDelay) {
+ 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 tinyalsa
diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc
new file mode 100644
index 0000000..6a3ffb8
--- /dev/null
+++ b/tests/src/pcm_loopback_test.cc
@@ -0,0 +1,230 @@
+/* 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 unsigned int kDefaultPeriodTimeInMs =
+ kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
+
+ 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) {
+ int res = pcm_readi(pcm_in, buffer.get(), frames);
+ if (res == -1) {
+ std::cout << pcm_get_error(pcm_in) << std::endl;
+ std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs));
+ continue;
+ }
+ EXPECT_EQ(pcm_readi(pcm_in, buffer.get(), frames), frames) << counter;
+ // Test the energy of the buffer after the sine tone samples fill in the buffer.
+ // Therefore, check the buffer 5 times later.
+ if (counter >= 5) {
+ double e = Energy(buffer.get(), frames * kInConfig.channels);
+ EXPECT_GT(e, 0.0) << counter;
+ }
+ counter++;
+ }
+ });
+
+ 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);
+ auto buffer = std::make_unique<unsigned char[]>(buffer_size);
+ int32_t counter = 0;
+ while (!stopping) {
+ generator.Read(buffer.get(), buffer_size);
+ EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter;
+ 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 tinyalsa
diff --git a/tests/src/pcm_out_test.cc b/tests/src/pcm_out_test.cc
new file mode 100644
index 0000000..cbc6983
--- /dev/null
+++ b/tests/src/pcm_out_test.cc
@@ -0,0 +1,216 @@
+/* pcm_out_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 PcmOutTest : public ::testing::Test {
+ protected:
+ PcmOutTest() : pcm_object(nullptr) {}
+ virtual ~PcmOutTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &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 = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm* pcm_object;
+};
+
+TEST_F(PcmOutTest, GetFileDescriptor) {
+ ASSERT_GT(pcm_get_file_descriptor(pcm_object), 0);
+}
+
+TEST_F(PcmOutTest, GetChannels) {
+ ASSERT_EQ(pcm_get_channels(pcm_object), kDefaultConfig.channels);
+}
+
+TEST_F(PcmOutTest, GetSamplingRate) {
+ ASSERT_EQ(pcm_get_rate(pcm_object), kDefaultConfig.rate);
+}
+
+TEST_F(PcmOutTest, GetFormat) {
+ ASSERT_EQ(pcm_get_format(pcm_object), kDefaultConfig.format);
+
+}
+
+TEST_F(PcmOutTest, GetErrorMessage) {
+ ASSERT_STREQ(pcm_get_error(pcm_object), "");
+}
+
+TEST_F(PcmOutTest, GetConfig) {
+ ASSERT_EQ(pcm_get_config(nullptr), nullptr);
+ ASSERT_EQ(std::memcmp(pcm_get_config(pcm_object), &kDefaultConfig, sizeof(pcm_config)), 0);
+}
+
+TEST_F(PcmOutTest, SetConfig) {
+ ASSERT_EQ(pcm_set_config(nullptr, nullptr), -EFAULT);
+ ASSERT_EQ(pcm_set_config(pcm_object, nullptr), 0);
+}
+
+TEST_F(PcmOutTest, GetBufferSize) {
+ unsigned int buffer_size = pcm_get_buffer_size(pcm_object);
+ ASSERT_EQ(buffer_size, kDefaultConfig.period_count * kDefaultConfig.period_size);
+}
+
+TEST_F(PcmOutTest, FramesBytesConvert) {
+ unsigned int bytes = pcm_frames_to_bytes(pcm_object, 1);
+ ASSERT_EQ(bytes, pcm_format_to_bits(kDefaultConfig.format) / 8 * kDefaultConfig.channels);
+
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, bytes + 1);
+ ASSERT_EQ(frames, 1);
+}
+
+TEST_F(PcmOutTest, GetAvailableAndTimestamp) {
+ unsigned int available = 0;
+ timespec time = { 0 };
+
+ ASSERT_LT(pcm_get_htimestamp(nullptr, nullptr, nullptr), 0);
+
+ ASSERT_EQ(pcm_get_htimestamp(pcm_object, &available, &time), 0);
+ ASSERT_NE(available, 0);
+ // ASSERT_NE(time.tv_nsec | time.tv_sec, 0);
+}
+
+TEST_F(PcmOutTest, GetSubdevice) {
+ ASSERT_EQ(pcm_get_subdevice(pcm_object), 0);
+}
+
+TEST_F(PcmOutTest, Readi) {
+ 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_readi(pcm_object, buffer.get(), frames), -EINVAL);
+}
+
+TEST_F(PcmOutTest, Writei) {
+ constexpr uint32_t write_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+ for (uint32_t i = 0; i < buffer_size; ++i) {
+ buffer[i] = static_cast<char>(i);
+ }
+
+ int written_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 < write_count; ++i) {
+ written_frames = pcm_writei(pcm_object, buffer.get(), frames);
+ ASSERT_EQ(written_frames, frames);
+ }
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames *
+ (write_count - kDefaultConfig.period_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);
+}
+
+class PcmOutMmapTest : public PcmOutTest {
+ protected:
+ PcmOutMmapTest() = default;
+ ~PcmOutMmapTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT | PCM_MMAP,
+ &kDefaultConfig);
+ ASSERT_NE(pcm_object, nullptr);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ }
+
+ virtual void TearDown() override {
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+ }
+};
+
+TEST_F(PcmOutMmapTest, Write) {
+ constexpr uint32_t write_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+ for (uint32_t i = 0; i < buffer_size; ++i) {
+ buffer[i] = static_cast<char>(i);
+ }
+
+ int written_frames = 0;
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ pcm_start(pcm_object);
+ auto start = std::chrono::steady_clock::now();
+ for (uint32_t i = 0; i < write_count; ++i) {
+ written_frames = pcm_mmap_write(pcm_object, buffer.get(), buffer_size);
+ ASSERT_EQ(written_frames, frames);
+ }
+ pcm_stop(pcm_object);
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames *
+ (write_count - kDefaultConfig.period_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);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_params_test.cc b/tests/src/pcm_params_test.cc
new file mode 100644
index 0000000..c8151e1
--- /dev/null
+++ b/tests/src/pcm_params_test.cc
@@ -0,0 +1,222 @@
+/* pcm_params_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 <cstring>
+#include <iostream>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+static inline unsigned int OrAllBits(const pcm_mask *mask) {
+ static constexpr size_t kTotalMaskBytes = 32;
+ unsigned int res = 0;
+ for (uint32_t i = 0; i < kTotalMaskBytes / sizeof(pcm_mask::bits[0]); ++i) {
+ res |= mask->bits[i];
+ }
+ return res;
+}
+
+TEST(PcmParamsTest, GetAndFreeParams) {
+ pcm_params *params = nullptr;
+
+ // test to get nonexistent card and device.
+ params = pcm_params_get(1000, 1000, PCM_IN);
+ ASSERT_EQ(params, nullptr);
+
+ // test free null params.
+ pcm_params_free(params);
+
+ // assume that card 0, device 0 is always available.
+ params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetParamsBitMask) {
+ // test to get mask with null params
+ ASSERT_EQ(pcm_params_get_mask(nullptr, PCM_PARAM_ACCESS), nullptr);
+
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ // test to get param which is not described in bit mask format
+ ASSERT_EQ(pcm_params_get_mask(params, PCM_PARAM_SAMPLE_BITS), nullptr);
+
+ // test to get mask out of pcm_param enum
+ ASSERT_EQ(pcm_params_get_mask(params, static_cast<pcm_param>(100)), nullptr);
+
+ const pcm_mask *mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ ASSERT_NE(mask, nullptr);
+
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetParamsInterval) {
+ // test to get interval with null params
+ ASSERT_EQ(pcm_params_get_min(nullptr, PCM_PARAM_SAMPLE_BITS), 0);
+ ASSERT_EQ(pcm_params_get_max(nullptr, PCM_PARAM_SAMPLE_BITS), 0);
+
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ // test to get param which is not described in interval format
+ ASSERT_EQ(pcm_params_get_min(params, PCM_PARAM_ACCESS), 0);
+ ASSERT_EQ(pcm_params_get_max(params, PCM_PARAM_ACCESS), 0);
+
+ // test to get interval out of pcm_param enum
+ ASSERT_EQ(pcm_params_get_min(params, static_cast<pcm_param>(100)), 0);
+ ASSERT_EQ(pcm_params_get_max(params, static_cast<pcm_param>(100)), 0);
+
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, ParamsToString) {
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ char long_string[1024] = { 0 };
+ int count = pcm_params_to_string(params, long_string, sizeof(long_string));
+ ASSERT_LE(static_cast<size_t>(count), sizeof(long_string));
+ ASSERT_GT(static_cast<size_t>(count), 0);
+
+ char short_string[1] = { 0 };
+ count = pcm_params_to_string(params, short_string, sizeof(short_string));
+ ASSERT_GT(static_cast<size_t>(count), sizeof(short_string));
+
+ int proper_string_len = count;
+ int proper_string_size = proper_string_len + 1;
+ auto proper_string = std::make_unique<char[]>(proper_string_size);
+ count = pcm_params_to_string(params, proper_string.get(), proper_string_size);
+ ASSERT_GT(static_cast<size_t>(count), 0);
+ ASSERT_EQ(static_cast<size_t>(count), proper_string_len);
+ ASSERT_EQ(std::strlen(proper_string.get()), proper_string_len);
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetPlaybackDeviceParams) {
+ pcm_params *params = pcm_params_get(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ const pcm_mask *access_mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ ASSERT_NE(access_mask, nullptr);
+ ASSERT_NE(OrAllBits(access_mask), 0);
+
+ const pcm_mask *format_mask = pcm_params_get_mask(params, PCM_PARAM_FORMAT);
+ ASSERT_NE(format_mask, nullptr);
+ ASSERT_NE(OrAllBits(format_mask), 0);
+
+ const pcm_mask *subformat_mask = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT);
+ ASSERT_NE(subformat_mask, nullptr);
+ ASSERT_NE(OrAllBits(subformat_mask), 0);
+
+ unsigned int sample_bits_min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
+ unsigned int sample_bits_max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
+ std::cout << "sample_bits: " << sample_bits_min << " - " << sample_bits_max << std::endl;
+ ASSERT_GT(sample_bits_min, 0);
+ ASSERT_GT(sample_bits_max, 0);
+
+ unsigned int frame_bits_min = pcm_params_get_min(params, PCM_PARAM_FRAME_BITS);
+ unsigned int frame_bits_max = pcm_params_get_max(params, PCM_PARAM_FRAME_BITS);
+ std::cout << "frame_bits: " << frame_bits_min << " - " << frame_bits_max << std::endl;
+ ASSERT_GT(frame_bits_min, 0);
+ ASSERT_GT(frame_bits_max, 0);
+
+ unsigned int channels_min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
+ unsigned int channels_max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
+ std::cout << "channels: " << channels_min << " - " << channels_max << std::endl;
+ ASSERT_GT(channels_min, 0);
+ ASSERT_GT(channels_max, 0);
+
+ unsigned int sampling_rate_min = pcm_params_get_min(params, PCM_PARAM_RATE);
+ unsigned int sampling_rate_max = pcm_params_get_max(params, PCM_PARAM_RATE);
+ std::cout << "sampling_rate: " << sampling_rate_min << " - " << sampling_rate_max << std::endl;
+ ASSERT_GT(sampling_rate_min, 0);
+ ASSERT_GT(sampling_rate_max, 0);
+
+ unsigned int period_time_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_TIME);
+ unsigned int period_time_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_TIME);
+ std::cout << "period_time: " << period_time_min << " - " << period_time_max << std::endl;
+ ASSERT_GT(period_time_min, 0);
+ ASSERT_GT(period_time_max, 0);
+
+ unsigned int period_size_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
+ unsigned int period_size_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
+ std::cout << "period_size: " << period_size_min << " - " << period_size_max << std::endl;
+ ASSERT_GT(period_size_min, 0);
+ ASSERT_GT(period_size_max, 0);
+
+ unsigned int period_bytes_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_BYTES);
+ unsigned int period_bytes_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_BYTES);
+ std::cout << "period_bytes: " << period_bytes_min << " - " << period_bytes_max << std::endl;
+ ASSERT_GT(period_bytes_min, 0);
+ ASSERT_GT(period_bytes_max, 0);
+
+ unsigned int period_count_min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
+ unsigned int period_count_max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
+ std::cout << "period_count: " << period_count_min << " - " << period_count_max << std::endl;
+ ASSERT_GT(period_count_min, 0);
+ ASSERT_GT(period_count_max, 0);
+
+ unsigned int buffer_time_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_TIME);
+ unsigned int buffer_time_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_TIME);
+ std::cout << "buffer_time: " << buffer_time_min << " - " << buffer_time_max << std::endl;
+ ASSERT_GT(buffer_time_min, 0);
+ ASSERT_GT(buffer_time_max, 0);
+
+ unsigned int buffer_size_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_SIZE);
+ unsigned int buffer_size_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_SIZE);
+ std::cout << "buffer_size: " << buffer_size_min << " - " << buffer_size_max << std::endl;
+ ASSERT_GT(buffer_size_min, 0);
+ ASSERT_GT(buffer_size_max, 0);
+
+ unsigned int buffer_bytes_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_BYTES);
+ unsigned int buffer_bytes_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_BYTES);
+ std::cout << "buffer_bytes: " << buffer_bytes_min << " - " << buffer_bytes_max << std::endl;
+ ASSERT_GT(buffer_bytes_min, 0);
+ ASSERT_GT(buffer_bytes_max, 0);
+
+ unsigned int tick_in_us_min = pcm_params_get_min(params, PCM_PARAM_TICK_TIME);
+ unsigned int tick_in_us_max = pcm_params_get_max(params, PCM_PARAM_TICK_TIME);
+ ASSERT_GT(tick_in_us_max, 0);
+ std::cout << "tick_in_us: " << tick_in_us_min << " - " << tick_in_us_max << std::endl;
+
+ pcm_params_free(params);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_test.cc b/tests/src/pcm_test.cc
new file mode 100644
index 0000000..2668350
--- /dev/null
+++ b/tests/src/pcm_test.cc
@@ -0,0 +1,103 @@
+/* pcm_out_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 <string>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+TEST(PcmTest, FormatToBits) {
+ // FIXME: Should we return 16 bits for INVALID?
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_INVALID), 16);
+
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_LE), 16);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_LE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S8), 8);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_LE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3LE), 24);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_BE), 16);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_BE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3BE), 24);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_BE), 32);
+}
+
+TEST(PcmTest, OpenAndCloseOutPcm) {
+ 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 = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm *pcm_object = pcm_open(1000, 1000, PCM_OUT, &kDefaultConfig);
+ ASSERT_FALSE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ // assume card 0, device 0 is always available
+ pcm_object = pcm_open(0, 0, PCM_OUT, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP | PCM_NOIRQ, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_NONBLOCK, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MONOTONIC, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ std::string name = "hw:0,0";
+ pcm_object = pcm_open_by_name(name.c_str(), PCM_OUT, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+}
+
+} // namespace testing
+} // namespace tinyalsa