OSDN Git Service

fix doc errors
[android-x86/external-alsa-lib.git] / src / control / tlv.c
1 /**
2  * \file control/tlv.c
3  * \brief dB conversion functions from control TLV information
4  * \author Takashi Iwai <tiwai@suse.de>
5  * \date 2007
6  */
7 /*
8  *  Control Interface - dB conversion functions from control TLV information
9  *
10  *  Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
11  *
12  *
13  *   This library is free software; you can redistribute it and/or modify
14  *   it under the terms of the GNU Lesser General Public License as
15  *   published by the Free Software Foundation; either version 2.1 of
16  *   the License, or (at your option) any later version.
17  *
18  *   This program is distributed in the hope that it will be useful,
19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *   GNU Lesser General Public License for more details.
22  *
23  *   You should have received a copy of the GNU Lesser General Public
24  *   License along with this library; if not, write to the Free Software
25  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
26  *
27  */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #ifndef HAVE_SOFT_FLOAT
34 #include <math.h>
35 #endif
36 #include "control_local.h"
37
38 #ifndef DOC_HIDDEN
39 /* convert to index of integer array */
40 #define int_index(size) (((size) + sizeof(int) - 1) / sizeof(int))
41 /* max size of a TLV entry for dB information (including compound one) */
42 #define MAX_TLV_RANGE_SIZE      256
43 #endif
44
45 /**
46  * \brief Parse TLV stream and retrieve dB information
47  * \param tlv the TLV source
48  * \param tlv_size the byte size of TLV source
49  * \param db_tlvp the pointer stored the dB TLV information
50  * \return the byte size of dB TLV information if found in the given
51  *   TLV source, or a negative error code.
52  *
53  * This function parses the given TLV source and stores the TLV start
54  * point if the TLV information regarding dB conversion is found.
55  * The stored TLV pointer can be passed to the convesion functions
56  * #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
57  * #snd_tlv_get_dB_range().
58  */
59 int snd_tlv_parse_dB_info(unsigned int *tlv,
60                           unsigned int tlv_size,
61                           unsigned int **db_tlvp)
62 {
63         unsigned int type;
64         unsigned int size;
65         int err;
66
67         *db_tlvp = NULL;
68         type = tlv[0];
69         size = tlv[1];
70         tlv_size -= 2 * sizeof(int);
71         if (size > tlv_size) {
72                 SNDERR("TLV size error");
73                 return -EINVAL;
74         }
75         switch (type) {
76         case SND_CTL_TLVT_CONTAINER:
77                 size = int_index(size) * sizeof(int);
78                 tlv += 2;
79                 while (size > 0) {
80                         unsigned int len;
81                         err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
82                         if (err < 0)
83                                 return err; /* error */
84                         if (err > 0)
85                                 return err; /* found */
86                         len = int_index(tlv[1]) + 2;
87                         size -= len * sizeof(int);
88                         tlv += len;
89                 }
90                 break;
91         case SND_CTL_TLVT_DB_SCALE:
92         case SND_CTL_TLVT_DB_MINMAX:
93         case SND_CTL_TLVT_DB_MINMAX_MUTE:
94 #ifndef HAVE_SOFT_FLOAT
95         case SND_CTL_TLVT_DB_LINEAR:
96 #endif
97         case SND_CTL_TLVT_DB_RANGE: {
98                 unsigned int minsize;
99                 if (type == SND_CTL_TLVT_DB_RANGE)
100                         minsize = 4 * sizeof(int);
101                 else
102                         minsize = 2 * sizeof(int);
103                 if (size < minsize) {
104                         SNDERR("Invalid dB_scale TLV size");
105                         return -EINVAL;
106                 }
107                 if (size > MAX_TLV_RANGE_SIZE) {
108                         SNDERR("Too big dB_scale TLV size: %d", size);
109                         return -EINVAL;
110                 }
111                 *db_tlvp = tlv;
112                 return size + sizeof(int) * 2;
113         }
114         default:
115                 break;
116         }
117         return -EINVAL; /* not found */
118 }
119
120 /**
121  * \brief Get the dB min/max values
122  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
123  * \param rangemin the minimum value of the raw volume
124  * \param rangemax the maximum value of the raw volume
125  * \param min the pointer to store the minimum dB value (in 0.01dB unit)
126  * \param max the pointer to store the maximum dB value (in 0.01dB unit)
127  * \return 0 if successful, or a negative error code
128  */
129 int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
130                          long *min, long *max)
131 {
132         int err;
133
134         switch (tlv[0]) {
135         case SND_CTL_TLVT_DB_RANGE: {
136                 unsigned int pos, len;
137                 len = int_index(tlv[1]);
138                 if (len > MAX_TLV_RANGE_SIZE)
139                         return -EINVAL;
140                 pos = 2;
141                 while (pos + 4 <= len) {
142                         long rmin, rmax;
143                         rangemin = (int)tlv[pos];
144                         rangemax = (int)tlv[pos + 1];
145                         err = snd_tlv_get_dB_range(tlv + pos + 2,
146                                                    rangemin, rangemax,
147                                                    &rmin, &rmax);
148                         if (err < 0)
149                                 return err;
150                         if (pos > 2) {
151                                 if (rmin < *min)
152                                         *min = rmin;
153                                 if (rmax > *max)
154                                         *max = rmax;
155                         } else {
156                                 *min = rmin;
157                                 *max = rmax;
158                         }
159                         pos += int_index(tlv[pos + 3]) + 4;
160                 }
161                 return 0;
162         }
163         case SND_CTL_TLVT_DB_SCALE: {
164                 int step;
165                 *min = (int)tlv[2];
166                 step = (tlv[3] & 0xffff);
167                 *max = *min + (long)(step * (rangemax - rangemin));
168                 return 0;
169         }
170         case SND_CTL_TLVT_DB_MINMAX:
171         case SND_CTL_TLVT_DB_MINMAX_MUTE:
172         case SND_CTL_TLVT_DB_LINEAR:
173                 *min = (int)tlv[2];
174                 *max = (int)tlv[3];
175                 return 0;
176         }
177         return -EINVAL;
178 }
179
180 /**
181  * \brief Convert the given raw volume value to a dB gain
182  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
183  * \param rangemin the minimum value of the raw volume
184  * \param rangemax the maximum value of the raw volume
185  * \param volume the raw volume value to convert
186  * \param db_gain the dB gain (in 0.01dB unit)
187  * \return 0 if successful, or a negative error code
188  */
189 int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
190                           long volume, long *db_gain)
191 {
192         switch (tlv[0]) {
193         case SND_CTL_TLVT_DB_RANGE: {
194                 unsigned int pos, len;
195                 len = int_index(tlv[1]);
196                 if (len > MAX_TLV_RANGE_SIZE)
197                         return -EINVAL;
198                 pos = 2;
199                 while (pos + 4 <= len) {
200                         rangemin = (int)tlv[pos];
201                         rangemax = (int)tlv[pos + 1];
202                         if (volume >= rangemin && volume <= rangemax)
203                                 return snd_tlv_convert_to_dB(tlv + pos + 2,
204                                                              rangemin, rangemax,
205                                                              volume, db_gain);
206                         pos += int_index(tlv[pos + 3]) + 4;
207                 }
208                 return -EINVAL;
209         }
210         case SND_CTL_TLVT_DB_SCALE: {
211                 int min, step, mute;
212                 min = tlv[2];
213                 step = (tlv[3] & 0xffff);
214                 mute = (tlv[3] >> 16) & 1;
215                 if (mute && volume == rangemin)
216                         *db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
217                 else
218                         *db_gain = (volume - rangemin) * step + min;
219                 return 0;
220         }
221         case SND_CTL_TLVT_DB_MINMAX:
222         case SND_CTL_TLVT_DB_MINMAX_MUTE: {
223                 int mindb, maxdb;
224                 mindb = tlv[2];
225                 maxdb = tlv[3];
226                 if (volume <= rangemin || rangemax <= rangemin) {
227                         if (tlv[0] == SND_CTL_TLVT_DB_MINMAX_MUTE)
228                                 *db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
229                         else
230                                 *db_gain = mindb;
231                 } else if (volume >= rangemax)
232                         *db_gain = maxdb;
233                 else
234                         *db_gain = (maxdb - mindb) * (volume - rangemin) /
235                                 (rangemax - rangemin) + mindb;
236                 return 0;
237         }
238 #ifndef HAVE_SOFT_FLOAT
239         case SND_CTL_TLVT_DB_LINEAR: {
240                 int mindb = tlv[2];
241                 int maxdb = tlv[3];
242                 if (volume <= rangemin || rangemax <= rangemin)
243                         *db_gain = mindb;
244                 else if (volume >= rangemax)
245                         *db_gain = maxdb;
246                 else {
247                         double val = (double)(volume - rangemin) /
248                                 (double)(rangemax - rangemin);
249                         if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
250                                 *db_gain = (long)(100.0 * 20.0 * log10(val)) +
251                                         maxdb;
252                         else {
253                                 /* FIXME: precalculate and cache these values */
254                                 double lmin = pow(10.0, mindb/2000.0);
255                                 double lmax = pow(10.0, maxdb/2000.0);
256                                 val = (lmax - lmin) * val + lmin;
257                                 *db_gain = (long)(100.0 * 20.0 * log10(val));
258                         }
259                 }
260                 return 0;
261         }
262 #endif
263         }
264         return -EINVAL;
265 }
266
267 /**
268  * \brief Convert from dB gain to the corresponding raw value
269  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
270  * \param rangemin the minimum value of the raw volume
271  * \param rangemax the maximum value of the raw volume
272  * \param db_gain the dB gain to convert (in 0.01dB unit)
273  * \param value the pointer to store the converted raw volume value
274  * \param xdir the direction for round-up. The value is round up
275  *        when this is positive.
276  * \return 0 if successful, or a negative error code
277  */
278 int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
279                             long db_gain, long *value, int xdir)
280 {
281         switch (tlv[0]) {
282         case SND_CTL_TLVT_DB_RANGE: {
283                 unsigned int pos, len;
284                 len = int_index(tlv[1]);
285                 if (len > MAX_TLV_RANGE_SIZE)
286                         return -EINVAL;
287                 pos = 2;
288                 while (pos + 4 <= len) {
289                         long dbmin, dbmax;
290                         rangemin = (int)tlv[pos];
291                         rangemax = (int)tlv[pos + 1];
292                         if (!snd_tlv_get_dB_range(tlv + pos + 2,
293                                                   rangemin, rangemax,
294                                                   &dbmin, &dbmax) &&
295                             db_gain >= dbmin && db_gain <= dbmax)
296                                 return snd_tlv_convert_from_dB(tlv + pos + 2,
297                                                                rangemin, rangemax,
298                                                                db_gain, value, xdir);
299                         pos += int_index(tlv[pos + 3]) + 4;
300                 }
301                 return -EINVAL;
302         }
303         case SND_CTL_TLVT_DB_SCALE: {
304                 int min, step, max;
305                 min = tlv[2];
306                 step = (tlv[3] & 0xffff);
307                 max = min + (int)(step * (rangemax - rangemin));
308                 if (db_gain <= min)
309                         *value = rangemin;
310                 else if (db_gain >= max)
311                         *value = rangemax;
312                 else {
313                         long v = (db_gain - min) * (rangemax - rangemin);
314                         if (xdir > 0)
315                                 v += (max - min) - 1;
316                         v = v / (max - min) + rangemin;
317                         *value = v;
318                 }
319                 return 0;
320         }
321         case SND_CTL_TLVT_DB_MINMAX:
322         case SND_CTL_TLVT_DB_MINMAX_MUTE: {
323                 int min, max;
324                 min = tlv[2];
325                 max = tlv[3];
326                 if (db_gain <= min)
327                         *value = rangemin;
328                 else if (db_gain >= max)
329                         *value = rangemax;
330                 else {
331                         long v = (db_gain - min) * (rangemax - rangemin);
332                         if (xdir > 0)
333                                 v += (max - min) - 1;
334                         v = v / (max - min) + rangemin;
335                         *value = v;
336                 }
337                 return 0;
338         }
339 #ifndef HAVE_SOFT_FLOAT
340         case SND_CTL_TLVT_DB_LINEAR: {
341                 int min, max;
342                 min = tlv[2];
343                 max = tlv[3];
344                 if (db_gain <= min)
345                         *value = rangemin;
346                 else if (db_gain >= max)
347                         *value = rangemax;
348                 else {
349                         /* FIXME: precalculate and cache vmin and vmax */
350                         double vmin, vmax, v;
351                         vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
352                                 pow(10.0,  (double)min / 2000.0);
353                         vmax = !max ? 1.0 : pow(10.0,  (double)max / 2000.0);
354                         v = pow(10.0, (double)db_gain / 2000.0);
355                         v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
356                         if (xdir > 0)
357                                 v = ceil(v);
358                         *value = (long)v + rangemin;
359                 }
360                 return 0;
361         }
362 #endif
363         default:
364                 break;
365         }
366         return -EINVAL;
367 }
368
369 #ifndef DOC_HIDDEN
370 #define TEMP_TLV_SIZE           4096
371 struct tlv_info {
372         long minval, maxval;
373         unsigned int *tlv;
374         unsigned int buf[TEMP_TLV_SIZE];
375 };
376 #endif
377
378 static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
379                         struct tlv_info *rec)
380 {
381         snd_ctl_elem_info_t *info;
382         int err;
383
384         snd_ctl_elem_info_alloca(&info);
385         snd_ctl_elem_info_set_id(info, id);
386         err = snd_ctl_elem_info(ctl, info);
387         if (err < 0)
388                 return err;
389         if (!snd_ctl_elem_info_is_tlv_readable(info))
390                 return -EINVAL;
391         if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER)
392                 return -EINVAL;
393         rec->minval = snd_ctl_elem_info_get_min(info);
394         rec->maxval = snd_ctl_elem_info_get_max(info);
395         err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
396         if (err < 0)
397                 return err;
398         err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
399         if (err < 0)
400                 return err;
401         return 0;
402 }
403
404 /**
405  * \brief Get the dB min/max values on the given control element
406  * \param ctl the control handler
407  * \param id the element id
408  * \param min the pointer to store the minimum dB value (in 0.01dB unit)
409  * \param max the pointer to store the maximum dB value (in 0.01dB unit)
410  * \return 0 if successful, or a negative error code
411  */
412 int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
413                          long *min, long *max)
414 {
415         struct tlv_info info;
416         int err;
417
418         err = get_tlv_info(ctl, id, &info);
419         if (err < 0)
420                 return err;
421         return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
422                                     min, max);
423 }
424         
425 /**
426  * \brief Convert the volume value to dB on the given control element
427  * \param ctl the control handler
428  * \param id the element id
429  * \param volume the raw volume value to convert
430  * \param db_gain the dB gain (in 0.01dB unit)
431  * \return 0 if successful, or a negative error code
432  */
433 int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
434                           long volume, long *db_gain)
435 {
436         struct tlv_info info;
437         int err;
438
439         err = get_tlv_info(ctl, id, &info);
440         if (err < 0)
441                 return err;
442         return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
443                                      volume, db_gain);
444 }
445
446 /**
447  * \brief Convert from dB gain to the raw volume value on the given control element
448  * \param ctl the control handler
449  * \param id the element id
450  * \param db_gain the dB gain to convert (in 0.01dB unit)
451  * \param value the pointer to store the converted raw volume value
452  * \param xdir the direction for round-up. The value is round up
453  *        when this is positive.
454  * \return 0 if successful, or a negative error code
455  */
456 int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
457                             long db_gain, long *value, int xdir)
458 {
459         struct tlv_info info;
460         int err;
461
462         err = get_tlv_info(ctl, id, &info);
463         if (err < 0)
464                 return err;
465         return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
466                                        db_gain, value, xdir);
467 }