"Tone Control",
"3D Control",
"PCM",
+ "Front",
"Surround",
"Center",
"LFE",
return count;
}
-static int snd_hctl_elem_decode_tlv_db_gain(unsigned int *tlv, unsigned int tlv_size, long volume, long *db_gain)
-{
- unsigned int type;
- unsigned int size;
- unsigned int idx = 0;
- int err = 0;
-
- type = tlv[idx++];
- size = tlv[idx++];
- tlv_size -= 2 * sizeof(unsigned int);
- if (size > tlv_size) {
- err = -EINVAL;
- goto __exit_decode_db_gain;
- }
- switch (type) {
- case SND_CTL_TLVT_CONTAINER:
- size += sizeof(unsigned int) - 1;
- size /= sizeof(unsigned int);
- while (idx < size) {
- if (tlv[idx+1] > (size - idx) * sizeof(unsigned int)) {
- err = -EINVAL;
- goto __exit_decode_db_gain;
- }
- err = snd_hctl_elem_decode_tlv_db_gain(tlv + idx, tlv[idx+1], volume, db_gain);
- if (!err)
- break; /* db_gain obtained */
- idx += 2 + (tlv[1] + sizeof(unsigned int) - 1) / sizeof(unsigned int);
- }
- break;
- case SND_CTL_TLVT_DB_SCALE:
- if (size != 2 * sizeof(unsigned int)) {
-#if 0
- while (size > 0) {
- printf("0x%x", tlv[idx++]);
- size -= sizeof(unsigned int);
- }
- printf("\n");
-#endif
- } else {
- int min,step,mute;
- min = tlv[2];
- step = (tlv[3] & 0xffff);
- mute = (tlv[3] >> 16) & 1;
- *db_gain = (volume * step) + min;
- if (mute && (volume == 0))
- *db_gain = -9999999;
- }
- break;
- default:
-#if 0
- printf("unk-%i-", type);
- while (size > 0) {
- printf("0x%x", tlv[idx++]);
- size -= sizeof(unsigned int);
- }
- printf("\n");
-#endif
- break;
- }
-
- __exit_decode_db_gain:
- return err;
-}
-
-/**
- * \brief Get db_gain information for an HCTL element
- * \param elem HCTL element
- * \param volume The current control volume
- * \param db_gain The return value in db_gain scale
- * \return 0 otherwise a negative error code on failure
- */
-int snd_hctl_elem_get_db_gain(snd_hctl_elem_t *elem, long volume, long *db_gain)
-{
- snd_ctl_elem_info_t *info;
- unsigned int *tlv;
- unsigned int tlv_size = 4096;
- int err;
-
- snd_ctl_elem_info_alloca(&info);
- err = snd_hctl_elem_info(elem, info);
- if (err < 0)
- return err;
-
- if (tlv_size < 2 * sizeof(unsigned int)) {
- err = -EINVAL;
- goto __exit_db_gain;
- }
- if (!snd_ctl_elem_info_is_tlv_readable(info)) {
- err = -EINVAL;
- goto __exit_db_gain;
- }
- tlv = malloc(tlv_size);
- if (tlv == NULL) {
- err = -ENOMEM;
- goto __exit_db_gain;
- }
- if ((err = snd_hctl_elem_tlv_read(elem, tlv, tlv_size)) < 0)
- goto __free_exit_db_gain;
- err = snd_hctl_elem_decode_tlv_db_gain(tlv, tlv_size, volume, db_gain);
- __free_exit_db_gain:
- free(tlv);
- __exit_db_gain:
- return err;
-}
-
-
-
-
-
-
/**
* \brief Get information for an HCTL element
* \param elem HCTL element
sm_selem_t selem;
selem_ctl_t ctls[CTL_LAST + 1];
unsigned int capture_item;
- struct {
+ struct selem_str {
unsigned int range: 1; /* Forced range */
+ unsigned int db_initialized: 1;
+ unsigned int db_init_error: 1;
long min, max;
unsigned int channels;
long vol[32];
unsigned int sw;
+ unsigned int *db_info;
} str[2];
} selem_none_t;
assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
if (simple->selem.id)
snd_mixer_selem_id_free(simple->selem.id);
+ /* free db range information */
+ free(simple->str[0].db_info);
+ free(simple->str[1].db_info);
free(simple);
}
return 0;
}
+/* convert to index of integer array */
+#define int_index(size) (((size) + sizeof(int) - 1) / sizeof(int))
+
+/* parse TLV stream and retrieve dB information
+ * return 0 if successly found and stored to rec,
+ * return 1 if no information is found,
+ * or return a negative error code
+ */
+static int parse_db_range(struct selem_str *rec, unsigned int *tlv,
+ unsigned int tlv_size)
+{
+ unsigned int type;
+ unsigned int size;
+ int err;
+
+ 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 = parse_db_range(rec, tlv, size);
+ if (err <= 0)
+ return err; /* error or found dB */
+ len = int_index(tlv[1]) + 2;
+ size -= len * sizeof(int);
+ tlv += len;
+ }
+ break;
+ case SND_CTL_TLVT_DB_SCALE:
+ rec->db_info = malloc(size + sizeof(int) * 2);
+ if (! rec->db_info)
+ return -ENOMEM;
+ memcpy(rec->db_info, tlv, size + sizeof(int) * 2);
+ return 0;
+ default:
+ break;
+ }
+ return 1; /* not found */
+}
+
+/* convert the given raw volume value to a dB gain
+ */
+static int convert_db_range(struct selem_str *rec, long volume, long *db_gain)
+{
+ int min, step, mute;
+
+ switch (rec->db_info[0]) {
+ case SND_CTL_TLVT_DB_SCALE:
+ min = rec->db_info[2];
+ step = (rec->db_info[3] & 0xffff);
+ mute = (rec->db_info[3] >> 16) & 1;
+ if (mute && volume == rec->min)
+ *db_gain = -9999999;
+ else
+ *db_gain = (volume - rec->min) * step + min;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/* initialize dB range information, reading TLV via hcontrol
+ */
+static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec)
+{
+ snd_ctl_elem_info_t *info;
+ unsigned int *tlv = NULL;
+ const unsigned int tlv_size = 4096;
+
+ snd_ctl_elem_info_alloca(&info);
+ if (snd_hctl_elem_info(ctl, info) < 0)
+ goto error;
+ if (! snd_ctl_elem_info_is_tlv_readable(info))
+ goto error;
+ tlv = malloc(tlv_size);
+ if (! tlv)
+ return -ENOMEM;
+ if (snd_hctl_elem_tlv_read(ctl, tlv, tlv_size) < 0)
+ goto error;
+ if (parse_db_range(rec, tlv, tlv_size) < 0)
+ goto error;
+ free(tlv);
+ rec->db_initialized = 1;
+ return 0;
+
+ error:
+ free(tlv);
+ rec->db_init_error = 1;
+ return -EINVAL;
+}
+
+static int get_dB_gain(snd_hctl_elem_t *ctl, struct selem_str *rec,
+ long volume, long *db_gain)
+{
+ if (rec->db_init_error)
+ return -EINVAL;
+ if (! rec->db_initialized) {
+ if (init_db_range(ctl, rec) < 0)
+ return -EINVAL;
+ }
+ return convert_db_range(rec, volume, db_gain);
+}
+
static int get_dB_ops(snd_mixer_elem_t *elem,
int dir,
snd_mixer_selem_channel_id_t channel,
int err = -EINVAL;
long volume, db_gain;
- if (dir == SM_PLAY) {
+ if (dir == SM_PLAY)
c = &s->ctls[CTL_PLAYBACK_VOLUME];
- if (c->type != 2)
- goto _err;
- } else if (dir == SM_CAPT) {
+ else if (dir == SM_CAPT)
c = &s->ctls[CTL_CAPTURE_VOLUME];
- if (c->type != 2)
- goto _err;
- } else {
+ else
goto _err;
- }
- err = get_volume_ops(elem, dir, channel, &volume);
- if (err < 0)
+ if (c->type != 2)
goto _err;
- err = snd_hctl_elem_get_db_gain(c->elem, volume, &db_gain);
- if (err < 0)
+ if ((err = get_volume_ops(elem, dir, channel, &volume)) < 0)
+ goto _err;
+ if ((err = get_dB_gain(c->elem, &s->str[dir], volume, &db_gain)) < 0)
goto _err;
err = 0;
*value = db_gain;
-_err:
- //if (err) printf("get_dB_ops:err=%d\n",err);
+ _err:
return err;
}