summaryrefslogtreecommitdiff
path: root/cras
diff options
context:
space:
mode:
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,