• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, long *imin,
114 				     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, enum CRAS_STREAM_DIRECTION dir,
163 		     struct cras_ionode_info *nodes, size_t num_nodes)
164 {
165 	struct cras_iodev_info devs[MAX_IODEVS];
166 	size_t num_devs;
167 	int rc;
168 
169 	if (dir == CRAS_STREAM_OUTPUT)
170 		rc = cras_client_get_output_devices(client, devs, nodes,
171 						    &num_devs, &num_nodes);
172 	else
173 		rc = cras_client_get_input_devices(client, devs, nodes,
174 						   &num_devs, &num_nodes);
175 	if (rc < 0)
176 		return 0;
177 	return num_nodes;
178 }
179 
180 /* 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)181 static int ctl_cras_read_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
182 				 long *value)
183 {
184 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
185 	struct cras_ionode_info nodes[MAX_IONODES];
186 	int num_nodes, i;
187 
188 	switch (key) {
189 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
190 		*value = !cras_client_get_user_muted(cras->client);
191 		break;
192 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
193 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
194 				      MAX_IONODES);
195 		for (i = 0; i < num_nodes; i++) {
196 			if (!nodes[i].active)
197 				continue;
198 			*value = nodes[i].volume;
199 			break;
200 		}
201 		break;
202 	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
203 		*value = !cras_client_get_system_capture_muted(cras->client);
204 		break;
205 	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
206 		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT, nodes,
207 				      MAX_IONODES);
208 		for (i = 0; i < num_nodes; i++) {
209 			if (!nodes[i].active)
210 				continue;
211 			*value = capture_gain_to_index(cras->client,
212 						       nodes[i].capture_gain);
213 			break;
214 		}
215 		break;
216 	default:
217 		return -EINVAL;
218 	}
219 
220 	return 0;
221 }
222 
223 /* Writes the given values to CRAS. */
ctl_cras_write_integer(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,long * value)224 static int ctl_cras_write_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
225 				  long *value)
226 {
227 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
228 	struct cras_ionode_info nodes[MAX_IONODES];
229 	int num_nodes, i;
230 	long gain;
231 
232 	switch (key) {
233 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
234 		cras_client_set_user_mute(cras->client, !(*value));
235 		break;
236 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
237 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
238 				      MAX_IONODES);
239 		for (i = 0; i < num_nodes; i++) {
240 			if (!nodes[i].active)
241 				continue;
242 			cras_client_set_node_volume(
243 				cras->client,
244 				cras_make_node_id(nodes[i].iodev_idx,
245 						  nodes[i].ionode_idx),
246 				*value);
247 		}
248 		break;
249 	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
250 		cras_client_set_system_capture_mute(cras->client, !(*value));
251 		break;
252 	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
253 		gain = capture_index_to_gain(cras->client, *value);
254 		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT, nodes,
255 				      MAX_IONODES);
256 		for (i = 0; i < num_nodes; i++) {
257 			if (!nodes[i].active)
258 				continue;
259 			cras_client_set_node_capture_gain(
260 				cras->client,
261 				cras_make_node_id(nodes[i].iodev_idx,
262 						  nodes[i].ionode_idx),
263 				gain);
264 		}
265 		break;
266 	default:
267 		return -EINVAL;
268 	}
269 
270 	return 0;
271 }
272 
273 static const snd_ctl_ext_callback_t ctl_cras_ext_callback = {
274 	.close = ctl_cras_close,
275 	.elem_count = ctl_cras_elem_count,
276 	.elem_list = ctl_cras_elem_list,
277 	.find_elem = ctl_cras_find_elem,
278 	.get_attribute = ctl_cras_get_attribute,
279 	.get_integer_info = ctl_cras_get_integer_info,
280 	.read_integer = ctl_cras_read_integer,
281 	.write_integer = ctl_cras_write_integer,
282 };
283 
SND_CTL_PLUGIN_DEFINE_FUNC(cras)284 SND_CTL_PLUGIN_DEFINE_FUNC(cras)
285 {
286 	struct ctl_cras *cras;
287 	int rc;
288 
289 	cras = malloc(sizeof(*cras));
290 	if (cras == NULL)
291 		return -ENOMEM;
292 
293 	rc = cras_client_create(&cras->client);
294 	if (rc != 0 || cras->client == NULL) {
295 		fprintf(stderr, "Couldn't create CRAS client\n");
296 		free(cras);
297 		return rc;
298 	}
299 
300 	rc = cras_client_connect(cras->client);
301 	if (rc < 0) {
302 		fprintf(stderr, "Couldn't connect to cras.\n");
303 		cras_client_destroy(cras->client);
304 		free(cras);
305 		return rc;
306 	}
307 
308 	rc = cras_client_run_thread(cras->client);
309 	if (rc < 0) {
310 		fprintf(stderr, "Couldn't start client thread.\n");
311 		cras_client_stop(cras->client);
312 		cras_client_destroy(cras->client);
313 		free(cras);
314 		return rc;
315 	}
316 
317 	rc = cras_client_connected_wait(cras->client);
318 	if (rc < 0) {
319 		fprintf(stderr, "CRAS client wouldn't connect.\n");
320 		cras_client_stop(cras->client);
321 		cras_client_destroy(cras->client);
322 		free(cras);
323 		return rc;
324 	}
325 
326 	cras->ext_ctl.version = SND_CTL_EXT_VERSION;
327 	cras->ext_ctl.card_idx = 0;
328 	strncpy(cras->ext_ctl.id, "cras", sizeof(cras->ext_ctl.id) - 1);
329 	cras->ext_ctl.id[sizeof(cras->ext_ctl.id) - 1] = '\0';
330 	strncpy(cras->ext_ctl.driver, "CRAS plugin",
331 		sizeof(cras->ext_ctl.driver) - 1);
332 	cras->ext_ctl.driver[sizeof(cras->ext_ctl.driver) - 1] = '\0';
333 	strncpy(cras->ext_ctl.name, "CRAS", sizeof(cras->ext_ctl.name) - 1);
334 	cras->ext_ctl.name[sizeof(cras->ext_ctl.name) - 1] = '\0';
335 	strncpy(cras->ext_ctl.longname, "CRAS",
336 		sizeof(cras->ext_ctl.longname) - 1);
337 	cras->ext_ctl.longname[sizeof(cras->ext_ctl.longname) - 1] = '\0';
338 	strncpy(cras->ext_ctl.mixername, "CRAS",
339 		sizeof(cras->ext_ctl.mixername) - 1);
340 	cras->ext_ctl.mixername[sizeof(cras->ext_ctl.mixername) - 1] = '\0';
341 	cras->ext_ctl.poll_fd = -1;
342 
343 	cras->ext_ctl.callback = &ctl_cras_ext_callback;
344 	cras->ext_ctl.private_data = cras;
345 
346 	rc = snd_ctl_ext_create(&cras->ext_ctl, name, mode);
347 	if (rc < 0) {
348 		cras_client_stop(cras->client);
349 		cras_client_destroy(cras->client);
350 		free(cras);
351 		return rc;
352 	}
353 
354 	*handlep = cras->ext_ctl.handle;
355 	return 0;
356 }
357 
358 SND_CTL_PLUGIN_SYMBOL(cras);
359