| /* Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "cras_observer.h" |
| |
| #include "cras_alert.h" |
| #include "cras_iodev_list.h" |
| #include "cras_types.h" |
| #include "utlist.h" |
| |
| struct cras_observer_client { |
| struct cras_observer_ops ops; |
| void *context; |
| struct cras_observer_client *next, *prev; |
| }; |
| |
| struct cras_observer_alerts { |
| struct cras_alert *output_volume; |
| struct cras_alert *output_mute; |
| struct cras_alert *capture_gain; |
| struct cras_alert *capture_mute; |
| struct cras_alert *nodes; |
| struct cras_alert *active_node; |
| struct cras_alert *output_node_volume; |
| struct cras_alert *node_left_right_swapped; |
| struct cras_alert *input_node_gain; |
| struct cras_alert *suspend_changed; |
| struct cras_alert *hotword_triggered; |
| /* If all events for active streams went through a single alert then |
| * we might miss some because the alert code does not send every |
| * alert message. To ensure that the event sent contains the correct |
| * number of active streams per direction, make the alerts |
| * per-direciton. */ |
| struct cras_alert *num_active_streams[CRAS_NUM_DIRECTIONS]; |
| struct cras_alert *non_empty_audio_state_changed; |
| struct cras_alert *bt_battery_changed; |
| struct cras_alert *num_input_streams_with_permission; |
| }; |
| |
| struct cras_observer_server { |
| struct cras_observer_alerts alerts; |
| struct cras_observer_client *clients; |
| }; |
| |
| struct cras_observer_alert_data_volume { |
| int32_t volume; |
| }; |
| |
| struct cras_observer_alert_data_mute { |
| int muted; |
| int user_muted; |
| int mute_locked; |
| }; |
| |
| struct cras_observer_alert_data_active_node { |
| enum CRAS_STREAM_DIRECTION direction; |
| cras_node_id_t node_id; |
| }; |
| |
| struct cras_observer_alert_data_node_volume { |
| cras_node_id_t node_id; |
| int32_t volume; |
| }; |
| |
| struct cras_observer_alert_data_node_lr_swapped { |
| cras_node_id_t node_id; |
| int swapped; |
| }; |
| |
| struct cras_observer_alert_data_suspend { |
| int suspended; |
| }; |
| |
| struct cras_observer_alert_data_streams { |
| enum CRAS_STREAM_DIRECTION direction; |
| uint32_t num_active_streams; |
| }; |
| |
| struct cras_observer_alert_data_input_streams { |
| uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]; |
| }; |
| |
| struct cras_observer_alert_data_hotword_triggered { |
| int64_t tv_sec; |
| int64_t tv_nsec; |
| }; |
| |
| struct cras_observer_non_empty_audio_state { |
| int non_empty; |
| }; |
| |
| struct cras_observer_alert_data_bt_battery_changed { |
| const char *address; |
| uint32_t level; |
| }; |
| |
| /* Global observer instance. */ |
| static struct cras_observer_server *g_observer; |
| |
| /* Empty observer ops. */ |
| static struct cras_observer_ops g_empty_ops; |
| |
| /* |
| * Alert handlers for delayed callbacks. |
| */ |
| |
| static void output_volume_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_volume *volume_data = |
| (struct cras_observer_alert_data_volume *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.output_volume_changed) |
| client->ops.output_volume_changed(client->context, |
| volume_data->volume); |
| } |
| } |
| |
| static void output_mute_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_mute *mute_data = |
| (struct cras_observer_alert_data_mute *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.output_mute_changed) |
| client->ops.output_mute_changed(client->context, |
| mute_data->muted, |
| mute_data->user_muted, |
| mute_data->mute_locked); |
| } |
| } |
| |
| static void capture_gain_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_volume *volume_data = |
| (struct cras_observer_alert_data_volume *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.capture_gain_changed) |
| client->ops.capture_gain_changed(client->context, |
| volume_data->volume); |
| } |
| } |
| |
| static void capture_mute_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_mute *mute_data = |
| (struct cras_observer_alert_data_mute *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.capture_mute_changed) |
| client->ops.capture_mute_changed( |
| client->context, mute_data->muted, |
| mute_data->mute_locked); |
| } |
| } |
| |
| static void nodes_prepare(struct cras_alert *alert) |
| { |
| cras_iodev_list_update_device_list(); |
| } |
| |
| static void nodes_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.nodes_changed) |
| client->ops.nodes_changed(client->context); |
| } |
| } |
| |
| static void active_node_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_active_node *node_data = |
| (struct cras_observer_alert_data_active_node *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.active_node_changed) |
| client->ops.active_node_changed(client->context, |
| node_data->direction, |
| node_data->node_id); |
| } |
| } |
| |
| static void output_node_volume_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_node_volume *node_data = |
| (struct cras_observer_alert_data_node_volume *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.output_node_volume_changed) |
| client->ops.output_node_volume_changed( |
| client->context, node_data->node_id, |
| node_data->volume); |
| } |
| } |
| |
| static void node_left_right_swapped_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_node_lr_swapped *node_data = |
| (struct cras_observer_alert_data_node_lr_swapped *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.node_left_right_swapped_changed) |
| client->ops.node_left_right_swapped_changed( |
| client->context, node_data->node_id, |
| node_data->swapped); |
| } |
| } |
| |
| static void input_node_gain_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_node_volume *node_data = |
| (struct cras_observer_alert_data_node_volume *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.input_node_gain_changed) |
| client->ops.input_node_gain_changed(client->context, |
| node_data->node_id, |
| node_data->volume); |
| } |
| } |
| |
| static void suspend_changed_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_suspend *suspend_data = |
| (struct cras_observer_alert_data_suspend *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.suspend_changed) |
| client->ops.suspend_changed(client->context, |
| suspend_data->suspended); |
| } |
| } |
| |
| static void num_active_streams_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_streams *streams_data = |
| (struct cras_observer_alert_data_streams *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.num_active_streams_changed) |
| client->ops.num_active_streams_changed( |
| client->context, streams_data->direction, |
| streams_data->num_active_streams); |
| } |
| } |
| |
| static void num_input_streams_with_permission_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_input_streams *input_streams_data = |
| (struct cras_observer_alert_data_input_streams *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.num_input_streams_with_permission_changed) |
| client->ops.num_input_streams_with_permission_changed( |
| client->context, |
| input_streams_data->num_input_streams); |
| } |
| } |
| |
| static void hotword_triggered_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_hotword_triggered *triggered_data = |
| (struct cras_observer_alert_data_hotword_triggered *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.hotword_triggered) |
| client->ops.hotword_triggered(client->context, |
| triggered_data->tv_sec, |
| triggered_data->tv_nsec); |
| } |
| } |
| |
| static void non_empty_audio_state_changed_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_non_empty_audio_state *non_empty_audio_data = |
| (struct cras_observer_non_empty_audio_state *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.non_empty_audio_state_changed) { |
| client->ops.non_empty_audio_state_changed( |
| client->context, |
| non_empty_audio_data->non_empty); |
| } |
| } |
| } |
| |
| static void bt_battery_changed_alert(void *arg, void *data) |
| { |
| struct cras_observer_client *client; |
| struct cras_observer_alert_data_bt_battery_changed *triggered_data = |
| (struct cras_observer_alert_data_bt_battery_changed *)data; |
| |
| DL_FOREACH (g_observer->clients, client) { |
| if (client->ops.bt_battery_changed) |
| client->ops.bt_battery_changed(client->context, |
| triggered_data->address, |
| triggered_data->level); |
| } |
| } |
| |
| static int cras_observer_server_set_alert(struct cras_alert **alert, |
| cras_alert_cb cb, |
| cras_alert_prepare prepare, |
| unsigned int flags) |
| { |
| *alert = cras_alert_create(prepare, flags); |
| if (!*alert) |
| return -ENOMEM; |
| return cras_alert_add_callback(*alert, cb, NULL); |
| } |
| |
| #define CRAS_OBSERVER_SET_ALERT(alert, prepare, flags) \ |
| do { \ |
| rc = cras_observer_server_set_alert(&g_observer->alerts.alert, \ |
| alert##_alert, prepare, \ |
| flags); \ |
| if (rc) \ |
| goto error; \ |
| } while (0) |
| |
| #define CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(alert, direction) \ |
| do { \ |
| rc = cras_observer_server_set_alert( \ |
| &g_observer->alerts.alert[direction], alert##_alert, \ |
| NULL, 0); \ |
| if (rc) \ |
| goto error; \ |
| } while (0) |
| |
| /* |
| * Public interface |
| */ |
| |
| int cras_observer_server_init() |
| { |
| int rc; |
| |
| memset(&g_empty_ops, 0, sizeof(g_empty_ops)); |
| g_observer = (struct cras_observer_server *)calloc( |
| 1, sizeof(struct cras_observer_server)); |
| if (!g_observer) |
| return -ENOMEM; |
| |
| CRAS_OBSERVER_SET_ALERT(output_volume, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(output_mute, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(capture_gain, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(capture_mute, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(nodes, nodes_prepare, 0); |
| CRAS_OBSERVER_SET_ALERT(active_node, nodes_prepare, |
| CRAS_ALERT_FLAG_KEEP_ALL_DATA); |
| CRAS_OBSERVER_SET_ALERT(output_node_volume, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(node_left_right_swapped, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(input_node_gain, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(suspend_changed, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(hotword_triggered, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(non_empty_audio_state_changed, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(bt_battery_changed, NULL, 0); |
| CRAS_OBSERVER_SET_ALERT(num_input_streams_with_permission, NULL, 0); |
| |
| CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(num_active_streams, |
| CRAS_STREAM_OUTPUT); |
| CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(num_active_streams, |
| CRAS_STREAM_INPUT); |
| CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(num_active_streams, |
| CRAS_STREAM_POST_MIX_PRE_DSP); |
| |
| return 0; |
| |
| error: |
| cras_observer_server_free(); |
| return rc; |
| } |
| |
| void cras_observer_server_free() |
| { |
| if (!g_observer) |
| return; |
| cras_alert_destroy(g_observer->alerts.output_volume); |
| cras_alert_destroy(g_observer->alerts.output_mute); |
| cras_alert_destroy(g_observer->alerts.capture_gain); |
| cras_alert_destroy(g_observer->alerts.capture_mute); |
| cras_alert_destroy(g_observer->alerts.nodes); |
| cras_alert_destroy(g_observer->alerts.active_node); |
| cras_alert_destroy(g_observer->alerts.output_node_volume); |
| cras_alert_destroy(g_observer->alerts.node_left_right_swapped); |
| cras_alert_destroy(g_observer->alerts.input_node_gain); |
| cras_alert_destroy(g_observer->alerts.suspend_changed); |
| cras_alert_destroy(g_observer->alerts.hotword_triggered); |
| cras_alert_destroy(g_observer->alerts.non_empty_audio_state_changed); |
| cras_alert_destroy(g_observer->alerts.bt_battery_changed); |
| cras_alert_destroy( |
| g_observer->alerts.num_input_streams_with_permission); |
| cras_alert_destroy( |
| g_observer->alerts.num_active_streams[CRAS_STREAM_OUTPUT]); |
| cras_alert_destroy( |
| g_observer->alerts.num_active_streams[CRAS_STREAM_INPUT]); |
| cras_alert_destroy( |
| g_observer->alerts |
| .num_active_streams[CRAS_STREAM_POST_MIX_PRE_DSP]); |
| free(g_observer); |
| g_observer = NULL; |
| } |
| |
| int cras_observer_ops_are_empty(const struct cras_observer_ops *ops) |
| { |
| return memcmp(ops, &g_empty_ops, sizeof(*ops)) == 0; |
| } |
| |
| void cras_observer_get_ops(const struct cras_observer_client *client, |
| struct cras_observer_ops *ops) |
| { |
| if (!ops) |
| return; |
| if (!client) |
| memset(ops, 0, sizeof(*ops)); |
| else |
| memcpy(ops, &client->ops, sizeof(*ops)); |
| } |
| |
| void cras_observer_set_ops(struct cras_observer_client *client, |
| const struct cras_observer_ops *ops) |
| { |
| if (!client) |
| return; |
| if (!ops) |
| memset(&client->ops, 0, sizeof(client->ops)); |
| else |
| memcpy(&client->ops, ops, sizeof(client->ops)); |
| } |
| |
| struct cras_observer_client * |
| cras_observer_add(const struct cras_observer_ops *ops, void *context) |
| { |
| struct cras_observer_client *client; |
| |
| client = (struct cras_observer_client *)calloc(1, sizeof(*client)); |
| if (!client) |
| return NULL; |
| client->context = context; |
| DL_APPEND(g_observer->clients, client); |
| cras_observer_set_ops(client, ops); |
| return client; |
| } |
| |
| void cras_observer_remove(struct cras_observer_client *client) |
| { |
| if (!client) |
| return; |
| DL_DELETE(g_observer->clients, client); |
| free(client); |
| } |
| |
| /* |
| * Public interface for notifiers. |
| */ |
| |
| void cras_observer_notify_output_volume(int32_t volume) |
| { |
| struct cras_observer_alert_data_volume data; |
| |
| data.volume = volume; |
| cras_alert_pending_data(g_observer->alerts.output_volume, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_output_mute(int muted, int user_muted, |
| int mute_locked) |
| { |
| struct cras_observer_alert_data_mute data; |
| |
| data.muted = muted; |
| data.user_muted = user_muted; |
| data.mute_locked = mute_locked; |
| cras_alert_pending_data(g_observer->alerts.output_mute, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_capture_gain(int32_t gain) |
| { |
| struct cras_observer_alert_data_volume data; |
| |
| data.volume = gain; |
| cras_alert_pending_data(g_observer->alerts.capture_gain, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_capture_mute(int muted, int mute_locked) |
| { |
| struct cras_observer_alert_data_mute data; |
| |
| data.muted = muted; |
| data.user_muted = 0; |
| data.mute_locked = mute_locked; |
| cras_alert_pending_data(g_observer->alerts.capture_mute, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_nodes(void) |
| { |
| cras_alert_pending(g_observer->alerts.nodes); |
| } |
| |
| void cras_observer_notify_active_node(enum CRAS_STREAM_DIRECTION dir, |
| cras_node_id_t node_id) |
| { |
| struct cras_observer_alert_data_active_node data; |
| |
| data.direction = dir; |
| data.node_id = node_id; |
| cras_alert_pending_data(g_observer->alerts.active_node, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_output_node_volume(cras_node_id_t node_id, |
| int32_t volume) |
| { |
| struct cras_observer_alert_data_node_volume data; |
| |
| data.node_id = node_id; |
| data.volume = volume; |
| cras_alert_pending_data(g_observer->alerts.output_node_volume, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_node_left_right_swapped(cras_node_id_t node_id, |
| int swapped) |
| { |
| struct cras_observer_alert_data_node_lr_swapped data; |
| |
| data.node_id = node_id; |
| data.swapped = swapped; |
| cras_alert_pending_data(g_observer->alerts.node_left_right_swapped, |
| &data, sizeof(data)); |
| } |
| |
| void cras_observer_notify_input_node_gain(cras_node_id_t node_id, int32_t gain) |
| { |
| struct cras_observer_alert_data_node_volume data; |
| |
| data.node_id = node_id; |
| data.volume = gain; |
| cras_alert_pending_data(g_observer->alerts.input_node_gain, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_suspend_changed(int suspended) |
| { |
| struct cras_observer_alert_data_suspend data; |
| |
| data.suspended = suspended; |
| cras_alert_pending_data(g_observer->alerts.suspend_changed, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir, |
| uint32_t num_active_streams) |
| { |
| struct cras_observer_alert_data_streams data; |
| struct cras_alert *alert; |
| |
| data.direction = dir; |
| data.num_active_streams = num_active_streams; |
| alert = g_observer->alerts.num_active_streams[dir]; |
| if (!alert) |
| return; |
| |
| cras_alert_pending_data(alert, &data, sizeof(data)); |
| } |
| |
| void cras_observer_notify_input_streams_with_permission( |
| uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]) |
| { |
| struct cras_observer_alert_data_input_streams data; |
| struct cras_alert *alert; |
| |
| memcpy(&data.num_input_streams, num_input_streams, |
| sizeof(*num_input_streams) * CRAS_NUM_CLIENT_TYPE); |
| alert = g_observer->alerts.num_input_streams_with_permission; |
| if (!alert) |
| return; |
| |
| cras_alert_pending_data(alert, &data, sizeof(data)); |
| } |
| |
| void cras_observer_notify_hotword_triggered(int64_t tv_sec, int64_t tv_nsec) |
| { |
| struct cras_observer_alert_data_hotword_triggered data; |
| |
| data.tv_sec = tv_sec; |
| data.tv_nsec = tv_nsec; |
| cras_alert_pending_data(g_observer->alerts.hotword_triggered, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_non_empty_audio_state_changed(int non_empty) |
| { |
| struct cras_observer_non_empty_audio_state data; |
| |
| data.non_empty = non_empty; |
| |
| cras_alert_pending_data( |
| g_observer->alerts.non_empty_audio_state_changed, &data, |
| sizeof(data)); |
| } |
| |
| void cras_observer_notify_bt_battery_changed(const char *address, |
| uint32_t level) |
| { |
| struct cras_observer_alert_data_bt_battery_changed data; |
| |
| data.address = address; |
| data.level = level; |
| |
| cras_alert_pending_data(g_observer->alerts.bt_battery_changed, &data, |
| sizeof(data)); |
| } |