xref: /aosp_15_r20/external/tinyalsa_new/src/mixer.c (revision 02e95f1a335b55495d41ca67eaf42361f13704fa)
1 /* mixer.c
2 **
3 ** Copyright 2011, The Android Open Source Project
4 **
5 ** Redistribution and use in source and binary forms, with or without
6 ** modification, are permitted provided that the following conditions are met:
7 **     * Redistributions of source code must retain the above copyright
8 **       notice, this list of conditions and the following disclaimer.
9 **     * Redistributions in binary form must reproduce the above copyright
10 **       notice, this list of conditions and the following disclaimer in the
11 **       documentation and/or other materials provided with the distribution.
12 **     * Neither the name of The Android Open Source Project nor the names of
13 **       its contributors may be used to endorse or promote products derived
14 **       from this software without specific prior written permission.
15 **
16 ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
17 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
20 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 ** DAMAGE.
27 */
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdint.h>
32 #include <stdbool.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <limits.h>
39 #include <time.h>
40 #include <poll.h>
41 
42 #include <sys/ioctl.h>
43 
44 #include <linux/ioctl.h>
45 
46 #ifndef __force
47 #define __force
48 #endif
49 
50 #ifndef __bitwise
51 #define __bitwise
52 #endif
53 
54 #ifndef __user
55 #define __user
56 #endif
57 
58 #include <time.h>
59 #include <sound/asound.h>
60 
61 #include <tinyalsa/mixer.h>
62 #include <tinyalsa/plugin.h>
63 
64 #include "mixer_io.h"
65 
66 /** A mixer control.
67  * @ingroup libtinyalsa-mixer
68  */
69 struct mixer_ctl {
70     /** The mixer that the mixer control belongs to */
71     struct mixer *mixer;
72     /** Information on the control's value (i.e. type, number of values) */
73     struct snd_ctl_elem_info info;
74     /** A list of string representations of enumerated values (only valid for enumerated controls) */
75     char **ename;
76     /** Pointer to the group that the control belongs to */
77     struct mixer_ctl_group *grp;
78 };
79 
80 struct mixer_ctl_group {
81     /** A continuous array of mixer controls */
82     struct mixer_ctl *ctl;
83     /** The number of mixer controls */
84     unsigned int count;
85     /** The number of events associated with this group */
86     unsigned int event_cnt;
87     /** The operations corresponding to this group */
88     const struct mixer_ops *ops;
89     /** Private data for storing group specific data */
90     void *data;
91 };
92 
93 /** A mixer handle.
94  * @ingroup libtinyalsa-mixer
95  */
96 struct mixer {
97     /** File descriptor for the card */
98     int fd;
99     /** Card information */
100     struct snd_ctl_card_info card_info;
101     /* Hardware (kernel interface) mixer control group */
102     struct mixer_ctl_group *h_grp;
103     /* Virtual (Plugin interface) mixer control group */
104     struct mixer_ctl_group *v_grp;
105     /* Total count of mixer controls from both  groups */
106     unsigned int total_count;
107     /* Flag to track if card information is already retrieved */
108     bool is_card_info_retrieved;
109 
110 };
111 
mixer_cleanup_control(struct mixer_ctl * ctl)112 static void mixer_cleanup_control(struct mixer_ctl *ctl)
113 {
114     unsigned int m;
115 
116     if (ctl->ename) {
117         unsigned int max = ctl->info.value.enumerated.items;
118         for (m = 0; m < max; m++)
119             free(ctl->ename[m]);
120         free(ctl->ename);
121     }
122 }
123 
mixer_grp_close(struct mixer * mixer,struct mixer_ctl_group * grp)124 static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp)
125 {
126     unsigned int n;
127 
128     if (!grp)
129         return;
130 
131     if (grp->ctl) {
132         for (n = 0; n < grp->count; n++)
133             mixer_cleanup_control(&grp->ctl[n]);
134         free(grp->ctl);
135     }
136 
137     free(grp);
138 
139     mixer->is_card_info_retrieved = false;
140 }
141 
142 /** Closes a mixer returned by @ref mixer_open.
143  * @param mixer A mixer handle.
144  * @ingroup libtinyalsa-mixer
145  */
mixer_close(struct mixer * mixer)146 void mixer_close(struct mixer *mixer)
147 {
148     if (!mixer)
149         return;
150 
151     if (mixer->fd >= 0 && mixer->h_grp)
152         mixer->h_grp->ops->close(mixer->h_grp->data);
153     mixer_grp_close(mixer, mixer->h_grp);
154 
155 #ifdef TINYALSA_USES_PLUGINS
156     if (mixer->v_grp)
157         mixer->v_grp->ops->close(mixer->v_grp->data);
158     mixer_grp_close(mixer, mixer->v_grp);
159 #endif
160 
161     free(mixer);
162 
163     /* TODO: verify frees */
164 }
165 
mixer_realloc_z(void * ptr,size_t curnum,size_t newnum,size_t size)166 static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t size)
167 {
168         int8_t *newp;
169 
170         newp = realloc(ptr, size * newnum);
171         if (!newp)
172             return NULL;
173 
174         memset(newp + (curnum * size), 0, (newnum - curnum) * size);
175         return newp;
176 }
177 
add_controls(struct mixer * mixer,struct mixer_ctl_group * grp)178 static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp)
179 {
180     struct snd_ctl_elem_list elist;
181     struct snd_ctl_elem_id *eid = NULL;
182     struct mixer_ctl *ctl;
183     const unsigned int old_count = grp->count;
184     unsigned int new_count;
185     unsigned int n;
186 
187     memset(&elist, 0, sizeof(elist));
188     if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
189         goto fail;
190 
191     if (old_count == elist.count)
192         return 0; /* no new controls return unchanged */
193 
194     if (old_count > elist.count)
195         return -1; /* driver has removed controls - this is bad */
196 
197     ctl = mixer_realloc_z(grp->ctl, old_count, elist.count,
198                           sizeof(struct mixer_ctl));
199     if (!ctl)
200         goto fail;
201 
202     grp->ctl = ctl;
203 
204     /* ALSA drivers are not supposed to remove or re-order controls that
205      * have already been created so we know that any new controls must
206      * be after the ones we have already collected
207      */
208     new_count = elist.count;
209     elist.space = new_count - old_count; /* controls we haven't seen before */
210     elist.offset = old_count; /* first control we haven't seen */
211 
212     eid = calloc(elist.space, sizeof(struct snd_ctl_elem_id));
213     if (!eid)
214         goto fail;
215 
216     elist.pids = eid;
217 
218     if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
219         goto fail;
220 
221     for (n = old_count; n < new_count; n++) {
222         struct snd_ctl_elem_info *ei = &grp->ctl[n].info;
223         ei->id.numid = eid[n - old_count].numid;
224         if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
225             goto fail_extend;
226         ctl[n].mixer = mixer;
227         ctl[n].grp = grp;
228     }
229 
230     grp->count = new_count;
231     mixer->total_count += (new_count - old_count);
232 
233     free(eid);
234     return 0;
235 
236 fail_extend:
237     /* cleanup the control we failed on but leave the ones that were already
238      * added. Also no advantage to shrinking the resized memory block, we
239      * might want to extend the controls again later
240      */
241     mixer_cleanup_control(&ctl[n]);
242 
243     grp->count = n;   /* keep controls we successfully added */
244     mixer->total_count += (n - old_count);
245     /* fall through... */
246 fail:
247     free(eid);
248     return -1;
249 }
250 
mixer_grp_open(struct mixer * mixer,unsigned int card,bool is_hw)251 static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw)
252 {
253     struct mixer_ctl_group *grp = NULL;
254     const struct mixer_ops *ops = NULL;
255     void *data = NULL;
256     int fd, ret;
257 
258     grp = calloc(1, sizeof(*grp));
259     if (!grp)
260         return -ENOMEM;
261 
262     if (is_hw) {
263         mixer->fd = -1;
264         fd = mixer_hw_open(card, &data, &ops);
265         if (fd < 0) {
266             ret = fd;
267             goto err_open;
268         }
269         mixer->fd = fd;
270         mixer->h_grp = grp;
271     }
272 #ifdef TINYALSA_USES_PLUGINS
273     else {
274         ret = mixer_plugin_open(card, &data, &ops);
275         if (ret < 0)
276             goto err_open;
277         mixer->v_grp = grp;
278     }
279 #endif
280     grp->ops = ops;
281     grp->data = data;
282 
283     if (!mixer->is_card_info_retrieved) {
284         ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO,
285                               &mixer->card_info);
286         if (ret < 0)
287             goto err_card_info;
288         mixer->is_card_info_retrieved = true;
289     }
290 
291     ret = add_controls(mixer, grp);
292     if (ret < 0)
293         goto err_card_info;
294 
295     return 0;
296 
297 err_card_info:
298     grp->ops->close(grp->data);
299 
300 err_open:
301     free(grp);
302     return ret;
303 
304 }
305 
306 /** Opens a mixer for a given card.
307  * @param card The card to open the mixer for.
308  * @returns An initialized mixer handle.
309  * @ingroup libtinyalsa-mixer
310  */
mixer_open(unsigned int card)311 struct mixer *mixer_open(unsigned int card)
312 {
313     struct mixer *mixer = NULL;
314     int h_status, v_status = -1;
315 
316     mixer = calloc(1, sizeof(*mixer));
317     if (!mixer)
318         goto fail;
319 
320     h_status = mixer_grp_open(mixer, card, true);
321 
322 #ifdef TINYALSA_USES_PLUGINS
323     v_status = mixer_grp_open(mixer, card, false);
324 #endif
325 
326     /* both hw and virtual should fail for mixer_open to fail */
327     if (h_status < 0 && v_status < 0)
328         goto fail;
329 
330     return mixer;
331 
332 fail:
333     if (mixer)
334         mixer_close(mixer);
335 
336     return NULL;
337 }
338 
339 /** Some controls may not be present at boot time, e.g. controls from runtime
340  * loadable DSP firmware. This function adds any new controls that have appeared
341  * since mixer_open() or the last call to this function. This assumes a well-
342  * behaved codec driver that does not delete controls that already exists, so
343  * any added controls must be after the last one we already saw. Scanning only
344  * the new controls is much faster than calling mixer_close() then mixer_open()
345  * to re-scan all controls.
346  *
347  * NOTE: this invalidates any struct mixer_ctl pointers previously obtained
348  * from mixer_get_ctl() and mixer_get_ctl_by_name(). Either refresh all your
349  * stored pointers after calling mixer_update_ctls(), or (better) do not
350  * store struct mixer_ctl pointers, instead lookup the control by name or
351  * id only when you are about to use it. The overhead of lookup by id
352  * using mixer_get_ctl() is negligible.
353  * @param mixer An initialized mixer handle.
354  * @returns 0 on success, -1 on failure
355  */
mixer_add_new_ctls(struct mixer * mixer)356 int mixer_add_new_ctls(struct mixer *mixer)
357 {
358     int rc1 = 0, rc2 = 0;
359 
360     if (!mixer)
361         return 0;
362 
363     /* add the h_grp controls */
364     if (mixer->h_grp)
365         rc1 = add_controls(mixer, mixer->h_grp);
366 
367 #ifdef TINYALSA_USES_PLUGINS
368     /* add the v_grp controls */
369     if (mixer->v_grp)
370         rc2 = add_controls(mixer, mixer->v_grp);
371 #endif
372 
373     if (rc1 < 0)
374         return rc1;
375     if (rc2 < 0)
376         return rc2;
377 
378     return 0;
379 }
380 
381 /** Gets the name of the mixer's card.
382  * @param mixer An initialized mixer handle.
383  * @returns The name of the mixer's card.
384  * @ingroup libtinyalsa-mixer
385  */
mixer_get_name(const struct mixer * mixer)386 const char *mixer_get_name(const struct mixer *mixer)
387 {
388     if (!mixer) {
389         return NULL;
390     }
391 
392     return (const char *)mixer->card_info.name;
393 }
394 
395 /** Gets the number of mixer controls for a given mixer.
396  * @param mixer An initialized mixer handle.
397  * @returns The number of mixer controls for the given mixer.
398  * @ingroup libtinyalsa-mixer
399  */
mixer_get_num_ctls(const struct mixer * mixer)400 unsigned int mixer_get_num_ctls(const struct mixer *mixer)
401 {
402     if (!mixer)
403         return 0;
404 
405     return mixer->total_count;
406 }
407 
408 /** Gets the number of mixer controls, that go by a specified name, for a given mixer.
409  * @param mixer An initialized mixer handle.
410  * @param name The name of the mixer control
411  * @returns The number of mixer controls, specified by @p name, for the given mixer.
412  * @ingroup libtinyalsa-mixer
413  */
mixer_get_num_ctls_by_name(const struct mixer * mixer,const char * name)414 unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name)
415 {
416     struct mixer_ctl_group *grp;
417     unsigned int n;
418     unsigned int count = 0;
419     struct mixer_ctl *ctl;
420 
421     if (!mixer || !name) {
422         return 0;
423     }
424 
425     if (mixer->h_grp) {
426         grp = mixer->h_grp;
427         ctl = grp->ctl;
428 
429         for (n = 0; n < grp->count; n++)
430             if (!strcmp(name, (char*) ctl[n].info.id.name))
431                 count++;
432     }
433 #ifdef TINYALSA_USES_PLUGINS
434     if (mixer->v_grp) {
435         grp = mixer->v_grp;
436         ctl = grp->ctl;
437 
438         for (n = 0; n < grp->count; n++)
439             if (!strcmp(name, (char*) ctl[n].info.id.name))
440                 count++;
441     }
442 #endif
443 
444     return count;
445 }
446 
447 /** Subscribes for the mixer events.
448  * @param mixer A mixer handle.
449  * @param subscribe value indicating subscribe or unsubscribe for events
450  * @returns On success, zero.
451  *  On failure, non-zero.
452  * @ingroup libtinyalsa-mixer
453  */
mixer_subscribe_events(struct mixer * mixer,int subscribe)454 int mixer_subscribe_events(struct mixer *mixer, int subscribe)
455 {
456     struct mixer_ctl_group *grp;
457 
458     if (!mixer) {
459         return -EINVAL;
460     }
461 
462     if (mixer->h_grp) {
463         grp = mixer->h_grp;
464         if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
465             return -1;
466     }
467 
468 #ifdef TINYALSA_USES_PLUGINS
469     if (mixer->v_grp) {
470         grp = mixer->v_grp;
471         if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
472             return -1;
473     }
474 #endif
475     return 0;
476 }
477 
478 /** Wait for mixer events.
479  * @param mixer A mixer handle.
480  * @param timeout timout value
481  * @returns On success, 1.
482  *  On failure, -errno.
483  *  On timeout, 0
484  * @ingroup libtinyalsa-mixer
485  */
mixer_wait_event(struct mixer * mixer,int timeout)486 int mixer_wait_event(struct mixer *mixer, int timeout)
487 {
488     struct pollfd *pfd;
489     struct mixer_ctl_group *grp;
490     int count = 0, num_fds = 0, i, ret = 0;
491 
492     if (!mixer) {
493         return -EINVAL;
494     }
495 
496     if (mixer->fd >= 0)
497         num_fds++;
498 
499 #ifdef TINYALSA_USES_PLUGINS
500     if (mixer->v_grp)
501         num_fds++;
502 #endif
503 
504     pfd = (struct pollfd *)calloc(num_fds, sizeof(struct pollfd));
505     if (!pfd)
506         return -ENOMEM;
507 
508     if (mixer->fd >= 0) {
509         pfd[count].fd = mixer->fd;
510         pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
511         count++;
512     }
513 
514 #ifdef TINYALSA_USES_PLUGINS
515     if (mixer->v_grp) {
516         grp = mixer->v_grp;
517         if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
518             pfd[count].events = POLLIN | POLLERR | POLLNVAL;
519             count++;
520         }
521     }
522 #endif
523 
524     if (!count)
525         goto exit;
526 
527     for (;;) {
528         int err;
529         err = poll(pfd, count, timeout);
530         if (err < 0) {
531             ret = -errno;
532             goto exit;
533         }
534         if (!err)
535             goto exit;
536 
537         for (i = 0; i < count; i++) {
538             if (pfd[i].revents & (POLLERR | POLLNVAL)) {
539                 ret = -EIO;
540                 goto exit;
541             }
542             if (pfd[i].revents & (POLLIN | POLLOUT)) {
543                 if ((i == 0) && mixer->fd >= 0) {
544                     grp = mixer->h_grp;
545                     grp->event_cnt++;
546                 }
547 #ifdef TINYALSA_USES_PLUGINS
548                  else {
549                     grp = mixer->v_grp;
550                     grp->event_cnt++;
551                 }
552 #endif
553                 ret = 1;
554                 goto exit;
555             }
556         }
557     }
558 exit:
559     free(pfd);
560     return ret;
561 }
562 
563 /** Consume a mixer event.
564  * If mixer_subscribe_events has been called,
565  * mixer_wait_event will identify when a control value has changed.
566  * This function will clear a single event from the mixer so that
567  * further events can be alerted.
568  *
569  * @param mixer A mixer handle.
570  * @returns 1 on success. 0, if no pending event. -errno on failure.
571  * @ingroup libtinyalsa-mixer
572  */
mixer_consume_event(struct mixer * mixer)573 int mixer_consume_event(struct mixer *mixer)
574 {
575     struct mixer_ctl_event ev;
576     if (!mixer) {
577         return -EINVAL;
578     }
579 
580     return mixer_read_event(mixer, &ev);
581 }
582 
583 /** Read a mixer control event.
584  * Try to read an control event from mixer.
585  *
586  * @param mixer A mixer handle.
587  * @param event Output parameter. If there is an event read form the mixer, this function will fill
588  * the event data into it.
589  * @returns 1 on success. 0, if no pending event. -errno on failure.
590  * @ingroup libtinyalsa-mixer
591  */
mixer_read_event(struct mixer * mixer,struct mixer_ctl_event * event)592 int mixer_read_event(struct mixer *mixer, struct mixer_ctl_event *event)
593 {
594     struct mixer_ctl_group *grp = NULL;
595     struct snd_ctl_event ev;
596     ssize_t bytes = 0;
597 
598     if (!mixer || !event) {
599         return -EINVAL;
600     }
601 
602     if (mixer->h_grp) {
603         if (mixer->h_grp->event_cnt > 0) {
604             grp = mixer->h_grp;
605         }
606     }
607 #ifdef TINYALSA_USES_PLUGINS
608     if (mixer->v_grp) {
609         if (mixer->v_grp->event_cnt > 0) {
610             grp = mixer->v_grp;
611         }
612     }
613 #endif
614     if (grp) {
615         grp->event_cnt--;
616         bytes = grp->ops->read_event(grp->data, &ev, sizeof(ev));
617 
618         if (bytes < 0) {
619             return -errno;
620         }
621 
622         if (bytes == sizeof(*event)) {
623             memcpy(event, &ev, sizeof(*event));
624             return 1;
625         }
626     }
627 
628     return 0;
629 }
630 
mixer_grp_get_count(struct mixer_ctl_group * grp)631 static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp)
632 {
633     if (!grp)
634         return 0;
635 
636     return grp->count;
637 }
638 
639 /** Gets a mixer control handle, by the mixer control's id.
640  * For non-const access, see @ref mixer_get_ctl
641  * @param mixer An initialized mixer handle.
642  * @param id The control's id in the given mixer.
643  * @returns A handle to the mixer control.
644  * @ingroup libtinyalsa-mixer
645  */
mixer_get_ctl_const(const struct mixer * mixer,unsigned int id)646 const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id)
647 {
648     unsigned int h_count;
649 
650     if (!mixer || (id >= mixer->total_count))
651         return NULL;
652 
653     h_count = mixer_grp_get_count(mixer->h_grp);
654 
655     if (id < h_count)
656         return mixer->h_grp->ctl + id;
657 #ifdef TINYALSA_USES_PLUGINS
658     else {
659         unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
660 	    if ((id - h_count) < v_count)
661             return mixer->v_grp->ctl + (id - h_count);
662     }
663 #endif
664 
665     return NULL;
666 }
667 
668 /** Gets a mixer control handle, by the mixer control's id.
669  * For const access, see @ref mixer_get_ctl_const
670  * @param mixer An initialized mixer handle.
671  * @param id The control's id in the given mixer.
672  * @returns A handle to the mixer control.
673  * @ingroup libtinyalsa-mixer
674  */
mixer_get_ctl(struct mixer * mixer,unsigned int id)675 struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
676 {
677     unsigned int h_count;
678 
679     if (!mixer || (id >= mixer->total_count))
680         return NULL;
681 
682     h_count = mixer_grp_get_count(mixer->h_grp);
683 
684     if (id < h_count)
685         return mixer->h_grp->ctl + id;
686 #ifdef TINYALSA_USES_PLUGINS
687     else {
688         unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
689 	    if ((id - h_count) < v_count)
690             return mixer->v_grp->ctl + (id - h_count);
691     }
692 #endif
693     return NULL;
694 }
695 
696 /** Gets the first instance of mixer control handle, by the mixer control's name.
697  * @param mixer An initialized mixer handle.
698  * @param name The control's name in the given mixer.
699  * @returns A handle to the mixer control.
700  * @ingroup libtinyalsa-mixer
701  */
mixer_get_ctl_by_name(struct mixer * mixer,const char * name)702 struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
703 {
704     if (!mixer || !name) {
705         return NULL;
706     }
707 
708     return mixer_get_ctl_by_name_and_index(mixer, name, 0);
709 }
710 
711 /** Gets an instance of mixer control handle, by the mixer control's name and index.
712  *  For instance, if two controls have the name of 'Volume', then and index of 1 would return the second control.
713  * @param mixer An initialized mixer handle.
714  * @param name The control's name in the given mixer.
715  * @param index The control's index.
716  * @returns A handle to the mixer control.
717  * @ingroup libtinyalsa-mixer
718  */
mixer_get_ctl_by_name_and_index(struct mixer * mixer,const char * name,unsigned int index)719 struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
720                                                   const char *name,
721                                                   unsigned int index)
722 {
723     struct mixer_ctl_group *grp;
724     unsigned int n;
725     struct mixer_ctl *ctl;
726 
727     if (!mixer || !name) {
728         return NULL;
729     }
730 
731     if (mixer->h_grp) {
732         grp = mixer->h_grp;
733         ctl = grp->ctl;
734 
735         for (n = 0; n < grp->count; n++)
736             if (!strcmp(name, (char*) ctl[n].info.id.name)) {
737                 if (index == 0) {
738                     return ctl + n;
739                 } else {
740                     index--;
741                 }
742             }
743     }
744 
745 #ifdef TINYALSA_USES_PLUGINS
746     if (mixer->v_grp) {
747         grp = mixer->v_grp;
748         ctl = grp->ctl;
749 
750         for (n = 0; n < grp->count; n++)
751             if (!strcmp(name, (char*) ctl[n].info.id.name)) {
752                 if (index == 0) {
753                     return ctl + n;
754                 } else {
755                     index--;
756                 }
757             }
758     }
759 #endif
760     return NULL;
761 }
762 
763 /** Gets an instance of mixer control handle, by the mixer control's name and device.
764  *  For instance, if two controls have same name,
765  *  e.g. 'Playback Channel map', then PCM device returns the specific control.
766  * @param mixer An initialized mixer handle.
767  * @param name The control's name in the given mixer.
768  * @param device The PCM device
769  * @returns A handle to the mixer control.
770  * @ingroup libtinyalsa-mixer
771  */
mixer_get_ctl_by_name_and_device(struct mixer * mixer,const char * name,unsigned int device)772 struct mixer_ctl *mixer_get_ctl_by_name_and_device(struct mixer *mixer,
773                                                    const char *name,
774                                                    unsigned int device)
775 {
776     struct mixer_ctl_group *grp;
777     unsigned int n;
778     struct mixer_ctl *ctl;
779 
780     if (!mixer || !name) {
781         return NULL;
782     }
783 
784     if (mixer->h_grp) {
785         grp = mixer->h_grp;
786         ctl = grp->ctl;
787 
788         for (n = 0; n < grp->count; n++) {
789             if (!strcmp(name, (char*) ctl[n].info.id.name) &&
790                     device == ctl[n].info.id.device) {
791                 return ctl + n;
792             }
793         }
794     }
795 
796 #ifdef TINYALSA_USES_PLUGINS
797     if (mixer->v_grp) {
798         grp = mixer->v_grp;
799         ctl = grp->ctl;
800 
801         for (n = 0; n < grp->count; n++) {
802             if (!strcmp(name, (char*) ctl[n].info.id.name) &&
803                     device == ctl[n].info.id.device) {
804                 return ctl + n;
805             }
806         }
807     }
808 #endif
809     return NULL;
810 }
811 
812 /** Updates the control's info.
813  * This is useful for a program that may be idle for a period of time.
814  * @param ctl An initialized control handle.
815  * @ingroup libtinyalsa-mixer
816  */
mixer_ctl_update(struct mixer_ctl * ctl)817 void mixer_ctl_update(struct mixer_ctl *ctl)
818 {
819     struct mixer_ctl_group *grp;
820 
821     if (!ctl)
822         return;
823 
824     grp  = ctl->grp;
825     grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info);
826 }
827 
828 /** Checks the control for TLV Read/Write access.
829  * @param ctl An initialized control handle.
830  * @returns On success, non-zero.
831  *  On failure, zero.
832  * @ingroup libtinyalsa-mixer
833  */
mixer_ctl_is_access_tlv_rw(const struct mixer_ctl * ctl)834 int mixer_ctl_is_access_tlv_rw(const struct mixer_ctl *ctl)
835 {
836     if (!ctl) {
837         return 0;
838     }
839 
840     return (ctl->info.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE);
841 }
842 
843 /** Gets the control's ID.
844  * @param ctl An initialized control handle.
845  * @returns On success, the control's ID is returned.
846  *  On error, UINT_MAX is returned instead.
847  * @ingroup libtinyalsa-mixer
848  */
mixer_ctl_get_id(const struct mixer_ctl * ctl)849 unsigned int mixer_ctl_get_id(const struct mixer_ctl *ctl)
850 {
851     if (!ctl)
852         return UINT_MAX;
853 
854     /* numid values start at 1, return a 0-base value that
855      * can be passed to mixer_get_ctl()
856      */
857     return ctl->info.id.numid - 1;
858 }
859 
860 /** Gets the name of the control.
861  * @param ctl An initialized control handle.
862  * @returns On success, the name of the control.
863  *  On error, NULL.
864  * @ingroup libtinyalsa-mixer
865  */
mixer_ctl_get_name(const struct mixer_ctl * ctl)866 const char *mixer_ctl_get_name(const struct mixer_ctl *ctl)
867 {
868     if (!ctl)
869         return NULL;
870 
871     return (const char *)ctl->info.id.name;
872 }
873 
mixer_ctl_get_device(const struct mixer_ctl * ctl)874 unsigned int mixer_ctl_get_device(const struct mixer_ctl *ctl)
875 {
876     if (!ctl)
877         return UINT_MAX;
878 
879     return ctl->info.id.device;
880 }
881 
882 /** Gets the value type of the control.
883  * @param ctl An initialized control handle
884  * @returns On success, the type of mixer control.
885  *  On failure, it returns @ref MIXER_CTL_TYPE_UNKNOWN
886  * @ingroup libtinyalsa-mixer
887  */
mixer_ctl_get_type(const struct mixer_ctl * ctl)888 enum mixer_ctl_type mixer_ctl_get_type(const struct mixer_ctl *ctl)
889 {
890     if (!ctl)
891         return MIXER_CTL_TYPE_UNKNOWN;
892 
893     switch (ctl->info.type) {
894     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:    return MIXER_CTL_TYPE_BOOL;
895     case SNDRV_CTL_ELEM_TYPE_INTEGER:    return MIXER_CTL_TYPE_INT;
896     case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM;
897     case SNDRV_CTL_ELEM_TYPE_BYTES:      return MIXER_CTL_TYPE_BYTE;
898     case SNDRV_CTL_ELEM_TYPE_IEC958:     return MIXER_CTL_TYPE_IEC958;
899     case SNDRV_CTL_ELEM_TYPE_INTEGER64:  return MIXER_CTL_TYPE_INT64;
900     default:                             return MIXER_CTL_TYPE_UNKNOWN;
901     };
902 }
903 
904 /** Gets the string that describes the value type of the control.
905  * @param ctl An initialized control handle
906  * @returns On success, a string describing type of mixer control.
907  * @ingroup libtinyalsa-mixer
908  */
mixer_ctl_get_type_string(const struct mixer_ctl * ctl)909 const char *mixer_ctl_get_type_string(const struct mixer_ctl *ctl)
910 {
911     if (!ctl)
912         return "";
913 
914     switch (ctl->info.type) {
915     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:    return "BOOL";
916     case SNDRV_CTL_ELEM_TYPE_INTEGER:    return "INT";
917     case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM";
918     case SNDRV_CTL_ELEM_TYPE_BYTES:      return "BYTE";
919     case SNDRV_CTL_ELEM_TYPE_IEC958:     return "IEC958";
920     case SNDRV_CTL_ELEM_TYPE_INTEGER64:  return "INT64";
921     default:                             return "Unknown";
922     };
923 }
924 
925 /** Gets the number of values in the control.
926  * @param ctl An initialized control handle
927  * @returns The number of values in the mixer control
928  * @ingroup libtinyalsa-mixer
929  */
mixer_ctl_get_num_values(const struct mixer_ctl * ctl)930 unsigned int mixer_ctl_get_num_values(const struct mixer_ctl *ctl)
931 {
932     if (!ctl)
933         return 0;
934 
935     return ctl->info.count;
936 }
937 
percent_to_int(const struct snd_ctl_elem_info * ei,int percent)938 static int percent_to_int(const struct snd_ctl_elem_info *ei, int percent)
939 {
940     if ((percent > 100) || (percent < 0)) {
941         return -EINVAL;
942     }
943 
944     int range = (ei->value.integer.max - ei->value.integer.min);
945 
946     return ei->value.integer.min + (range * percent) / 100;
947 }
948 
int_to_percent(const struct snd_ctl_elem_info * ei,int value)949 static int int_to_percent(const struct snd_ctl_elem_info *ei, int value)
950 {
951     int range = (ei->value.integer.max - ei->value.integer.min);
952 
953     if (range == 0)
954         return 0;
955 
956     return ((value - ei->value.integer.min) * 100) / range;
957 }
958 
959 /** Gets a percentage representation of a specified control value.
960  * @param ctl An initialized control handle.
961  * @param id The index of the value within the control.
962  * @returns On success, the percentage representation of the control value.
963  *  On failure, -EINVAL is returned.
964  * @ingroup libtinyalsa-mixer
965  */
mixer_ctl_get_percent(const struct mixer_ctl * ctl,unsigned int id)966 int mixer_ctl_get_percent(const struct mixer_ctl *ctl, unsigned int id)
967 {
968     if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
969         return -EINVAL;
970 
971     return int_to_percent(&ctl->info, mixer_ctl_get_value(ctl, id));
972 }
973 
974 /** Sets the value of a control by percent, specified by the value index.
975  * @param ctl An initialized control handle.
976  * @param id The index of the value to set
977  * @param percent A percentage value between 0 and 100.
978  * @returns On success, zero is returned.
979  *  On failure, non-zero is returned.
980  * @ingroup libtinyalsa-mixer
981  */
mixer_ctl_set_percent(struct mixer_ctl * ctl,unsigned int id,int percent)982 int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
983 {
984     if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
985         return -EINVAL;
986 
987     return mixer_ctl_set_value(ctl, id, percent_to_int(&ctl->info, percent));
988 }
989 
990 /** Gets the value of a control.
991  * @param ctl An initialized control handle.
992  * @param id The index of the control value.
993  * @returns On success, the specified value is returned.
994  *  On failure, -EINVAL is returned.
995  * @ingroup libtinyalsa-mixer
996  */
mixer_ctl_get_value(const struct mixer_ctl * ctl,unsigned int id)997 int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
998 {
999     struct mixer_ctl_group *grp;
1000     struct snd_ctl_elem_value ev;
1001     int ret;
1002 
1003     if (!ctl || (id >= ctl->info.count))
1004         return -EINVAL;
1005 
1006     grp = ctl->grp;
1007     memset(&ev, 0, sizeof(ev));
1008     ev.id.numid = ctl->info.id.numid;
1009     ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
1010     if (ret < 0)
1011         return ret;
1012 
1013     switch (ctl->info.type) {
1014     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
1015         return !!ev.value.integer.value[id];
1016 
1017     case SNDRV_CTL_ELEM_TYPE_INTEGER:
1018         return ev.value.integer.value[id];
1019 
1020     case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
1021         return ev.value.enumerated.item[id];
1022 
1023     case SNDRV_CTL_ELEM_TYPE_BYTES:
1024         return ev.value.bytes.data[id];
1025 
1026     default:
1027         return -EINVAL;
1028     }
1029 
1030     return 0;
1031 }
1032 
1033 /** Gets the contents of a control's value array.
1034  * @param ctl An initialized control handle.
1035  * @param array A pointer to write the array data to.
1036  *  The size of this array must be equal to the number of items in the array
1037  *  multiplied by the size of each item.
1038  * @param count The number of items in the array.
1039  *  This parameter must match the number of items in the control.
1040  *  The number of items in the control may be accessed via @ref mixer_ctl_get_num_values
1041  * @returns On success, zero.
1042  *  On failure, non-zero.
1043  * @ingroup libtinyalsa-mixer
1044  */
mixer_ctl_get_array(const struct mixer_ctl * ctl,void * array,size_t count)1045 int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
1046 {
1047     struct mixer_ctl_group *grp;
1048     struct snd_ctl_elem_value ev;
1049     int ret = 0;
1050     size_t size;
1051     void *source;
1052 
1053     if (!ctl || !array || count == 0) {
1054         return -EINVAL;
1055     }
1056 
1057     grp = ctl->grp;
1058 
1059     if (count > ctl->info.count)
1060         return -EINVAL;
1061 
1062     memset(&ev, 0, sizeof(ev));
1063     ev.id.numid = ctl->info.id.numid;
1064 
1065     switch (ctl->info.type) {
1066     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
1067     case SNDRV_CTL_ELEM_TYPE_INTEGER:
1068         ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
1069         if (ret < 0)
1070             return ret;
1071         size = sizeof(ev.value.integer.value[0]);
1072         source = ev.value.integer.value;
1073         break;
1074 
1075     case SNDRV_CTL_ELEM_TYPE_BYTES:
1076         /* check if this is new bytes TLV */
1077         if (mixer_ctl_is_access_tlv_rw(ctl)) {
1078             struct snd_ctl_tlv *tlv;
1079             int ret;
1080 
1081             if (count > SIZE_MAX - sizeof(*tlv))
1082                 return -EINVAL;
1083 
1084             tlv = calloc(1, sizeof(*tlv) + count);
1085             if (!tlv)
1086                 return -ENOMEM;
1087 
1088             tlv->numid = ctl->info.id.numid;
1089             tlv->length = count;
1090             ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
1091 
1092             source = tlv->tlv;
1093             memcpy(array, source, count);
1094 
1095             free(tlv);
1096 
1097             return ret;
1098         } else {
1099             ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
1100             if (ret < 0)
1101                 return ret;
1102             size = sizeof(ev.value.bytes.data[0]);
1103             source = ev.value.bytes.data;
1104             break;
1105         }
1106 
1107     case SNDRV_CTL_ELEM_TYPE_IEC958:
1108         ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
1109         if (ret < 0)
1110             return ret;
1111         size = sizeof(ev.value.iec958);
1112         source = &ev.value.iec958;
1113         break;
1114 
1115     default:
1116         return -EINVAL;
1117     }
1118 
1119     memcpy(array, source, size * count);
1120 
1121     return 0;
1122 }
1123 
1124 /** Sets the value of a control, specified by the value index.
1125  * @param ctl An initialized control handle.
1126  * @param id The index of the value within the control.
1127  * @param value The value to set.
1128  *  This must be in a range specified by @ref mixer_ctl_get_range_min
1129  *  and @ref mixer_ctl_get_range_max.
1130  * @returns On success, zero is returned.
1131  *  On failure, non-zero is returned.
1132  * @ingroup libtinyalsa-mixer
1133  */
mixer_ctl_set_value(struct mixer_ctl * ctl,unsigned int id,int value)1134 int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
1135 {
1136     struct mixer_ctl_group *grp;
1137     struct snd_ctl_elem_value ev;
1138     int ret;
1139 
1140     if (!ctl || id >= ctl->info.count) {
1141         return -EINVAL;
1142     }
1143 
1144     grp = ctl->grp;
1145     memset(&ev, 0, sizeof(ev));
1146     ev.id.numid = ctl->info.id.numid;
1147     ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
1148     if (ret < 0)
1149         return ret;
1150 
1151     switch (ctl->info.type) {
1152     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
1153         ev.value.integer.value[id] = !!value;
1154         break;
1155 
1156     case SNDRV_CTL_ELEM_TYPE_INTEGER:
1157         ev.value.integer.value[id] = value;
1158         break;
1159 
1160     case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
1161         ev.value.enumerated.item[id] = value;
1162         break;
1163 
1164     case SNDRV_CTL_ELEM_TYPE_BYTES:
1165         ev.value.bytes.data[id] = value;
1166         break;
1167 
1168     default:
1169         return -EINVAL;
1170     }
1171 
1172     return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
1173 }
1174 
1175 /** Sets the contents of a control's value array.
1176  * @param ctl An initialized control handle.
1177  * @param array The array containing control values.
1178  * @param count The number of values in the array.
1179  *  This must match the number of values in the control.
1180  *  The number of values in a control may be accessed via @ref mixer_ctl_get_num_values
1181  * @returns On success, zero.
1182  *  On failure, non-zero.
1183  * @ingroup libtinyalsa-mixer
1184  */
mixer_ctl_set_array(struct mixer_ctl * ctl,const void * array,size_t count)1185 int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
1186 {
1187     struct mixer_ctl_group *grp;
1188     struct snd_ctl_elem_value ev;
1189     size_t size;
1190     void *dest;
1191 
1192     if (!ctl || !array || count == 0) {
1193         return -EINVAL;
1194     }
1195 
1196     grp = ctl->grp;
1197 
1198     if (count > ctl->info.count)
1199         return -EINVAL;
1200 
1201     memset(&ev, 0, sizeof(ev));
1202     ev.id.numid = ctl->info.id.numid;
1203 
1204     switch (ctl->info.type) {
1205     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
1206     case SNDRV_CTL_ELEM_TYPE_INTEGER:
1207         size = sizeof(ev.value.integer.value[0]);
1208         dest = ev.value.integer.value;
1209         break;
1210 
1211     case SNDRV_CTL_ELEM_TYPE_BYTES:
1212         /* check if this is new bytes TLV */
1213         if (mixer_ctl_is_access_tlv_rw(ctl)) {
1214             struct snd_ctl_tlv *tlv;
1215             int ret = 0;
1216 
1217             if (count > SIZE_MAX - sizeof(*tlv))
1218                 return -EINVAL;
1219 
1220             tlv = calloc(1, sizeof(*tlv) + count);
1221             if (!tlv)
1222                 return -ENOMEM;
1223 
1224             tlv->numid = ctl->info.id.numid;
1225             tlv->length = count;
1226             memcpy(tlv->tlv, array, count);
1227 
1228             ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
1229             free(tlv);
1230 
1231             return ret;
1232         } else {
1233             size = sizeof(ev.value.bytes.data[0]);
1234             dest = ev.value.bytes.data;
1235         }
1236         break;
1237 
1238     case SNDRV_CTL_ELEM_TYPE_IEC958:
1239         size = sizeof(ev.value.iec958);
1240         dest = &ev.value.iec958;
1241         break;
1242 
1243     default:
1244         return -EINVAL;
1245     }
1246 
1247     memcpy(dest, array, size * count);
1248 
1249     return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
1250 }
1251 
1252 /** Gets the minimum value of an control.
1253  * The control must have an integer type.
1254  * The type of the control can be checked with @ref mixer_ctl_get_type.
1255  * @param ctl An initialized control handle.
1256  * @returns On success, the minimum value of the control.
1257  *  On failure, -EINVAL.
1258  * @ingroup libtinyalsa-mixer
1259  */
mixer_ctl_get_range_min(const struct mixer_ctl * ctl)1260 int mixer_ctl_get_range_min(const struct mixer_ctl *ctl)
1261 {
1262     if (!ctl || ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER) {
1263         return -EINVAL;
1264     }
1265 
1266     return ctl->info.value.integer.min;
1267 }
1268 
1269 /** Gets the maximum value of an control.
1270  * The control must have an integer type.
1271  * The type of the control can be checked with @ref mixer_ctl_get_type.
1272  * @param ctl An initialized control handle.
1273  * @returns On success, the maximum value of the control.
1274  *  On failure, -EINVAL.
1275  * @ingroup libtinyalsa-mixer
1276  */
mixer_ctl_get_range_max(const struct mixer_ctl * ctl)1277 int mixer_ctl_get_range_max(const struct mixer_ctl *ctl)
1278 {
1279     if (!ctl || ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER) {
1280         return -EINVAL;
1281     }
1282 
1283     return ctl->info.value.integer.max;
1284 }
1285 
1286 /** Get the number of enumerated items in the control.
1287  * @param ctl An initialized control handle.
1288  * @returns The number of enumerated items in the control.
1289  * @ingroup libtinyalsa-mixer
1290  */
mixer_ctl_get_num_enums(const struct mixer_ctl * ctl)1291 unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl)
1292 {
1293     if (!ctl) {
1294         return 0;
1295     }
1296 
1297     return ctl->info.value.enumerated.items;
1298 }
1299 
mixer_ctl_fill_enum_string(struct mixer_ctl * ctl)1300 static int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
1301 {
1302     struct mixer_ctl_group *grp = ctl->grp;
1303     struct snd_ctl_elem_info tmp;
1304     unsigned int m;
1305     char **enames;
1306 
1307     if (ctl->ename) {
1308         return 0;
1309     }
1310 
1311     enames = calloc(ctl->info.value.enumerated.items, sizeof(char*));
1312     if (!enames)
1313         goto fail;
1314     for (m = 0; m < ctl->info.value.enumerated.items; m++) {
1315         memset(&tmp, 0, sizeof(tmp));
1316         tmp.id.numid = ctl->info.id.numid;
1317         tmp.value.enumerated.item = m;
1318         if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
1319             goto fail;
1320         enames[m] = strdup(tmp.value.enumerated.name);
1321         if (!enames[m])
1322             goto fail;
1323     }
1324     ctl->ename = enames;
1325     return 0;
1326 
1327 fail:
1328     if (enames) {
1329         for (m = 0; m < ctl->info.value.enumerated.items; m++) {
1330             if (enames[m]) {
1331                 free(enames[m]);
1332             }
1333         }
1334         free(enames);
1335     }
1336     return -1;
1337 }
1338 
1339 /** Gets the string representation of an enumerated item.
1340  * @param ctl An initialized control handle.
1341  * @param enum_id The index of the enumerated value.
1342  * @returns A string representation of the enumerated item.
1343  * @ingroup libtinyalsa-mixer
1344  */
mixer_ctl_get_enum_string(struct mixer_ctl * ctl,unsigned int enum_id)1345 const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
1346                                       unsigned int enum_id)
1347 {
1348     if (!ctl || ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED ||
1349             enum_id >= ctl->info.value.enumerated.items) {
1350         return NULL;
1351     }
1352 
1353     if (mixer_ctl_fill_enum_string(ctl) < 0) {
1354         return NULL;
1355     }
1356 
1357     return (const char *)ctl->ename[enum_id];
1358 }
1359 
1360 /** Set an enumeration value by string value.
1361  * @param ctl An enumerated mixer control.
1362  * @param string The string representation of an enumeration.
1363  * @returns On success, zero.
1364  *  On failure, zero.
1365  * @ingroup libtinyalsa-mixer
1366  */
mixer_ctl_set_enum_by_string(struct mixer_ctl * ctl,const char * string)1367 int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
1368 {
1369     struct mixer_ctl_group *grp;
1370     unsigned int i, num_enums;
1371     struct snd_ctl_elem_value ev;
1372     int ret;
1373 
1374     if (!ctl || !string || ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
1375         return -EINVAL;
1376     }
1377 
1378     if (mixer_ctl_fill_enum_string(ctl) < 0) {
1379         return -EINVAL;
1380     }
1381 
1382     grp = ctl->grp;
1383     num_enums = ctl->info.value.enumerated.items;
1384     for (i = 0; i < num_enums; i++) {
1385         if (!strcmp(string, ctl->ename[i])) {
1386             memset(&ev, 0, sizeof(ev));
1387             ev.value.enumerated.item[0] = i;
1388             ev.id.numid = ctl->info.id.numid;
1389             ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
1390             if (ret < 0)
1391                 return ret;
1392             return 0;
1393         }
1394     }
1395 
1396     return -EINVAL;
1397 }
1398