Merge remote-tracking branch 'remotes/aosp/upstream-master' into master am: 4c7b4979b6

Original change: https://android-review.googlesource.com/c/platform/external/tinyalsa_new/+/1726515

Change-Id: I3ebfcb1a8a1c22df1df755d65bdb571be5829dc8
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 @@
 
     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 @@
 #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 @@
         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 @@
 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, 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 @@
     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