pcm: improve 32-bit software volume scaling

Replace the factor calculation from pcm-alsa.c, which
is based on signal *power* ratios, with the fp_factor()
calculation that is based on amplitude ratios. Because
power changes with the square of amplitude, this means
1 dB of power equals 2 dB of amplitude.

Rockbox's volume controls are amplitude-based, so the
smallest step size for pcm-alsa was effectively 2 dB.
The fp_factor() method supports 0.1 dB steps and is a
bit more accurate besides, so it's simply superior in
all respects (aside from taking a few more CPU cycles
to calculate the factor).

Change-Id: I34d143c225d8b5e085cde299fc405f83c13314bf
This commit is contained in:
Aidan MacDonald 2026-02-18 17:41:36 +00:00
parent bcdec75f94
commit 5b5cd252d0
7 changed files with 46 additions and 87 deletions

View file

@ -51,23 +51,29 @@ static typeof (memcpy) *pcm_scaling_fn = NULL;
/* take care of some defines for 32-bit software vol */
#if (PCM_NATIVE_BITDEPTH > 16) /* >16-bit */
# define HAVE_SWVOL_32
# define PCM_VOL_SAMPLE_SIZE (2 * sizeof (int32_t))
# define PCM_DBL_BUF_SIZE_T int32_t
# if !defined(PCM_DC_OFFSET_VALUE)
/* PCM_DC_OFFSET_VALUE is only needed due to hardware quirk on Eros Q */
# define PCM_DC_OFFSET_VALUE 0
# endif
# define PCM_VOL_SAMPLE_SIZE (2 * sizeof (int32_t))
# define PCM_DBL_BUF_SIZE_T int32_t
#else /* 16-BIT */
# define PCM_VOL_SAMPLE_SIZE PCM_SAMPLE_SIZE
# define PCM_DBL_BUF_SIZE_T int16_t
# define PCM_VOL_SAMPLE_SIZE (2 * sizeof (int16_t))
# define PCM_DBL_BUF_SIZE_T int16_t
#endif /* 16-BIT */
#if !defined(PCM_DC_OFFSET_VALUE)
/* PCM_DC_OFFSET_VALUE is only needed due to hardware quirk on Eros Q */
# define PCM_DC_OFFSET_VALUE 0
#endif
/*
* 16-bit samples are scaled up to an effective bit depth
* of (16+fracbits) bits and must be shifted to produce a
* native bit depth sample. Positive values correspond to
* right shifts (throwing away bits of the scaled result)
* while negative values correspond to left shifts (where
* the scaled result completely fits in the native sample).
*/
#define PCM_SCALE_SHIFT (16 + PCM_SW_VOLUME_FRACBITS - PCM_NATIVE_BITDEPTH)
/***
** Volume scaling routines
** If unbuffered, called externally by pcm driver
@ -83,10 +89,10 @@ static typeof (memcpy) *pcm_scaling_fn = NULL;
/* Scale sample by PCM factor */
static inline int32_t pcm_scale_sample(PCM_F_T f, int32_t s)
{
#if defined(HAVE_SWVOL_32)
return (f * s + PCM_DC_OFFSET_VALUE) >> (32 - PCM_NATIVE_BITDEPTH);
#if PCM_SCALE_SHIFT > 0
return (f * s + PCM_DC_OFFSET_VALUE) >> PCM_SCALE_SHIFT;
#else
return (f * s) >> PCM_SW_VOLUME_FRACBITS;
return (f * s + PCM_DC_OFFSET_VALUE) << (-PCM_SCALE_SHIFT);
#endif
}
@ -347,28 +353,10 @@ static uint32_t pcm_centibels_to_factor(int volume)
{
if (volume == PCM_MUTE_LEVEL)
return 0; /* mute */
#if defined(HAVE_SWVOL_32)
/*
* 32-bit software volume taken from pcm-alsa.c
*/
volume += 48; /* -42dB .. 0dB => 5dB .. 48dB */
/* NOTE if vol_dB = 5 then vol_shift = 1 but r = 1 so we do vol_shift - 1 >= 0
* otherwise vol_dB >= 0 implies vol_shift >= 2 so vol_shift - 2 >= 0 */
int vol_shift = volume / 3;
int r = volume % 3;
int32_t dig_vol_mult;
if(r == 0)
dig_vol_mult = 1 << vol_shift;
else if(r == 1)
dig_vol_mult = 1 << vol_shift | 1 << (vol_shift - 2);
else
dig_vol_mult = 1 << vol_shift | 1 << (vol_shift - 1);
return dig_vol_mult;
#else /* standard software volume */
/* Centibels -> fixedpoint */
return (uint32_t)fp_factor(fp_div(volume, 10, PCM_SW_VOLUME_FRACBITS),
PCM_SW_VOLUME_FRACBITS);
#endif /* HAVE_SWVOL_32 */
}

