• 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 	NUM_CTL_CRAS_MIXER_ELEMS
18 };
19 
20 /* Hold info specific to each control. */
21 struct cras_mixer_control {
22 	const char *name;
23 	int type;
24 	unsigned int access;
25 	unsigned int count;
26 };
27 
28 /* CRAS mixer elements. */
29 static const struct cras_mixer_control cras_elems[NUM_CTL_CRAS_MIXER_ELEMS] = {
30 	{ "Master Playback Switch", SND_CTL_ELEM_TYPE_BOOLEAN,
31 	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
32 	{ "Master Playback Volume", SND_CTL_ELEM_TYPE_INTEGER,
33 	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
34 };
35 
36 /* Holds the client and ctl plugin pointers. */
37 struct ctl_cras {
38 	snd_ctl_ext_t ext_ctl;
39 	struct cras_client *client;
40 };
41 
42 /* Frees resources when the plugin is closed. */
ctl_cras_close(snd_ctl_ext_t * ext_ctl)43 static void ctl_cras_close(snd_ctl_ext_t *ext_ctl)
44 {
45 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
46 
47 	if (cras) {
48 		cras_client_stop(cras->client);
49 		cras_client_destroy(cras->client);
50 	}
51 	free(cras);
52 }
53 
54 /* Lists available controls. */
ctl_cras_elem_list(snd_ctl_ext_t * ext_ctl,unsigned int offset,snd_ctl_elem_id_t * id)55 static int ctl_cras_elem_list(snd_ctl_ext_t *ext_ctl, unsigned int offset,
56 			      snd_ctl_elem_id_t *id)
57 {
58 	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
59 	if (offset >= NUM_CTL_CRAS_MIXER_ELEMS)
60 		return -EINVAL;
61 	snd_ctl_elem_id_set_name(id, cras_elems[offset].name);
62 	return 0;
63 }
64 
65 /* Returns the number of available controls. */
ctl_cras_elem_count(snd_ctl_ext_t * ext_ctl)66 static int ctl_cras_elem_count(snd_ctl_ext_t *ext_ctl)
67 {
68 	return NUM_CTL_CRAS_MIXER_ELEMS;
69 }
70 
71 /* 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)72 static snd_ctl_ext_key_t ctl_cras_find_elem(snd_ctl_ext_t *ext_ctl,
73 					    const snd_ctl_elem_id_t *id)
74 {
75 	const char *name;
76 	unsigned int numid;
77 
78 	numid = snd_ctl_elem_id_get_numid(id);
79 	if (numid - 1 < NUM_CTL_CRAS_MIXER_ELEMS)
80 		return numid - 1;
81 
82 	name = snd_ctl_elem_id_get_name(id);
83 
84 	for (numid = 0; numid < NUM_CTL_CRAS_MIXER_ELEMS; numid++)
85 		if (strcmp(cras_elems[numid].name, name) == 0)
86 			return numid;
87 
88 	return SND_CTL_EXT_KEY_NOT_FOUND;
89 }
90 
91 /* 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)92 static int ctl_cras_get_attribute(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
93 				  int *type, unsigned int *acc,
94 				  unsigned int *count)
95 {
96 	if (key >= NUM_CTL_CRAS_MIXER_ELEMS)
97 		return -EINVAL;
98 	*type = cras_elems[key].type;
99 	*acc = cras_elems[key].access;
100 	*count = cras_elems[key].count;
101 	return 0;
102 }
103 
104 /* Returns the range of the specified control.  The volume sliders always run
105  * 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)106 static int ctl_cras_get_integer_info(snd_ctl_ext_t *ext_ctl,
107 				     snd_ctl_ext_key_t key, long *imin,
108 				     long *imax, long *istep)
109 {
110 	*istep = 0;
111 	*imin = 0;
112 	*imax = 100;
113 	return 0;
114 }
115 
get_nodes(struct cras_client * client,enum CRAS_STREAM_DIRECTION dir,struct cras_ionode_info * nodes,size_t num_nodes)116 static int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION dir,
117 		     struct cras_ionode_info *nodes, size_t num_nodes)
118 {
119 	struct cras_iodev_info devs[MAX_IODEVS];
120 	size_t num_devs;
121 	int rc;
122 
123 	if (dir == CRAS_STREAM_OUTPUT)
124 		rc = cras_client_get_output_devices(client, devs, nodes,
125 						    &num_devs, &num_nodes);
126 	else
127 		rc = cras_client_get_input_devices(client, devs, nodes,
128 						   &num_devs, &num_nodes);
129 	if (rc < 0)
130 		return 0;
131 	return num_nodes;
132 }
133 
134 /* 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)135 static int ctl_cras_read_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
136 				 long *value)
137 {
138 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
139 	struct cras_ionode_info nodes[MAX_IONODES];
140 	int num_nodes, i;
141 
142 	switch (key) {
143 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
144 		*value = !cras_client_get_user_muted(cras->client);
145 		break;
146 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
147 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
148 				      MAX_IONODES);
149 		for (i = 0; i < num_nodes; i++) {
150 			if (!nodes[i].active)
151 				continue;
152 			*value = nodes[i].volume;
153 			break;
154 		}
155 		break;
156 	default:
157 		return -EINVAL;
158 	}
159 
160 	return 0;
161 }
162 
163 /* Writes the given values to CRAS. */
ctl_cras_write_integer(snd_ctl_ext_t * ext_ctl,snd_ctl_ext_key_t key,long * value)164 static int ctl_cras_write_integer(snd_ctl_ext_t *ext_ctl, snd_ctl_ext_key_t key,
165 				  long *value)
166 {
167 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
168 	struct cras_ionode_info nodes[MAX_IONODES];
169 	int num_nodes, i;
170 
171 	switch (key) {
172 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
173 		cras_client_set_user_mute(cras->client, !(*value));
174 		break;
175 	case CTL_CRAS_MIXER_PLAYBACK_VOLUME:
176 		num_nodes = get_nodes(cras->client, CRAS_STREAM_OUTPUT, nodes,
177 				      MAX_IONODES);
178 		for (i = 0; i < num_nodes; i++) {
179 			if (!nodes[i].active)
180 				continue;
181 			cras_client_set_node_volume(
182 				cras->client,
183 				cras_make_node_id(nodes[i].iodev_idx,
184 						  nodes[i].ionode_idx),
185 				*value);
186 		}
187 		break;
188 	default:
189 		return -EINVAL;
190 	}
191 
192 	return 0;
193 }
194 
195 static const snd_ctl_ext_callback_t ctl_cras_ext_callback = {
196 	.close = ctl_cras_close,
197 	.elem_count = ctl_cras_elem_count,
198 	.elem_list = ctl_cras_elem_list,
199 	.find_elem = ctl_cras_find_elem,
200 	.get_attribute = ctl_cras_get_attribute,
201 	.get_integer_info = ctl_cras_get_integer_info,
202 	.read_integer = ctl_cras_read_integer,
203 	.write_integer = ctl_cras_write_integer,
204 };
205 
SND_CTL_PLUGIN_DEFINE_FUNC(cras)206 SND_CTL_PLUGIN_DEFINE_FUNC(cras)
207 {
208 	struct ctl_cras *cras;
209 	int rc;
210 
211 	cras = malloc(sizeof(*cras));
212 	if (cras == NULL)
213 		return -ENOMEM;
214 
215 	rc = cras_client_create(&cras->client);
216 	if (rc != 0 || cras->client == NULL) {
217 		fprintf(stderr, "Couldn't create CRAS client\n");
218 		free(cras);
219 		return rc;
220 	}
221 
222 	rc = cras_client_connect(cras->client);
223 	if (rc < 0) {
224 		fprintf(stderr, "Couldn't connect to cras.\n");
225 		cras_client_destroy(cras->client);
226 		free(cras);
227 		return rc;
228 	}
229 
230 	rc = cras_client_run_thread(cras->client);
231 	if (rc < 0) {
232 		fprintf(stderr, "Couldn't start client thread.\n");
233 		cras_client_stop(cras->client);
234 		cras_client_destroy(cras->client);
235 		free(cras);
236 		return rc;
237 	}
238 
239 	rc = cras_client_connected_wait(cras->client);
240 	if (rc < 0) {
241 		fprintf(stderr, "CRAS client wouldn't connect.\n");
242 		cras_client_stop(cras->client);
243 		cras_client_destroy(cras->client);
244 		free(cras);
245 		return rc;
246 	}
247 
248 	cras->ext_ctl.version = SND_CTL_EXT_VERSION;
249 	cras->ext_ctl.card_idx = 0;
250 	strncpy(cras->ext_ctl.id, "cras", sizeof(cras->ext_ctl.id) - 1);
251 	cras->ext_ctl.id[sizeof(cras->ext_ctl.id) - 1] = '\0';
252 	strncpy(cras->ext_ctl.driver, "CRAS plugin",
253 		sizeof(cras->ext_ctl.driver) - 1);
254 	cras->ext_ctl.driver[sizeof(cras->ext_ctl.driver) - 1] = '\0';
255 	strncpy(cras->ext_ctl.name, "CRAS", sizeof(cras->ext_ctl.name) - 1);
256 	cras->ext_ctl.name[sizeof(cras->ext_ctl.name) - 1] = '\0';
257 	strncpy(cras->ext_ctl.longname, "CRAS",
258 		sizeof(cras->ext_ctl.longname) - 1);
259 	cras->ext_ctl.longname[sizeof(cras->ext_ctl.longname) - 1] = '\0';
260 	strncpy(cras->ext_ctl.mixername, "CRAS",
261 		sizeof(cras->ext_ctl.mixername) - 1);
262 	cras->ext_ctl.mixername[sizeof(cras->ext_ctl.mixername) - 1] = '\0';
263 	cras->ext_ctl.poll_fd = -1;
264 
265 	cras->ext_ctl.callback = &ctl_cras_ext_callback;
266 	cras->ext_ctl.private_data = cras;
267 
268 	rc = snd_ctl_ext_create(&cras->ext_ctl, name, mode);
269 	if (rc < 0) {
270 		cras_client_stop(cras->client);
271 		cras_client_destroy(cras->client);
272 		free(cras);
273 		return rc;
274 	}
275 
276 	*handlep = cras->ext_ctl.handle;
277 	return 0;
278 }
279 
280 SND_CTL_PLUGIN_SYMBOL(cras);
281