[PATCH] UBUNTU: SAUCE: hda-intel volume remapping for non-linear volume scales Bug: #249233

Colin Ian King colin.king at canonical.com
Wed Apr 22 17:25:41 BST 2009


This fixes volume scaling on a variety of Realtek chips. One can
select linear or parabolic volume remapping to scale (remap) a
volume level to physical volume level between a desired minimum
and maximum range. This allows adjustment if the volume is not audible
at low settings or distorts if set too high.

Linear mapping scales the volume linearly between a given low and high.

Parabolic mapping scales the volume between a given low and high
so that low volume levels are scaled more favourably (using an inverse
squared parabolic function) than high volume ranges to overcome
non-linear volume scaling found on Realtek model cw020 hardware.

The default is no mapping whatsoever.

Signed-off-by: Colin Ian King <colin.king at canonical.com>
---
 ubuntu/sound/alsa-kernel/pci/hda/hda_intel.c |   23 +++++++
 ubuntu/sound/alsa-kernel/pci/hda/hda_local.h |    7 ++
 ubuntu/sound/alsa-kernel/pci/hda/vmaster.c   |   84 +++++++++++++++++++++++++-
 3 files changed, 111 insertions(+), 3 deletions(-)

diff --git a/ubuntu/sound/alsa-kernel/pci/hda/hda_intel.c b/ubuntu/sound/alsa-kernel/pci/hda/hda_intel.c
index 39bc3c1..7f9c530 100644
--- a/ubuntu/sound/alsa-kernel/pci/hda/hda_intel.c
+++ b/ubuntu/sound/alsa-kernel/pci/hda/hda_intel.c
@@ -58,6 +58,10 @@ static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1};
 static int single_cmd;
 static int enable_msi;
 
+int volume_remap;
+int volume_offset_min;
+int volume_offset_max = 100;
+
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
 module_param_array(id, charp, NULL, 0444);
@@ -76,6 +80,15 @@ MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs "
 		 "(for debugging only).");
 module_param(enable_msi, int, 0444);
 MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)");
+module_param(volume_remap, int, 0444);
+MODULE_PARM_DESC(volume_remap, "Enable volume remapping "
+		 "(0 = off, 1 = linear, 2 = parabolic).");
+module_param(volume_offset_min, int, 0444);
+MODULE_PARM_DESC(volume_offset_min,
+		 "volume minimum offset when remapping enabled.");
+module_param(volume_offset_max, int, 0444);
+MODULE_PARM_DESC(volume_offset_max,
+		 "volume maximum offset when remapping enabled.");
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 /* power_save option is defined in hda_codec.c */
@@ -1966,6 +1979,16 @@ static int __devinit azx_probe(struct pci_dev *pci,
 		return -ENOENT;
 	}
 
+	/* avoid overflow */
+	if (volume_offset_max < 0)
+		volume_offset_max = 0;
+	if (volume_offset_max > 100)
+		volume_offset_max = 100;
+	if (volume_offset_min < 0)
+		volume_offset_min = 0;
+	if (volume_offset_min > volume_offset_max)
+		volume_offset_min = volume_offset_max;
+
 	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
 	if (!card) {
 		snd_printk(KERN_ERR SFX "Error creating card!\n");
diff --git a/ubuntu/sound/alsa-kernel/pci/hda/hda_local.h b/ubuntu/sound/alsa-kernel/pci/hda/hda_local.h
index 99e797a..a19bf25 100644
--- a/ubuntu/sound/alsa-kernel/pci/hda/hda_local.h
+++ b/ubuntu/sound/alsa-kernel/pci/hda/hda_local.h
@@ -484,5 +484,12 @@ void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
 struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
 						 const unsigned int *tlv);
 int snd_ctl_add_slave(struct snd_kcontrol *master, struct snd_kcontrol *slave);
+
+/*
+ * volume scaling settings
+ */
+extern int volume_remap;
+extern int volume_offset_min;
+extern int volume_offset_max;
 		      
 #endif /* __SOUND_HDA_LOCAL_H */
diff --git a/ubuntu/sound/alsa-kernel/pci/hda/vmaster.c b/ubuntu/sound/alsa-kernel/pci/hda/vmaster.c
index 2da49d2..ed5e3ca 100644
--- a/ubuntu/sound/alsa-kernel/pci/hda/vmaster.c
+++ b/ubuntu/sound/alsa-kernel/pci/hda/vmaster.c
@@ -13,6 +13,9 @@
 #include <sound/core.h>
 #include <sound/control.h>
 
+#include "hda_codec.h"
+#include "hda_local.h"
+
 /*
  * a subset of information returned via ctl info callback
  */