View file

@ -204,7 +204,7 @@ static void audiohw_set_volume_v1(int vol_l, int vol_r)
int sw_volume_l = l <= min_pcm ? min_pcm : MIN(l, max_pcm);
int sw_volume_r = r <= min_pcm ? min_pcm : MIN(r, max_pcm);
pcm_set_mixer_volume(sw_volume_l / 20, sw_volume_r / 20);
pcm_set_mixer_volume(sw_volume_l, sw_volume_r);
}
static void audiohw_set_volume_v2(int vol_l, int vol_r)
@ -236,7 +236,7 @@ static void audiohw_set_volume_v2(int vol_l, int vol_r)
alsa_controls_set_ints("Right Playback Volume", 1, &r);
/* Dial back PCM mixer to avoid compression */
pcm_set_mixer_volume(global_settings.volume_limit / 2, global_settings.volume_limit / 2);
pcm_set_mixer_volume(global_settings.volume_limit, global_settings.volume_limit);
}
void audiohw_set_volume(int vol_l, int vol_r)
@ -274,7 +274,7 @@ void audiohw_set_lineout_volume(int vol_l, int vol_r)
} else {
int sw_volume_l = l <= min_pcm ? min_pcm : MIN(l, max_pcm);
int sw_volume_r = r <= min_pcm ? min_pcm : MIN(r, max_pcm);
pcm_set_mixer_volume(sw_volume_l / 20, sw_volume_r / 20);
pcm_set_mixer_volume(sw_volume_l, sw_volume_r);
}
}
}

View file

