diff options
-rw-r--r-- | src/pcm_hw.c | 17 | ||||
-rw-r--r-- | tests/include/pcm_test_device.h | 24 | ||||
-rw-r--r-- | tests/src/pcm_loopback_test.cc | 8 | ||||
-rw-r--r-- | tests/src/pcm_test.cc | 92 |
4 files changed, 109 insertions, 32 deletions
diff --git a/src/pcm_hw.c b/src/pcm_hw.c index 38b2e83..4792895 100644 --- a/src/pcm_hw.c +++ b/src/pcm_hw.c @@ -111,16 +111,25 @@ static int pcm_hw_open(unsigned int card, unsigned int device, snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p'); - if (flags & PCM_NONBLOCK) - fd = open(fn, O_RDWR|O_NONBLOCK); - else - fd = open(fn, O_RDWR); + // Open the device with non-blocking flag to avoid to be blocked in kernel when all of the + // substreams of this PCM device are opened by others. + fd = open(fn, O_RDWR | O_NONBLOCK); if (fd < 0) { free(hw_data); return fd; } + if ((flags & PCM_NONBLOCK) == 0) { + // Set the file descriptor to blocking mode. + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK) < 0) { + fprintf(stderr, "failed to set to blocking mode on %s", fn); + close(fd); + free(hw_data); + return -ENODEV; + } + } + hw_data->card = card; hw_data->device = device; hw_data->fd = fd; diff --git a/tests/include/pcm_test_device.h b/tests/include/pcm_test_device.h index 7ced192..a0ea811 100644 --- a/tests/include/pcm_test_device.h +++ b/tests/include/pcm_test_device.h @@ -29,6 +29,8 @@ #ifndef TINYALSA_TESTS_PCM_TEST_H_ #define TINYALSA_TESTS_PCM_TEST_H_ +#include "tinyalsa/pcm.h" + namespace tinyalsa { namespace testing { @@ -44,9 +46,25 @@ namespace testing { #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; +static constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD; +static constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PLAYBACK_DEVICE; +static constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE; + +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, +}; } // namespace testing } // namespace tinyalsa diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc index d48b319..3da3231 100644 --- a/tests/src/pcm_loopback_test.cc +++ b/tests/src/pcm_loopback_test.cc @@ -193,10 +193,6 @@ class PcmLoopbackTest : public ::testing::Test { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - 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_format kPcmForamt = F::kFormat; @@ -212,10 +208,6 @@ using Formats = ::testing::Types<S16bitlePcmFormat, FloatPcmFormat>; TYPED_TEST_SUITE(PcmLoopbackTest, Formats); TYPED_TEST(PcmLoopbackTest, Loopback) { - static constexpr unsigned int kDefaultChannels = this->kDefaultChannels; - static constexpr unsigned int kDefaultSamplingRate = this->kDefaultSamplingRate; - static constexpr unsigned int kDefaultPeriodSize = this->kDefaultPeriodSize; - // static constexpr unsigned int kDefaultPeriodCount = this->kDefaultPeriodCount; static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs; static constexpr pcm_format kPcmForamt = this->kPcmForamt; pcm *pcm_in = this->pcm_in; diff --git a/tests/src/pcm_test.cc b/tests/src/pcm_test.cc index da114de..9a14aa2 100644 --- a/tests/src/pcm_test.cc +++ b/tests/src/pcm_test.cc @@ -26,13 +26,20 @@ ** DAMAGE. */ -#include <string> +#include <cstdio> +#include <fstream> #include <iostream> +#include <memory> +#include <string_view> +#include <string> +#include <thread> #include <gtest/gtest.h> #include "tinyalsa/pcm.h" +#include "pcm_test_device.h" + namespace tinyalsa { namespace testing { @@ -54,22 +61,6 @@ TEST(PcmTest, FormatToBits) { } 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); @@ -101,5 +92,72 @@ TEST(PcmTest, OpenAndCloseOutPcm) { ASSERT_EQ(pcm_close(pcm_object), 0); } +TEST(PcmTest, OpenWithoutBlocking) { + char loopback_device_info_path[120] = {}; + snprintf(loopback_device_info_path, sizeof(loopback_device_info_path), + "/proc/asound/card%d/pcm%dp/info", kLoopbackCard, kLoopbackPlaybackDevice); + + std::ifstream info_file_stream{loopback_device_info_path}; + if (!info_file_stream.is_open()) { + GTEST_SKIP(); + } + + char buffer[256] = {}; + int32_t subdevice_count = 0; + while (info_file_stream.good()) { + info_file_stream.getline(buffer, sizeof(buffer)); + std::cout << buffer << std::endl; + std::string_view line{buffer}; + if (line.find("subdevices_count") != std::string_view::npos) { + auto subdevice_count_string = line.substr(line.find(":") + 1); + std::cout << subdevice_count_string << std::endl; + subdevice_count = std::stoi(std::string{subdevice_count_string}); + } + } + + ASSERT_GT(subdevice_count, 0); + + auto pcm_array = std::make_unique<pcm *[]>(subdevice_count); + std::thread *open_thread = new std::thread{[&pcm_array, subdevice_count] { + // Occupy all substreams + for (int32_t i = 0; i < subdevice_count; i++) { + pcm_array[i] = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, + &kDefaultConfig); + EXPECT_TRUE(pcm_is_ready(pcm_array[i])); + } + + // Expect that pcm_open is not blocked in the kernel and return a bad_object pointer. + pcm *pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, + &kDefaultConfig); + if (pcm_is_ready(pcm_object)) { + // open_thread is blocked in kernel because of the substream is all occupied. pcm_open + // returns because the main thread has released all pcm structures in pcm_array. We just + // need to close the pcm_object here. + pcm_close(pcm_object); + return; + } + + // Release all substreams + for (int32_t i = 0; i < subdevice_count; i++) { + pcm_close(pcm_array[i]); + pcm_array[i] = nullptr; + } + }}; + + static constexpr int64_t kTimeoutMs = 500; + std::this_thread::sleep_for(std::chrono::milliseconds(kTimeoutMs)); + if (pcm_array[0] == nullptr) { + open_thread->join(); + } else { + for (int32_t i = 0; i < subdevice_count; i++) { + pcm_close(pcm_array[i]); + pcm_array[i] = nullptr; + } + open_thread->join(); + FAIL() << "The open_thread is blocked in kernel or the kTimeoutMs(" << kTimeoutMs << + ") is too short to complete"; + } +} + } // namespace testing } // namespace tinyalsa |