summaryrefslogtreecommitdiff
path: root/cras
diff options
context:
space:
mode:
authorPin-chih Lin <johnylin@google.com>2020-07-29 15:46:47 +0800
committerCommit Bot <commit-bot@chromium.org>2020-08-10 08:31:24 +0000
commit592625514061ca68630566b86dacbe47cd027a93 (patch)
treea5e49fc349925fe2b5ce5876218de23d24b1f971 /cras
parent3418709c9ee544524a130228a6c4bba41089dcc4 (diff)
downloadadhd-592625514061ca68630566b86dacbe47cd027a93.tar.gz
CRAS: Re-open iodev for higher channel count per request
Re-open the device with the format of the attached stream if it has higher channel count than the current format of the device. Fallback device will be transciently enabled during the device re-opening. For stream_list_add(), keep streams in the stream list descending ordered by channel count. It guarantees that while iterating the stream list, the first attachable stream will have the highest channel count. BUG=b:162211010 TEST=On Stadia gameplay, it will first provide two 2-channel streams then a 6-channel stream. Check num_channels in device stat is 6 (for a 5.1 sound system) and all 6 channels have sounds. TEST=FEATURES="test" emerge adhd Change-Id: Ic822f35adcabaf8b72231b84551bc78f3bc75288 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2319443 Tested-by: Pin-chih Lin <johnylin@chromium.org> Reviewed-by: Cheng-Yi Chiang <cychiang@chromium.org> Commit-Queue: Pin-chih Lin <johnylin@chromium.org> Auto-Submit: Pin-chih Lin <johnylin@chromium.org>
Diffstat (limited to 'cras')
-rw-r--r--cras/src/server/cras_iodev_list.c48
-rw-r--r--cras/src/server/stream_list.c9
-rw-r--r--cras/src/server/stream_list.h4
-rw-r--r--cras/src/tests/iodev_list_unittest.cc104
-rw-r--r--cras/src/tests/stream_list_unittest.cc63
5 files changed, 207 insertions, 21 deletions
diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c
index 29ed64b6..41850f34 100644
--- a/cras/src/server/cras_iodev_list.c
+++ b/cras/src/server/cras_iodev_list.c
@@ -682,6 +682,11 @@ static int init_and_attach_streams(struct cras_iodev *dev)
if (!can_attach)
continue;
+ /*
+ * Note that the stream list is descending ordered by channel
+ * count, which guarantees the first attachable stream will have
+ * the highest channel count.
+ */
rc = init_device(dev, stream);
if (rc) {
syslog(LOG_ERR, "Enable %s failed, rc = %d",
@@ -806,6 +811,7 @@ static int stream_added_cb(struct cras_rstream *rstream)
struct cras_iodev *iodevs[10];
unsigned int num_iodevs;
int rc;
+ bool iodev_reopened;
if (stream_list_suspended)
return 0;
@@ -816,24 +822,42 @@ static int stream_added_cb(struct cras_rstream *rstream)
/* Add the new stream to all enabled iodevs at once to avoid offset
* in shm level between different ouput iodevs. */
num_iodevs = 0;
+ iodev_reopened = false;
DL_FOREACH (enabled_devs[rstream->direction], edev) {
if (num_iodevs >= ARRAY_SIZE(iodevs)) {
syslog(LOG_ERR, "too many enabled devices");
break;
}
- rc = init_device(edev->dev, rstream);
- if (rc) {
- /* Error log but don't return error here, because
- * stopping audio could block video playback.
+ if (cras_iodev_is_open(edev->dev) &&
+ (rstream->format.num_channels >
+ edev->dev->format->num_channels)) {
+ /* Re-open the device with the format of the attached
+ * stream if it has higher channel count than the
+ * current format of the device. Fallback device will
+ * be transciently enabled during the device re-opening.
*/
- syslog(LOG_ERR, "Init %s failed, rc = %d",
- edev->dev->info.name, rc);
- schedule_init_device_retry(edev->dev);
- continue;
- }
+ syslog(LOG_INFO, "re-open %s for higher channel count",
+ edev->dev->info.name);
+ possibly_enable_fallback(rstream->direction, false);
+ cras_iodev_list_suspend_dev(edev->dev->info.idx);
+ cras_iodev_list_resume_dev(edev->dev->info.idx);
+ possibly_disable_fallback(rstream->direction);
+ iodev_reopened = true;
+ } else {
+ rc = init_device(edev->dev, rstream);
+ if (rc) {
+ /* Error log but don't return error here, because
+ * stopping audio could block video playback.
+ */
+ syslog(LOG_ERR, "Init %s failed, rc = %d",
+ edev->dev->info.name, rc);
+ schedule_init_device_retry(edev->dev);
+ continue;
+ }
- iodevs[num_iodevs++] = edev->dev;
+ iodevs[num_iodevs++] = edev->dev;
+ }
}
if (num_iodevs) {
rc = add_stream_to_open_devs(rstream, iodevs, num_iodevs);
@@ -841,9 +865,9 @@ static int stream_added_cb(struct cras_rstream *rstream)
syslog(LOG_ERR, "adding stream to thread fail");
return rc;
}
- } else {
+ } else if (!iodev_reopened) {
/* Enable fallback device if no other iodevs can be initialized
- * successfully.
+ * or re-opened successfully.
* For error codes like EAGAIN and ENOENT, a new iodev will be
* enabled soon so streams are going to route there. As for the
* rest of the error cases, silence will be played or recorded
diff --git a/cras/src/server/stream_list.c b/cras/src/server/stream_list.c
index d247ec89..719608a4 100644
--- a/cras/src/server/stream_list.c
+++ b/cras/src/server/stream_list.c
@@ -81,12 +81,19 @@ int stream_list_add(struct stream_list *list,
struct cras_rstream **stream)
{
int rc;
+ struct cras_rstream *next_stream;
rc = list->stream_create_cb(stream_config, stream);
if (rc)
return rc;
- DL_APPEND(list->streams, *stream);
+ /* Keep stream list in descending order by channel count. */
+ DL_FOREACH (list->streams, next_stream) {
+ if ((*stream)->format.num_channels >=
+ next_stream->format.num_channels)
+ break;
+ }
+ DL_INSERT(list->streams, next_stream, *stream);
rc = list->stream_added_cb(*stream);
if (rc) {
DL_DELETE(list->streams, *stream);
diff --git a/cras/src/server/stream_list.h b/cras/src/server/stream_list.h
index ae77a333..0a9b86a2 100644
--- a/cras/src/server/stream_list.h
+++ b/cras/src/server/stream_list.h
@@ -30,8 +30,8 @@ void stream_list_destroy(struct stream_list *list);
struct cras_rstream *stream_list_get(struct stream_list *list);
-/* Creates a cras_rstream from cras_rstreaem_config and adds the cras_rstream
- * to stream_list.
+/* Creates a cras_rstream from cras_rstream_config and inserts the cras_rstream
+ * to stream_list in descending order by channel count.
*
* Args:
* list - stream_list to add streams.
diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc
index f9644faa..d6a58d5d 100644
--- a/cras/src/tests/iodev_list_unittest.cc
+++ b/cras/src/tests/iodev_list_unittest.cc
@@ -83,6 +83,7 @@ static size_t cras_observer_notify_node_left_right_swapped_called;
static size_t cras_observer_notify_input_node_gain_called;
static int cras_iodev_open_called;
static int cras_iodev_open_ret[8];
+static struct cras_audio_format cras_iodev_open_fmt;
static int set_mute_called;
static std::vector<struct cras_iodev*> set_mute_dev_vector;
static std::vector<unsigned int> audio_thread_dev_start_ramp_dev_vector;
@@ -129,6 +130,10 @@ class IoDevTestSuite : public testing::Test {
channel_counts_[0] = 2;
channel_counts_[1] = 0;
+ fmt_.format = SND_PCM_FORMAT_S16_LE;
+ fmt_.frame_rate = 48000;
+ fmt_.num_channels = 2;
+
memset(&d1_, 0, sizeof(d1_));
memset(&d2_, 0, sizeof(d2_));
memset(&d3_, 0, sizeof(d3_));
@@ -266,6 +271,7 @@ class IoDevTestSuite : public testing::Test {
struct cras_iodev d1_;
struct cras_iodev d2_;
struct cras_iodev d3_;
+ struct cras_audio_format fmt_;
size_t sample_rates_[3];
size_t channel_counts_[2];
static int set_volume_1_called_;
@@ -303,6 +309,8 @@ TEST_F(IoDevTestSuite, SetSuspendResume) {
rc = cras_iodev_list_add_output(&d1_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 1));
@@ -348,6 +356,56 @@ TEST_F(IoDevTestSuite, SetSuspendResume) {
EXPECT_EQ(3, cras_observer_notify_active_node_called);
}
+/* Check that the suspend/resume call of active iodev will be triggered and
+ * fallback device will be transciently enabled while adding a new stream whose
+ * channel count is higher than the active iodev. */
+TEST_F(IoDevTestSuite, ReopenDevForHigherChannels) {
+ struct cras_rstream rstream, rstream2;
+ struct cras_rstream* stream_list = NULL;
+ int rc;
+
+ memset(&rstream, 0, sizeof(rstream));
+ memset(&rstream2, 0, sizeof(rstream2));
+ rstream.format = fmt_;
+ rstream2.format = fmt_;
+ rstream2.format.num_channels = 6;
+
+ cras_iodev_list_init();
+
+ d1_.direction = CRAS_STREAM_OUTPUT;
+ rc = cras_iodev_list_add_output(&d1_);
+ ASSERT_EQ(0, rc);
+
+ d1_.format = &fmt_;
+
+ audio_thread_add_open_dev_called = 0;
+ cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+ cras_make_node_id(d1_.info.idx, 1));
+ DL_APPEND(stream_list, &rstream);
+ stream_list_get_ret = stream_list;
+ stream_add_cb(&rstream);
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+ EXPECT_EQ(1, audio_thread_add_open_dev_called);
+ EXPECT_EQ(1, cras_iodev_open_called);
+ EXPECT_EQ(2, cras_iodev_open_fmt.num_channels);
+
+ audio_thread_add_stream_called = 0;
+ audio_thread_add_open_dev_called = 0;
+ cras_iodev_open_called = 0;
+
+ /* stream_list should be descending ordered by channel count. */
+ DL_PREPEND(stream_list, &rstream2);
+ stream_list_get_ret = stream_list;
+ stream_add_cb(&rstream2);
+ /* Added both rstreams to fallback device, then re-opened d1. */
+ EXPECT_EQ(4, audio_thread_add_stream_called);
+ EXPECT_EQ(2, audio_thread_add_open_dev_called);
+ EXPECT_EQ(2, cras_iodev_open_called);
+ EXPECT_EQ(6, cras_iodev_open_fmt.num_channels);
+
+ cras_iodev_list_deinit();
+}
+
/* Check that after resume, all output devices enter ramp mute state if there is
* any output stream. */
TEST_F(IoDevTestSuite, RampMuteAfterResume) {
@@ -369,6 +427,9 @@ TEST_F(IoDevTestSuite, RampMuteAfterResume) {
rc = cras_iodev_list_add_input(&d2_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 1));
@@ -424,6 +485,8 @@ TEST_F(IoDevTestSuite, InitDevFailShouldEnableFallback) {
rc = cras_iodev_list_add_output(&d1_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+
cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 0));
@@ -457,6 +520,9 @@ TEST_F(IoDevTestSuite, InitDevWithEchoRef) {
rc = cras_iodev_list_add_input(&d2_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 0));
/* No close call happened, because no stream exists. */
@@ -502,6 +568,9 @@ TEST_F(IoDevTestSuite, SelectNodeOpenFailShouldScheduleRetry) {
rc = cras_iodev_list_add_output(&d2_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 1));
DL_APPEND(stream_list, &rstream);
@@ -571,12 +640,15 @@ TEST_F(IoDevTestSuite, InitDevFailShouldScheduleRetry) {
struct cras_rstream* stream_list = NULL;
memset(&rstream, 0, sizeof(rstream));
+ rstream.format = fmt_;
cras_iodev_list_init();
d1_.direction = CRAS_STREAM_OUTPUT;
rc = cras_iodev_list_add_output(&d1_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+
cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 0));
@@ -603,6 +675,7 @@ TEST_F(IoDevTestSuite, InitDevFailShouldScheduleRetry) {
EXPECT_EQ(1, cras_tm_create_timer_called);
EXPECT_EQ(1, audio_thread_add_stream_called);
+ dummy_empty_iodev[CRAS_STREAM_OUTPUT].format = &fmt_;
cras_tm_timer_cb = NULL;
cras_iodev_open_ret[3] = -5;
stream_add_cb(&rstream);
@@ -626,6 +699,8 @@ TEST_F(IoDevTestSuite, PinnedStreamInitFailShouldScheduleRetry) {
rc = cras_iodev_list_add_output(&d1_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+
rstream.is_pinned = 1;
rstream.pinned_dev_idx = d1_.info.idx;
@@ -681,6 +756,9 @@ TEST_F(IoDevTestSuite, SelectNode) {
rc = cras_iodev_list_add_output(&d2_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
audio_thread_rm_open_dev_called = 0;
@@ -751,6 +829,9 @@ TEST_F(IoDevTestSuite, SelectPreviouslyEnabledNode) {
rc = cras_iodev_list_add_output(&d2_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
audio_thread_rm_open_dev_called = 0;
device_enabled_count = 0;
@@ -1024,7 +1105,7 @@ TEST_F(IoDevTestSuite, EnableDisableDevice) {
TEST_F(IoDevTestSuite, AddRemoveInput) {
struct cras_iodev_info* dev_info;
int rc, i;
- uint32_t found_mask;
+ uint64_t found_mask;
d1_.direction = CRAS_STREAM_INPUT;
d2_.direction = CRAS_STREAM_INPUT;
@@ -1057,8 +1138,8 @@ TEST_F(IoDevTestSuite, AddRemoveInput) {
found_mask = 0;
for (i = 0; i < rc; i++) {
uint32_t idx = dev_info[i].idx;
- EXPECT_EQ(0, (found_mask & (1 << idx)));
- found_mask |= (1 << idx);
+ EXPECT_EQ(0, (found_mask & (static_cast<uint64_t>(1) << idx)));
+ found_mask |= (static_cast<uint64_t>(1) << idx);
}
}
if (rc > 0)
@@ -1317,6 +1398,10 @@ TEST_F(IoDevTestSuite, AddActiveNode) {
rc = cras_iodev_list_add_output(&d3_);
ASSERT_EQ(0, rc);
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+ d3_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d3_.info.idx, 1));
@@ -1365,6 +1450,8 @@ TEST_F(IoDevTestSuite, DrainTimerCancel) {
rc = cras_iodev_list_add_output(&d1_);
EXPECT_EQ(0, rc);
+ d1_.format = &fmt_;
+
audio_thread_add_open_dev_called = 0;
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 1));
@@ -1447,6 +1534,9 @@ TEST_F(IoDevTestSuite, AddRemovePinnedStream) {
d2_.info.idx = 2;
EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
// Setup pinned stream.
memset(&rstream, 0, sizeof(rstream));
rstream.is_pinned = 1;
@@ -1506,6 +1596,9 @@ TEST_F(IoDevTestSuite, SuspendResumePinnedStream) {
d2_.direction = CRAS_STREAM_OUTPUT;
EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
+ d1_.format = &fmt_;
+ d2_.format = &fmt_;
+
// Setup pinned stream.
memset(&rstream, 0, sizeof(rstream));
rstream.is_pinned = 1;
@@ -1561,6 +1654,8 @@ TEST_F(IoDevTestSuite, HotwordStreamsAddedThenSuspendResume) {
d1_.direction = CRAS_STREAM_INPUT;
EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+ d1_.format = &fmt_;
+
memset(&rstream, 0, sizeof(rstream));
rstream.is_pinned = 1;
rstream.pinned_dev_idx = d1_.info.idx;
@@ -1606,6 +1701,8 @@ TEST_F(IoDevTestSuite, HotwordStreamsAddedAfterSuspend) {
d1_.direction = CRAS_STREAM_INPUT;
EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+ d1_.format = &fmt_;
+
memset(&rstream, 0, sizeof(rstream));
rstream.is_pinned = 1;
rstream.pinned_dev_idx = d1_.info.idx;
@@ -1787,6 +1884,7 @@ int cras_iodev_open(struct cras_iodev* iodev,
const struct cras_audio_format* fmt) {
if (cras_iodev_open_ret[cras_iodev_open_called] == 0)
iodev->state = CRAS_IODEV_STATE_OPEN;
+ cras_iodev_open_fmt = *fmt;
return cras_iodev_open_ret[cras_iodev_open_called++];
}
diff --git a/cras/src/tests/stream_list_unittest.cc b/cras/src/tests/stream_list_unittest.cc
index 8f3c2e3e..40be35d0 100644
--- a/cras/src/tests/stream_list_unittest.cc
+++ b/cras/src/tests/stream_list_unittest.cc
@@ -28,13 +28,16 @@ static int removed_cb(struct cras_rstream* rstream) {
static unsigned int create_called;
static struct cras_rstream_config* create_config;
-static struct cras_rstream dummy_rstream;
static int create_rstream_cb(struct cras_rstream_config* stream_config,
struct cras_rstream** stream) {
create_called++;
create_config = stream_config;
- *stream = &dummy_rstream;
- dummy_rstream.stream_id = 0x3003;
+ *stream = (struct cras_rstream*)malloc(sizeof(struct cras_rstream));
+ (*stream)->stream_id = stream_config->stream_id;
+ (*stream)->direction = stream_config->direction;
+ if (stream_config->format)
+ (*stream)->format = *(stream_config->format);
+
return 0;
}
@@ -43,6 +46,7 @@ static struct cras_rstream* destroyed_stream;
static void destroy_rstream_cb(struct cras_rstream* rstream) {
destroy_called++;
destroyed_stream = rstream;
+ free(rstream);
}
static void reset_test_data() {
@@ -57,6 +61,10 @@ TEST(StreamList, AddRemove) {
struct cras_rstream* s1;
struct cras_rstream_config s1_config;
+ s1_config.stream_id = 0x3003;
+ s1_config.direction = CRAS_STREAM_OUTPUT;
+ s1_config.format = NULL;
+
reset_test_data();
l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
destroy_rstream_cb, NULL);
@@ -72,6 +80,55 @@ TEST(StreamList, AddRemove) {
stream_list_destroy(l);
}
+TEST(StreamList, AddInDescendingOrderByChannels) {
+ struct stream_list* l;
+ struct cras_rstream* s1;
+ struct cras_rstream* s2;
+ struct cras_rstream* s3;
+ struct cras_audio_format s1_format, s2_format, s3_format;
+ struct cras_rstream_config s1_config, s2_config, s3_config;
+
+ s1_config.stream_id = 0x4001;
+ s1_config.direction = CRAS_STREAM_INPUT;
+ s1_format.num_channels = 6;
+ s1_config.format = &s1_format;
+
+ s2_config.stream_id = 0x4002;
+ s2_config.direction = CRAS_STREAM_OUTPUT;
+ s2_format.num_channels = 8;
+ s2_config.format = &s2_format;
+
+ s3_config.stream_id = 0x4003;
+ s3_config.direction = CRAS_STREAM_OUTPUT;
+ s3_format.num_channels = 2;
+ s3_config.format = &s3_format;
+
+ reset_test_data();
+ l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
+ destroy_rstream_cb, NULL);
+ stream_list_add(l, &s1_config, &s1);
+ EXPECT_EQ(1, add_called);
+ EXPECT_EQ(1, create_called);
+ EXPECT_EQ(6, stream_list_get(l)->format.num_channels);
+
+ stream_list_add(l, &s2_config, &s2);
+ EXPECT_EQ(2, add_called);
+ EXPECT_EQ(2, create_called);
+ EXPECT_EQ(8, stream_list_get(l)->format.num_channels);
+ EXPECT_EQ(6, stream_list_get(l)->next->format.num_channels);
+
+ stream_list_add(l, &s3_config, &s3);
+ EXPECT_EQ(3, add_called);
+ EXPECT_EQ(3, create_called);
+ EXPECT_EQ(8, stream_list_get(l)->format.num_channels);
+ EXPECT_EQ(6, stream_list_get(l)->next->format.num_channels);
+ EXPECT_EQ(2, stream_list_get(l)->next->next->format.num_channels);
+ EXPECT_EQ(0, stream_list_rm(l, 0x4001));
+ EXPECT_EQ(0, stream_list_rm(l, 0x4002));
+ EXPECT_EQ(0, stream_list_rm(l, 0x4003));
+ stream_list_destroy(l);
+}
+
extern "C" {
struct cras_timer* cras_tm_create_timer(struct cras_tm* tm,