• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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