blob: 0f17dc92143968b8f19d4b45b137a562af27bf86 [file] [log] [blame]
/* 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));
}