@ -95,21 +95,21 @@ void audiohw_set_volume(int vol_l, int vol_r)
if (ak_hw < 0)
return;
vol[0] = vol_l / 20;
vol[1] = vol_r / 20;
vol[0] = vol_l;
vol[1] = vol_r;
for (int i = 0; i < 2; i++)
{
if (vol[i] > -56)
if (vol[i] > -1120)
{
if (vol[i] < -12)
if (vol[i] < -240)
{
vol_hw[i] = 1;
vol_sw[i] = vol[i] + 12;
vol_sw[i] = vol[i] + 240;
}
else
{
vol_hw[i] = 25 - (-vol[i] * 2);
vol_hw[i] = 25 - (-vol[i] / 10);
vol_sw[i] = 0;
}
}

View file

@ -50,6 +50,7 @@
#include "pcm_sampr.h"
#include "audiohw.h"
#include "pcm-alsa.h"
#include "fixedpoint.h"
#include "logf.h"
@ -267,46 +268,14 @@ error:
}
#if defined(HAVE_ALSA_32BIT)
/* Digital volume explanation:
* with very good approximation (<0.1dB) the convertion from dB to multiplicative
* factor, for dB>=0, is 2^(dB/3). We can then notice that if we write dB=3*k+r
* then this is 2^k*2^(r/3) so we only need to look at r=0,1,2. For r=0 this is
* 1, for r=1 we have 2^(1/3)~=1.25 so we approximate by 1+1/4, and 2^(2/3)~=1.5
* so we approximate by 1+1/2. To go from negative to nonnegative we notice that
* 48 dB => 63095 factor ~= 2^16 so we virtually pre-multiply everything by 2^(-16)
* and add 48dB to the input volume. We cannot go lower -43dB because several
* values between -48dB and -43dB would require a fractional multiplier, which is
* stupid to implement for such very low volume. */
static int dig_vol_mult_l = 2 << 16; /* multiplicative factor to apply to each sample */
static int dig_vol_mult_r = 2 << 16; /* multiplicative factor to apply to each sample */
/* Multiplicative factors applied to each sample */
static int32_t dig_vol_mult_l = 0;
static int32_t dig_vol_mult_r = 0;
void pcm_set_mixer_volume(int vol_db_l, int vol_db_r)
{
if(vol_db_l > 0 || vol_db_r > 0 || vol_db_l < -43 || vol_db_r < -43)
panicf("invalid pcm alsa volume %d %d", vol_db_l, vol_db_r);
if(format != SND_PCM_FORMAT_S32_LE)
panicf("this function assumes 32-bit sample size");
vol_db_l += 48; /* -42dB .. 0dB => 5dB .. 48dB */
vol_db_r += 48; /* -42dB .. 0dB => 5dB .. 48dB */
/* NOTE if vol_dB = 5 then vol_shift = 1 but r = 1 so we do vol_shift - 1 >= 0
* otherwise vol_dB >= 0 implies vol_shift >= 2 so vol_shift - 2 >= 0 */
int vol_shift_l = vol_db_l / 3;
int vol_shift_r = vol_db_r / 3;
int r_l = vol_db_l % 3;
int r_r = vol_db_r % 3;
if(r_l == 0)
dig_vol_mult_l = 1 << vol_shift_l;
else if(r_l == 1)
dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 2);
else
dig_vol_mult_l = 1 << vol_shift_l | 1 << (vol_shift_l - 1);
logf("l: %d dB -> factor = %d", vol_db_l - 48, dig_vol_mult_l);
if(r_r == 0)
dig_vol_mult_r = 1 << vol_shift_r;
else if(r_r == 1)
dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 2);
else
dig_vol_mult_r = 1 << vol_shift_r | 1 << (vol_shift_r - 1);
logf("r: %d dB -> factor = %d", vol_db_r - 48, dig_vol_mult_r);
dig_vol_mult_l = fp_factor(fp_div(vol_db_l, 10, 16), 16);
dig_vol_mult_r = fp_factor(fp_div(vol_db_r, 10, 16), 16);
}
#endif

View file

@ -21,10 +21,12 @@
#define __PCM_ALSA_RB_H__
#include <config.h>
#include <limits.h>
#if defined(HAVE_ALSA_32BIT)
/* Set the PCM volume in dB: each sample with have this volume applied digitally
* before being sent to ALSA. Volume must satisfy -43 <= dB <= 0 */
* before being sent to ALSA. Effective range -79 dB to 0 dB */
void pcm_set_mixer_volume(int vol_db_l, int vol_db_r);
#endif

View file

@ -415,7 +415,7 @@ void audiohw_set_volume(int vol_l, int vol_r)
printf(" set driver volume %d (%d dB)\n", drv_vol, curve->level[drv_vol] / 10);
nwz_set_driver_vol(drv_vol);
printf(" set digital volume %d dB\n", vol / 10);
pcm_set_mixer_volume(vol / 10, vol / 10);
pcm_set_mixer_volume(vol, vol);
}
void audiohw_close(void)

View file

@ -200,8 +200,8 @@ void audiohw_set_volume(int vol_l, int vol_r)
}
else /* PCM5102A */
{
l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (l / 20);
r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (r / 20);
l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : l;
r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : r;
pcm_set_master_volume(l, r);
}
@ -214,4 +214,4 @@ void audiohw_set_filter_roll_off(int value)
es9018k2m_set_filter_roll_off(value);
}
}
#endif /* !defined(BOOTLOADER) */
#endif /* !defined(BOOTLOADER) */