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 <string.h>
32#include <unistd.h>
33#include <fcntl.h>
34#include <errno.h>
35#include <ctype.h>
36
37#include <sys/ioctl.h>
38
39#include <linux/ioctl.h>
40#define __force
41#define __bitwise
42#define __user
43#include <sound/asound.h>
44
45#include <tinyalsa/asoundlib.h>
46
47struct mixer_ctl {
48    struct mixer *mixer;
49    struct snd_ctl_elem_info *info;
50    char **ename;
51};
52
53struct mixer {
54    int fd;
55    struct snd_ctl_elem_info *info;
56    struct mixer_ctl *ctl;
57    unsigned int count;
58};
59
60void mixer_close(struct mixer *mixer)
61{
62    unsigned int n,m;
63
64    if (!mixer)
65        return;
66
67    if (mixer->fd >= 0)
68        close(mixer->fd);
69
70    if (mixer->ctl) {
71        for (n = 0; n < mixer->count; n++) {
72            if (mixer->ctl[n].ename) {
73                unsigned int max = mixer->ctl[n].info->value.enumerated.items;
74                for (m = 0; m < max; m++)
75                    free(mixer->ctl[n].ename[m]);
76                free(mixer->ctl[n].ename);
77            }
78        }
79        free(mixer->ctl);
80    }
81
82    if (mixer->info)
83        free(mixer->info);
84
85    free(mixer);
86
87    /* TODO: verify frees */
88}
89
90struct mixer *mixer_open(unsigned int card)
91{
92    struct snd_ctl_elem_list elist;
93    struct snd_ctl_elem_info tmp;
94    struct snd_ctl_elem_id *eid = NULL;
95    struct mixer *mixer = NULL;
96    unsigned int n, m;
97    int fd;
98    char fn[256];
99
100    snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
101    fd = open(fn, O_RDWR);
102    if (fd < 0)
103        return 0;
104
105    memset(&elist, 0, sizeof(elist));
106    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
107        goto fail;
108
109    mixer = calloc(1, sizeof(*mixer));
110    if (!mixer)
111        goto fail;
112
113    mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
114    mixer->info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
115    if (!mixer->ctl || !mixer->info)
116        goto fail;
117
118    eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
119    if (!eid)
120        goto fail;
121
122    mixer->count = elist.count;
123    mixer->fd = fd;
124    elist.space = mixer->count;
125    elist.pids = eid;
126    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
127        goto fail;
128
129    for (n = 0; n < mixer->count; n++) {
130        struct snd_ctl_elem_info *ei = mixer->info + n;
131        ei->id.numid = eid[n].numid;
132        if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
133            goto fail;
134        mixer->ctl[n].info = ei;
135        mixer->ctl[n].mixer = mixer;
136        if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
137            char **enames = calloc(ei->value.enumerated.items, sizeof(char*));
138            if (!enames)
139                goto fail;
140            mixer->ctl[n].ename = enames;
141            for (m = 0; m < ei->value.enumerated.items; m++) {
142                memset(&tmp, 0, sizeof(tmp));
143                tmp.id.numid = ei->id.numid;
144                tmp.value.enumerated.item = m;
145                if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
146                    goto fail;
147                enames[m] = strdup(tmp.value.enumerated.name);
148                if (!enames[m])
149                    goto fail;
150            }
151        }
152    }
153
154    free(eid);
155    return mixer;
156
157fail:
158    /* TODO: verify frees in failure case */
159    if (eid)
160        free(eid);
161    if (mixer)
162        mixer_close(mixer);
163    else if (fd >= 0)
164        close(fd);
165    return 0;
166}
167
168unsigned int mixer_get_num_ctls(struct mixer *mixer)
169{
170    if (!mixer)
171        return 0;
172
173    return mixer->count;
174}
175
176struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
177{
178    if (mixer && (id < mixer->count))
179        return mixer->ctl + id;
180
181    return NULL;
182}
183
184struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
185{
186    unsigned int n;
187
188    if (!mixer)
189        return NULL;
190
191    for (n = 0; n < mixer->count; n++)
192        if (!strcmp(name, (char*) mixer->info[n].id.name))
193            return mixer->ctl + n;
194
195    return NULL;
196}
197
198const char *mixer_ctl_get_name(struct mixer_ctl *ctl)
199{
200    if (!ctl)
201        return NULL;
202
203    return (const char *)ctl->info->id.name;
204}
205
206enum mixer_ctl_type mixer_ctl_get_type(struct mixer_ctl *ctl)
207{
208    if (!ctl)
209        return MIXER_CTL_TYPE_UNKNOWN;
210
211    switch (ctl->info->type) {
212    case SNDRV_CTL_ELEM_TYPE_BOOLEAN:    return MIXER_CTL_TYPE_BOOL;
213    case SNDRV_CTL_ELEM_TYPE_INTEGER:    return MIXER_CTL_TYPE_INT;
214    case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM;
215    case SNDRV_CTL_ELEM_TYPE_BYTES:      return MIXER_CTL_TYPE_BYTE;
216    case SNDRV_CTL_ELEM_TYPE_IEC958:     return MIXER_CTL_TYPE_IEC958;
217    case SNDRV_CTL_ELEM_TYPE_INTEGER64:  return MIXER_CTL_TYPE_INT64;
218    default:                             return MIXER_CTL_TYPE_UNKNOWN;
219    };
220}
221
222const char *mixer_ctl_get_type_string(struct mixer_ctl *ctl)
223{
224    if (!ctl)
225        return "";
226
227    switch (ctl->info->type) {
228    case SNDRV_CTL_ELEM_TYPE_BOOLEAN:    return "BOOL";
229    case SNDRV_CTL_ELEM_TYPE_INTEGER:    return "INT";
230    case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM";
231    case SNDRV_CTL_ELEM_TYPE_BYTES:      return "BYTE";
232    case SNDRV_CTL_ELEM_TYPE_IEC958:     return "IEC958";
233    case SNDRV_CTL_ELEM_TYPE_INTEGER64:  return "INT64";
234    default:                             return "Unknown";
235    };
236}
237
238unsigned int mixer_ctl_get_num_values(struct mixer_ctl *ctl)
239{
240    if (!ctl)
241        return 0;
242
243    return ctl->info->count;
244}
245
246static int percent_to_int(struct snd_ctl_elem_info *ei, int percent)
247{
248    int range;
249
250    if (percent > 100)
251        percent = 100;
252    else if (percent < 0)
253        percent = 0;
254
255    range = (ei->value.integer.max - ei->value.integer.min);
256
257    return ei->value.integer.min + (range * percent) / 100;
258}
259
260static int int_to_percent(struct snd_ctl_elem_info *ei, int value)
261{
262    int range = (ei->value.integer.max - ei->value.integer.min);
263
264    if (range == 0)
265        return 0;
266
267    return ((value - ei->value.integer.min) / range) * 100;
268}
269
270int mixer_ctl_get_percent(struct mixer_ctl *ctl, unsigned int id)
271{
272    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
273        return -EINVAL;
274
275    return int_to_percent(ctl->info, mixer_ctl_get_value(ctl, id));
276}
277
278int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
279{
280    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
281        return -EINVAL;
282
283    return mixer_ctl_set_value(ctl, id, percent_to_int(ctl->info, percent));
284}
285
286int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id)
287{
288    struct snd_ctl_elem_value ev;
289    int ret;
290
291    if (!ctl || (id >= ctl->info->count))
292        return -EINVAL;
293
294    memset(&ev, 0, sizeof(ev));
295    ev.id.numid = ctl->info->id.numid;
296    ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
297    if (ret < 0)
298        return ret;
299
300    switch (ctl->info->type) {
301    case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
302        return !!ev.value.integer.value[id];
303
304    case SNDRV_CTL_ELEM_TYPE_INTEGER:
305        return ev.value.integer.value[id];
306
307    case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
308        return ev.value.enumerated.item[id];
309
310    case SNDRV_CTL_ELEM_TYPE_BYTES:
311        return ev.value.bytes.data[id];
312
313    default:
314        return -EINVAL;
315    }
316
317    return 0;
318}
319
320int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
321{
322    struct snd_ctl_elem_value ev;
323    int ret;
324
325    if (!ctl || (id >= ctl->info->count))
326        return -EINVAL;
327
328    memset(&ev, 0, sizeof(ev));
329    ev.id.numid = ctl->info->id.numid;
330    ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
331    if (ret < 0)
332        return ret;
333
334    switch (ctl->info->type) {
335    case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
336        ev.value.integer.value[id] = !!value;
337        break;
338
339    case SNDRV_CTL_ELEM_TYPE_INTEGER:
340        ev.value.integer.value[id] = value;
341        break;
342
343    case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
344        ev.value.enumerated.item[id] = value;
345        break;
346
347    default:
348        return -EINVAL;
349    }
350
351    return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
352}
353
354int mixer_ctl_get_range_min(struct mixer_ctl *ctl)
355{
356    int ret;
357
358    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
359        return -EINVAL;
360
361    return ctl->info->value.integer.min;
362}
363
364int mixer_ctl_get_range_max(struct mixer_ctl *ctl)
365{
366    int ret;
367
368    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_INTEGER))
369        return -EINVAL;
370
371    return ctl->info->value.integer.max;
372}
373
374unsigned int mixer_ctl_get_num_enums(struct mixer_ctl *ctl)
375{
376    if (!ctl)
377        return 0;
378
379    return ctl->info->value.enumerated.items;
380}
381
382const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
383                                      unsigned int enum_id)
384{
385    int ret;
386
387    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) ||
388        (enum_id >= ctl->info->value.enumerated.items))
389        return NULL;
390
391    return (const char *)ctl->ename[enum_id];
392}
393
394int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
395{
396    unsigned int i, num_enums;
397    struct snd_ctl_elem_value ev;
398    int ret;
399
400    if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED))
401        return -EINVAL;
402
403    num_enums = ctl->info->value.enumerated.items;
404    for (i = 0; i < num_enums; i++) {
405        if (!strcmp(string, ctl->ename[i])) {
406            memset(&ev, 0, sizeof(ev));
407            ev.value.enumerated.item[0] = i;
408            ev.id.numid = ctl->info->id.numid;
409            ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
410            if (ret < 0)
411                return ret;
412            return 0;
413        }
414    }
415
416    return -EINVAL;
417}
418
419