aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Wilson <simonrules@users.noreply.github.com>2021-06-02 09:22:23 -0600
committerGitHub <noreply@github.com>2021-06-02 09:22:23 -0600
commit496e653335d27557bbd1f795b1eacd2948ab192a (patch)
tree7cf42df6c5c17b207be376eb00fe91d42bd9e352
parent36f9078ef5410279725207daa00943ee48b05253 (diff)
parentea4546b237269915e832fd74a9883a2dadf46093 (diff)
Merge pull request #208 from dvdli/tinyalsa-pcm-open-blocked
force pcm_open to open device with the non-blocking flag
-rw-r--r--src/pcm_hw.c17
-rw-r--r--tests/include/pcm_test_device.h24
-rw-r--r--tests/src/pcm_loopback_test.cc8
-rw-r--r--tests/src/pcm_test.cc92
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