blob: 76b0c039caaf9dcb9a33455bd6bdc2a5aebf443a [file] [log] [blame]
/* Copyright (c) 2012 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 <alsa/asoundlib.h>
#include <alsa/control_external.h>
#include <cras_client.h>
static const size_t MAX_IODEVS = 10; /* Max devices to print out. */
static const size_t MAX_IONODES = 20; /* Max ionodes to print out. */
/* Support basic input/output volume/mute only. */
enum CTL_CRAS_MIXER_CONTROLS {
CTL_CRAS_MIXER_PLAYBACK_SWITCH,
CTL_CRAS_MIXER_PLAYBACK_VOLUME,
NUM_CTL_CRAS_MIXER_ELEMS
};
/* Hold info specific to each control. */
struct cras_mixer_control {
const char *name;
int type;
unsigned int access;
unsigned int count;
};
/* CRAS mixer elements. */
static const struct cras_mixer_control cras_elems[NUM_CTL_CRAS_MIXER_ELEMS] = {
{ "Master Playback Switch", SND_CTL_ELEM_TYPE_BOOLEAN,
SND_CTL_EXT_ACCESS_READWRITE, 1 },
{ "Master Playback Volume", SND_CTL_ELEM_TYPE_INTEGER,
SND_CTL_EXT_ACCESS_READWRITE, 1 },
};
/* Holds the client and ctl plugin pointers. */
struct ctl_cras {
snd_ctl_ext_t ext_ctl;
struct cras_client *client;
};
/* Frees resources when the plugin is closed. */
static void ctl_cras_close(snd_ctl_ext_t *ext_ctl)
{
struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
if (cras) {
cras_client_stop(cras->client);
cras_client_destroy(cras->client);
}
free(cras);
}
/* Lists available controls. */
static int ctl_cras_elem_list(snd_ctl_ext_t *ext_ctl, unsigned int offset,
snd_ctl_elem_id_t *id)
{
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
if (offset >= NUM_CTL_CRAS_MIXER_ELEMS)
return -EINVAL;
snd_ctl_elem_id_set_name(id, cras_elems[offset].name);
return 0;
}
/* Returns the number of available controls. */
static int ctl_cras_elem_count(snd_ctl_ext_t *ext_ctl)
{
return NUM_CTL_CRAS_MIXER_ELEMS;
}
/* Gets a control key from a search id. */
static snd_ctl_ext_key_t ctl_cras_find_elem(snd_ctl_ext_t *ext_ctl,
const snd_ctl_elem_id_t *id)
{
const char *name;
unsigned int numid;
numid = snd_ctl_elem_id_get_numid(id);
if (numid - 1 < NUM_CTL_CRAS_MIXER_ELEMS)
return numid - 1;
name = snd_ctl_elem_id_get_name(id);
for (numid = 0; numid < NUM_CTL_CRAS_MIXER_ELEMS; numid++)
if (strcmp(cras_elems[numid].name, name) == 0)
return numid;
return SND_CTL_EXT_KEY_NOT_FOUND;
}
/* Fills accessibility, type and count based on the specified control. */
static int ctl_cras_get_attribute(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
int *type, unsigned int *acc,
unsigned int *count)
{
if (key >= NUM_CTL_CRAS_MIXER_ELEMS)
return -EINVAL;
*type = cras_elems[key].type;
*acc = cras_elems[key].access;
*count = cras_elems[key].count;
return 0;
}
/* Returns the range of the specified control. The volume sliders always run
* from 0 to 100 for CRAS. */
static int ctl_cras_get_integer_info(snd_ctl_ext_t *ext_ctl,
snd_ctl_ext_key_t key, long *imin,
long *imax, long *istep)
{
*istep = 0;
*imin = 0;
*imax = 100;
return 0;
}
static int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION dir,
struct cras_ionode_info *nodes, size_t num_nodes)
{
struct cras_iodev_info devs[MAX_IODEVS];
size_t num_devs;
int rc;
if (dir == CRAS_STREAM_OUTPUT)
rc = cras_client_get_output_devices(client, devs, nodes,
&num_devs, &num_nodes);
else
rc = cras_client_get_input_devices(client, devs, nodes,
&num_devs, &num_nodes);
if (rc < 0)
return 0;
return num_nodes;
}
/* Gets the value of the given control from CRAS and puts it in value. */
static int ctl_cras_read_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
long *value)
{
struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
struct cras_ionode_info nodes[MAX_IONODES];
int num_nodes, i;
switch (key) {
case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
*value = !cras_client_get_user_muted(cras->client);
break;
case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
MAX_IONODES);
for (i = 0; i < num_nodes; i++) {
if (!nodes[i].active)
continue;
*value = nodes[i].volume;
break;
}
break;
default:
return -EINVAL;
}
return 0;
}
/* Writes the given values to CRAS. */
static int ctl_cras_write_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
long *value)
{
struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
struct cras_ionode_info nodes[MAX_IONODES];
int num_nodes, i;
switch (key) {
case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
cras_client_set_user_mute(cras->client, !(*value));
break;
case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
MAX_IONODES);
for (i = 0; i < num_nodes; i++) {
if (!nodes[i].active)
continue;
cras_client_set_node_volume(
cras->client,
cras_make_node_id(nodes[i].iodev_idx,
nodes[i].ionode_idx),
*value);
}
break;
default:
return -EINVAL;
}
return 0;
}
static const snd_ctl_ext_callback_t ctl_cras_ext_callback = {
.close = ctl_cras_close,
.elem_count = ctl_cras_elem_count,
.elem_list = ctl_cras_elem_list,
.find_elem = ctl_cras_find_elem,
.get_attribute = ctl_cras_get_attribute,
.get_integer_info = ctl_cras_get_integer_info,
.read_integer = ctl_cras_read_integer,
.write_integer = ctl_cras_write_integer,
};
SND_CTL_PLUGIN_DEFINE_FUNC(cras)
{
struct ctl_cras *cras;
int rc;
cras = malloc(sizeof(*cras));
if (cras == NULL)
return -ENOMEM;
rc = cras_client_create(&cras->client);
if (rc != 0 || cras->client == NULL) {
fprintf(stderr, "Couldn't create CRAS client\n");
free(cras);
return rc;
}
rc = cras_client_connect(cras->client);
if (rc < 0) {
fprintf(stderr, "Couldn't connect to cras.\n");
cras_client_destroy(cras->client);
free(cras);
return rc;
}
rc = cras_client_run_thread(cras->client);
if (rc < 0) {
fprintf(stderr, "Couldn't start client thread.\n");
cras_client_stop(cras->client);
cras_client_destroy(cras->client);
free(cras);
return rc;
}
rc = cras_client_connected_wait(cras->client);
if (rc < 0) {
fprintf(stderr, "CRAS client wouldn't connect.\n");
cras_client_stop(cras->client);
cras_client_destroy(cras->client);
free(cras);
return rc;
}
cras->ext_ctl.version = SND_CTL_EXT_VERSION;
cras->ext_ctl.card_idx = 0;
strncpy(cras->ext_ctl.id, "cras", sizeof(cras->ext_ctl.id) - 1);
cras->ext_ctl.id[sizeof(cras->ext_ctl.id) - 1] = '\0';
strncpy(cras->ext_ctl.driver, "CRAS plugin",
sizeof(cras->ext_ctl.driver) - 1);
cras->ext_ctl.driver[sizeof(cras->ext_ctl.driver) - 1] = '\0';
strncpy(cras->ext_ctl.name, "CRAS", sizeof(cras->ext_ctl.name) - 1);
cras->ext_ctl.name[sizeof(cras->ext_ctl.name) - 1] = '\0';
strncpy(cras->ext_ctl.longname, "CRAS",
sizeof(cras->ext_ctl.longname) - 1);
cras->ext_ctl.longname[sizeof(cras->ext_ctl.longname) - 1] = '\0';
strncpy(cras->ext_ctl.mixername, "CRAS",
sizeof(cras->ext_ctl.mixername) - 1);
cras->ext_ctl.mixername[sizeof(cras->ext_ctl.mixername) - 1] = '\0';
cras->ext_ctl.poll_fd = -1;
cras->ext_ctl.callback = &ctl_cras_ext_callback;
cras->ext_ctl.private_data = cras;
rc = snd_ctl_ext_create(&cras->ext_ctl, name, mode);
if (rc < 0) {
cras_client_stop(cras->client);
cras_client_destroy(cras->client);
free(cras);
return rc;
}
*handlep = cras->ext_ctl.handle;
return 0;
}
SND_CTL_PLUGIN_SYMBOL(cras);