From f5a5b946867677de76c405ee72e2ea47e36e4c83 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Fri, 5 Apr 2013 04:36:05 -0400 Subject: Implement universal in-PCM-driver software volume control. Implements double-buffered volume, balance and prescaling control in the main PCM driver when HAVE_SW_VOLUME_CONTROL is defined ensuring that all PCM is volume controlled and level changes are low in latency. Supports -73 to +6 dB using a 15-bit factor so that no large-integer math is needed. Low-level hardware drivers do not have to implement it themselves but parameters can be changed (currently defined in pcm-internal.h) to work best with a particular SoC or to provide different volume ranges. Volume and prescale calls should be made in the codec driver. It should appear as a normal hardware interface. PCM volume calls expect .1 dB units. Change-Id: Idf6316a64ef4fb8abcede10707e1e6c6d01d57db Reviewed-on: http://gerrit.rockbox.org/423 Reviewed-by: Michael Sevakis Tested-by: Michael Sevakis --- firmware/SOURCES | 3 + firmware/export/config/ondavx747.h | 5 - firmware/export/config/ondavx777.h | 5 - firmware/export/jz4740-codec.h | 2 +- firmware/export/pcm-internal.h | 54 ++++- firmware/export/pcm_sw_volume.h | 40 ++++ firmware/export/sound.h | 3 - firmware/pcm.c | 195 +++++++-------- firmware/pcm_sw_volume.c | 264 +++++++++++++++++++++ firmware/sound.c | 15 +- firmware/target/mips/ingenic_jz47xx/codec-jz4740.c | 27 ++- lib/rbcodec/dsp/dsp_misc.c | 18 -- lib/rbcodec/dsp/pga.h | 3 - 13 files changed, 478 insertions(+), 156 deletions(-) create mode 100644 firmware/export/pcm_sw_volume.h create mode 100644 firmware/pcm_sw_volume.c diff --git a/firmware/SOURCES b/firmware/SOURCES index 92b2f5f87b..964d57ff5d 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -355,6 +355,9 @@ sound.c pcm_sampr.c pcm.c pcm_mixer.c +#ifdef HAVE_SW_VOLUME_CONTROL +pcm_sw_volume.c +#endif /* HAVE_SW_VOLUME_CONTROL */ #ifdef HAVE_RECORDING enc_base.c #endif /* HAVE_RECORDING */ diff --git a/firmware/export/config/ondavx747.h b/firmware/export/config/ondavx747.h index d303ea5925..8499c15ce9 100644 --- a/firmware/export/config/ondavx747.h +++ b/firmware/export/config/ondavx747.h @@ -132,11 +132,6 @@ /* has no volume control, so we use the software ones */ #define HAVE_SW_VOLUME_CONTROL -/* software controlled volume ranges from -73 -> 0 dB, other than that - is controlled by hardware */ -#define SW_VOLUME_MIN -73 -#define SW_VOLUME_MAX 0 - /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ diff --git a/firmware/export/config/ondavx777.h b/firmware/export/config/ondavx777.h index 33bf6442af..a254b0177c 100644 --- a/firmware/export/config/ondavx777.h +++ b/firmware/export/config/ondavx777.h @@ -126,11 +126,6 @@ /* has no volume control, so we use the software ones */ #define HAVE_SW_VOLUME_CONTROL -/* software controlled volume ranges from -73 -> 0 dB, other than that - is controlled by hardware */ -#define SW_VOLUME_MIN -73 -#define SW_VOLUME_MAX 0 - /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ diff --git a/firmware/export/jz4740-codec.h b/firmware/export/jz4740-codec.h index 37d2347f5b..3c088f5bf7 100644 --- a/firmware/export/jz4740-codec.h +++ b/firmware/export/jz4740-codec.h @@ -24,6 +24,6 @@ #define VOLUME_MIN -730 #define VOLUME_MAX 60 -void audiohw_set_volume(int v); +void audiohw_set_master_vol(int vol_l, int vol_r); #endif /* __JZ4740_CODEC_H_ */ diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h index 397cf6832f..03e5c5e6e7 100644 --- a/firmware/export/pcm-internal.h +++ b/firmware/export/pcm-internal.h @@ -24,6 +24,19 @@ #include "config.h" +#ifdef HAVE_SW_VOLUME_CONTROL +/* Default settings - architecture may have other optimal values */ + +#define PCM_FACTOR_BITS 15 /* Allows -73 to +6dB gain, sans 64-bit math */ +#define PCM_PLAY_DBL_BUF_SAMPLES 1024 /* Max 4KByte chunks */ +#define PCM_DBL_BUF_BSS /* In DRAM, uncached may be better */ +#define PCM_FACTOR_MIN 0x00000 /* Minimum final factor */ +#define PCM_FACTOR_MAX 0x10000 /* Maximum final factor */ + +#define PCM_FACTOR_UNITY (1 << PCM_FACTOR_BITS) +#endif /* HAVE_SW_VOLUME_CONTROL */ + +#define PCM_SAMPLE_SIZE (2 * sizeof (int16_t)) /* Cheapo buffer align macro to align to the 16-16 PCM size */ #define ALIGN_AUDIOBUF(start, size) \ ({ (start) = (void *)(((uintptr_t)(start) + 3) & ~3); \ @@ -34,6 +47,23 @@ void pcm_do_peak_calculation(struct pcm_peaks *peaks, bool active, /** The following are for internal use between pcm.c and target- specific portion **/ +/* Call registered callback to obtain next buffer */ +static inline bool pcm_get_more_int(const void **addr, size_t *size) +{ + extern volatile pcm_play_callback_type pcm_callback_for_more; + pcm_play_callback_type get_more = pcm_callback_for_more; + + if (UNLIKELY(!get_more)) + return false; + + *addr = NULL; + *size = 0; + get_more(addr, size); + ALIGN_AUDIOBUF(*addr, *size); + + return *addr && *size; +} + static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb( pcm_status_callback_type callback, enum pcm_dma_status status) { @@ -43,14 +73,34 @@ static FORCE_INLINE enum pcm_dma_status pcm_call_status_cb( return callback(status); } -static FORCE_INLINE enum pcm_dma_status -pcm_play_dma_status_callback(enum pcm_dma_status status) +static FORCE_INLINE enum pcm_dma_status pcm_play_call_status_cb( + enum pcm_dma_status status) { extern enum pcm_dma_status (* volatile pcm_play_status_callback)(enum pcm_dma_status); return pcm_call_status_cb(pcm_play_status_callback, status); } +static FORCE_INLINE enum pcm_dma_status +pcm_play_dma_status_callback(enum pcm_dma_status status) +{ +#ifdef HAVE_SW_VOLUME_CONTROL + extern enum pcm_dma_status + pcm_play_dma_status_callback_int(enum pcm_dma_status status); + return pcm_play_dma_status_callback_int(status); +#else + return pcm_play_call_status_cb(status); +#endif /* HAVE_SW_VOLUME_CONTROL */ +} + +#ifdef HAVE_SW_VOLUME_CONTROL +void pcm_play_dma_start_int(const void *addr, size_t size); +void pcm_play_dma_pause_int(bool pause); +void pcm_play_dma_stop_int(void); +void pcm_play_stop_int(void); +const void *pcm_play_dma_get_peak_buffer_int(int *count); +#endif /* HAVE_SW_VOLUME_CONTROL */ + /* Called by the bottom layer ISR when more data is needed. Returns true * if a new buffer is available, false otherwise. */ bool pcm_play_dma_complete_callback(enum pcm_dma_status status, diff --git a/firmware/export/pcm_sw_volume.h b/firmware/export/pcm_sw_volume.h new file mode 100644 index 0000000000..b86e78f500 --- /dev/null +++ b/firmware/export/pcm_sw_volume.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2013 by Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef PCM_SW_VOLUME_H +#define PCM_SW_VOLUME_H + +#ifdef HAVE_SW_VOLUME_CONTROL + +#include + +#define PCM_MUTE_LEVEL INT_MIN + +#ifdef AUDIOHW_HAVE_PRESCALER +/* Set the prescaler value for all PCM playback */ +void pcm_set_prescaler(int prescale); +#endif /* AUDIOHW_HAVE_PRESCALER */ + +/* Set the per-channel volume cut/gain for all PCM playback */ +void pcm_set_master_volume(int vol_l, int vol_r); + +#endif /* HAVE_SW_VOLUME_CONTROL */ + +#endif /* PCM_SW_VOLUME_H */ diff --git a/firmware/export/sound.h b/firmware/export/sound.h index ba6120ce8f..ebf728c7c7 100644 --- a/firmware/export/sound.h +++ b/firmware/export/sound.h @@ -32,9 +32,6 @@ enum { DSP_CALLBACK_SET_TREBLE, DSP_CALLBACK_SET_CHANNEL_CONFIG, DSP_CALLBACK_SET_STEREO_WIDTH, -#ifdef HAVE_SW_VOLUME_CONTROL - DSP_CALLBACK_SET_SW_VOLUME, -#endif }; #endif diff --git a/firmware/pcm.c b/firmware/pcm.c index 94b0d6eefb..6bf0e12c8d 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -86,7 +86,7 @@ static bool pcm_is_ready = false; /* The registered callback function to ask for more mp3 data */ -static volatile pcm_play_callback_type +volatile pcm_play_callback_type pcm_callback_for_more SHAREDBSS_ATTR = NULL; /* The registered callback function to inform of DMA status */ volatile pcm_status_callback_type @@ -102,9 +102,89 @@ unsigned long pcm_sampr SHAREDBSS_ATTR = HW_SAMPR_DEFAULT; /* samplerate frequency selection index */ int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT; -/* Called internally by functions to reset the state */ -static void pcm_play_stopped(void) +static void pcm_play_data_start_int(const void *addr, size_t size); +static void pcm_play_pause_int(bool play); +void pcm_play_stop_int(void); + +#ifndef HAVE_SW_VOLUME_CONTROL +/** Standard hw volume control functions - otherwise, see pcm_sw_volume.c **/ +static inline void pcm_play_dma_start_int(const void *addr, size_t size) +{ + pcm_play_dma_start(addr, size); +} + +static inline void pcm_play_dma_pause_int(bool pause) +{ + if (pause || pcm_get_bytes_waiting() > 0) + { + pcm_play_dma_pause(pause); + } + else + { + logf(" no data"); + pcm_play_data_start_int(NULL, 0); + } +} + +static inline void pcm_play_dma_stop_int(void) +{ + pcm_play_dma_stop(); +} + +static inline const void * pcm_play_dma_get_peak_buffer_int(int *count) +{ + return pcm_play_dma_get_peak_buffer(count); +} + +bool pcm_play_dma_complete_callback(enum pcm_dma_status status, + const void **addr, size_t *size) +{ + /* Check status callback first if error */ + if (status < PCM_DMAST_OK) + status = pcm_play_dma_status_callback(status); + + if (status >= PCM_DMAST_OK && pcm_get_more_int(addr, size)) + return true; + + /* Error, callback missing or no more DMA to do */ + pcm_play_stop_int(); + return false; +} +#endif /* ndef HAVE_SW_VOLUME_CONTROL */ + +static void pcm_play_data_start_int(const void *addr, size_t size) { + ALIGN_AUDIOBUF(addr, size); + + if ((addr && size) || pcm_get_more_int(&addr, &size)) + { + pcm_apply_settings(); + logf(" pcm_play_dma_start_int"); + pcm_play_dma_start_int(addr, size); + pcm_playing = true; + pcm_paused = false; + } + else + { + /* Force a stop */ + logf(" pcm_play_stop_int"); + pcm_play_stop_int(); + } +} + +static void pcm_play_pause_int(bool play) +{ + if (play) + pcm_apply_settings(); + + logf(" pcm_play_dma_pause_int"); + pcm_play_dma_pause_int(!play); + pcm_paused = !play && pcm_playing; +} + +void pcm_play_stop_int(void) +{ + pcm_play_dma_stop_int(); pcm_callback_for_more = NULL; pcm_play_status_callback = NULL; pcm_paused = false; @@ -195,7 +275,7 @@ void pcm_calculate_peaks(int *left, int *right) static struct pcm_peaks peaks; int count; - const void *addr = pcm_play_dma_get_peak_buffer(&count); + const void *addr = pcm_play_dma_get_peak_buffer_int(&count); pcm_do_peak_calculation(&peaks, pcm_playing && !pcm_paused, addr, count); @@ -207,9 +287,9 @@ void pcm_calculate_peaks(int *left, int *right) *right = peaks.right; } -const void* pcm_get_peak_buffer(int * count) +const void * pcm_get_peak_buffer(int *count) { - return pcm_play_dma_get_peak_buffer(count); + return pcm_play_dma_get_peak_buffer_int(count); } bool pcm_is_playing(void) @@ -233,8 +313,6 @@ void pcm_init(void) { logf("pcm_init"); - pcm_play_stopped(); - pcm_set_frequency(HW_SAMPR_DEFAULT); logf(" pcm_play_dma_init"); @@ -258,41 +336,6 @@ bool pcm_is_initialized(void) return pcm_is_ready; } -/* Common code to pcm_play_data and pcm_play_pause */ -static void pcm_play_data_start(const void *addr, size_t size) -{ - ALIGN_AUDIOBUF(addr, size); - - if (!(addr && size)) - { - pcm_play_callback_type get_more = pcm_callback_for_more; - addr = NULL; - size = 0; - - if (get_more) - { - logf(" get_more"); - get_more(&addr, &size); - ALIGN_AUDIOBUF(addr, size); - } - } - - if (addr && size) - { - logf(" pcm_play_dma_start"); - pcm_apply_settings(); - pcm_play_dma_start(addr, size); - pcm_playing = true; - pcm_paused = false; - return; - } - - /* Force a stop */ - logf(" pcm_play_dma_stop"); - pcm_play_dma_stop(); - pcm_play_stopped(); -} - void pcm_play_data(pcm_play_callback_type get_more, pcm_status_callback_type status_cb, const void *start, size_t size) @@ -304,41 +347,12 @@ void pcm_play_data(pcm_play_callback_type get_more, pcm_callback_for_more = get_more; pcm_play_status_callback = status_cb; - logf(" pcm_play_data_start"); - pcm_play_data_start(start, size); + logf(" pcm_play_data_start_int"); + pcm_play_data_start_int(start, size); pcm_play_unlock(); } -bool pcm_play_dma_complete_callback(enum pcm_dma_status status, - const void **addr, size_t *size) -{ - /* Check status callback first if error */ - if (status < PCM_DMAST_OK) - status = pcm_play_dma_status_callback(status); - - pcm_play_callback_type get_more = pcm_callback_for_more; - - if (get_more && status >= PCM_DMAST_OK) - { - *addr = NULL; - *size = 0; - - /* Call registered callback to obtain next buffer */ - get_more(addr, size); - ALIGN_AUDIOBUF(*addr, *size); - - if (*addr && *size) - return true; - } - - /* Error, callback missing or no more DMA to do */ - pcm_play_dma_stop(); - pcm_play_stopped(); - - return false; -} - void pcm_play_pause(bool play) { logf("pcm_play_pause: %s", play ? "play" : "pause"); @@ -347,28 +361,8 @@ void pcm_play_pause(bool play) if (play == pcm_paused && pcm_playing) { - if (!play) - { - logf(" pcm_play_dma_pause"); - pcm_play_dma_pause(true); - pcm_paused = true; - } - else if (pcm_get_bytes_waiting() > 0) - { - logf(" pcm_play_dma_pause"); - pcm_apply_settings(); - pcm_play_dma_pause(false); - pcm_paused = false; - } - else - { - logf(" pcm_play_dma_start: no data"); - pcm_play_data_start(NULL, 0); - } - } - else - { - logf(" no change"); + logf(" pcm_play_pause_int"); + pcm_play_pause_int(play); } pcm_play_unlock(); @@ -382,13 +376,8 @@ void pcm_play_stop(void) if (pcm_playing) { - logf(" pcm_play_dma_stop"); - pcm_play_dma_stop(); - pcm_play_stopped(); - } - else - { - logf(" not playing"); + logf(" pcm_play_stop_int"); + pcm_play_stop_int(); } pcm_play_unlock(); diff --git a/firmware/pcm_sw_volume.c b/firmware/pcm_sw_volume.c new file mode 100644 index 0000000000..bcd498fe46 --- /dev/null +++ b/firmware/pcm_sw_volume.c @@ -0,0 +1,264 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2013 by Michael Sevakis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include "pcm.h" +#include "pcm-internal.h" +#include "dsp-util.h" +#include "fixedpoint.h" +#include "pcm_sw_volume.h" + +/* source buffer from client */ +static const void * volatile src_buf_addr = NULL; +static size_t volatile src_buf_rem = 0; + +#define PCM_PLAY_DBL_BUF_SIZE (PCM_PLAY_DBL_BUF_SAMPLE*PCM_SAMPLE_SIZE) + +/* double buffer and frame length control */ +static int16_t pcm_dbl_buf[2][PCM_PLAY_DBL_BUF_SAMPLES*2] + PCM_DBL_BUF_BSS MEM_ALIGN_ATTR; +static size_t pcm_dbl_buf_size[2]; +static int pcm_dbl_buf_num = 0; +static size_t frame_size; +static unsigned int frame_count, frame_err, frame_frac; + +#ifdef AUDIOHW_HAVE_PRESCALER +static int32_t prescale_factor = PCM_FACTOR_UNITY; +static int32_t vol_factor_l = 0, vol_factor_r = 0; +#endif /* AUDIOHW_HAVE_PRESCALER */ + +/* pcm scaling factors */ +static int32_t pcm_factor_l = 0, pcm_factor_r = 0; + +#define PCM_FACTOR_CLIP(f) \ + MAX(MIN((f), PCM_FACTOR_MAX), PCM_FACTOR_MIN) +#define PCM_SCALE_SAMPLE(f, s) \ + (((f) * (s) + PCM_FACTOR_UNITY/2) >> PCM_FACTOR_BITS) + + +/* TODO: #include CPU-optimized routines and move this to /firmware/asm */ +static inline void pcm_copy_buffer(int16_t *dst, const int16_t *src, + size_t size) +{ + int32_t factor_l = pcm_factor_l; + int32_t factor_r = pcm_factor_r; + + if (LIKELY(factor_l <= PCM_FACTOR_UNITY && factor_r <= PCM_FACTOR_UNITY)) + { + /* All cut or unity */ + while (size) + { + *dst++ = PCM_SCALE_SAMPLE(factor_l, *src++); + *dst++ = PCM_SCALE_SAMPLE(factor_r, *src++); + size -= PCM_SAMPLE_SIZE; + } + } + else + { + /* Any positive gain requires clipping */ + while (size) + { + *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_l, *src++)); + *dst++ = clip_sample_16(PCM_SCALE_SAMPLE(factor_r, *src++)); + size -= PCM_SAMPLE_SIZE; + } + } +} + +bool pcm_play_dma_complete_callback(enum pcm_dma_status status, + const void **addr, size_t *size) +{ + /* Check status callback first if error */ + if (status < PCM_DMAST_OK) + status = pcm_play_call_status_cb(status); + + size_t sz = pcm_dbl_buf_size[pcm_dbl_buf_num]; + + if (status >= PCM_DMAST_OK && sz) + { + /* Do next chunk */ + *addr = pcm_dbl_buf[pcm_dbl_buf_num]; + *size = sz; + return true; + } + else + { + /* This is a stop chunk or error */ + pcm_play_stop_int(); + return false; + } +} + +/* Equitably divide large source buffers amongst double buffer frames; + frames smaller than or equal to the double buffer chunk size will play + in one chunk */ +static void update_frame_params(size_t size) +{ + int count = size / PCM_SAMPLE_SIZE; + frame_count = (count + PCM_PLAY_DBL_BUF_SAMPLES - 1) / + PCM_PLAY_DBL_BUF_SAMPLES; + int perframe = count / frame_count; + frame_size = perframe * PCM_SAMPLE_SIZE; + frame_frac = count - perframe * frame_count; + frame_err = 0; +} + +/* Obtain the next buffer and prepare it for pcm driver playback */ +enum pcm_dma_status +pcm_play_dma_status_callback_int(enum pcm_dma_status status) +{ + if (status != PCM_DMAST_STARTED) + return status; + + size_t size = pcm_dbl_buf_size[pcm_dbl_buf_num]; + const void *addr = src_buf_addr + size; + + size = src_buf_rem - size; + + if (size == 0 && pcm_get_more_int(&addr, &size)) + { + update_frame_params(size); + pcm_play_call_status_cb(PCM_DMAST_STARTED); + } + + src_buf_addr = addr; + src_buf_rem = size; + + if (size != 0) + { + size = frame_size; + + if ((frame_err += frame_frac) >= frame_count) + { + frame_err -= frame_count; + size += PCM_SAMPLE_SIZE; + } + } + + pcm_dbl_buf_num ^= 1; + pcm_dbl_buf_size[pcm_dbl_buf_num] = size; + pcm_copy_buffer(pcm_dbl_buf[pcm_dbl_buf_num], addr, size); + + return PCM_DMAST_OK; +} + +/* Prefill double buffer and start pcm driver */ +static void start_pcm(bool reframe) +{ + pcm_dbl_buf_num = 0; + pcm_dbl_buf_size[0] = 0; + + if (reframe) + update_frame_params(src_buf_rem); + + pcm_play_dma_status_callback(PCM_DMAST_STARTED); + pcm_play_dma_status_callback(PCM_DMAST_STARTED); + + pcm_play_dma_start(pcm_dbl_buf[1], pcm_dbl_buf_size[1]); +} + +void pcm_play_dma_start_int(const void *addr, size_t size) +{ + src_buf_addr = addr; + src_buf_rem = size; + start_pcm(true); +} + +void pcm_play_dma_pause_int(bool pause) +{ + if (pause) + pcm_play_dma_pause(true); + else if (src_buf_rem) + start_pcm(false); /* Reprocess in case volume level changed */ + else + pcm_play_stop_int(); /* Playing frame was last frame */ +} + +void pcm_play_dma_stop_int(void) +{ + pcm_play_dma_stop(); + src_buf_addr = NULL; + src_buf_rem = 0; +} + +/* Return playing buffer from the source buffer */ +const void * pcm_play_dma_get_peak_buffer_int(int *count) +{ + const void *addr = src_buf_addr; + size_t size = src_buf_rem; + const void *addr2 = src_buf_addr; + + if (addr == addr2 && size) + { + *count = size / PCM_SAMPLE_SIZE; + return addr; + } + + *count = 0; + return NULL; +} + +/* Return the scale factor corresponding to the centibel level */ +static int32_t pcm_centibels_to_factor(int volume) +{ + if (volume == PCM_MUTE_LEVEL) + return 0; /* mute */ + + /* Centibels -> fixedpoint */ + return fp_factor(PCM_FACTOR_UNITY*volume / 10, PCM_FACTOR_BITS); +} + +#ifdef AUDIOHW_HAVE_PRESCALER +/* Produce final pcm scale factor */ +static void pcm_sync_prescaler(void) +{ + int32_t factor_l = fp_mul(prescale_factor, vol_factor_l, PCM_FACTOR_BITS); + int32_t factor_r = fp_mul(prescale_factor, vol_factor_r, PCM_FACTOR_BITS); + pcm_factor_l = PCM_FACTOR_CLIP(factor_l); + pcm_factor_r = PCM_FACTOR_CLIP(factor_r); +} + +/* Set the prescaler value for all PCM playback */ +void pcm_set_prescaler(int prescale) +{ + prescale_factor = pcm_centibels_to_factor(-prescale); + pcm_sync_prescaler(); +} + +/* Set the per-channel volume cut/gain for all PCM playback */ +void pcm_set_master_volume(int vol_l, int vol_r) +{ + vol_factor_l = pcm_centibels_to_factor(vol_l); + vol_factor_r = pcm_centibels_to_factor(vol_r); + pcm_sync_prescaler(); +} + +#else /* ndef AUDIOHW_HAVE_PRESCALER */ + +/* Set the per-channel volume cut/gain for all PCM playback */ +void pcm_set_master_volume(int vol_l, int vol_r) +{ + int32_t factor_l = pcm_centibels_to_factor(vol_l); + int32_t factor_r = pcm_centibels_to_factor(vol_r); + pcm_factor_l = PCM_FACTOR_CLIP(factor_l); + pcm_factor_r = PCM_FACTOR_CLIP(factor_r); +} +#endif /* AUDIOHW_HAVE_PRESCALER */ diff --git a/firmware/sound.c b/firmware/sound.c index e442e87f8c..2ffef0e72b 100644 --- a/firmware/sound.c +++ b/firmware/sound.c @@ -26,6 +26,9 @@ #include "logf.h" #include "system.h" #include "i2c.h" +#ifdef HAVE_SW_VOLUME_CONTROL +#include "pcm_sw_volume.h" +#endif /* HAVE_SW_VOLUME_CONTROL */ /* TODO * find a nice way to handle 1.5db steps -> see wm8751 ifdef in sound_set_bass/treble @@ -215,7 +218,7 @@ static void set_prescaled_volume(void) dsp_callback(DSP_CALLBACK_SET_PRESCALE, prescale); #endif - if (current_volume == VOLUME_MIN) + if (current_volume <= VOLUME_MIN) prescale = 0; /* Make sure the chip gets muted at VOLUME_MIN */ l = r = current_volume + prescale; @@ -231,13 +234,11 @@ static void set_prescaled_volume(void) r += ((r - (VOLUME_MIN - ONE_DB)) * current_balance) / VOLUME_RANGE; } -#ifdef HAVE_SW_VOLUME_CONTROL - dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0); -#endif - /* ypr0 with sdl has separate volume controls */ #if !defined(HAVE_SDL_AUDIO) || defined(SAMSUNG_YPR0) -#if CONFIG_CODEC == MAS3507D +#if defined(HAVE_SW_VOLUME_CONTROL) || defined(HAVE_JZ4740_CODEC) + audiohw_set_master_vol(l, r); +#elif CONFIG_CODEC == MAS3507D dac_volume(tenthdb2reg(l), tenthdb2reg(r), false); #elif defined(HAVE_UDA1380) || defined(HAVE_WM8975) || defined(HAVE_WM8758) \ || defined(HAVE_WM8711) || defined(HAVE_WM8721) || defined(HAVE_WM8731) \ @@ -253,7 +254,7 @@ static void set_prescaled_volume(void) #elif defined(HAVE_TLV320) || defined(HAVE_WM8978) || defined(HAVE_WM8985) || defined(HAVE_IMX233_CODEC) || defined(HAVE_AIC3X) audiohw_set_headphone_vol(tenthdb2master(l), tenthdb2master(r)); -#elif defined(HAVE_JZ4740_CODEC) || defined(HAVE_SDL_AUDIO) || defined(ANDROID) +#elif defined(HAVE_SDL_AUDIO) || defined(ANDROID) audiohw_set_volume(current_volume); #endif #else /* HAVE_SDL_AUDIO */ diff --git a/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c b/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c index ab9efc91b0..065433e12a 100644 --- a/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/codec-jz4740.c @@ -24,11 +24,12 @@ #include "sound.h" #include "jz4740.h" #include "system.h" +#include "pcm_sw_volume.h" /* TODO */ const struct sound_settings_info audiohw_settings[] = { #ifdef HAVE_SW_VOLUME_CONTROL - [SOUND_VOLUME] = {"dB", 0, 1, SW_VOLUME_MIN, 6, 0}, + [SOUND_VOLUME] = {"dB", 0, 1, -74, 6, -25}, #else [SOUND_VOLUME] = {"dB", 0, 1, 0, 6, 0}, #endif @@ -293,16 +294,24 @@ void audiohw_init(void) i2s_codec_init(); } -void audiohw_set_volume(int v) +void audiohw_set_master_vol(int vol_l, int vol_r) { - if(v >= 0) - { - /* 0 <= v <= 60 */ - unsigned int codec_volume = ICDC_CDCCR2_HPVOL(v / 20); +#ifdef HAVE_SW_VOLUME_CONTROL + /* SW volume for <= 1.0 gain, HW at unity, < VOLUME_MIN == MUTE */ + int sw_volume_l = vol_l < VOLUME_MIN ? PCM_MUTE_LEVEL : MIN(vol_l, 0); + int sw_volume_r = vol_r < VOLUME_MIN ? PCM_MUTE_LEVEL : MIN(vol_r, 0); + pcm_set_master_volume(sw_volume_l, sw_volume_r); +#endif /* HAVE_SW_VOLUME_CONTROL */ - if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_HPVOL(0x3)) != codec_volume) - REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_HPVOL(0x3)) | codec_volume; - } + /* NOTE: the channel being cut if balance is not equal will need + adjusting downward so maintain proportion if using volume boost */ + + /* HW volume for > 1.0 gain */ + int v = MAX(vol_l, vol_r); + unsigned int hw_volume = v > 0 ? ICDC_CDCCR2_HPVOL(v / 20) : 0; + + if((REG_ICDC_CDCCR2 & ICDC_CDCCR2_HPVOL(0x3)) != hw_volume) + REG_ICDC_CDCCR2 = (REG_ICDC_CDCCR2 & ~ICDC_CDCCR2_HPVOL(0x3)) | hw_volume; } void audiohw_set_frequency(int freq) diff --git a/lib/rbcodec/dsp/dsp_misc.c b/lib/rbcodec/dsp/dsp_misc.c index 1083215c17..a8c9423cfb 100644 --- a/lib/rbcodec/dsp/dsp_misc.c +++ b/lib/rbcodec/dsp/dsp_misc.c @@ -35,11 +35,6 @@ #endif #include -#if defined(HAVE_SW_TONE_CONTROLS) && defined(HAVE_SW_VOLUME_CONTROL) -/* Still need this for volume control */ -#include "settings.h" -#endif - /** Firmware callback interface **/ /* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ @@ -58,19 +53,6 @@ int dsp_callback(int msg, intptr_t param) case DSP_CALLBACK_SET_TREBLE: tone_set_treble(param); break; - /* FIXME: This must be done by bottom-level PCM driver so it works with - all PCM, not here and not in mixer. I won't fully support it - here with all streams. -- jethead71 */ -#ifdef HAVE_SW_VOLUME_CONTROL - case DSP_CALLBACK_SET_SW_VOLUME: - if (global_settings.volume < SW_VOLUME_MAX || - global_settings.volume > SW_VOLUME_MIN) - { - int vol_gain = get_replaygain_int(global_settings.volume * 100); - pga_set_gain(PGA_VOLUME, vol_gain); - } - break; -#endif /* HAVE_SW_VOLUME_CONTROL */ #endif /* HAVE_SW_TONE_CONTROLS */ case DSP_CALLBACK_SET_CHANNEL_CONFIG: channel_mode_set_config(param); diff --git a/lib/rbcodec/dsp/pga.h b/lib/rbcodec/dsp/pga.h index f0c4c4717f..c05265873b 100644 --- a/lib/rbcodec/dsp/pga.h +++ b/lib/rbcodec/dsp/pga.h @@ -28,9 +28,6 @@ enum pga_gain_ids { PGA_EQ_PRECUT = 0, PGA_REPLAYGAIN, -#ifdef HAVE_SW_VOLUME_CONTROL - PGA_VOLUME, -#endif PGA_NUM_GAINS, }; -- cgit v1.2.3