1 /** 2 * \file control/tlv.c 3 * \brief dB conversion functions from control TLV information 4 * \author Takashi Iwai <tiwai (at) 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 (at) 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 #ifndef HAVE_SOFT_FLOAT 93 case SND_CTL_TLVT_DB_LINEAR: 94 #endif 95 case SND_CTL_TLVT_DB_RANGE: { 96 unsigned int minsize; 97 if (type == SND_CTL_TLVT_DB_RANGE) 98 minsize = 4 * sizeof(int); 99 else 100 minsize = 2 * sizeof(int); 101 if (size < minsize) { 102 SNDERR("Invalid dB_scale TLV size"); 103 return -EINVAL; 104 } 105 if (size > MAX_TLV_RANGE_SIZE) { 106 SNDERR("Too big dB_scale TLV size: %d", size); 107 return -EINVAL; 108 } 109 *db_tlvp = tlv; 110 return size + sizeof(int) * 2; 111 } 112 default: 113 break; 114 } 115 return -EINVAL; /* not found */ 116 } 117 118 /** 119 * \brief Get the dB min/max values 120 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info() 121 * \param rangemin the minimum value of the raw volume 122 * \param rangemax the maximum value of the raw volume 123 * \param min the pointer to store the minimum dB value (in 0.01dB unit) 124 * \param max the pointer to store the maximum dB value (in 0.01dB unit) 125 * \return 0 if successful, or a negative error code 126 */ 127 int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax, 128 long *min, long *max) 129 { 130 int err; 131 132 switch (tlv[0]) { 133 case SND_CTL_TLVT_DB_RANGE: { 134 unsigned int pos, len; 135 len = int_index(tlv[1]); 136 if (len > MAX_TLV_RANGE_SIZE) 137 return -EINVAL; 138 pos = 2; 139 while (pos + 4 <= len) { 140 long rmin, rmax; 141 rangemin = (int)tlv[pos]; 142 rangemax = (int)tlv[pos + 1]; 143 err = snd_tlv_get_dB_range(tlv + pos + 2, 144 rangemin, rangemax, 145 &rmin, &rmax); 146 if (err < 0) 147 return err; 148 if (pos > 2) { 149 if (rmin < *min) 150 *min = rmin; 151 if (rmax > *max) 152 *max = rmax; 153 } else { 154 *min = rmin; 155 *max = rmax; 156 } 157 pos += int_index(tlv[pos + 3]) + 4; 158 } 159 return 0; 160 } 161 case SND_CTL_TLVT_DB_SCALE: { 162 int step; 163 *min = (int)tlv[2]; 164 step = (tlv[3] & 0xffff); 165 *max = *min + (long)(step * (rangemax - rangemin)); 166 return 0; 167 } 168 case SND_CTL_TLVT_DB_LINEAR: 169 *min = (int)tlv[2]; 170 *max = (int)tlv[3]; 171 return 0; 172 } 173 return -EINVAL; 174 } 175 176 /** 177 * \brief Convert the given raw volume value to a dB gain 178 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info() 179 * \param rangemin the minimum value of the raw volume 180 * \param rangemax the maximum value of the raw volume 181 * \param volume the raw volume value to convert 182 * \param db_gain the dB gain (in 0.01dB unit) 183 * \return 0 if successful, or a negative error code 184 */ 185 int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax, 186 long volume, long *db_gain) 187 { 188 switch (tlv[0]) { 189 case SND_CTL_TLVT_DB_RANGE: { 190 unsigned int pos, len; 191 len = int_index(tlv[1]); 192 if (len > MAX_TLV_RANGE_SIZE) 193 return -EINVAL; 194 pos = 2; 195 while (pos + 4 <= len) { 196 rangemin = (int)tlv[pos]; 197 rangemax = (int)tlv[pos + 1]; 198 if (volume >= rangemin && volume <= rangemax) 199 return snd_tlv_convert_to_dB(tlv + pos + 2, 200 rangemin, rangemax, 201 volume, db_gain); 202 pos += int_index(tlv[pos + 3]) + 4; 203 } 204 return -EINVAL; 205 } 206 case SND_CTL_TLVT_DB_SCALE: { 207 int min, step, mute; 208 min = tlv[2]; 209 step = (tlv[3] & 0xffff); 210 mute = (tlv[3] >> 16) & 1; 211 if (mute && volume == rangemin) 212 *db_gain = SND_CTL_TLV_DB_GAIN_MUTE; 213 else 214 *db_gain = (volume - rangemin) * step + min; 215 return 0; 216 } 217 #ifndef HAVE_SOFT_FLOAT 218 case SND_CTL_TLVT_DB_LINEAR: { 219 int mindb = tlv[2]; 220 int maxdb = tlv[3]; 221 if (volume <= rangemin || rangemax <= rangemin) 222 *db_gain = mindb; 223 else if (volume >= rangemax) 224 *db_gain = maxdb; 225 else { 226 double val = (double)(volume - rangemin) / 227 (double)(rangemax - rangemin); 228 if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE) 229 *db_gain = (long)(100.0 * 20.0 * log10(val)) + 230 maxdb; 231 else { 232 /* FIXME: precalculate and cache these values */ 233 double lmin = pow(10.0, mindb/2000.0); 234 double lmax = pow(10.0, maxdb/2000.0); 235 val = (lmax - lmin) * val + lmin; 236 *db_gain = (long)(100.0 * 20.0 * log10(val)); 237 } 238 } 239 return 0; 240 } 241 #endif 242 } 243 return -EINVAL; 244 } 245 246 /** 247 * \brief Convert from dB gain to the corresponding raw value 248 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info() 249 * \param rangemin the minimum value of the raw volume 250 * \param rangemax the maximum value of the raw volume 251 * \param db_gain the dB gain to convert (in 0.01dB unit) 252 * \param value the pointer to store the converted raw volume value 253 * \param xdir the direction for round-up. The value is round up 254 * when this is positive. 255 * \return 0 if successful, or a negative error code 256 */ 257 int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax, 258 long db_gain, long *value, int xdir) 259 { 260 switch (tlv[0]) { 261 case SND_CTL_TLVT_DB_RANGE: { 262 unsigned int pos, len; 263 len = int_index(tlv[1]); 264 if (len > MAX_TLV_RANGE_SIZE) 265 return -EINVAL; 266 pos = 2; 267 while (pos + 4 <= len) { 268 long dbmin, dbmax; 269 rangemin = (int)tlv[pos]; 270 rangemax = (int)tlv[pos + 1]; 271 if (!snd_tlv_get_dB_range(tlv + pos + 2, 272 rangemin, rangemax, 273 &dbmin, &dbmax) && 274 db_gain >= dbmin && db_gain <= dbmax) 275 return snd_tlv_convert_from_dB(tlv + pos + 2, 276 rangemin, rangemax, 277 db_gain, value, xdir); 278 pos += int_index(tlv[pos + 3]) + 4; 279 } 280 return -EINVAL; 281 } 282 case SND_CTL_TLVT_DB_SCALE: { 283 int min, step, max; 284 min = tlv[2]; 285 step = (tlv[3] & 0xffff); 286 max = min + (int)(step * (rangemax - rangemin)); 287 if (db_gain <= min) 288 *value = rangemin; 289 else if (db_gain >= max) 290 *value = rangemax; 291 else { 292 long v = (db_gain - min) * (rangemax - rangemin); 293 if (xdir > 0) 294 v += (max - min) - 1; 295 v = v / (max - min) + rangemin; 296 *value = v; 297 } 298 return 0; 299 } 300 #ifndef HAVE_SOFT_FLOAT 301 case SND_CTL_TLVT_DB_LINEAR: { 302 int min, max; 303 min = tlv[2]; 304 max = tlv[3]; 305 if (db_gain <= min) 306 *value = rangemin; 307 else if (db_gain >= max) 308 *value = rangemax; 309 else { 310 /* FIXME: precalculate and cache vmin and vmax */ 311 double vmin, vmax, v; 312 vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 : 313 pow(10.0, (double)min / 2000.0); 314 vmax = !max ? 1.0 : pow(10.0, (double)max / 2000.0); 315 v = pow(10.0, (double)db_gain / 2000.0); 316 v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin); 317 if (xdir > 0) 318 v = ceil(v); 319 *value = (long)v + rangemin; 320 } 321 return 0; 322 } 323 #endif 324 default: 325 break; 326 } 327 return -EINVAL; 328 } 329 330 #ifndef DOC_HIDDEN 331 #define TEMP_TLV_SIZE 4096 332 struct tlv_info { 333 long minval, maxval; 334 unsigned int *tlv; 335 unsigned int buf[TEMP_TLV_SIZE]; 336 }; 337 #endif 338 339 static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, 340 struct tlv_info *rec) 341 { 342 snd_ctl_elem_info_t *info; 343 int err; 344 345 snd_ctl_elem_info_alloca(&info); 346 snd_ctl_elem_info_set_id(info, id); 347 err = snd_ctl_elem_info(ctl, info); 348 if (err < 0) 349 return err; 350 if (!snd_ctl_elem_info_is_tlv_readable(info)) 351 return -EINVAL; 352 if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER) 353 return -EINVAL; 354 rec->minval = snd_ctl_elem_info_get_min(info); 355 rec->maxval = snd_ctl_elem_info_get_max(info); 356 err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf)); 357 if (err < 0) 358 return err; 359 err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv); 360 if (err < 0) 361 return err; 362 return 0; 363 } 364 365 /** 366 * \brief Get the dB min/max values on the given control element 367 * \param ctl the control handler 368 * \param id the element id 369 * \param volume the raw volume value to convert 370 * \param min the pointer to store the minimum dB value (in 0.01dB unit) 371 * \param max the pointer to store the maximum dB value (in 0.01dB unit) 372 * \return 0 if successful, or a negative error code 373 */ 374 int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, 375 long *min, long *max) 376 { 377 struct tlv_info info; 378 int err; 379 380 err = get_tlv_info(ctl, id, &info); 381 if (err < 0) 382 return err; 383 return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval, 384 min, max); 385 } 386 387 /** 388 * \brief Convert the volume value to dB on the given control element 389 * \param ctl the control handler 390 * \param id the element id 391 * \param volume the raw volume value to convert 392 * \param db_gain the dB gain (in 0.01dB unit) 393 * \return 0 if successful, or a negative error code 394 */ 395 int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, 396 long volume, long *db_gain) 397 { 398 struct tlv_info info; 399 int err; 400 401 err = get_tlv_info(ctl, id, &info); 402 if (err < 0) 403 return err; 404 return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval, 405 volume, db_gain); 406 } 407 408 /** 409 * \brief Convert from dB gain to the raw volume value on the given control element 410 * \param ctl the control handler 411 * \param id the element id 412 * \param db_gain the dB gain to convert (in 0.01dB unit) 413 * \param value the pointer to store the converted raw volume value 414 * \param xdir the direction for round-up. The value is round up 415 * when this is positive. 416 * \return 0 if successful, or a negative error code 417 */ 418 int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, 419 long db_gain, long *value, int xdir) 420 { 421 struct tlv_info info; 422 int err; 423 424 err = get_tlv_info(ctl, id, &info); 425 if (err < 0) 426 return err; 427 return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval, 428 db_gain, value, xdir); 429 } 430