Thank you for your donation!


Cloudsmith graciously provides open-source package management and distribution for our project.


Problem: Volume goes to Zero on change
#51
In my case I have the soundcard device driver in my hand, as I need a specific driver for my custom hardware.

Usually an ALSA device driver registers the Volume control in a form of a 'snd_kcontrol_new' structure:
Code:
static const struct snd_kcontrol_new hifibunny3_codec_controls[] = {
   SOC_DOUBLE_R_TLV("Digital Playback Volume", ES9038Q2M_VOLUME1, ES9038Q2M_VOLUME2, 0, 255, 1, volume_tlv),
}

This kind of interface is accessed by MPD that way, that the 1st register (VOLUME1) is written first, then in a separate transaction the second register (VOLUME2) is written.
Unfortunately, within a few milliseconds, MPD just writes 0 volume as well to these registers (as I've reported it before using a logic analyzer).

There is also an other way in linux of defining the Volume control structure, using 'custom', external functions (vol_get/vol_set) for updating the HW registers:
Code:
static const struct snd_kcontrol_new hifibunny3_codec_controls[] = {
    SOC_DOUBLE_R_EXT_TLV("Digital Playback Volume",
            ES9038Q2M_VOLUME1,
            ES9038Q2M_VOLUME2, 0, 255, 1,
            hifibunny3_vol_get,
            hifibunny3_vol_set,
            volume_tlv),
}

With using this method, I could also log with the help of kernel messages the input parameters, what was sent out by MPD.
It completely confirms my findings, that shortly after setting the left volume, and then the right volume separately, then a new transaction is just writes 0 to both LEFT/RIGHT volume registers by MPD.

With the ALSA method, the volume setting seems to be also two transactions: first the left volume, then in a separate transaction the right volume is set to the desired value. But the problematic 'zeroing' transaction is completely missing.

However, as I had complete control, how to implement the _vol_get/_vol_set functions, I have implemented a simple filter function: 
- first of all: I synced left/right volume values in every transaction (usually I need complete sync on both channels).
- and if there is an incoming 'zeroing' transaction within 50ms of the previous valid volume setting transaction, then I just ignore it, and skip the I2C transaction on the bus.
Code:
[ 6837.860647] ...hifibunny3_vol_get: 255, 255.
[ 6837.861750] ...hifibunny3_vol_set(6837847650752): 241, 241.                  # New value, synced
[ 6837.863233] ...hifibunny3_vol_get: 241, 241.
[ 6837.864549] ...hifibunny3_vol_get: 241, 241.
[ 6837.864607] ...hifibunny3_vol_set(6837851548405): b2b zero, skipping         # filtering out the zeroing
[ 6837.866072] ...hifibunny3_vol_get: 241, 241.
[ 6837.927397] ...hifibunny3_vol_get: 241, 241.

This way I have made my device driver work properly under the buggy MPD way of setting HW volume.
Reply
#52
(02-09-2023, 05:31 PM)meszarosa Wrote: In my case I have the soundcard device driver in my hand, as I need a specific driver for my custom hardware.

Usually an ALSA device driver registers the Volume control in a form of a 'snd_kcontrol_new' structure:
Code:
static const struct snd_kcontrol_new hifibunny3_codec_controls[] = {
  SOC_DOUBLE_R_TLV("Digital Playback Volume", ES9038Q2M_VOLUME1, ES9038Q2M_VOLUME2, 0, 255, 1, volume_tlv),
}

This kind of interface is accessed by MPD that way, that the 1st register (VOLUME1) is written first, then in a separate transaction the second register (VOLUME2) is written.
Unfortunately, within a few milliseconds, MPD just writes 0 volume as well to these registers (as I've reported it before using a logic analyzer).

There is also an other way in linux of defining the Volume control structure, using 'custom', external functions (vol_get/vol_set) for updating the HW registers:
Code:
static const struct snd_kcontrol_new hifibunny3_codec_controls[] = {
    SOC_DOUBLE_R_EXT_TLV("Digital Playback Volume",
            ES9038Q2M_VOLUME1,
            ES9038Q2M_VOLUME2, 0, 255, 1,
            hifibunny3_vol_get,
            hifibunny3_vol_set,
            volume_tlv),
}

With using this method, I could also log with the help of kernel messages the input parameters, what was sent out by MPD.
It completely confirms my findings, that shortly after setting the left volume, and then the right volume separately, then a new transaction is just writes 0 to both LEFT/RIGHT volume registers by MPD.

With the ALSA method, the volume setting seems to be also two transactions: first the left volume, then in a separate transaction the right volume is set to the desired value. But the problematic 'zeroing' transaction is completely missing.

However, as I had complete control, how to implement the _vol_get/_vol_set functions, I have implemented a simple filter function: 
- first of all: I synced left/right volume values in every transaction (usually I need complete sync on both channels).
- and if there is an incoming 'zeroing' transaction within 50ms of the previous valid volume setting transaction, then I just ignore it, and skip the I2C transaction on the bus.
Code:
[ 6837.860647] ...hifibunny3_vol_get: 255, 255.
[ 6837.861750] ...hifibunny3_vol_set(6837847650752): 241, 241.                  # New value, synced
[ 6837.863233] ...hifibunny3_vol_get: 241, 241.
[ 6837.864549] ...hifibunny3_vol_get: 241, 241.
[ 6837.864607] ...hifibunny3_vol_set(6837851548405): b2b zero, skipping         # filtering out the zeroing
[ 6837.866072] ...hifibunny3_vol_get: 241, 241.
[ 6837.927397] ...hifibunny3_vol_get: 241, 241.

This way I have made my device driver work properly under the buggy MPD way of setting HW volume.

Strong evidence pointing to a bug in MPD. We need to get a generic repro for MPD maintainer to consider. Did the compile instructions I sent you a while back work?

For upcoming moOde 8.3.0 release I'm testing a workaround that uses ALSA (amixer) to set Hardware volume instead of MPD and so far it seems to work well.

-Tim
Enjoy the Music!
moodeaudio.org | Mastodon Feed | GitHub
Reply


Forum Jump: