diff options
author | Jeremy Wu <jrwu@google.com> | 2024-04-19 08:40:12 +0000 |
---|---|---|
committer | Chromeos LUCI <chromeos-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2024-05-14 12:02:01 +0000 |
commit | 83d168a00d7500b17d91b92566007fe2e3889828 (patch) | |
tree | c2143d60057701aee4e559e3806d567749cd7d4b | |
parent | 2bfebafecbaace3f09707556e79d14ed00e6ecf0 (diff) | |
download | adhd-83d168a00d7500b17d91b92566007fe2e3889828.tar.gz |
floss: implement |cras_lea_manager|
This CL implements |cras_lea_manager|, the component that resolves
events and issues requests while managing iodev resources for LEA.
BUG=b:317682584
TEST=Apply full patch series and verify unicast works
Change-Id: I014b5295d3740be9f25a1356faf84d234fadca04
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/5467607
Tested-by: chromeos-cop-builder@chromeos-cop.iam.gserviceaccount.com <chromeos-cop-builder@chromeos-cop.iam.gserviceaccount.com>
Commit-Queue: Jeremy Wu <jrwu@google.com>
Reviewed-by: Hsinyu Chao <hychao@chromium.org>
-rw-r--r-- | cras/src/server/BUILD.bazel | 2 | ||||
-rw-r--r-- | cras/src/server/cras_fl_media_adapter.c | 13 | ||||
-rw-r--r-- | cras/src/server/cras_fl_media_adapter.h | 2 | ||||
-rw-r--r-- | cras/src/server/cras_lea_iodev.c | 7 | ||||
-rw-r--r-- | cras/src/server/cras_lea_iodev.h | 4 | ||||
-rw-r--r-- | cras/src/server/cras_lea_manager.c | 425 | ||||
-rw-r--r-- | cras/src/server/cras_lea_manager.h | 101 | ||||
-rw-r--r-- | cras/src/tests/BUILD.bazel | 26 | ||||
-rw-r--r-- | cras/src/tests/lea_manager_unittest.cc | 316 |
9 files changed, 891 insertions, 5 deletions
diff --git a/cras/src/server/BUILD.bazel b/cras/src/server/BUILD.bazel index c18f0cdf..a3fe868f 100644 --- a/cras/src/server/BUILD.bazel +++ b/cras/src/server/BUILD.bazel @@ -439,6 +439,8 @@ cc_library( "cras_audio_thread_monitor.h", "cras_lea_iodev.c", "cras_lea_iodev.h", + "cras_lea_manager.c", + "cras_lea_manager.h", "cras_bt_adapter.c", "cras_bt_adapter.h", "cras_bt_battery_provider.c", diff --git a/cras/src/server/cras_fl_media_adapter.c b/cras/src/server/cras_fl_media_adapter.c index 2684c3e0..c6df28c7 100644 --- a/cras/src/server/cras_fl_media_adapter.c +++ b/cras/src/server/cras_fl_media_adapter.c @@ -16,6 +16,7 @@ #include "cras/common/check.h" #include "cras/server/platform/features/features.h" #include "cras/src/server/cras_a2dp_manager.h" +#include "cras/src/server/cras_lea_manager.h" #include "cras/src/server/cras_bt_io.h" #include "cras/src/server/cras_bt_log.h" #include "cras/src/server/cras_bt_policy.h" @@ -74,7 +75,11 @@ int handle_on_lea_group_connected(struct fl_media* active_fm, return -EINVAL; } - // TODO: notify BLE manager + if (!active_fm->lea) { + active_fm->lea = cras_floss_lea_create(active_fm); + } + + cras_floss_lea_add_group(active_fm->lea, name, group_id); return 0; } @@ -91,7 +96,7 @@ int handle_on_lea_group_disconnected(struct fl_media* active_fm, int group_id) { return -EINVAL; } - // TODO: notify BLE manager + cras_floss_lea_remove_group(active_fm->lea, group_id); return 0; } @@ -117,7 +122,9 @@ int handle_on_lea_audio_conf(struct fl_media* active_fm, return -EINVAL; } - // TODO: notify BLE manager + cras_floss_lea_audio_conf_updated(active_fm->lea, direction, group_id, + snk_audio_location, src_audio_location, + available_contexts); return 0; } diff --git a/cras/src/server/cras_fl_media_adapter.h b/cras/src/server/cras_fl_media_adapter.h index 5379996b..0334cfe8 100644 --- a/cras/src/server/cras_fl_media_adapter.h +++ b/cras/src/server/cras_fl_media_adapter.h @@ -36,6 +36,8 @@ struct fl_media { DBusConnection* conn; // Object representing the connected A2DP headset. struct cras_a2dp* a2dp; + // Object representing the LEA service. + struct cras_lea* lea; // Object representing the connected HFP headset. struct cras_hfp* hfp; struct bt_io_manager* bt_io_mgr; diff --git a/cras/src/server/cras_lea_iodev.c b/cras/src/server/cras_lea_iodev.c index 032a4be2..c8b17f1b 100644 --- a/cras/src/server/cras_lea_iodev.c +++ b/cras/src/server/cras_lea_iodev.c @@ -19,6 +19,7 @@ #include "cras/src/server/audio_thread_log.h" #include "cras/src/server/cras_audio_area.h" #include "cras/src/server/cras_audio_thread_monitor.h" +#include "cras/src/server/cras_lea_manager.h" #include "cras/src/server/cras_iodev.h" #include "cras/src/server/cras_iodev_list.h" #include "cras/src/server/cras_utf8.h" @@ -41,6 +42,8 @@ struct lea_io { // How many frames of audio samples we prefer to write in one // socket write. unsigned int write_block; + // The associated |cras_lea| object. + struct cras_lea* lea; // The associated ID of the corresponding LE audio group. int group_id; // If the device has been configured and attached with any stream. @@ -59,7 +62,8 @@ static void lea_free_base_resources(struct lea_io* leaio) { free(leaio->base.supported_formats); } -struct cras_iodev* lea_iodev_create(const char* name, +struct cras_iodev* lea_iodev_create(struct cras_lea* lea, + const char* name, int group_id, enum CRAS_STREAM_DIRECTION dir) { struct lea_io* leaio; @@ -73,6 +77,7 @@ struct cras_iodev* lea_iodev_create(const char* name, } leaio->started = 0; + leaio->lea = lea; leaio->group_id = group_id; iodev = &leaio->base; diff --git a/cras/src/server/cras_lea_iodev.h b/cras/src/server/cras_lea_iodev.h index 064bbabc..c47f56b3 100644 --- a/cras/src/server/cras_lea_iodev.h +++ b/cras/src/server/cras_lea_iodev.h @@ -18,11 +18,13 @@ extern "C" { * Note that if a group supports both input and output, two `lea_iodev`s * will be instantiated. * Args: + * lea - The associated |cras_lea| object. * name - Name associated to the LE audio group. * group - ID of the associated group. * dir - The direction of the iodev. */ -struct cras_iodev* lea_iodev_create(const char* name, +struct cras_iodev* lea_iodev_create(struct cras_lea* lea, + const char* name, int group_id, enum CRAS_STREAM_DIRECTION dir); diff --git a/cras/src/server/cras_lea_manager.c b/cras/src/server/cras_lea_manager.c new file mode 100644 index 00000000..8daf8528 --- /dev/null +++ b/cras/src/server/cras_lea_manager.c @@ -0,0 +1,425 @@ +/* Copyright 2024 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // for ppoll +#endif + +#include "cras/src/server/cras_lea_manager.h" + +#include <errno.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <syslog.h> + +#include "cras/server/main_message.h" +#include "cras/src/server/cras_lea_iodev.h" +#include "cras/src/server/cras_fl_media.h" +#include "cras/src/server/cras_iodev_list.h" +#include "cras/src/server/cras_system_state.h" +#include "cras/src/server/cras_tm.h" +#include "cras_config.h" +#include "cras_util.h" +#include "third_party/utlist/utlist.h" + +#define FLOSS_LEA_DATA_PATH "/run/bluetooth/audio/.lea_data" + +/* Object (list) holding information about LE-Audio groups. */ +struct lea_group { + char* name; + int group_id; + uint8_t direction; /* Bitmask of |LEA_AUDIO_DIRECTION| */ + uint16_t available_contexts; /* Bitmask of |LEA_AUDIO_CONTEXT_TYPE| */ + uint32_t data_interval_us; + uint32_t sample_rate; + uint8_t bits_per_sample; + uint8_t channels_count; + + struct cras_iodev *idev, *odev; + struct lea_group *prev, *next; +}; + +/* Object holding information and resources of a connected LEA headset. */ +struct cras_lea { + // Object representing the media interface of BT adapter. + struct fl_media* fm; + // A list of connected LE-Audio groups. + // The first group in the list is the primary group. + struct lea_group* connected_groups; + // The file descriptor for LEA socket. + int fd; + // If the input has started. This is used to determine if + // a lea start or stop is required. + int idev_started; + // If the output has started. This is used to determine if + // a lea start or stop is required. + int odev_started; +}; + +int cras_floss_lea_configure_sink_for_voice_communication( + struct cras_lea* lea) { + return floss_media_lea_sink_metadata_changed( + lea->fm, FL_LEA_AUDIO_SOURCE_VOICE_COMMUNICATION, 1.0); +} + +int cras_floss_lea_configure_source_for_voice_communication( + struct cras_lea* lea) { + return floss_media_lea_source_metadata_changed( + lea->fm, FL_LEA_AUDIO_USAGE_VOICE_COMMUNICATION, + FL_LEA_AUDIO_CONTENT_TYPE_MUSIC, 0.0); +} + +int cras_floss_lea_configure_source_for_media(struct cras_lea* lea) { + return floss_media_lea_source_metadata_changed( + lea->fm, FL_LEA_AUDIO_USAGE_MEDIA, FL_LEA_AUDIO_CONTENT_TYPE_MUSIC, 1.0); +} + +void fill_floss_lea_skt_addr(struct sockaddr_un* addr) { + memset(addr, 0, sizeof(*addr)); + addr->sun_family = AF_UNIX; + snprintf(addr->sun_path, CRAS_MAX_SOCKET_PATH_SIZE, FLOSS_LEA_DATA_PATH); +} + +static void set_dev_started(struct cras_lea* lea, + enum CRAS_STREAM_DIRECTION dir, + int started) { + if (dir == CRAS_STREAM_INPUT) { + lea->idev_started = started; + } else if (dir == CRAS_STREAM_OUTPUT) { + lea->odev_started = started; + } +} + +/* Creates a |cras_lea| object representing the LEA service. */ +struct cras_lea* cras_floss_lea_create(struct fl_media* fm) { + struct cras_lea* lea; + lea = (struct cras_lea*)calloc(1, sizeof(*lea)); + + if (!lea) { + return NULL; + } + + lea->fm = fm; + lea->fd = -1; + + return lea; +} + +int cras_floss_lea_start(struct cras_lea* lea, + thread_callback cb, + enum CRAS_STREAM_DIRECTION dir) { + int skt_fd; + int rc; + struct sockaddr_un addr; + struct timespec timeout = {10, 0}; + struct pollfd poll_fd; + struct lea_group* group = lea->connected_groups; + + if ((dir == CRAS_STREAM_INPUT && lea->idev_started) || + (dir == CRAS_STREAM_OUTPUT && lea->odev_started)) { + return -EINVAL; + } + + if (!group) { + return -EINVAL; + } + + if (dir == CRAS_STREAM_INPUT) { + rc = floss_media_lea_peer_start_audio_request( + lea->fm, &group->data_interval_us, &group->sample_rate, + &group->bits_per_sample, &group->channels_count); + } else if (dir == CRAS_STREAM_OUTPUT) { + rc = floss_media_lea_host_start_audio_request( + lea->fm, &group->data_interval_us, &group->sample_rate, + &group->bits_per_sample, &group->channels_count); + } else { + syslog(LOG_ERR, "%s: unsupported direction %d", __func__, dir); + return -EINVAL; + } + + if (rc < 0) { + return rc; + } + + /* Check if the socket connection has been started by another + * direction's iodev. We can skip the data channel setup if so. */ + if (lea->idev_started || lea->odev_started) { + goto start_dev; + } + + skt_fd = socket(PF_UNIX, SOCK_STREAM | O_NONBLOCK, 0); + if (skt_fd < 0) { + syslog(LOG_WARNING, "Create LEA socket failed with error %d", errno); + rc = skt_fd; + goto error; + } + + fill_floss_lea_skt_addr(&addr); + + syslog(LOG_DEBUG, "Connect to LEA socket at %s ", addr.sun_path); + rc = connect(skt_fd, (struct sockaddr*)&addr, sizeof(addr)); + if (rc < 0) { + syslog(LOG_WARNING, "Connect to LEA socket failed with error %d", errno); + goto error; + } + + poll_fd.fd = skt_fd; + poll_fd.events = POLLIN | POLLOUT; + + rc = ppoll(&poll_fd, 1, &timeout, NULL); + if (rc <= 0) { + syslog(LOG_WARNING, "Poll for LEA socket failed with error %d", errno); + goto error; + } + + if (poll_fd.revents & (POLLERR | POLLHUP)) { + syslog(LOG_WARNING, "LEA socket error, revents: %u.", poll_fd.revents); + rc = -1; + goto error; + } + + lea->fd = skt_fd; + + audio_thread_add_events_callback(lea->fd, cb, lea, + POLLOUT | POLLIN | POLLERR | POLLHUP); + +start_dev: + set_dev_started(lea, dir, 1); + + return 0; +error: + if (dir == CRAS_STREAM_INPUT) { + floss_media_lea_peer_stop_audio_request(lea->fm); + } else if (dir == CRAS_STREAM_OUTPUT) { + floss_media_lea_host_stop_audio_request(lea->fm); + } + + if (skt_fd >= 0) { + close(skt_fd); + unlink(addr.sun_path); + } + return rc; +} + +int cras_floss_lea_stop(struct cras_lea* lea, enum CRAS_STREAM_DIRECTION dir) { + int rc; + + // i/odev_started is only used to determine LEA status. + if (!(lea->idev_started || lea->odev_started)) { + return 0; + } + + set_dev_started(lea, dir, 0); + + if (dir == CRAS_STREAM_INPUT) { + rc = floss_media_lea_peer_stop_audio_request(lea->fm); + if (rc < 0) { + syslog(LOG_ERR, "%s: Failed to stop peer audio request", __func__); + return rc; + } + } else if (dir == CRAS_STREAM_OUTPUT) { + rc = floss_media_lea_host_stop_audio_request(lea->fm); + if (rc < 0) { + syslog(LOG_ERR, "%s: Failed to stop host audio request", __func__); + return rc; + } + } + + if (lea->idev_started || lea->odev_started) { + return 0; + } + + if (lea->fd >= 0) { + audio_thread_rm_callback_sync(cras_iodev_list_get_audio_thread(), lea->fd); + close(lea->fd); + } + lea->fd = -1; + + return 0; +} + +int cras_floss_lea_fill_format(struct cras_lea* lea, + size_t** rates, + snd_pcm_format_t** formats, + size_t** channel_counts) { + struct lea_group* group = lea->connected_groups; + + if (group == NULL) { + return 0; + } + + *rates = (size_t*)malloc(2 * sizeof(size_t)); + if (!*rates) { + return -ENOMEM; + } + (*rates)[0] = group->sample_rate; + (*rates)[1] = 0; + + *formats = (snd_pcm_format_t*)malloc(2 * sizeof(snd_pcm_format_t)); + if (!*formats) { + return -ENOMEM; + } + switch (group->bits_per_sample) { + case 16: + (*formats)[0] = SND_PCM_FORMAT_S16_LE; + break; + case 24: + (*formats)[0] = SND_PCM_FORMAT_S24_3LE; + break; + case 32: + (*formats)[0] = SND_PCM_FORMAT_S32_LE; + break; + default: + syslog(LOG_ERR, "%s: Unknown bits_per_sample %d", __func__, + group->bits_per_sample); + return -EINVAL; + } + (*formats)[1] = (snd_pcm_format_t)0; + + *channel_counts = (size_t*)malloc(2 * sizeof(size_t)); + if (!*channel_counts) { + return -ENOMEM; + } + (*channel_counts)[0] = group->channels_count; + (*channel_counts)[1] = 0; + return 0; +} + +// TODO: use software volume if VCP is missing. +void cras_floss_lea_set_volume(struct cras_lea* lea, unsigned int volume) { + syslog(LOG_DEBUG, "%s: set_volume(%u)", __func__, volume); + struct lea_group* group = lea->connected_groups; + floss_media_lea_set_group_volume(lea->fm, group->group_id, + volume * 255 / 100); +} + +void cras_floss_lea_destroy(struct cras_lea* lea) { + struct lea_group* group; + DL_FOREACH (lea->connected_groups, group) { + if (group->idev) { + lea_iodev_destroy(group->idev); + } + if (group->odev) { + lea_iodev_destroy(group->odev); + } + DL_DELETE(lea->connected_groups, group); + free(group->name); + free(group); + } + + if (lea->fd >= 0) { + close(lea->fd); + } + + free(lea); +} + +void cras_floss_lea_set_active(struct cras_lea* lea, + int group_id, + unsigned enabled) { + // Action is needed (and meaningful) only when there is no stream. + if (lea->idev_started || lea->odev_started) { + return; + } + + if (!enabled) { + group_id = FL_LEA_GROUP_NONE; + } + + floss_media_lea_set_active_group(lea->fm, group_id); +} + +int cras_floss_lea_get_fd(struct cras_lea* lea) { + return lea->fd; +} + +// TODO: check I/O availability instead of adding both +void cras_floss_lea_add_group(struct cras_lea* lea, + const char* name, + int group_id) { + struct lea_group* group; + DL_FOREACH (lea->connected_groups, group) { + if (group_id == group->group_id) { + syslog(LOG_WARNING, "%s: Skipping added group %s", __func__, name); + return; + } + } + + group = (struct lea_group*)calloc(1, sizeof(struct lea_group)); + group->name = strdup(name); + group->group_id = group_id; + + DL_APPEND(lea->connected_groups, group); + + group->idev = lea_iodev_create(lea, name, group_id, CRAS_STREAM_INPUT); + group->odev = lea_iodev_create(lea, name, group_id, CRAS_STREAM_OUTPUT); + + // Set plugged and UI will see these iodevs. + cras_iodev_set_node_plugged(group->idev->active_node, 1); + cras_iodev_set_node_plugged(group->odev->active_node, 1); + + cras_iodev_list_notify_nodes_changed(); +} + +void cras_floss_lea_remove_group(struct cras_lea* lea, int group_id) { + struct lea_group* group; + DL_FOREACH (lea->connected_groups, group) { + if (group_id == group->group_id) { + lea_iodev_destroy(group->idev); + lea_iodev_destroy(group->odev); + + DL_DELETE(lea->connected_groups, group); + free(group->name); + free(group); + } + } +} + +int cras_floss_lea_audio_conf_updated(struct cras_lea* lea, + uint8_t direction, + int group_id, + uint32_t snk_audio_location, + uint32_t src_audio_location, + uint16_t available_contexts) { + struct lea_group* group = lea->connected_groups; + + if (!group || group->group_id != group_id) { + syslog(LOG_WARNING, "Cannot find lea_group %d to update audio conf", + group_id); + return -ENOENT; + } + + group->available_contexts = available_contexts; + + if ((group->direction & LEA_AUDIO_DIRECTION_OUTPUT) != + (direction & LEA_AUDIO_DIRECTION_OUTPUT)) { + if (direction & LEA_AUDIO_DIRECTION_OUTPUT) { + group->odev->active_node->plugged = 1; + gettimeofday(&group->odev->active_node->plugged_time, NULL); + } else { + cras_iodev_list_disable_and_close_dev_group(group->odev); + } + } + + if ((group->direction & LEA_AUDIO_DIRECTION_INPUT) != + (direction & LEA_AUDIO_DIRECTION_INPUT)) { + if (direction & LEA_AUDIO_DIRECTION_INPUT) { + group->idev->active_node->plugged = 1; + gettimeofday(&group->idev->active_node->plugged_time, NULL); + } else { + cras_iodev_list_disable_and_close_dev_group(group->idev); + } + } + + group->direction = direction; + + cras_iodev_list_notify_nodes_changed(); + + return 0; +} diff --git a/cras/src/server/cras_lea_manager.h b/cras/src/server/cras_lea_manager.h new file mode 100644 index 00000000..9585b552 --- /dev/null +++ b/cras/src/server/cras_lea_manager.h @@ -0,0 +1,101 @@ +/* Copyright 2024 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CRAS_SRC_SERVER_CRAS_LEA_MANAGER_H_ +#define CRAS_SRC_SERVER_CRAS_LEA_MANAGER_H_ + +#include "cras/src/server/audio_thread.h" +#include "cras/src/server/cras_iodev.h" +#include "cras_audio_format.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct cras_lea; +struct fl_media; + +enum LEA_AUDIO_CONTEXT_TYPE { + LEA_AUDIO_CONTEXT_UNINITIALIZED = 0x0000, + LEA_AUDIO_CONTEXT_UNSPECIFIED = 0x0001, + LEA_AUDIO_CONTEXT_CONVERSATIONAL = 0x0002, + LEA_AUDIO_CONTEXT_MEDIA = 0x0004, + LEA_AUDIO_CONTEXT_GAME = 0x0008, + LEA_AUDIO_CONTEXT_INSTRUCTIONAL = 0x0010, + LEA_AUDIO_CONTEXT_VOICEASSISTANTS = 0x0020, + LEA_AUDIO_CONTEXT_LIVE = 0x0040, + LEA_AUDIO_CONTEXT_SOUNDEFFECTS = 0x0080, + LEA_AUDIO_CONTEXT_NOTIFICATIONS = 0x0100, + LEA_AUDIO_CONTEXT_RINGTONE = 0x0200, + LEA_AUDIO_CONTEXT_ALERTS = 0x0400, + LEA_AUDIO_CONTEXT_EMERGENCYALARM = 0x0800, + LEA_AUDIO_CONTEXT_RFU = 0x1000, +}; + +enum LEA_AUDIO_DIRECTION { + LEA_AUDIO_DIRECTION_NONE = 0, + LEA_AUDIO_DIRECTION_OUTPUT = (1 << 0), + LEA_AUDIO_DIRECTION_INPUT = (1 << 1), +}; + +enum LEA_GROUP_STATUS { + LEA_GROUP_INACTIVE, + LEA_GROUP_ACTIVE, + LEA_GROUP_TURNED_IDLE_DURING_CALL, +}; + +enum LEA_GROUP_NODE_STATUS { + LEA_GROUP_NODE_ADDED = 1, + LEA_GROUP_NODE_REMOVED, +}; + +int cras_floss_lea_configure_sink_for_voice_communication(struct cras_lea* lea); + +int cras_floss_lea_configure_source_for_voice_communication( + struct cras_lea* lea); + +int cras_floss_lea_configure_source_for_media(struct cras_lea* lea); + +struct cras_lea* cras_floss_lea_create(struct fl_media* fm); + +void cras_floss_lea_destroy(struct cras_lea* lea); + +int cras_floss_lea_start(struct cras_lea* lea, + thread_callback cb, + enum CRAS_STREAM_DIRECTION dir); + +int cras_floss_lea_stop(struct cras_lea* lea, enum CRAS_STREAM_DIRECTION dir); + +int cras_floss_lea_get_fd(struct cras_lea* lea); + +void cras_floss_lea_set_active(struct cras_lea* lea, + int group_id, + unsigned enabled); + +void cras_floss_lea_set_volume(struct cras_lea* lea, unsigned int volume); + +int cras_floss_lea_fill_format(struct cras_lea* lea, + size_t** rates, + snd_pcm_format_t** formats, + size_t** channel_counts); + +void cras_floss_lea_add_group(struct cras_lea* lea, + const char* name, + int group_id); + +void cras_floss_lea_remove_group(struct cras_lea* lea, int group_id); + +int cras_floss_lea_audio_conf_updated(struct cras_lea* lea, + uint8_t direction, + int group_id, + uint32_t snk_audio_location, + uint32_t src_audio_location, + uint16_t available_contexts); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // CRAS_SRC_SERVER_CRAS_LEA_MANAGER_H_ diff --git a/cras/src/tests/BUILD.bazel b/cras/src/tests/BUILD.bazel index e0771ce7..d955d66f 100644 --- a/cras/src/tests/BUILD.bazel +++ b/cras/src/tests/BUILD.bazel @@ -411,6 +411,32 @@ cc_test( ) cc_test( + name = "lea_manager_unittest", + srcs = [ + ":lea_manager_unittest.cc", + "//cras/src/server:cras_lea_manager.c", + ], + copts = [ + "-fdata-sections", + "-ffunction-sections", + ], + deps = [ + ":test_support", + "//cras/common:rust_common_cc", + "//cras/server/platform/dlc:cc", + "//cras/server/platform/features:override", + "//cras/src/common:all_headers", + "//cras/src/server:all_headers", + "//cras/src/server/rust:cc", + "@iniparser", + "@pkg_config//alsa", + "@pkg_config//dbus-1", + "@pkg_config//gtest", + "@pkg_config//gtest_main", + ], +) + +cc_test( name = "blob_wrapper_unittest", srcs = [ ":blob_wrapper_unittest.cc", diff --git a/cras/src/tests/lea_manager_unittest.cc b/cras/src/tests/lea_manager_unittest.cc new file mode 100644 index 00000000..820e4e10 --- /dev/null +++ b/cras/src/tests/lea_manager_unittest.cc @@ -0,0 +1,316 @@ +// Copyright 2024 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <gtest/gtest.h> + +#include "cras/src/server/cras_lea_iodev.h" +#include "cras/src/server/cras_lea_manager.h" +#include "cras/src/server/cras_fl_media.h" +#include "cras/src/server/cras_iodev.h" +#include "cras/src/server/cras_iodev_list.h" +#include "cras_types.h" + +static size_t connect_called; +static int connect_ret; +static struct cras_lea* lea_iodev_create_lea_val; +static struct cras_iodev cras_idev; +static struct cras_iodev cras_odev; +struct cras_iodev* lea_iodev_create_idev_ret; +struct cras_iodev* lea_iodev_create_odev_ret; +static size_t lea_iodev_create_called; +static size_t lea_iodev_destroy_called; +static size_t cras_iodev_set_node_plugged_called; +static int cras_iodev_set_node_plugged_value; +static size_t notify_nodes_changed_called; +static size_t floss_media_lea_host_start_audio_request_called; +static size_t floss_media_lea_host_stop_audio_request_called; +static size_t floss_media_lea_peer_start_audio_request_called; +static size_t floss_media_lea_peer_stop_audio_request_called; +static size_t floss_media_lea_set_group_volume_called; +static unsigned int floss_media_lea_set_group_volume_volume_val; +static int socket_ret; +static int audio_thread_add_events_callback_called; +static int audio_thread_add_events_callback_fd; +static thread_callback audio_thread_add_events_callback_cb; +static void* audio_thread_add_events_callback_data; +static int audio_thread_config_events_callback_called; +static enum AUDIO_THREAD_EVENTS_CB_TRIGGER + audio_thread_config_events_callback_trigger; + +void ResetStubData() { + connect_called = 0; + connect_ret = 0; + lea_iodev_create_lea_val = NULL; + lea_iodev_create_idev_ret = &cras_idev; + lea_iodev_create_odev_ret = &cras_odev; + lea_iodev_create_called = 0; + lea_iodev_destroy_called = 0; + cras_iodev_set_node_plugged_called = 0; + cras_iodev_set_node_plugged_value = 0; + notify_nodes_changed_called = 0; + floss_media_lea_host_start_audio_request_called = 0; + floss_media_lea_host_stop_audio_request_called = 0; + floss_media_lea_peer_start_audio_request_called = 0; + floss_media_lea_peer_stop_audio_request_called = 0; + floss_media_lea_set_group_volume_called = 0; + floss_media_lea_set_group_volume_volume_val = 0; + audio_thread_add_events_callback_called = 0; + audio_thread_add_events_callback_fd = 0; + audio_thread_add_events_callback_cb = NULL; + audio_thread_add_events_callback_data = NULL; + audio_thread_config_events_callback_called = 0; + socket_ret = 456; +} + +namespace { + +class LeaManagerTestSuite : public testing::Test { + protected: + virtual void SetUp() { ResetStubData(); } + + virtual void TearDown() {} +}; + +TEST_F(LeaManagerTestSuite, PCMCreateDestroy) { + struct cras_lea* lea = cras_floss_lea_create(NULL); + ASSERT_NE(lea, (struct cras_lea*)NULL); + + cras_floss_lea_add_group(lea, "name", 0); + EXPECT_EQ(lea, lea_iodev_create_lea_val); + EXPECT_EQ(lea_iodev_create_called, 2); + EXPECT_EQ(cras_iodev_set_node_plugged_called, 2); + EXPECT_EQ(notify_nodes_changed_called, 1); + + cras_floss_lea_remove_group(lea, 0); + EXPECT_EQ(lea_iodev_destroy_called, 2); + + cras_floss_lea_destroy(lea); +} + +TEST_F(LeaManagerTestSuite, StartWithSocketFail) { + struct cras_lea* lea = cras_floss_lea_create(NULL); + ASSERT_NE(lea, (struct cras_lea*)NULL); + + cras_floss_lea_add_group(lea, "name", 0); + + socket_ret = -1; + + thread_callback rwcb = reinterpret_cast<thread_callback>(0xdeadbeef); + EXPECT_EQ(socket_ret, cras_floss_lea_start(lea, rwcb, CRAS_STREAM_OUTPUT)); + + EXPECT_EQ(floss_media_lea_host_start_audio_request_called, 1); + EXPECT_EQ(audio_thread_add_events_callback_called, 0); + EXPECT_EQ(floss_media_lea_host_stop_audio_request_called, 1); + EXPECT_EQ(connect_called, 0); + EXPECT_EQ(cras_floss_lea_get_fd(lea), -1); + + cras_floss_lea_remove_group(lea, 0); + EXPECT_EQ(lea_iodev_destroy_called, 2); + + cras_floss_lea_destroy(lea); +} + +TEST_F(LeaManagerTestSuite, StartWithConnectFail) { + struct cras_lea* lea = cras_floss_lea_create(NULL); + ASSERT_NE(lea, (struct cras_lea*)NULL); + + cras_floss_lea_add_group(lea, "name", 0); + + connect_ret = -1; + + thread_callback rwcb = reinterpret_cast<thread_callback>(0xdeadbeef); + EXPECT_EQ(connect_ret, cras_floss_lea_start(lea, rwcb, CRAS_STREAM_OUTPUT)); + + EXPECT_EQ(floss_media_lea_host_start_audio_request_called, 1); + EXPECT_EQ(connect_called, 1); + EXPECT_EQ(audio_thread_add_events_callback_called, 0); + EXPECT_EQ(floss_media_lea_host_stop_audio_request_called, 1); + EXPECT_EQ(cras_floss_lea_get_fd(lea), -1); + + cras_floss_lea_remove_group(lea, 0); + EXPECT_EQ(lea_iodev_destroy_called, 2); + + cras_floss_lea_destroy(lea); +} + +TEST_F(LeaManagerTestSuite, StartStop) { + struct cras_lea* lea = cras_floss_lea_create(NULL); + ASSERT_NE(lea, (struct cras_lea*)NULL); + + cras_floss_lea_add_group(lea, "name", 0); + + EXPECT_EQ(cras_floss_lea_get_fd(lea), -1); + + thread_callback rwcb = reinterpret_cast<thread_callback>(0xdeadbeef); + cras_floss_lea_start(lea, rwcb, CRAS_STREAM_OUTPUT); + EXPECT_EQ(floss_media_lea_host_start_audio_request_called, 1); + EXPECT_EQ(cras_floss_lea_get_fd(lea), socket_ret); + + cras_floss_lea_start(lea, rwcb, CRAS_STREAM_INPUT); + EXPECT_EQ(floss_media_lea_peer_start_audio_request_called, 1); + EXPECT_EQ(audio_thread_add_events_callback_called, 1); + EXPECT_EQ(audio_thread_add_events_callback_fd, socket_ret); + EXPECT_EQ((struct cras_lea*)audio_thread_add_events_callback_data, lea); + + cras_floss_lea_stop(lea, CRAS_STREAM_OUTPUT); + EXPECT_EQ(floss_media_lea_host_stop_audio_request_called, 1); + EXPECT_EQ(cras_floss_lea_get_fd(lea), socket_ret); + + cras_floss_lea_stop(lea, CRAS_STREAM_INPUT); + EXPECT_EQ(floss_media_lea_peer_stop_audio_request_called, 1); + EXPECT_EQ(cras_floss_lea_get_fd(lea), -1); + + cras_floss_lea_remove_group(lea, 0); + EXPECT_EQ(lea_iodev_destroy_called, 2); + + cras_floss_lea_destroy(lea); +} + +TEST_F(LeaManagerTestSuite, SetVolume) { + struct cras_lea* lea = cras_floss_lea_create(NULL); + ASSERT_NE(lea, (struct cras_lea*)NULL); + + cras_floss_lea_add_group(lea, "name", 0); + EXPECT_EQ(lea, lea_iodev_create_lea_val); + EXPECT_EQ(lea_iodev_create_called, 2); + + cras_floss_lea_set_volume(lea, 100); + EXPECT_EQ(floss_media_lea_set_group_volume_called, 1); + EXPECT_EQ(floss_media_lea_set_group_volume_volume_val, 255); + + cras_floss_lea_set_volume(lea, 0); + EXPECT_EQ(floss_media_lea_set_group_volume_called, 2); + EXPECT_EQ(floss_media_lea_set_group_volume_volume_val, 0); + + cras_floss_lea_set_volume(lea, 50); + EXPECT_EQ(floss_media_lea_set_group_volume_called, 3); + EXPECT_EQ(floss_media_lea_set_group_volume_volume_val, 127); + + cras_floss_lea_set_volume(lea, 20); + EXPECT_EQ(floss_media_lea_set_group_volume_called, 4); + EXPECT_EQ(floss_media_lea_set_group_volume_volume_val, 51); + + cras_floss_lea_remove_group(lea, 0); + EXPECT_EQ(lea_iodev_destroy_called, 2); + + cras_floss_lea_destroy(lea); +} +} // namespace + +extern "C" { + +// socket and connect +int socket(int domain, int type, int protocol) { + return socket_ret; +} + +int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { + connect_called++; + return connect_ret; +} + +// From audio_thread +struct audio_thread_event_log* atlog; + +// From cras_bt_log +struct cras_bt_event_log* btlog; + +void audio_thread_add_events_callback(int fd, + thread_callback cb, + void* data, + int events) { + audio_thread_add_events_callback_called++; + audio_thread_add_events_callback_fd = fd; + audio_thread_add_events_callback_cb = cb; + audio_thread_add_events_callback_data = data; +} + +void audio_thread_config_events_callback( + int fd, + enum AUDIO_THREAD_EVENTS_CB_TRIGGER trigger) { + audio_thread_config_events_callback_called++; + audio_thread_config_events_callback_trigger = trigger; +} + +int audio_thread_rm_callback_sync(struct audio_thread* thread, int fd) { + return 0; +} + +// From cras iodev list +struct audio_thread* cras_iodev_list_get_audio_thread() { + return NULL; +} + +void cras_iodev_set_node_plugged(struct cras_ionode* ionode, int plugged) { + cras_iodev_set_node_plugged_called++; + cras_iodev_set_node_plugged_value = plugged; +} + +// From cras iodev +void cras_iodev_list_notify_nodes_changed() { + notify_nodes_changed_called++; +} + +// From cras_lea_iodev +struct cras_iodev* lea_iodev_create(struct cras_lea* lea, + const char* name, + int group_id, + enum CRAS_STREAM_DIRECTION dir) { + lea_iodev_create_lea_val = lea; + lea_iodev_create_called++; + + switch (dir) { + case CRAS_STREAM_OUTPUT: + return lea_iodev_create_odev_ret; + case CRAS_STREAM_INPUT: + return lea_iodev_create_idev_ret; + default: + return NULL; + } + + return NULL; +} + +void lea_iodev_destroy(struct cras_iodev* iodev) { + lea_iodev_destroy_called++; + return; +} + +// From cras_fl_media +int floss_media_lea_host_start_audio_request(struct fl_media* fm, + uint32_t* data_interval_us, + uint32_t* sample_rate, + uint8_t* bits_per_sample, + uint8_t* channels_count) { + floss_media_lea_host_start_audio_request_called++; + return 0; +} + +int floss_media_lea_peer_start_audio_request(struct fl_media* fm, + uint32_t* data_interval_us, + uint32_t* sample_rate, + uint8_t* bits_per_sample, + uint8_t* channels_count) { + floss_media_lea_peer_start_audio_request_called++; + return 0; +} + +int floss_media_lea_host_stop_audio_request(struct fl_media* fm) { + floss_media_lea_host_stop_audio_request_called++; + return 0; +} + +int floss_media_lea_peer_stop_audio_request(struct fl_media* fm) { + floss_media_lea_peer_stop_audio_request_called++; + return 0; +} + +int floss_media_lea_set_group_volume(struct fl_media* fm, + int group_id, + uint8_t volume) { + floss_media_lea_set_group_volume_called++; + floss_media_lea_set_group_volume_volume_val = volume; + return 0; +} +} // extern "C" |