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