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 <jethead71@rockbox.org> Tested-by: Michael Sevakis <jethead71@rockbox.org>
This commit is contained in:
parent
a9049a79d7
commit
f5a5b94686
13 changed files with 478 additions and 156 deletions
|
@ -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 */
|
||||
|
|
|
@ -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 | \
|
||||
|
|
|
@ -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 | \
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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,
|
||||
|
|
40
firmware/export/pcm_sw_volume.h
Normal file
40
firmware/export/pcm_sw_volume.h
Normal file
|
@ -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 <audiohw.h>
|
||||
|
||||
#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 */
|
|
@ -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
|
||||
|
||||
|
|
195
firmware/pcm.c
195
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();
|
||||
|
|
264
firmware/pcm_sw_volume.c
Normal file
264
firmware/pcm_sw_volume.c
Normal file
|
@ -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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -35,11 +35,6 @@
|
|||
#endif
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue