Home | History | Annotate | Download | only in alsa_plugin
      1 /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2  * Use of this source code is governed by a BSD-style license that can be
      3  * found in the LICENSE file.
      4  */
      5 
      6 #include <alsa/asoundlib.h>
      7 #include <alsa/control_external.h>
      8 #include <cras_client.h>
      9 
     10 static const size_t MAX_IODEVS = 10; /* Max devices to print out. */
     11 static const size_t MAX_IONODES = 20; /* Max ionodes to print out. */
     12 
     13 /* Support basic input/output volume/mute only. */
     14 enum CTL_CRAS_MIXER_CONTROLS {
     15 	CTL_CRAS_MIXER_PLAYBACK_SWITCH,
     16 	CTL_CRAS_MIXER_PLAYBACK_VOLUME,
     17 	CTL_CRAS_MIXER_CAPTURE_SWITCH,
     18 	CTL_CRAS_MIXER_CAPTURE_VOLUME,
     19 	NUM_CTL_CRAS_MIXER_ELEMS
     20 };
     21 
     22 /* Hold info specific to each control. */
     23 struct cras_mixer_control {
     24 	const char *name;
     25 	int type;
     26 	unsigned int access;
     27 	unsigned int count;
     28 };
     29 
     30 /* CRAS mixer elements. */
     31 static const struct cras_mixer_control cras_elems[NUM_CTL_CRAS_MIXER_ELEMS] = {
     32 	{"Master Playback Switch", SND_CTL_ELEM_TYPE_BOOLEAN,
     33 		SND_CTL_EXT_ACCESS_READWRITE, 1},
     34 	{"Master Playback Volume", SND_CTL_ELEM_TYPE_INTEGER,
     35 		SND_CTL_EXT_ACCESS_READWRITE, 1},
     36 	{"Capture Switch", SND_CTL_ELEM_TYPE_BOOLEAN,
     37 		SND_CTL_EXT_ACCESS_READWRITE, 1},
     38 	{"Capture Volume", SND_CTL_ELEM_TYPE_INTEGER,
     39 		SND_CTL_EXT_ACCESS_READWRITE, 1},
     40 };
     41 
     42 /* Holds the client and ctl plugin pointers. */
     43 struct ctl_cras {
     44 	snd_ctl_ext_t ext_ctl;
     45 	struct cras_client *client;
     46 };
     47 
     48 /* Frees resources when the plugin is closed. */
     49 static void ctl_cras_close(snd_ctl_ext_t *ext_ctl)
     50 {
     51 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
     52 
     53 	if (cras) {
     54 		cras_client_stop(cras->client);
     55 		cras_client_destroy(cras->client);
     56 	}
     57 	free(cras);
     58 }
     59 
     60 /* Lists available controls. */
     61 static int ctl_cras_elem_list(snd_ctl_ext_t *ext_ctl, unsigned int offset,
     62 			      snd_ctl_elem_id_t *id)
     63 {
     64 	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
     65 	if (offset >= NUM_CTL_CRAS_MIXER_ELEMS)
     66 		return -EINVAL;
     67 	snd_ctl_elem_id_set_name(id, cras_elems[offset].name);
     68 	return 0;
     69 }
     70 
     71 /* Returns the number of available controls. */
     72 static int ctl_cras_elem_count(snd_ctl_ext_t *ext_ctl)
     73 {
     74 	return NUM_CTL_CRAS_MIXER_ELEMS;
     75 }
     76 
     77 /* Gets a control key from a search id. */
     78 static snd_ctl_ext_key_t ctl_cras_find_elem(snd_ctl_ext_t *ext_ctl,
     79 					    const snd_ctl_elem_id_t *id)
     80 {
     81 	const char *name;
     82 	unsigned int numid;
     83 
     84 	numid = snd_ctl_elem_id_get_numid(id);
     85 	if (numid - 1 < NUM_CTL_CRAS_MIXER_ELEMS)
     86 		return numid - 1;
     87 
     88 	name = snd_ctl_elem_id_get_name(id);
     89 
     90 	for (numid = 0; numid < NUM_CTL_CRAS_MIXER_ELEMS; numid++)
     91 		if (strcmp(cras_elems[numid].name, name) == 0)
     92 			return numid;
     93 
     94 	return SND_CTL_EXT_KEY_NOT_FOUND;
     95 }
     96 
     97 /* Fills accessibility, type and count based on the specified control. */
     98 static int ctl_cras_get_attribute(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
     99 				  int *type, unsigned int *acc,
    100 				  unsigned int *count)
    101 {
    102 	if (key >= NUM_CTL_CRAS_MIXER_ELEMS)
    103 		return -EINVAL;
    104 	*type = cras_elems[key].type;
    105 	*acc = cras_elems[key].access;
    106 	*count = cras_elems[key].count;
    107 	return 0;
    108 }
    109 
    110 /* Returns the range of the specified control.  The volume sliders always run
    111  * from 0 to 100 for CRAS. */
    112 static int ctl_cras_get_integer_info(snd_ctl_ext_t *ext_ctl,
    113 				     snd_ctl_ext_key_t key,
    114 				     long *imin, long *imax, long *istep)
    115 {
    116 	*istep = 0;
    117 	*imin = 0;
    118 	*imax = 100;
    119 	return 0;
    120 }
    121 
    122 static long capture_index_to_gain(struct cras_client *client, long index)
    123 {
    124 	long min;
    125 	long max;
    126 	long dB_step;
    127 
    128 	min = cras_client_get_system_min_capture_gain(client);
    129 	max = cras_client_get_system_max_capture_gain(client);
    130 	if (min >= max)
    131 		return min;
    132 
    133 	dB_step = (max - min) / 100;
    134 
    135 	if (index <= 0)
    136 		return min;
    137 	if (index >= 100)
    138 		return max;
    139 	return index * dB_step + min;
    140 }
    141 
    142 static long capture_gain_to_index(struct cras_client *client, long gain)
    143 {
    144 	long min;
    145 	long max;
    146 	long dB_step;
    147 
    148 	min = cras_client_get_system_min_capture_gain(client);
    149 	max = cras_client_get_system_max_capture_gain(client);
    150 	if (min >= max)
    151 		return 0;
    152 
    153 	dB_step = (max - min) / 100;
    154 
    155 	if (gain <= min)
    156 		return 0;
    157 	if (gain >= max)
    158 		return 100;
    159 	return (gain - min) / dB_step;
    160 }
    161 
    162 static int get_nodes(struct cras_client *client,
    163 		     enum CRAS_STREAM_DIRECTION dir,
    164 		     struct cras_ionode_info *nodes,
    165 		     size_t num_nodes)
    166 {
    167 	struct cras_iodev_info devs[MAX_IODEVS];
    168 	size_t num_devs;
    169 	int rc;
    170 
    171 	if (dir == CRAS_STREAM_OUTPUT)
    172 		rc = cras_client_get_output_devices(client, devs, nodes,
    173 						    &num_devs, &num_nodes);
    174 	else
    175 		rc = cras_client_get_input_devices(client, devs, nodes,
    176 						   &num_devs, &num_nodes);
    177 	if (rc < 0)
    178 		return 0;
    179 	return num_nodes;
    180 }
    181 
    182 /* Gets the value of the given control from CRAS and puts it in value. */
    183 static int ctl_cras_read_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
    184 				 long *value)
    185 {
    186 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
    187 	struct cras_ionode_info nodes[MAX_IONODES];
    188 	int num_nodes, i;
    189 
    190 	switch (key) {
    191 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
    192 		*value = !cras_client_get_user_muted(cras->client);
    193 		break;
    194 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
    195 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT,
    196 				      nodes, MAX_IONODES);
    197 		for (i = 0; i < num_nodes; i++) {
    198 			if (!nodes[i].active)
    199 				continue;
    200 			*value = nodes[i].volume;
    201 			break;
    202 		}
    203 		break;
    204 	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
    205 		*value = !cras_client_get_system_capture_muted(cras->client);
    206 		break;
    207 	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
    208 		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT,
    209 				      nodes, MAX_IONODES);
    210 		for (i = 0; i < num_nodes; i++) {
    211 			if (!nodes[i].active)
    212 				continue;
    213 			*value = capture_gain_to_index(
    214 					cras->client,
    215 					nodes[i].capture_gain);
    216 			break;
    217 		}
    218 		break;
    219 	default:
    220 		return -EINVAL;
    221 	}
    222 
    223 	return 0;
    224 }
    225 
    226 /* Writes the given values to CRAS. */
    227 static int ctl_cras_write_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
    228 				   long *value)
    229 {
    230 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
    231 	struct cras_ionode_info nodes[MAX_IONODES];
    232 	int num_nodes, i;
    233 	long gain;
    234 
    235 	switch (key) {
    236 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
    237 		cras_client_set_user_mute(cras->client, !(*value));
    238 		break;
    239 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
    240 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT,
    241 				      nodes, MAX_IONODES);
    242 		for (i = 0; i < num_nodes; i++) {
    243 			if (!nodes[i].active)
    244 				continue;
    245 			cras_client_set_node_volume(cras->client,
    246 					cras_make_node_id(nodes[i].iodev_idx,
    247 							  nodes[i].ionode_idx),
    248 					*value);
    249 		}
    250 		break;
    251 	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
    252 		cras_client_set_system_capture_mute(cras->client, !(*value));
    253 		break;
    254 	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
    255 		gain = capture_index_to_gain(cras->client, *value);
    256 		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT,
    257 				      nodes, MAX_IONODES);
    258 		for (i = 0; i < num_nodes; i++) {
    259 			if (!nodes[i].active)
    260 				continue;
    261 			cras_client_set_node_capture_gain(cras->client,
    262 					cras_make_node_id(nodes[i].iodev_idx,
    263 							  nodes[i].ionode_idx),
    264 					gain);
    265 		}
    266 		break;
    267 	default:
    268 		return -EINVAL;
    269 	}
    270 
    271 	return 0;
    272 }
    273 
    274 static const snd_ctl_ext_callback_t ctl_cras_ext_callback = {
    275 	.close = ctl_cras_close,
    276 	.elem_count = ctl_cras_elem_count,
    277 	.elem_list = ctl_cras_elem_list,
    278 	.find_elem = ctl_cras_find_elem,
    279 	.get_attribute = ctl_cras_get_attribute,
    280 	.get_integer_info = ctl_cras_get_integer_info,
    281 	.read_integer = ctl_cras_read_integer,
    282 	.write_integer = ctl_cras_write_integer,
    283 };
    284 
    285 SND_CTL_PLUGIN_DEFINE_FUNC(cras)
    286 {
    287 	struct ctl_cras *cras;
    288 	int rc;
    289 
    290 	cras = malloc(sizeof(*cras));
    291 	if (cras == NULL)
    292 		return -ENOMEM;
    293 
    294 	rc = cras_client_create(&cras->client);
    295 	if (rc != 0 || cras->client == NULL) {
    296 		fprintf(stderr, "Couldn't create CRAS client\n");
    297 		free(cras);
    298 		return rc;
    299 	}
    300 
    301 	rc = cras_client_connect(cras->client);
    302 	if (rc < 0) {
    303 		fprintf(stderr, "Couldn't connect to cras.\n");
    304 		cras_client_destroy(cras->client);
    305 		free(cras);
    306 		return rc;
    307 	}
    308 
    309 	rc = cras_client_run_thread(cras->client);
    310 	if (rc < 0) {
    311 		fprintf(stderr, "Couldn't start client thread.\n");
    312 		cras_client_stop(cras->client);
    313 		cras_client_destroy(cras->client);
    314 		free(cras);
    315 		return rc;
    316 	}
    317 
    318 	rc = cras_client_connected_wait(cras->client);
    319 	if (rc < 0) {
    320 		fprintf(stderr, "CRAS client wouldn't connect.\n");
    321 		cras_client_stop(cras->client);
    322 		cras_client_destroy(cras->client);
    323 		free(cras);
    324 		return rc;
    325 	}
    326 
    327 	cras->ext_ctl.version = SND_CTL_EXT_VERSION;
    328 	cras->ext_ctl.card_idx = 0;
    329 	strncpy(cras->ext_ctl.id, "cras", sizeof(cras->ext_ctl.id) - 1);
    330 	cras->ext_ctl.id[sizeof(cras->ext_ctl.id) - 1] = '\0';
    331 	strncpy(cras->ext_ctl.driver, "CRAS plugin",
    332 		sizeof(cras->ext_ctl.driver) - 1);
    333 	cras->ext_ctl.driver[sizeof(cras->ext_ctl.driver) - 1] = '\0';
    334 	strncpy(cras->ext_ctl.name, "CRAS", sizeof(cras->ext_ctl.name) - 1);
    335 	cras->ext_ctl.name[sizeof(cras->ext_ctl.name) - 1] = '\0';
    336 	strncpy(cras->ext_ctl.longname, "CRAS",
    337 		sizeof(cras->ext_ctl.longname) - 1);
    338 	cras->ext_ctl.longname[sizeof(cras->ext_ctl.longname) - 1] = '\0';
    339 	strncpy(cras->ext_ctl.mixername, "CRAS",
    340 		sizeof(cras->ext_ctl.mixername) - 1);
    341 	cras->ext_ctl.mixername[sizeof(cras->ext_ctl.mixername) - 1] = '\0';
    342 	cras->ext_ctl.poll_fd = -1;
    343 
    344 	cras->ext_ctl.callback = &ctl_cras_ext_callback;
    345 	cras->ext_ctl.private_data = cras;
    346 
    347 	rc = snd_ctl_ext_create(&cras->ext_ctl, name, mode);
    348 	if (rc < 0) {
    349 		cras_client_stop(cras->client);
    350 		cras_client_destroy(cras->client);
    351 		free(cras);
    352 		return rc;
    353 	}
    354 
    355 	*handlep = cras->ext_ctl.handle;
    356 	return 0;
    357 }
    358 
    359 SND_CTL_PLUGIN_SYMBOL(cras);
    360