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. */
ctl_cras_close(snd_ctl_ext_t * ext_ctl)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. */
ctl_cras_elem_list(snd_ctl_ext_t * ext_ctl,unsigned int offset,snd_ctl_elem_id_t * id)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. */
ctl_cras_elem_count(snd_ctl_ext_t * ext_ctl)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. */
ctl_cras_find_elem(snd_ctl_ext_t * ext_ctl,const snd_ctl_elem_id_t * 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. */
ctl_cras_get_attribute(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,int * type,unsigned int * acc,unsigned int * count)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. */
ctl_cras_get_integer_info(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,long * imin,long * imax,long * istep)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
capture_index_to_gain(struct cras_client * client,long index)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
capture_gain_to_index(struct cras_client * client,long gain)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
get_nodes(struct cras_client * client,enum CRAS_STREAM_DIRECTION dir,struct cras_ionode_info * nodes,size_t num_nodes)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. */
ctl_cras_read_integer(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,long * 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. */
ctl_cras_write_integer(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,long * value)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
SND_CTL_PLUGIN_DEFINE_FUNC(cras)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