mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-11-04 13:30:08 -05:00
Inner this library, layouts of all structures are public. At a compilation time, each size of the structures can be calculated. It means that we can use automatic variable instead of calling alloca(3) to program this library because in both ways storages are kept on stack frame of process VMA. Besides, the usage of automatic variables requires less instructions than calls of alloca(3). Furthermore, alloca(3) is not described in any C language standards. This commit replaces calls of alloca(3) just for structures with automatic variables, for control features. Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Takashi Iwai <tiwai@suse.de>
497 lines
13 KiB
C
497 lines
13 KiB
C
/**
|
|
* \file control/tlv.c
|
|
* \brief dB conversion functions from control TLV information
|
|
* \author Takashi Iwai <tiwai@suse.de>
|
|
* \date 2007
|
|
*/
|
|
/*
|
|
* Control Interface - dB conversion functions from control TLV information
|
|
*
|
|
* Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#ifndef HAVE_SOFT_FLOAT
|
|
#include <math.h>
|
|
#endif
|
|
#include "control_local.h"
|
|
|
|
#ifndef DOC_HIDDEN
|
|
/* convert to index of integer array */
|
|
#define int_index(size) (((size) + sizeof(int) - 1) / sizeof(int))
|
|
/* max size of a TLV entry for dB information (including compound one) */
|
|
#define MAX_TLV_RANGE_SIZE 256
|
|
#endif
|
|
|
|
/**
|
|
* \brief Parse TLV stream and retrieve dB information
|
|
* \param tlv the TLV source
|
|
* \param tlv_size the byte size of TLV source
|
|
* \param db_tlvp the pointer stored the dB TLV information
|
|
* \return the byte size of dB TLV information if found in the given
|
|
* TLV source, or a negative error code.
|
|
*
|
|
* This function parses the given TLV source and stores the TLV start
|
|
* point if the TLV information regarding dB conversion is found.
|
|
* The stored TLV pointer can be passed to the convesion functions
|
|
* #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
|
|
* #snd_tlv_get_dB_range().
|
|
*/
|
|
int snd_tlv_parse_dB_info(unsigned int *tlv,
|
|
unsigned int tlv_size,
|
|
unsigned int **db_tlvp)
|
|
{
|
|
unsigned int type;
|
|
unsigned int size;
|
|
int err;
|
|
|
|
*db_tlvp = NULL;
|
|
type = tlv[0];
|
|
size = tlv[1];
|
|
tlv_size -= 2 * sizeof(int);
|
|
if (size > tlv_size) {
|
|
SNDERR("TLV size error");
|
|
return -EINVAL;
|
|
}
|
|
switch (type) {
|
|
case SND_CTL_TLVT_CONTAINER:
|
|
size = int_index(size) * sizeof(int);
|
|
tlv += 2;
|
|
while (size > 0) {
|
|
unsigned int len;
|
|
err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
|
|
if (err < 0)
|
|
return err; /* error */
|
|
if (err > 0)
|
|
return err; /* found */
|
|
len = int_index(tlv[1]) + 2;
|
|
size -= len * sizeof(int);
|
|
tlv += len;
|
|
}
|
|
break;
|
|
case SND_CTL_TLVT_DB_SCALE:
|
|
case SND_CTL_TLVT_DB_MINMAX:
|
|
case SND_CTL_TLVT_DB_MINMAX_MUTE:
|
|
#ifndef HAVE_SOFT_FLOAT
|
|
case SND_CTL_TLVT_DB_LINEAR:
|
|
#endif
|
|
case SND_CTL_TLVT_DB_RANGE: {
|
|
unsigned int minsize;
|
|
if (type == SND_CTL_TLVT_DB_RANGE)
|
|
minsize = 4 * sizeof(int);
|
|
else
|
|
minsize = 2 * sizeof(int);
|
|
if (size < minsize) {
|
|
SNDERR("Invalid dB_scale TLV size");
|
|
return -EINVAL;
|
|
}
|
|
if (size > MAX_TLV_RANGE_SIZE) {
|
|
SNDERR("Too big dB_scale TLV size: %d", size);
|
|
return -EINVAL;
|
|
}
|
|
*db_tlvp = tlv;
|
|
return size + sizeof(int) * 2;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return -EINVAL; /* not found */
|
|
}
|
|
|
|
/**
|
|
* \brief Get the dB min/max values
|
|
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
|
|
* \param rangemin the minimum value of the raw volume
|
|
* \param rangemax the maximum value of the raw volume
|
|
* \param min the pointer to store the minimum dB value (in 0.01dB unit)
|
|
* \param max the pointer to store the maximum dB value (in 0.01dB unit)
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
|
|
long *min, long *max)
|
|
{
|
|
int err;
|
|
|
|
switch (tlv[0]) {
|
|
case SND_CTL_TLVT_DB_RANGE: {
|
|
unsigned int pos, len;
|
|
len = int_index(tlv[1]);
|
|
if (len > MAX_TLV_RANGE_SIZE)
|
|
return -EINVAL;
|
|
pos = 2;
|
|
while (pos + 4 <= len) {
|
|
long rmin, rmax;
|
|
long submin, submax;
|
|
submin = (int)tlv[pos];
|
|
submax = (int)tlv[pos + 1];
|
|
if (rangemax < submax)
|
|
submax = rangemax;
|
|
err = snd_tlv_get_dB_range(tlv + pos + 2,
|
|
submin, submax,
|
|
&rmin, &rmax);
|
|
if (err < 0)
|
|
return err;
|
|
if (pos > 2) {
|
|
if (rmin < *min)
|
|
*min = rmin;
|
|
if (rmax > *max)
|
|
*max = rmax;
|
|
} else {
|
|
*min = rmin;
|
|
*max = rmax;
|
|
}
|
|
if (rangemax == submax)
|
|
return 0;
|
|
pos += int_index(tlv[pos + 3]) + 4;
|
|
}
|
|
return 0;
|
|
}
|
|
case SND_CTL_TLVT_DB_SCALE: {
|
|
int step;
|
|
if (tlv[3] & 0x10000)
|
|
*min = SND_CTL_TLV_DB_GAIN_MUTE;
|
|
else
|
|
*min = (int)tlv[2];
|
|
step = (tlv[3] & 0xffff);
|
|
*max = (int)tlv[2] + step * (rangemax - rangemin);
|
|
return 0;
|
|
}
|
|
case SND_CTL_TLVT_DB_MINMAX:
|
|
case SND_CTL_TLVT_DB_LINEAR:
|
|
*min = (int)tlv[2];
|
|
*max = (int)tlv[3];
|
|
return 0;
|
|
case SND_CTL_TLVT_DB_MINMAX_MUTE:
|
|
*min = SND_CTL_TLV_DB_GAIN_MUTE;
|
|
*max = (int)tlv[3];
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* \brief Convert the given raw volume value to a dB gain
|
|
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
|
|
* \param rangemin the minimum value of the raw volume
|
|
* \param rangemax the maximum value of the raw volume
|
|
* \param volume the raw volume value to convert
|
|
* \param db_gain the dB gain (in 0.01dB unit)
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
|
|
long volume, long *db_gain)
|
|
{
|
|
switch (tlv[0]) {
|
|
case SND_CTL_TLVT_DB_RANGE: {
|
|
unsigned int pos, len;
|
|
len = int_index(tlv[1]);
|
|
if (len > MAX_TLV_RANGE_SIZE)
|
|
return -EINVAL;
|
|
pos = 2;
|
|
while (pos + 4 <= len) {
|
|
rangemin = (int)tlv[pos];
|
|
rangemax = (int)tlv[pos + 1];
|
|
if (volume >= rangemin && volume <= rangemax)
|
|
return snd_tlv_convert_to_dB(tlv + pos + 2,
|
|
rangemin, rangemax,
|
|
volume, db_gain);
|
|
pos += int_index(tlv[pos + 3]) + 4;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
case SND_CTL_TLVT_DB_SCALE: {
|
|
int min, step, mute;
|
|
min = tlv[2];
|
|
step = (tlv[3] & 0xffff);
|
|
mute = (tlv[3] >> 16) & 1;
|
|
if (mute && volume <= rangemin)
|
|
*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
|
|
else
|
|
*db_gain = (volume - rangemin) * step + min;
|
|
return 0;
|
|
}
|
|
case SND_CTL_TLVT_DB_MINMAX:
|
|
case SND_CTL_TLVT_DB_MINMAX_MUTE: {
|
|
int mindb, maxdb;
|
|
mindb = tlv[2];
|
|
maxdb = tlv[3];
|
|
if (volume <= rangemin || rangemax <= rangemin) {
|
|
if (tlv[0] == SND_CTL_TLVT_DB_MINMAX_MUTE)
|
|
*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
|
|
else
|
|
*db_gain = mindb;
|
|
} else if (volume >= rangemax)
|
|
*db_gain = maxdb;
|
|
else
|
|
*db_gain = (maxdb - mindb) * (volume - rangemin) /
|
|
(rangemax - rangemin) + mindb;
|
|
return 0;
|
|
}
|
|
#ifndef HAVE_SOFT_FLOAT
|
|
case SND_CTL_TLVT_DB_LINEAR: {
|
|
int mindb = tlv[2];
|
|
int maxdb = tlv[3];
|
|
if (volume <= rangemin || rangemax <= rangemin)
|
|
*db_gain = mindb;
|
|
else if (volume >= rangemax)
|
|
*db_gain = maxdb;
|
|
else {
|
|
double val = (double)(volume - rangemin) /
|
|
(double)(rangemax - rangemin);
|
|
if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
|
|
*db_gain = (long)(100.0 * 20.0 * log10(val)) +
|
|
maxdb;
|
|
else {
|
|
/* FIXME: precalculate and cache these values */
|
|
double lmin = pow(10.0, mindb/2000.0);
|
|
double lmax = pow(10.0, maxdb/2000.0);
|
|
val = (lmax - lmin) * val + lmin;
|
|
*db_gain = (long)(100.0 * 20.0 * log10(val));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* \brief Convert from dB gain to the corresponding raw value
|
|
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
|
|
* \param rangemin the minimum value of the raw volume
|
|
* \param rangemax the maximum value of the raw volume
|
|
* \param db_gain the dB gain to convert (in 0.01dB unit)
|
|
* \param value the pointer to store the converted raw volume value
|
|
* \param xdir the direction for round-up. The value is round up
|
|
* when this is positive.
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
|
|
long db_gain, long *value, int xdir)
|
|
{
|
|
switch (tlv[0]) {
|
|
case SND_CTL_TLVT_DB_RANGE: {
|
|
long dbmin, dbmax, prev_submax;
|
|
unsigned int pos, len;
|
|
len = int_index(tlv[1]);
|
|
if (len < 6 || len > MAX_TLV_RANGE_SIZE)
|
|
return -EINVAL;
|
|
pos = 2;
|
|
prev_submax = 0;
|
|
while (pos + 4 <= len) {
|
|
long submin, submax;
|
|
submin = (int)tlv[pos];
|
|
submax = (int)tlv[pos + 1];
|
|
if (rangemax < submax)
|
|
submax = rangemax;
|
|
if (!snd_tlv_get_dB_range(tlv + pos + 2,
|
|
submin, submax,
|
|
&dbmin, &dbmax) &&
|
|
db_gain >= dbmin && db_gain <= dbmax)
|
|
return snd_tlv_convert_from_dB(tlv + pos + 2,
|
|
submin, submax,
|
|
db_gain, value, xdir);
|
|
else if (db_gain < dbmin) {
|
|
*value = xdir > 0 || pos == 2 ? submin : prev_submax;
|
|
return 0;
|
|
}
|
|
prev_submax = submax;
|
|
if (rangemax == submax)
|
|
break;
|
|
pos += int_index(tlv[pos + 3]) + 4;
|
|
}
|
|
*value = prev_submax;
|
|
return 0;
|
|
}
|
|
case SND_CTL_TLVT_DB_SCALE: {
|
|
int min, step, max;
|
|
min = tlv[2];
|
|
step = (tlv[3] & 0xffff);
|
|
max = min + (int)(step * (rangemax - rangemin));
|
|
if (db_gain <= min)
|
|
if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
|
|
(tlv[3] & 0x10000))
|
|
*value = rangemin + 1;
|
|
else
|
|
*value = rangemin;
|
|
else if (db_gain >= max)
|
|
*value = rangemax;
|
|
else {
|
|
long v = (db_gain - min) * (rangemax - rangemin);
|
|
if (xdir > 0)
|
|
v += (max - min) - 1;
|
|
v = v / (max - min) + rangemin;
|
|
*value = v;
|
|
}
|
|
return 0;
|
|
}
|
|
case SND_CTL_TLVT_DB_MINMAX:
|
|
case SND_CTL_TLVT_DB_MINMAX_MUTE: {
|
|
int min, max;
|
|
min = tlv[2];
|
|
max = tlv[3];
|
|
if (db_gain <= min)
|
|
if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
|
|
tlv[0] == SND_CTL_TLVT_DB_MINMAX_MUTE)
|
|
*value = rangemin + 1;
|
|
else
|
|
*value = rangemin;
|
|
else if (db_gain >= max)
|
|
*value = rangemax;
|
|
else {
|
|
long v = (db_gain - min) * (rangemax - rangemin);
|
|
if (xdir > 0)
|
|
v += (max - min) - 1;
|
|
v = v / (max - min) + rangemin;
|
|
*value = v;
|
|
}
|
|
return 0;
|
|
}
|
|
#ifndef HAVE_SOFT_FLOAT
|
|
case SND_CTL_TLVT_DB_LINEAR: {
|
|
int min, max;
|
|
min = tlv[2];
|
|
max = tlv[3];
|
|
if (db_gain <= min)
|
|
*value = rangemin;
|
|
else if (db_gain >= max)
|
|
*value = rangemax;
|
|
else {
|
|
/* FIXME: precalculate and cache vmin and vmax */
|
|
double vmin, vmax, v;
|
|
vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
|
|
pow(10.0, (double)min / 2000.0);
|
|
vmax = !max ? 1.0 : pow(10.0, (double)max / 2000.0);
|
|
v = pow(10.0, (double)db_gain / 2000.0);
|
|
v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
|
|
if (xdir > 0)
|
|
v = ceil(v);
|
|
*value = (long)v + rangemin;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifndef DOC_HIDDEN
|
|
#define TEMP_TLV_SIZE 4096
|
|
struct tlv_info {
|
|
long minval, maxval;
|
|
unsigned int *tlv;
|
|
unsigned int buf[TEMP_TLV_SIZE];
|
|
};
|
|
#endif
|
|
|
|
static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
|
|
struct tlv_info *rec)
|
|
{
|
|
snd_ctl_elem_info_t info = {0};
|
|
int err;
|
|
|
|
snd_ctl_elem_info_set_id(&info, id);
|
|
err = snd_ctl_elem_info(ctl, &info);
|
|
if (err < 0)
|
|
return err;
|
|
if (!snd_ctl_elem_info_is_tlv_readable(&info))
|
|
return -EINVAL;
|
|
if (snd_ctl_elem_info_get_type(&info) != SND_CTL_ELEM_TYPE_INTEGER)
|
|
return -EINVAL;
|
|
rec->minval = snd_ctl_elem_info_get_min(&info);
|
|
rec->maxval = snd_ctl_elem_info_get_max(&info);
|
|
err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
|
|
if (err < 0)
|
|
return err;
|
|
err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
|
|
if (err < 0)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Get the dB min/max values on the given control element
|
|
* \param ctl the control handler
|
|
* \param id the element id
|
|
* \param min the pointer to store the minimum dB value (in 0.01dB unit)
|
|
* \param max the pointer to store the maximum dB value (in 0.01dB unit)
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
|
|
long *min, long *max)
|
|
{
|
|
struct tlv_info info;
|
|
int err;
|
|
|
|
err = get_tlv_info(ctl, id, &info);
|
|
if (err < 0)
|
|
return err;
|
|
return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
|
|
min, max);
|
|
}
|
|
|
|
/**
|
|
* \brief Convert the volume value to dB on the given control element
|
|
* \param ctl the control handler
|
|
* \param id the element id
|
|
* \param volume the raw volume value to convert
|
|
* \param db_gain the dB gain (in 0.01dB unit)
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
|
|
long volume, long *db_gain)
|
|
{
|
|
struct tlv_info info;
|
|
int err;
|
|
|
|
err = get_tlv_info(ctl, id, &info);
|
|
if (err < 0)
|
|
return err;
|
|
return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
|
|
volume, db_gain);
|
|
}
|
|
|
|
/**
|
|
* \brief Convert from dB gain to the raw volume value on the given control element
|
|
* \param ctl the control handler
|
|
* \param id the element id
|
|
* \param db_gain the dB gain to convert (in 0.01dB unit)
|
|
* \param value the pointer to store the converted raw volume value
|
|
* \param xdir the direction for round-up. The value is round up
|
|
* when this is positive.
|
|
* \return 0 if successful, or a negative error code
|
|
*/
|
|
int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
|
|
long db_gain, long *value, int xdir)
|
|
{
|
|
struct tlv_info info;
|
|
int err;
|
|
|
|
err = get_tlv_info(ctl, id, &info);
|
|
if (err < 0)
|
|
return err;
|
|
return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
|
|
db_gain, value, xdir);
|
|
}
|