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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 */
snd_tlv_parse_dB_info(unsigned int * tlv,unsigned int tlv_size,unsigned int ** db_tlvp)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[SNDRV_CTL_TLVO_TYPE];
69 size = tlv[SNDRV_CTL_TLVO_LEN];
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[SNDRV_CTL_TLVO_LEN]) + 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 */
snd_tlv_get_dB_range(unsigned int * tlv,long rangemin,long rangemax,long * min,long * max)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[SNDRV_CTL_TLVO_TYPE]) {
135 case SND_CTL_TLVT_DB_RANGE: {
136 unsigned int pos, len;
137 len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
138 if (len > MAX_TLV_RANGE_SIZE)
139 return -EINVAL;
140 pos = 2;
141 while (pos + 4 <= len) {
142 long rmin, rmax;
143 long submin, submax;
144 submin = (int)tlv[pos];
145 submax = (int)tlv[pos + 1];
146 if (rangemax < submax)
147 submax = rangemax;
148 err = snd_tlv_get_dB_range(tlv + pos + 2,
149 submin, submax,
150 &rmin, &rmax);
151 if (err < 0)
152 return err;
153 if (pos > 2) {
154 if (rmin < *min)
155 *min = rmin;
156 if (rmax > *max)
157 *max = rmax;
158 } else {
159 *min = rmin;
160 *max = rmax;
161 }
162 if (rangemax == submax)
163 return 0;
164 pos += int_index(tlv[pos + 3]) + 4;
165 }
166 return 0;
167 }
168 case SND_CTL_TLVT_DB_SCALE: {
169 int step;
170 if (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000)
171 *min = SND_CTL_TLV_DB_GAIN_MUTE;
172 else
173 *min = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
174 step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
175 *max = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] +
176 step * (rangemax - rangemin);
177 return 0;
178 }
179 case SND_CTL_TLVT_DB_MINMAX:
180 case SND_CTL_TLVT_DB_LINEAR:
181 *min = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
182 *max = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
183 return 0;
184 case SND_CTL_TLVT_DB_MINMAX_MUTE:
185 *min = SND_CTL_TLV_DB_GAIN_MUTE;
186 *max = (int)tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
187 return 0;
188 }
189 return -EINVAL;
190 }
191
192 /**
193 * \brief Convert the given raw volume value to a dB gain
194 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
195 * \param rangemin the minimum value of the raw volume
196 * \param rangemax the maximum value of the raw volume
197 * \param volume the raw volume value to convert
198 * \param db_gain the dB gain (in 0.01dB unit)
199 * \return 0 if successful, or a negative error code
200 */
snd_tlv_convert_to_dB(unsigned int * tlv,long rangemin,long rangemax,long volume,long * db_gain)201 int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
202 long volume, long *db_gain)
203 {
204 unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
205
206 switch (type) {
207 case SND_CTL_TLVT_DB_RANGE: {
208 unsigned int pos, len;
209 len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
210 if (len > MAX_TLV_RANGE_SIZE)
211 return -EINVAL;
212 pos = 2;
213 while (pos + 4 <= len) {
214 rangemin = (int)tlv[pos];
215 rangemax = (int)tlv[pos + 1];
216 if (volume >= rangemin && volume <= rangemax)
217 return snd_tlv_convert_to_dB(tlv + pos + 2,
218 rangemin, rangemax,
219 volume, db_gain);
220 pos += int_index(tlv[pos + 3]) + 4;
221 }
222 return -EINVAL;
223 }
224 case SND_CTL_TLVT_DB_SCALE: {
225 int min, step, mute;
226 min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
227 step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
228 mute = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] >> 16) & 1;
229 if (mute && volume <= rangemin)
230 *db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
231 else
232 *db_gain = (volume - rangemin) * step + min;
233 return 0;
234 }
235 case SND_CTL_TLVT_DB_MINMAX:
236 case SND_CTL_TLVT_DB_MINMAX_MUTE: {
237 int mindb, maxdb;
238 mindb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
239 maxdb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
240 if (volume <= rangemin || rangemax <= rangemin) {
241 if (type == SND_CTL_TLVT_DB_MINMAX_MUTE)
242 *db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
243 else
244 *db_gain = mindb;
245 } else if (volume >= rangemax)
246 *db_gain = maxdb;
247 else
248 *db_gain = (maxdb - mindb) * (volume - rangemin) /
249 (rangemax - rangemin) + mindb;
250 return 0;
251 }
252 #ifndef HAVE_SOFT_FLOAT
253 case SND_CTL_TLVT_DB_LINEAR: {
254 int mindb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
255 int maxdb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
256 if (volume <= rangemin || rangemax <= rangemin)
257 *db_gain = mindb;
258 else if (volume >= rangemax)
259 *db_gain = maxdb;
260 else {
261 double val = (double)(volume - rangemin) /
262 (double)(rangemax - rangemin);
263 if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
264 *db_gain = (long)(100.0 * 20.0 * log10(val)) +
265 maxdb;
266 else {
267 /* FIXME: precalculate and cache these values */
268 double lmin = pow(10.0, mindb/2000.0);
269 double lmax = pow(10.0, maxdb/2000.0);
270 val = (lmax - lmin) * val + lmin;
271 *db_gain = (long)(100.0 * 20.0 * log10(val));
272 }
273 }
274 return 0;
275 }
276 #endif
277 }
278 return -EINVAL;
279 }
280
281 /**
282 * \brief Convert from dB gain to the corresponding raw value
283 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
284 * \param rangemin the minimum value of the raw volume
285 * \param rangemax the maximum value of the raw volume
286 * \param db_gain the dB gain to convert (in 0.01dB unit)
287 * \param value the pointer to store the converted raw volume value
288 * \param xdir the direction for round-up. The value is round up
289 * when this is positive. A negative value means round down.
290 * Zero means round-up to nearest.
291 * \return 0 if successful, or a negative error code
292 */
snd_tlv_convert_from_dB(unsigned int * tlv,long rangemin,long rangemax,long db_gain,long * value,int xdir)293 int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
294 long db_gain, long *value, int xdir)
295 {
296 unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
297
298 switch (type) {
299 case SND_CTL_TLVT_DB_RANGE: {
300 long dbmin, dbmax, prev_submax;
301 unsigned int pos, len;
302 len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
303 if (len < 6 || len > MAX_TLV_RANGE_SIZE)
304 return -EINVAL;
305 pos = 2;
306 prev_submax = 0;
307 while (pos + 4 <= len) {
308 long submin, submax;
309 submin = (int)tlv[pos];
310 submax = (int)tlv[pos + 1];
311 if (rangemax < submax)
312 submax = rangemax;
313 if (!snd_tlv_get_dB_range(tlv + pos + 2,
314 submin, submax,
315 &dbmin, &dbmax) &&
316 db_gain >= dbmin && db_gain <= dbmax)
317 return snd_tlv_convert_from_dB(tlv + pos + 2,
318 submin, submax,
319 db_gain, value, xdir);
320 else if (db_gain < dbmin) {
321 *value = xdir > 0 || pos == 2 ? submin : prev_submax;
322 return 0;
323 }
324 prev_submax = submax;
325 if (rangemax == submax)
326 break;
327 pos += int_index(tlv[pos + 3]) + 4;
328 }
329 *value = prev_submax;
330 return 0;
331 }
332 case SND_CTL_TLVT_DB_SCALE: {
333 int min, step, max, mute;
334 min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
335 step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff;
336 mute = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000;
337 max = min + (int)(step * (rangemax - rangemin));
338 if (db_gain <= min)
339 if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
340 mute)
341 *value = rangemin + 1;
342 else
343 *value = rangemin;
344 else if (db_gain >= max)
345 *value = rangemax;
346 else {
347 long v = (db_gain - min) * (rangemax - rangemin);
348 if (xdir > 0)
349 v += (max - min) - 1;
350 else if (xdir == 0)
351 v += ((max - min) + 1) / 2;
352 v = v / (max - min) + rangemin;
353 *value = v;
354 }
355 return 0;
356 }
357 case SND_CTL_TLVT_DB_MINMAX:
358 case SND_CTL_TLVT_DB_MINMAX_MUTE: {
359 int min, max;
360 min = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
361 max = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
362 if (db_gain <= min)
363 if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
364 type == SND_CTL_TLVT_DB_MINMAX_MUTE)
365 *value = rangemin + 1;
366 else
367 *value = rangemin;
368 else if (db_gain >= max)
369 *value = rangemax;
370 else {
371 long v = (db_gain - min) * (rangemax - rangemin);
372 if (xdir > 0)
373 v += (max - min) - 1;
374 else if (xdir == 0)
375 v += ((max - min) + 1) / 2;
376 v = v / (max - min) + rangemin;
377 *value = v;
378 }
379 return 0;
380 }
381 #ifndef HAVE_SOFT_FLOAT
382 case SND_CTL_TLVT_DB_LINEAR: {
383 int min, max;
384 min = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
385 max = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
386 if (db_gain <= min)
387 *value = rangemin;
388 else if (db_gain >= max)
389 *value = rangemax;
390 else {
391 /* FIXME: precalculate and cache vmin and vmax */
392 double vmin, vmax, v;
393 vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
394 pow(10.0, (double)min / 2000.0);
395 vmax = !max ? 1.0 : pow(10.0, (double)max / 2000.0);
396 v = pow(10.0, (double)db_gain / 2000.0);
397 v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
398 if (xdir > 0)
399 v = ceil(v);
400 else if (xdir == 0)
401 v = lrint(v);
402 *value = (long)v + rangemin;
403 }
404 return 0;
405 }
406 #endif
407 default:
408 break;
409 }
410 return -EINVAL;
411 }
412
413 #ifndef DOC_HIDDEN
414 #define TEMP_TLV_SIZE 4096
415 struct tlv_info {
416 long minval, maxval;
417 unsigned int *tlv;
418 unsigned int buf[TEMP_TLV_SIZE];
419 };
420 #endif
421
get_tlv_info(snd_ctl_t * ctl,const snd_ctl_elem_id_t * id,struct tlv_info * rec)422 static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
423 struct tlv_info *rec)
424 {
425 snd_ctl_elem_info_t info = {0};
426 int err;
427
428 snd_ctl_elem_info_set_id(&info, id);
429 err = snd_ctl_elem_info(ctl, &info);
430 if (err < 0)
431 return err;
432 if (!snd_ctl_elem_info_is_tlv_readable(&info))
433 return -EINVAL;
434 if (snd_ctl_elem_info_get_type(&info) != SND_CTL_ELEM_TYPE_INTEGER)
435 return -EINVAL;
436 rec->minval = snd_ctl_elem_info_get_min(&info);
437 rec->maxval = snd_ctl_elem_info_get_max(&info);
438 err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
439 if (err < 0)
440 return err;
441 err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
442 if (err < 0)
443 return err;
444 return 0;
445 }
446
447 /**
448 * \brief Get the dB min/max values on the given control element
449 * \param ctl the control handler
450 * \param id the element id
451 * \param min the pointer to store the minimum dB value (in 0.01dB unit)
452 * \param max the pointer to store the maximum dB value (in 0.01dB unit)
453 * \return 0 if successful, or a negative error code
454 */
snd_ctl_get_dB_range(snd_ctl_t * ctl,const snd_ctl_elem_id_t * id,long * min,long * max)455 int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
456 long *min, long *max)
457 {
458 struct tlv_info info;
459 int err;
460
461 err = get_tlv_info(ctl, id, &info);
462 if (err < 0)
463 return err;
464 return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
465 min, max);
466 }
467
468 /**
469 * \brief Convert the volume value to dB on the given control element
470 * \param ctl the control handler
471 * \param id the element id
472 * \param volume the raw volume value to convert
473 * \param db_gain the dB gain (in 0.01dB unit)
474 * \return 0 if successful, or a negative error code
475 */
snd_ctl_convert_to_dB(snd_ctl_t * ctl,const snd_ctl_elem_id_t * id,long volume,long * db_gain)476 int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
477 long volume, long *db_gain)
478 {
479 struct tlv_info info;
480 int err;
481
482 err = get_tlv_info(ctl, id, &info);
483 if (err < 0)
484 return err;
485 return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
486 volume, db_gain);
487 }
488
489 /**
490 * \brief Convert from dB gain to the raw volume value on the given control element
491 * \param ctl the control handler
492 * \param id the element id
493 * \param db_gain the dB gain to convert (in 0.01dB unit)
494 * \param value the pointer to store the converted raw volume value
495 * \param xdir the direction for round-up. The value is round up
496 * when this is positive.
497 * \return 0 if successful, or a negative error code
498 */
snd_ctl_convert_from_dB(snd_ctl_t * ctl,const snd_ctl_elem_id_t * id,long db_gain,long * value,int xdir)499 int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
500 long db_gain, long *value, int xdir)
501 {
502 struct tlv_info info;
503 int err;
504
505 err = get_tlv_info(ctl, id, &info);
506 if (err < 0)
507 return err;
508 return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
509 db_gain, value, xdir);
510 }
511