196 lines
7.9 KiB
C
196 lines
7.9 KiB
C
|
|
/*
|
|||
|
|
* ESPRESSIF MIT License
|
|||
|
|
*
|
|||
|
|
* Copyright (c) 2022 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
|
|||
|
|
*
|
|||
|
|
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in
|
|||
|
|
* which case, it is free of charge, to any person obtaining a copy of this
|
|||
|
|
* software and associated documentation files (the "Software"), to deal in the
|
|||
|
|
* Software without restriction, including without limitation the rights to use,
|
|||
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|||
|
|
* the Software, and to permit persons to whom the Software is furnished to do
|
|||
|
|
* so, subject to the following conditions:
|
|||
|
|
*
|
|||
|
|
* The above copyright notice and this permission notice shall be included in
|
|||
|
|
* all copies or substantial portions of the Software.
|
|||
|
|
*
|
|||
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|||
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|||
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
|
|
* SOFTWARE.
|
|||
|
|
*
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
* |----------------Digital Gain---------------------|--Analog
|
|||
|
|
* Gain-|
|
|||
|
|
*
|
|||
|
|
* |--------------| |--------------------| |------------------|
|
|||
|
|
* |---------| |----------------| | Audio Stream |--->| Audio Process Gain
|
|||
|
|
* |--->| Codec DAC Volume |--->| PA Gain |--->| Speaker Output |
|
|||
|
|
* |--------------| |--------------------| |------------------|
|
|||
|
|
* |---------| |----------------|
|
|||
|
|
*
|
|||
|
|
* The speaker playback route is shown as the block diagram above. The speaker
|
|||
|
|
* loudness is affected by both audio Digital Gain and Analog Gain.
|
|||
|
|
*
|
|||
|
|
* Digital Gain:
|
|||
|
|
* Audio Process Gain: Audio Process, such as ALC, AGC, DRC target MAX Gain.
|
|||
|
|
* Codec DAC Volume: The audio codec DAC volume control, such as ES8311
|
|||
|
|
* DAC_Volume control register.
|
|||
|
|
*
|
|||
|
|
* Analog Gain:
|
|||
|
|
* PA Gain: The speaker power amplifier Gain, which is determined by the
|
|||
|
|
* hardware circuit board.
|
|||
|
|
*
|
|||
|
|
* User can control the speaker playback volume by adjusting Codec DAC Volume.
|
|||
|
|
*
|
|||
|
|
* We use volume level (1-100) to represent the volume levels, level 100 is the
|
|||
|
|
* MAX volume. We create a volume mapping index table for the user to set the
|
|||
|
|
* volume level through Codec DAC volume. The default mapping table maps volume
|
|||
|
|
* level(1-100) to Codec DAC Volume (-49.5dB, 0dB). The volume setting has 25
|
|||
|
|
* volume levels. Level step is 4, and the corresponding to Codec DAC Volume
|
|||
|
|
* Gain is 2 dB step. Normally, Codec DAC volume -50 dB reproduces a minimal
|
|||
|
|
* speaker loudness, and the 2 dB step allows the user to detect the volume
|
|||
|
|
* change.
|
|||
|
|
*
|
|||
|
|
* Gain and Decibel Reference:
|
|||
|
|
* https://www.espressif.com/zh-hans/media_overview/blog
|
|||
|
|
*
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#include "audio_volume.h"
|
|||
|
|
|
|||
|
|
#include <math.h>
|
|||
|
|
#include <string.h>
|
|||
|
|
|
|||
|
|
#include "audio_mem.h"
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* The speaker playback route gain (Audio Process Gain + Codec DAC Volume + PA
|
|||
|
|
* Gain) needs to ensure that the speaker PA output is not saturated and exceeds
|
|||
|
|
* the speaker rated power. We define the maximum route gain as MAX_GAIN. To
|
|||
|
|
* ensure the speaker PA output is not saturated, MAX_GAIN can be calculated
|
|||
|
|
* simply by the formula. MAX_GAIN = 20 * log(Vpa/Vdac) Vpa: PA power supply
|
|||
|
|
* Vdac: Codec DAC power supply
|
|||
|
|
* e.g., Vpa = 5V, Vdac = 3.3V, then MAX_GAIN = 20 * log(5/3.3) = 3.6 dB.
|
|||
|
|
* If the speaker rated power is lower than the speaker PA MAX power, MAX_GAIN
|
|||
|
|
* should be defined according to the speaker rated power.
|
|||
|
|
*
|
|||
|
|
*/
|
|||
|
|
#define VPA (5.0)
|
|||
|
|
#define VDAC (3.3)
|
|||
|
|
#define MAX_GAIN (20.0 * log10(VPA / VDAC))
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
* User can customize the volume setting by modifying the mapping table and
|
|||
|
|
* adjust the volume step according to the speaker playback system, and the
|
|||
|
|
* other volume levels shift the value accordingly. Integers are used instead of
|
|||
|
|
* floating-point variables to reduce storage space. -80 means -40 dB, 0 means 0
|
|||
|
|
* dB.
|
|||
|
|
*/
|
|||
|
|
static const int8_t dac_volume_offset[] = {
|
|||
|
|
-99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85,
|
|||
|
|
-84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70,
|
|||
|
|
-69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55,
|
|||
|
|
-54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40,
|
|||
|
|
-39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25,
|
|||
|
|
-24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10,
|
|||
|
|
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief Get DAC volume offset from user set volume, you can use an array or
|
|||
|
|
* function to finish this map
|
|||
|
|
*
|
|||
|
|
* @note The max DAC volume is 0 dB when the user volume is 100. 0 dB means
|
|||
|
|
* there is no attenuation of the sound source, and it is the original sound
|
|||
|
|
* source. It can not exceed 0 dB. Otherwise, there is a risk of clipping noise.
|
|||
|
|
* @note For better audio dynamic range, we'd better use 0dB full scale digital
|
|||
|
|
* gain and lower analog gain.
|
|||
|
|
* @note DAC volume offset is positively correlated with the user volume.
|
|||
|
|
*
|
|||
|
|
* @param volume User set volume (1-100)
|
|||
|
|
*
|
|||
|
|
* @return
|
|||
|
|
* - Codec DAC volume offset. The max value must be 0 dB.
|
|||
|
|
*/
|
|||
|
|
static inline float codec_get_dac_volume_offset(int volume) {
|
|||
|
|
float offset = dac_volume_offset[volume - 1] / 2.0;
|
|||
|
|
return offset;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief The register value is linear to the dac_volume
|
|||
|
|
*/
|
|||
|
|
static inline uint8_t audio_codec_calculate_reg(volume_handle_t vol_handle,
|
|||
|
|
float dac_volume) {
|
|||
|
|
codec_dac_volume_config_t *handle = (codec_dac_volume_config_t *)vol_handle;
|
|||
|
|
uint8_t reg = (uint8_t)(dac_volume / (handle->dac_vol_symbol *
|
|||
|
|
handle->volume_accuracy) +
|
|||
|
|
handle->zero_volume_reg);
|
|||
|
|
return reg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
volume_handle_t audio_codec_volume_init(codec_dac_volume_config_t *config) {
|
|||
|
|
codec_dac_volume_config_t *handle = (codec_dac_volume_config_t *)audio_calloc(
|
|||
|
|
1, sizeof(codec_dac_volume_config_t));
|
|||
|
|
memcpy(handle, config, sizeof(codec_dac_volume_config_t));
|
|||
|
|
if (!handle->offset_conv_volume) {
|
|||
|
|
handle->offset_conv_volume = codec_get_dac_volume_offset;
|
|||
|
|
}
|
|||
|
|
return (volume_handle_t)handle;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief Take zero dac_volume as the origin and calculate the volume offset
|
|||
|
|
* according to the register value
|
|||
|
|
*/
|
|||
|
|
float audio_codec_cal_dac_volume(volume_handle_t vol_handle) {
|
|||
|
|
codec_dac_volume_config_t *handle = (codec_dac_volume_config_t *)vol_handle;
|
|||
|
|
float dac_volume = handle->dac_vol_symbol * handle->volume_accuracy *
|
|||
|
|
(handle->reg_value - handle->zero_volume_reg);
|
|||
|
|
return dac_volume;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uint8_t audio_codec_get_dac_reg_value(volume_handle_t vol_handle, int volume) {
|
|||
|
|
float dac_volume = 0;
|
|||
|
|
int user_volume = volume;
|
|||
|
|
codec_dac_volume_config_t *handle = (codec_dac_volume_config_t *)vol_handle;
|
|||
|
|
|
|||
|
|
if (user_volume < 0) {
|
|||
|
|
user_volume = 0;
|
|||
|
|
} else if (user_volume > 100) {
|
|||
|
|
user_volume = 100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (user_volume == 0) {
|
|||
|
|
dac_volume =
|
|||
|
|
handle->min_dac_volume; // Make sure the speaker voice is near silent
|
|||
|
|
} else {
|
|||
|
|
/*
|
|||
|
|
* For better audio performance, at the max volume, we need to ensure:
|
|||
|
|
* Audio Process Gain + Codec DAC Volume + PA Gain <= MAX_GAIN.
|
|||
|
|
* The PA Gain and Audio Process Gain are known when the board design is
|
|||
|
|
* fixed, so max Codec DAC Volume = MAX_GAIN - PA Gain - Audio Process
|
|||
|
|
* Gain,then the volume mapping table shift accordingly.
|
|||
|
|
*/
|
|||
|
|
dac_volume = handle->offset_conv_volume(user_volume) + MAX_GAIN -
|
|||
|
|
handle->board_pa_gain;
|
|||
|
|
dac_volume = dac_volume < handle->max_dac_volume ? dac_volume
|
|||
|
|
: handle->max_dac_volume;
|
|||
|
|
}
|
|||
|
|
handle->reg_value = audio_codec_calculate_reg(handle, dac_volume);
|
|||
|
|
handle->user_volume = user_volume;
|
|||
|
|
return handle->reg_value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void audio_codec_volume_deinit(volume_handle_t vol_handle) {
|
|||
|
|
if (vol_handle) {
|
|||
|
|
audio_free(vol_handle);
|
|||
|
|
vol_handle = NULL;
|
|||
|
|
}
|
|||
|
|
}
|