diff options
Diffstat (limited to 'cras')
-rw-r--r-- | cras/src/server/cras_iodev_list.c | 48 | ||||
-rw-r--r-- | cras/src/server/stream_list.c | 9 | ||||
-rw-r--r-- | cras/src/server/stream_list.h | 4 | ||||
-rw-r--r-- | cras/src/tests/iodev_list_unittest.cc | 104 | ||||
-rw-r--r-- | cras/src/tests/stream_list_unittest.cc | 63 |
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, |