Message ID | D4F4479A3531074699A555716DB7F84A9D966E9522@ABGEX71E.FSC.NET |
---|---|
State | New |
Headers | show |
Hi ----- Original Message ----- > I see a problem with input volume control on a Windows7 guest > using the qemu Intel HDA codec. In short, moving the volume slider for > the input volume from 0% to 1% under Windows results in > the "gain" values in the emulated HW to jump from 0 to 40 (out of 74) > (looking at "st->gain_[left|right]" in hda_audio_command()). This makes > it impossible to control sound volume in the low-gain range using the > windows guest. > > Here is a table of Windows slider position vs. actual gain value to > illustrate the problem ("0.5" is a slider position between 0 and 1 that > can be reached with skillful slider shifting, windows still displays the > numerical value "0"): > > slider gain > 0.5 34 > 1 40 > 2 43 > 3 46 > 5 48 > 10 54 > 20 60 > 50 68 > 75 71 > 89 73 > 99 73 > 100 74 > > For output, the situation is similar but less (1% on Win7 corresponds to > a gain value of 19/74). > > I am using the spice driver connected to pulseaudio to control the > volume in the host (PA adds another level of complication which I am > going to address in a posting to pulseaudio-discuss soon). The spice- > > > > I am using a Win7(32bit) guest on a Fedora 21 host. > qemu-system-x86-2.1.3-6.fc21.x86_64 > spice-gtk3-0.27-6.fc21.x86_64 > pulseaudio-6.0-2.fc21.1.x86_64 > > By using low-level controls in a Linux VM (e.g. "amixer -c 0 set Capture > $x), I was able to set the gain levels to any value $x between 0 and 74, > just as one would expect. So this is not a problem of QEMU alone. > Rather, it's related to how the Windows HDA codec driver and the QEMU > emulated HW interact. For the Windows side I can only guess, but it > seems that Windows uses a ~30dB overall scale for the min-max range of > the input volume slider (and ~60dB for output). I'm not sure if that's > general Win7 behavior or related to the driver. According to > https://msdn.microsoft.com/en-us/library/windows/hardware/ff536251% > 28v=vs.85%29.aspx, audio drivers can set volume-related registry values, > but I didn't find any of those on my system. > > I experimentally changed the value of QEMU_HDA_AMP_STEPS in > hw/audio/hda-codec.c from 74 to 32, artificially reducing the dynamic > range of the HDA codec to 32 dB. This improved matters much, allowing me > to set a gain value as low as 4 (-28dB / 12.5%) from the Windows guest > at 1%. Going one step further and using 32dB dynamic range with a 0.5 dB > step size, I could even set a gain value of 5 (-29.5dB / 8%). Very nice! It is way better indeed. I think your patch looks good too. > The "right" solution for this problem would probably be to implement > proper dB scaling in the the spice client code, as the note in the git > commit suggests > (http://cgit.freedesktop.org/spice/spice/commit/server/snd_worker.c?id=d1758b328811979beb58ff9ddb9cf4f318fa28f7). > I am not sure how difficult this might be. AFAICS it would require > changes in the general QEMU audio API to deal with dB. That would be indeed a welcome improvement. > While this clean solution is not available, I would suggest to decrease > the dynamic range for the the emulated Amps in the QEMU hda codec code. > AFAICS, that would also match the dynamic range of actual HD Audio HW > better. I am attaching a tentative patch that does exactly that for > input, changing nothing for output (the output volume setting behaves > acceptably for the default QEMU settings, see above). Please comment. Please send a git formatted patch to the ML: http://wiki.qemu.org/Contribute/SubmitAPatch thanks a lot
--- qemu-2.1.3/hw/audio/hda-codec-common.h 2015-04-20 14:44:18.621197997 +0200 +++ qemu-2.1.3/hw/audio/hda-codec-common.h.new 2015-04-20 14:43:24.852627519 +0200 @@ -28,16 +28,22 @@ #define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x12) #define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x22) #define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x32) -#define QEMU_HDA_AMP_CAPS \ +#define QEMU_HDA_AMP_OUT_CAPS \ (AC_AMPCAP_MUTE | \ - (QEMU_HDA_AMP_STEPS << AC_AMPCAP_OFFSET_SHIFT) | \ - (QEMU_HDA_AMP_STEPS << AC_AMPCAP_NUM_STEPS_SHIFT) | \ - (3 << AC_AMPCAP_STEP_SIZE_SHIFT)) + (QEMU_HDA_AMP_OUT_STEPS << AC_AMPCAP_OFFSET_SHIFT) | \ + (QEMU_HDA_AMP_OUT_STEPS << AC_AMPCAP_NUM_STEPS_SHIFT) | \ + (QEMU_HDA_AMP_OUT_STEP_SIZE << AC_AMPCAP_STEP_SIZE_SHIFT)) +#define QEMU_HDA_AMP_IN_CAPS \ + (AC_AMPCAP_MUTE | \ + (QEMU_HDA_AMP_IN_STEPS << AC_AMPCAP_OFFSET_SHIFT) | \ + (QEMU_HDA_AMP_IN_STEPS << AC_AMPCAP_NUM_STEPS_SHIFT) | \ + (QEMU_HDA_AMP_IN_STEP_SIZE << AC_AMPCAP_STEP_SIZE_SHIFT)) #else #define QEMU_HDA_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x11) #define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x21) #define QEMU_HDA_ID_MICRO ((QEMU_HDA_ID_VENDOR << 16) | 0x31) -#define QEMU_HDA_AMP_CAPS QEMU_HDA_AMP_NONE +#define QEMU_HDA_AMP_OUT_CAPS QEMU_HDA_AMP_NONE +#define QEMU_HDA_AMP_IN_CAPS QEMU_HDA_AMP_NONE #endif @@ -61,7 +67,7 @@ static const desc_param glue(common_para .val = QEMU_HDA_AMP_NONE, },{ .id = AC_PAR_AMP_OUT_CAP, - .val = QEMU_HDA_AMP_CAPS, + .val = QEMU_HDA_AMP_OUT_CAPS, }, }; @@ -86,7 +92,7 @@ static const desc_param glue(common_para .val = AC_SUPFMT_PCM, },{ .id = AC_PAR_AMP_IN_CAP, - .val = QEMU_HDA_AMP_CAPS, + .val = QEMU_HDA_AMP_IN_CAPS, },{ .id = AC_PAR_AMP_OUT_CAP, .val = QEMU_HDA_AMP_NONE, @@ -453,4 +459,5 @@ static const desc_codec glue(micro_, PAR #undef QEMU_HDA_ID_OUTPUT #undef QEMU_HDA_ID_DUPLEX #undef QEMU_HDA_ID_MICRO -#undef QEMU_HDA_AMP_CAPS +#undef QEMU_HDA_AMP_IN_CAPS +#undef QEMU_HDA_AMP_OUT_CAPS --- qemu-2.1.3/hw/audio/hda-codec.c 2015-04-20 15:20:38.069019517 +0200 +++ qemu-2.1.3/hw/audio/hda-codec.c.new 2015-04-20 15:17:32.443070187 +0200 @@ -116,7 +116,17 @@ #define QEMU_HDA_PCM_FORMATS (AC_SUPPCM_BITS_16 | \ 0x1fc /* 16 -> 96 kHz */) #define QEMU_HDA_AMP_NONE (0) -#define QEMU_HDA_AMP_STEPS 0x4a +/* Amplifier properties */ +/* Step size: 0: 0.25dB 1: 0.5dB 2: 0.75dB 3: 1dB */ +/* + Until we have a way to propagate dB values cleanly to the host + e.g. through spice, it is better to use a smaller dynamic range + for input. Here we use 0x40 * 0.5 dB = 32dB. +*/ +#define QEMU_HDA_AMP_OUT_STEPS 0x4a +#define QEMU_HDA_AMP_OUT_STEP_SIZE 3 +#define QEMU_HDA_AMP_IN_STEPS 0x40 +#define QEMU_HDA_AMP_IN_STEP_SIZE 1 #define PARAM mixemu #define HDA_MIXER @@ -258,15 +268,16 @@ left = st->mute_left ? 0 : st->gain_left; right = st->mute_right ? 0 : st->gain_right; - left = left * 255 / QEMU_HDA_AMP_STEPS; - right = right * 255 / QEMU_HDA_AMP_STEPS; - if (!st->state->mixer) { return; } if (st->output) { + left = left * 255 / QEMU_HDA_AMP_OUT_STEPS; + right = right * 255 / QEMU_HDA_AMP_OUT_STEPS; AUD_set_volume_out(st->voice.out, muted, left, right); } else { + left = left * 255 / QEMU_HDA_AMP_IN_STEPS; + right = right * 255 / QEMU_HDA_AMP_IN_STEPS; AUD_set_volume_in(st->voice.in, muted, left, right); } } @@ -512,8 +512,8 @@ st->node = node; if (type == AC_WID_AUD_OUT) { /* unmute output by default */ - st->gain_left = QEMU_HDA_AMP_STEPS; - st->gain_right = QEMU_HDA_AMP_STEPS; + st->gain_left = QEMU_HDA_AMP_OUT_STEPS; + st->gain_right = QEMU_HDA_AMP_OUT_STEPS; st->bpos = sizeof(st->buf); st->output = true; } else {