A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

Provide high resolution volume and prescaler to hosted targets.

HAVE_SW_VOLUME_CONTROL is required and at this time only affects the
SDL targets using pcm-sdl.c.

Enables balance control in SDL targets, unless mono volume is in use.

Compiles software volume control as unbuffered when
PCM_SW_VOLUME_UNBUFFERED is defined. This avoids the overhead and
extra latency introduced by the double buffer when it is not needed.
Use this config when the target's PCM driver is buffered and sufficient
latency exists to perform safely the volume scaling.

Simulated targets that are double-buffered when made as native targets
remain so in the sim in order to run the same code.

Change-Id: Ifa77d2d3ae7376c65afecdfc785a084478cb5ffb
Reviewed-on: http://gerrit.rockbox.org/457
Reviewed-by: Michael Sevakis <jethead71@rockbox.org>
Tested-by: Michael Sevakis <jethead71@rockbox.org>

+177 -94
+30 -17
firmware/drivers/audio/sdl.c
··· 23 23 #include "config.h" 24 24 #include "sound.h" 25 25 #include "pcm_sampr.h" 26 + #ifdef HAVE_SW_VOLUME_CONTROL 27 + #include "pcm_sw_volume.h" 28 + #include "fixedpoint.h" 29 + #endif 26 30 27 31 /** 28 - * Audio Hardware api. Make them do nothing as we cannot properly simulate with 29 - * SDL. if we used DSP we would run code that doesn't actually run on the target 32 + * Audio Hardware api. Make some of them do nothing as we cannot properly 33 + * simulate with SDL. if we used DSP we would run code that doesn't actually 34 + * run on the target 30 35 **/ 31 - 32 36 #ifdef HAVE_SW_VOLUME_CONTROL 33 - #include "pcm_sw_volume.h" 34 - 35 - void audiohw_set_volume(int vol_l, int vol_r) 37 + static int sdl_volume_level(int volume) 36 38 { 37 - pcm_set_master_volume(vol_l, vol_r); 39 + int shift = (1 - sound_numdecimals(SOUND_VOLUME)) << 16; 40 + int minvol = fp_mul(sound_min(SOUND_VOLUME), fp_exp10(shift, 16), 16); 41 + return volume <= minvol ? INT_MIN : volume; 38 42 } 39 - 40 - #else /* ndef HAVE_SW_VOLUME_CONTROL */ 41 - 43 + #endif /* HAVE_SW_VOLUME_CONTROL */ 42 44 45 + #if defined(AUDIOHW_HAVE_MONO_VOLUME) 43 46 void audiohw_set_volume(int volume) 44 47 { 45 - #if CONFIG_CODEC == SWCODEC 48 + #ifdef HAVE_SW_VOLUME_CONTROL 49 + volume = sdl_volume_level(volume); 50 + pcm_set_master_volume(volume, volume); 51 + #elif CONFIG_CODEC == SWCODEC 46 52 extern void pcm_set_mixer_volume(int volume); 47 53 pcm_set_mixer_volume(volume); 48 54 #endif 49 55 (void)volume; 50 56 } 51 - #endif /* HAVE_SW_VOLUME_CONTROL */ 52 - 53 - /** 54 - * stubs here, for the simulator 55 - **/ 57 + #else /* !AUDIOHW_HAVE_MONO_VOLUME */ 58 + void audiohw_set_volume(int vol_l, int vol_r) 59 + { 60 + #ifdef HAVE_SW_VOLUME_CONTROL 61 + vol_l = sdl_volume_level(vol_l); 62 + vol_r = sdl_volume_level(vol_r); 63 + pcm_set_master_volume(vol_l, vol_r); 64 + #endif 65 + (void)vol_l; (void)vol_r; 66 + } 67 + #endif /* AUDIOHW_HAVE_MONO_VOLUME */ 56 68 57 69 #if defined(AUDIOHW_HAVE_PRESCALER) 58 70 void audiohw_set_prescaler(int value) ··· 62 74 #endif 63 75 (void)value; 64 76 } 65 - #endif 77 + #endif /* AUDIOHW_HAVE_PRESCALER */ 78 + 66 79 #if defined(AUDIOHW_HAVE_BALANCE) 67 80 void audiohw_set_balance(int value) { (void)value; } 68 81 #endif
-5
firmware/export/audiohw.h
··· 111 111 #include "hosted_codec.h" 112 112 #endif 113 113 114 - #if defined(SIMULATOR) && !defined(HAVE_SW_VOLUME_CONTROL) 115 - /* For now, without software volume control, sim only supports mono control */ 116 - #define AUDIOHW_HAVE_MONO_VOLUME 117 - #endif 118 - 119 114 /* convert caps into defines */ 120 115 #ifdef AUDIOHW_CAPS 121 116 /* Tone controls */
+18
firmware/export/config.h
··· 1139 1139 #define ROCKBOX_HAS_LOGDISKF 1140 1140 #endif 1141 1141 1142 + #if defined(HAVE_SDL_AUDIO) \ 1143 + && !(CONFIG_PLATFORM & PLATFORM_MAEMO5) \ 1144 + && !defined(HAVE_SW_VOLUME_CONTROL) \ 1145 + && CONFIG_CODEC == SWCODEC 1146 + /* SW volume is needed for accurate control and no double buffering should be 1147 + * required. If target uses SW volume, then its definitions are used instead 1148 + * so things are as on target. */ 1149 + #define HAVE_SW_VOLUME_CONTROL 1150 + #define PCM_SW_VOLUME_UNBUFFERED /* pcm driver itself is buffered */ 1151 + #ifdef SIMULATOR 1152 + /* For sim, nice res for ~ -127dB..+36dB that so far covers all targets */ 1153 + #define PCM_SW_VOLUME_FRACBITS (24) 1154 + #else 1155 + /* For app, use fractional-only setup for -79..+0, no large-integer math */ 1156 + #define PCM_SW_VOLUME_FRACBITS (16) 1157 + #endif /* SIMULATOR */ 1158 + #endif /* default SDL SW volume conditions */ 1159 + 1142 1160 /* null audiohw setting macro for when codec header is included for reasons 1143 1161 other than audio support */ 1144 1162 #define AUDIOHW_SETTING(name, us, nd, st, minv, maxv, defv, expr...)
+5
firmware/export/hosted_codec.h
··· 21 21 #ifndef HOSTED_CODEC_H 22 22 #define HOSTED_CODEC_H 23 23 24 + #if defined(HAVE_SDL_AUDIO) \ 25 + && !(CONFIG_PLATFORM & PLATFORM_MAEMO5) 26 + AUDIOHW_SETTING(VOLUME, "dB", 0, 1, -80, 0, 0) 27 + #else 24 28 #define AUDIOHW_CAPS (MONO_VOL_CAP) 25 29 AUDIOHW_SETTING(VOLUME, "dB", 0, 1, -99, 0, 0) 30 + #endif /* CONFIG_PLATFORM & PLATFORM_SDL */ 26 31 27 32 #if (CONFIG_PLATFORM & PLATFORM_ANDROID) 28 33 /* Bass and treble tone controls */
+31 -8
firmware/export/pcm-internal.h
··· 27 27 #ifdef HAVE_SW_VOLUME_CONTROL 28 28 /* Default settings - architecture may have other optimal values */ 29 29 30 - #define PCM_FACTOR_BITS 15 /* Allows -73 to +6dB gain, sans 64-bit math */ 30 + #ifndef PCM_SW_VOLUME_FRACBITS 31 + /* Allows -73 to +6dB gain, sans large integer math */ 32 + #define PCM_SW_VOLUME_FRACBITS (15) 33 + #endif 34 + 35 + /* Constants selected based on integer math overflow avoidance */ 36 + #if PCM_SW_VOLUME_FRACBITS <= 16 37 + #define PCM_FACTOR_MAX 0x00010000u 38 + #define PCM_FACTOR_UNITY (1u << PCM_SW_VOLUME_FRACBITS) 39 + #elif PCM_SW_VOLUME_FRACBITS <= 31 40 + #define PCM_FACTOR_MAX 0x80000000u 41 + #define PCM_FACTOR_UNITY (1u << PCM_SW_VOLUME_FRACBITS) 42 + #endif /* PCM_SW_VOLUME_FRACBITS */ 43 + 44 + #ifdef PCM_SW_VOLUME_UNBUFFERED 45 + /* Copies buffer with volume scaling applied */ 46 + void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size); 47 + #define pcm_copy_buffer pcm_sw_volume_copy_buffer 48 + #else /* !PCM_SW_VOLUME_UNBUFFERED */ 49 + #ifdef HAVE_SDL_AUDIO 50 + #define pcm_copy_buffer memcpy 51 + #endif 52 + #ifndef PCM_PLAY_DBL_BUF_SAMPLES 31 53 #define PCM_PLAY_DBL_BUF_SAMPLES 1024 /* Max 4KByte chunks */ 54 + #endif 55 + #ifndef PCM_DBL_BUF_BSS 32 56 #define PCM_DBL_BUF_BSS /* In DRAM, uncached may be better */ 33 - #define PCM_FACTOR_MIN 0x00000 /* Minimum final factor */ 34 - #define PCM_FACTOR_MAX 0x10000 /* Maximum final factor */ 57 + #endif 58 + #endif /* PCM_SW_VOLUME_UNBUFFERED */ 35 59 36 - #define PCM_FACTOR_UNITY (1 << PCM_FACTOR_BITS) 37 60 #endif /* HAVE_SW_VOLUME_CONTROL */ 38 61 39 62 #define PCM_SAMPLE_SIZE (2 * sizeof (int16_t)) ··· 84 107 static FORCE_INLINE enum pcm_dma_status 85 108 pcm_play_dma_status_callback(enum pcm_dma_status status) 86 109 { 87 - #ifdef HAVE_SW_VOLUME_CONTROL 110 + #if defined(HAVE_SW_VOLUME_CONTROL) && !defined(PCM_SW_VOLUME_UNBUFFERED) 88 111 extern enum pcm_dma_status 89 112 pcm_play_dma_status_callback_int(enum pcm_dma_status status); 90 113 return pcm_play_dma_status_callback_int(status); 91 114 #else 92 115 return pcm_play_call_status_cb(status); 93 - #endif /* HAVE_SW_VOLUME_CONTROL */ 116 + #endif /* HAVE_SW_VOLUME_CONTROL && !PCM_SW_VOLUME_UNBUFFERED */ 94 117 } 95 118 96 - #ifdef HAVE_SW_VOLUME_CONTROL 119 + #if defined(HAVE_SW_VOLUME_CONTROL) && !defined(PCM_SW_VOLUME_UNBUFFERED) 97 120 void pcm_play_dma_start_int(const void *addr, size_t size); 98 121 void pcm_play_dma_pause_int(bool pause); 99 122 void pcm_play_dma_stop_int(void); 100 123 void pcm_play_stop_int(void); 101 124 const void *pcm_play_dma_get_peak_buffer_int(int *count); 102 - #endif /* HAVE_SW_VOLUME_CONTROL */ 125 + #endif /* HAVE_SW_VOLUME_CONTROL && !PCM_SW_VOLUME_UNBUFFERED */ 103 126 104 127 /* Called by the bottom layer ISR when more data is needed. Returns true 105 128 * if a new buffer is available, false otherwise. */
+6
firmware/export/pcm_sw_volume.h
··· 21 21 #ifndef PCM_SW_VOLUME_H 22 22 #define PCM_SW_VOLUME_H 23 23 24 + /*** 25 + ** Note: Only PCM drivers that are themselves buffered should use the 26 + ** PCM_SW_VOLUME_UNBUFFERED configuration. This may be part of the platform, 27 + ** the library or a hardware necessity. Normally, it shouldn't be used and 28 + ** only the port developer can properly decide. 29 + **/ 24 30 #ifdef HAVE_SW_VOLUME_CONTROL 25 31 26 32 #include <audiohw.h>
+4 -3
firmware/pcm.c
··· 106 106 static void pcm_play_pause_int(bool play); 107 107 void pcm_play_stop_int(void); 108 108 109 - #ifndef HAVE_SW_VOLUME_CONTROL 110 - /** Standard hw volume control functions - otherwise, see pcm_sw_volume.c **/ 109 + #if !defined(HAVE_SW_VOLUME_CONTROL) || defined(PCM_SW_VOLUME_UNBUFFERED) 110 + /** Standard hw volume/unbuffered control functions - otherwise, see 111 + ** pcm_sw_volume.c **/ 111 112 static inline void pcm_play_dma_start_int(const void *addr, size_t size) 112 113 { 113 114 pcm_play_dma_start(addr, size); ··· 150 151 pcm_play_stop_int(); 151 152 return false; 152 153 } 153 - #endif /* ndef HAVE_SW_VOLUME_CONTROL */ 154 + #endif /* !HAVE_SW_VOLUME_CONTROL || PCM_SW_VOLUME_UNBUFFERED */ 154 155 155 156 static void pcm_play_data_start_int(const void *addr, size_t size) 156 157 {
+79 -42
firmware/pcm_sw_volume.c
··· 26 26 #include "fixedpoint.h" 27 27 #include "pcm_sw_volume.h" 28 28 29 - /* source buffer from client */ 30 - static const void * volatile src_buf_addr = NULL; 31 - static size_t volatile src_buf_rem = 0; 29 + /* volume factors set by pcm_set_master_volume */ 30 + static uint32_t vol_factor_l = 0, vol_factor_r = 0; 32 31 33 - #define PCM_PLAY_DBL_BUF_SIZE (PCM_PLAY_DBL_BUF_SAMPLE*PCM_SAMPLE_SIZE) 32 + #ifdef AUDIOHW_HAVE_PRESCALER 33 + /* prescale factor set by pcm_set_prescaler */ 34 + static uint32_t prescale_factor = PCM_FACTOR_UNITY; 35 + #endif /* AUDIOHW_HAVE_PRESCALER */ 34 36 35 - /* double buffer and frame length control */ 36 - static int16_t pcm_dbl_buf[2][PCM_PLAY_DBL_BUF_SAMPLES*2] 37 - PCM_DBL_BUF_BSS MEM_ALIGN_ATTR; 38 - static size_t pcm_dbl_buf_size[2]; 39 - static int pcm_dbl_buf_num = 0; 40 - static size_t frame_size; 41 - static unsigned int frame_count, frame_err, frame_frac; 37 + /* final pcm scaling factors */ 38 + static uint32_t pcm_factor_l = 0, pcm_factor_r = 0; 42 39 43 - static int32_t vol_factor_l = 0, vol_factor_r = 0; 44 - #ifdef AUDIOHW_HAVE_PRESCALER 45 - static int32_t prescale_factor = PCM_FACTOR_UNITY; 46 - #endif /* AUDIOHW_HAVE_PRESCALER */ 40 + /*** 41 + ** Volume scaling routine 42 + ** If unbuffered, called externally by pcm driver 43 + **/ 47 44 48 - /* pcm scaling factors */ 49 - static int32_t pcm_factor_l = 0, pcm_factor_r = 0; 45 + /* TODO: #include CPU-optimized routines and move this to /firmware/asm */ 50 46 51 - #define PCM_FACTOR_CLIP(f) \ 52 - MAX(MIN((f), PCM_FACTOR_MAX), PCM_FACTOR_MIN) 53 - #define PCM_SCALE_SAMPLE(f, s) \ 54 - (((f) * (s) + PCM_FACTOR_UNITY/2) >> PCM_FACTOR_BITS) 47 + #if PCM_SW_VOLUME_FRACBITS <= 16 48 + #define PCM_F_T int32_t 49 + #else 50 + #define PCM_F_T int64_t /* Requires large integer math */ 51 + #endif /* PCM_SW_VOLUME_FRACBITS */ 55 52 53 + static inline int32_t pcm_scale_sample(PCM_F_T f, int32_t s) 54 + { 55 + return (f * s + (PCM_F_T)PCM_FACTOR_UNITY/2) >> PCM_SW_VOLUME_FRACBITS; 56 + } 56 57 57 - /* TODO: #include CPU-optimized routines and move this to /firmware/asm */ 58 - static inline void pcm_copy_buffer(int16_t *dst, const int16_t *src, 59 - size_t size) 58 + /* Copies buffer with volume scaling applied */ 59 + #ifndef PCM_SW_VOLUME_UNBUFFERED 60 + static inline 61 + #endif 62 + void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size) 60 63 { 61 - int32_t factor_l = pcm_factor_l; 62 - int32_t factor_r = pcm_factor_r; 64 + int16_t *d = dst; 65 + const int16_t *s = src; 66 + uint32_t factor_l = pcm_factor_l; 67 + uint32_t factor_r = pcm_factor_r; 63 68 64 - if (LIKELY(factor_l <= PCM_FACTOR_UNITY && factor_r <= PCM_FACTOR_UNITY)) 69 + if (factor_l == PCM_FACTOR_UNITY && factor_r == PCM_FACTOR_UNITY) 70 + { 71 + /* Both unity */ 72 + memcpy(dst, src, size); 73 + } 74 + else if (LIKELY(factor_l <= PCM_FACTOR_UNITY && 75 + factor_r <= PCM_FACTOR_UNITY)) 65 76 { 66 - /* All cut or unity */ 77 + /* Either cut, both <= UNITY */ 67 78 while (size) 68 79 { 69 - *dst++ = PCM_SCALE_SAMPLE(factor_l, *src++); 70 - *dst++ = PCM_SCALE_SAMPLE(factor_r, *src++); 80 + *d++ = pcm_scale_sample(factor_l, *s++); 81 + *d++ = pcm_scale_sample(factor_r, *s++); 71 82 size -= PCM_SAMPLE_SIZE; 72 83 } 73 84 } 74 85 else 75 86 { 76 - /* Any positive gain requires clipping */ 87 + /* Either positive gain, requires clipping */ 77 88 while (size) 78 89 { 79 - *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_l, *src++)); 80 - *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_r, *src++)); 90 + *d++ = clip_sample_16(pcm_scale_sample(factor_l, *s++)); 91 + *d++ = clip_sample_16(pcm_scale_sample(factor_r, *s++)); 81 92 size -= PCM_SAMPLE_SIZE; 82 93 } 83 94 } 84 95 } 85 96 97 + #ifndef PCM_SW_VOLUME_UNBUFFERED 98 + /* source buffer from client */ 99 + static const void * volatile src_buf_addr = NULL; 100 + static size_t volatile src_buf_rem = 0; 101 + 102 + #define PCM_PLAY_DBL_BUF_SIZE (PCM_PLAY_DBL_BUF_SAMPLE*PCM_SAMPLE_SIZE) 103 + 104 + /* double buffer and frame length control */ 105 + static int16_t pcm_dbl_buf[2][PCM_PLAY_DBL_BUF_SAMPLES*2] 106 + PCM_DBL_BUF_BSS MEM_ALIGN_ATTR; 107 + static size_t pcm_dbl_buf_size[2]; 108 + static int pcm_dbl_buf_num = 0; 109 + static size_t frame_size; 110 + static unsigned int frame_count, frame_err, frame_frac; 111 + 112 + /** Overrides of certain functions in pcm.c and pcm-internal.h **/ 113 + 86 114 bool pcm_play_dma_complete_callback(enum pcm_dma_status status, 87 115 const void **addr, size_t *size) 88 116 { ··· 155 183 156 184 pcm_dbl_buf_num ^= 1; 157 185 pcm_dbl_buf_size[pcm_dbl_buf_num] = size; 158 - pcm_copy_buffer(pcm_dbl_buf[pcm_dbl_buf_num], addr, size); 186 + pcm_sw_volume_copy_buffer(pcm_dbl_buf[pcm_dbl_buf_num], addr, size); 159 187 160 188 return PCM_DMAST_OK; 161 189 } ··· 216 244 return NULL; 217 245 } 218 246 247 + #endif /* PCM_SW_VOLUME_UNBUFFERED */ 248 + 249 + 250 + /** Internal **/ 251 + 219 252 /* Return the scale factor corresponding to the centibel level */ 220 - static int32_t pcm_centibels_to_factor(int volume) 253 + static uint32_t pcm_centibels_to_factor(int volume) 221 254 { 222 255 if (volume == PCM_MUTE_LEVEL) 223 256 return 0; /* mute */ 224 257 225 258 /* Centibels -> fixedpoint */ 226 - return fp_factor(fp_div(volume, 10, PCM_FACTOR_BITS), PCM_FACTOR_BITS); 259 + return (uint32_t)fp_factor(fp_div(volume, 10, PCM_SW_VOLUME_FRACBITS), 260 + PCM_SW_VOLUME_FRACBITS); 227 261 } 228 262 263 + 264 + /** Public functions **/ 265 + 229 266 /* Produce final pcm scale factor */ 230 267 static void pcm_sync_prescaler(void) 231 268 { 232 - int32_t factor_l = vol_factor_l; 233 - int32_t factor_r = vol_factor_r; 269 + uint32_t factor_l = vol_factor_l; 270 + uint32_t factor_r = vol_factor_r; 234 271 #ifdef AUDIOHW_HAVE_PRESCALER 235 - factor_l = fp_mul(prescale_factor, factor_l, PCM_FACTOR_BITS); 236 - factor_r = fp_mul(prescale_factor, factor_r, PCM_FACTOR_BITS); 272 + factor_l = fp_mul(prescale_factor, factor_l, PCM_SW_VOLUME_FRACBITS); 273 + factor_r = fp_mul(prescale_factor, factor_r, PCM_SW_VOLUME_FRACBITS); 237 274 #endif 238 - pcm_factor_l = PCM_FACTOR_CLIP(factor_l); 239 - pcm_factor_r = PCM_FACTOR_CLIP(factor_r); 275 + pcm_factor_l = MIN(factor_l, PCM_FACTOR_MAX); 276 + pcm_factor_r = MIN(factor_r, PCM_FACTOR_MAX); 240 277 } 241 278 242 279 #ifdef AUDIOHW_HAVE_PRESCALER
+4 -19
firmware/target/hosted/sdl/pcm-sdl.c
··· 51 51 extern bool debug_audio; 52 52 #endif 53 53 54 - #ifdef HAVE_SW_VOLUME_CONTROL 55 - static int sim_volume = SDL_MIX_MAXVOLUME; 56 - #else 57 - static int sim_volume = 0; 58 - #endif 59 - 60 54 #if CONFIG_CODEC == SWCODEC 61 55 static int cvt_status = -1; 62 56 ··· 177 171 cvt.len = rd * pcm_sample_bytes; 178 172 cvt.buf = (Uint8 *) malloc(cvt.len * cvt.len_mult); 179 173 180 - memcpy(cvt.buf, pcm_data, cvt.len); 174 + pcm_copy_buffer(cvt.buf, pcm_data, cvt.len); 181 175 182 176 SDL_ConvertAudio(&cvt); 183 - SDL_MixAudio(udata->stream, cvt.buf, cvt.len_cvt, sim_volume); 177 + memcpy(udata->stream, cvt.buf, cvt.len_cvt); 184 178 185 179 udata->num_in = cvt.len / pcm_sample_bytes; 186 180 udata->num_out = cvt.len_cvt / pcm_sample_bytes; ··· 223 217 } 224 218 } else { 225 219 udata->num_in = udata->num_out = MIN(udata->num_in, udata->num_out); 226 - SDL_MixAudio(udata->stream, pcm_data, 227 - udata->num_out * pcm_sample_bytes, sim_volume); 220 + pcm_copy_buffer(udata->stream, pcm_data, 221 + udata->num_out * pcm_sample_bytes); 228 222 #ifdef DEBUG 229 223 if (udata->debug != NULL) { 230 224 fwrite(pcm_data, sizeof(Uint8), udata->num_out * pcm_sample_bytes, ··· 417 411 void pcm_play_dma_postinit(void) 418 412 { 419 413 } 420 - 421 - #ifndef HAVE_SW_VOLUME_CONTROL 422 - void pcm_set_mixer_volume(int volume) 423 - { 424 - int minvol = sound_min(SOUND_VOLUME); 425 - int volrange = sound_max(SOUND_VOLUME) - minvol; 426 - sim_volume = SDL_MIX_MAXVOLUME * (volume / 10 - minvol) / volrange; 427 - } 428 - #endif /* HAVE_SW_VOLUME_CONTROL */ 429 414 430 415 #endif /* CONFIG_CODEC == SWCODEC */