@@ -34,6 +37,7 @@ struct link_master {
 	struct list_head slaves;
 	struct link_ctl_info info;
 	int val;		/* the master value */
+	struct mutex val_mutex;	/* master value mutex lock */
 };
 
 /*
@@ -109,12 +113,66 @@ static int master_init(struct link_master *master)
 		master->info = slave->info;
 		master->info.count = 1; /* always mono */
 		/* set full volume as default (= no attenuation) */
+		mutex_lock(&master->val_mutex);
 		master->val = master->info.max_val;
+		mutex_unlock(&master->val_mutex);
 		return 0;
 	}
 	return -ENOENT;
 }
 
+unsigned int vol_remap_parabolic(unsigned int min, unsigned int max,
+				 int offset_percent_min, int offset_percent_max,
+				 unsigned int original)
+{
+	unsigned int range = max - min;
+	unsigned int mapped;
+
+	if (range == 0)
+		mapped = max;
+	else {
+		unsigned int offset_min = (range * offset_percent_min) / 100;
+		unsigned int offset_max = (range * offset_percent_max) / 100;
+		mapped = offset_min + ((int_sqrt(range * (original - min)) *
+				       (offset_max - offset_min)) / range);
+	}
+	return mapped;
+}
+
+unsigned int vol_remap_linear(unsigned int min, unsigned int max,
+			      int offset_percent_min, int offset_percent_max,
+			      unsigned int original)
+{
+	unsigned int range = max - min;
+	unsigned int mapped;
+
+	if (range == 0)
+		mapped = max;
+	else {
+		unsigned int offset_min = (range * offset_percent_min) / 100;
+		unsigned int offset_max = (range * offset_percent_max) / 100;
+		mapped = offset_min + (((original-min) *
+				       (offset_max - offset_min)) / range);
+	}
+	return mapped;
+}
+
+unsigned int vol_remap(unsigned int min, unsigned int max,
+		       unsigned int original)
+{
+	switch (volume_remap) {
+	case 2:
+		return vol_remap_parabolic(min, max, volume_offset_min,
+					   volume_offset_max, original);
+	case 1:
+		return vol_remap_linear(min, max, volume_offset_min,
+					volume_offset_max, original);
+	case 0:
+	default:
+		return original;
+	}
+}
+
 static int slave_get_val(struct link_slave *slave,
 			 struct snd_ctl_elem_value *ucontrol)
 {
@@ -279,7 +337,11 @@ static int master_get(struct snd_kcontrol *kcontrol,
 	int err = master_init(master);
 	if (err < 0)
 		return err;
+
+	mutex_lock(&master->val_mutex);
 	ucontrol->value.integer.value[0] = master->val;
+	mutex_unlock(&master->val_mutex);
+
 	return 0;
 }
 
@@ -294,20 +356,35 @@ static int master_put(struct snd_kcontrol *kcontrol,
 	err = master_init(master);
 	if (err < 0)
 		return err;
+
+	mutex_lock(&master->val_mutex);
 	old_val = master->val;
-	if (ucontrol->value.integer.value[0] == old_val)
+	if (ucontrol->value.integer.value[0] == old_val) {
+		mutex_unlock(&master->val_mutex);
 		return 0;
+	}
 
 	uval = kmalloc(sizeof(*uval), GFP_KERNEL);
-	if (!uval)
+	if (!uval) {
+		mutex_unlock(&master->val_mutex);
 		return -ENOMEM;
+	}
+
 	list_for_each_entry(slave, &master->slaves, list) {
 		master->val = old_val;
 		uval->id = slave->slave.id;
 		slave_get_val(slave, uval);
-		master->val = ucontrol->value.integer.value[0];
+
+		/* Put to slaves a remapped volume */
+		master->val = vol_remap(master->info.min_val,
+					master->info.max_val,
+					ucontrol->value.integer.value[0]);
 		slave_put_val(slave, uval);
+		/* And actually save the original unremapped volume
+		   for master_get() */
+		master->val = ucontrol->value.integer.value[0];
 	}
+	mutex_unlock(&master->val_mutex);
 	kfree(uval);
 	return 1;
 }
@@ -342,6 +419,7 @@ struct snd_kcontrol *snd_ctl_make_virtual_master(char *name,
 	if (!master)
 		return NULL;
 	INIT_LIST_HEAD(&master->slaves);
+	mutex_init(&master->val_mutex);
 
 	kctl = snd_ctl_new1(&knew, master);
 	if (!kctl) {
-- 
1.5.4.3


--=-/qQ7kPxC0mv5vgunQwMd--




More information about the kernel-team mailing list