上次对音量设置不太在乎,结果发现其中还是有不少知识的,补上一篇关于音量寄存器和硬件电路关系的笔记。

设置音量

(ES8311_REG32, 0xff) // DAC 音量,默认是0,最大255,可以编个映射的函数设定音量曲线调整。
寄存器

简单粗暴的方法是直接把0-100映射到寄存器的0-0xff值,缺点是同样的程序,不同的功放会导致声音大小不一样。

官方组件中的说明:为了平衡在不同平台上播放相同内容的响度差异,需要了解音频增益的相关机制。简单说来音频增益包括软件增益 (可调节)和硬件增益 (不可调节) 两部分。软件增益可以通过改变音频数据的幅值或者改变音量寄存器实现。硬件增益受外围电路的影响,主要取决于模拟信号的放大系数。实现中选取了典型的影响参数 esp_codec_dev_hw_gain_t,作为配置参数进行配置,以抵消平台间响度差异,详情可参考代码注释 [esp_codec_dev_vol.h]。

于是跟进“简单“看一下,又花了一晚上补知识 。
ES8311中,0x32寄存器的0-255对应-95.5dB-32.0dB,寄存器值每改变1,对应改变0.5dB。人耳对声音的感知是对数关系,如果我们希望让耳朵感觉调音量大小时是连续变化的,就要按dB来进行改变,虽然寄存器值和DB一一对应,但其中涉及一些计算,需要先按DB计算,并参考一些DB值,最后换成寄存器对应值进行写入。

音量响度的直观感觉有这样一个参考:

声压级 (dB SPL) 对应的声音场景 可听性
-10 dB 实验室超静环境 极少数人可感知
0 dB 人耳最小可听阈值 健康年轻人勉强可闻
10 dB 树叶沙沙声 清晰可闻
30 dB: 安静的图书馆
60 dB: 正常对话
85 dB: 长期暴露可能损伤听力
120 dB: 摇滚音乐会前排

从ES8311本身来看,达到32dB已经是寄存器的极限了,但在ES8311后面的功放芯片电路(PA)还会产生增益,最后要把软件增益和硬件增益相加才是最终增益,为了不至于换个功率大点的功放就要重写调音量范围的程序,需要考虑用总的增益来设置这个寄存器。做到所见即所得。就是程序里写多少DB,输出就是多少DB(当然是在功放提供的最大范围内)。
硬件电路的增益要测量,具体了解个大概意思就行,我以耳朵感受为准,估个差不多的值。
这其中还有一个匹配的情况,如果DAC输出电压 > PA输入电压:那在软件设置增益时需要衰减,如果DAC输出电压 < PA输入电压:那在软件设置增益时需要放大。
这么说很枯燥,上例子:
本次硬件电路中,ES8311的输出范围是0-3.3V,PA的输入范围是0-5V,因此这两个在匹配时,已经因为电压的原因产生了一个负增益值:
20 * log10(3.5 / 5) = -3.61 dB
因此软件设置时,首先就得增加3.61dB,后面输入时才能达到0dB。
假设PA输出时增益是20,那么理论上寄存器设成-17.39时,通过PA输入,再通过输出,增益就是0dB,简单的数学加法
-17.39 - 3.16 + 20 = 0。
反过来说,如果你想达到最终20dB的响度,那寄存器就要设成3.61,相对于寄存器,后面硬件电路实际提供的增益只有17.39dB。这就是官方说明里那段话的意思。

官方的新组件中通过以下函数来实现硬件总增益计算,供后面校准实际增益用,其中pa_gain是PA的增益,以刚才的例子中电压及PA增益来计算,这个函数的返回值是17.39,先记住这个数,后面分析程序要用。

float esp_codec_dev_col_calc_hw_gain(esp_codec_dev_hw_gain_t *hw_gain)
{
    float pa_voltage = hw_gain->pa_voltage;
    float dac_voltage = hw_gain->codec_dac_voltage;
    if (pa_voltage == 0.0) {
        pa_voltage = 5.0;
    }
    if (dac_voltage == 0.0) {
        dac_voltage = 3.3;
    }
    return 20 * log10(dac_voltage / pa_voltage) + hw_gain->pa_gain;
}

官方新版组件是在初始化ES8311过程时调用了这个函数,保留了这个校准值。

下面具体分析设置音量的函数es8311_set_vol()。

static int es8311_set_vol(const audio_codec_if_t *h, float db_value)
{
    audio_codec_es8311_t *codec = (audio_codec_es8311_t *) h;
    if (codec == NULL) {
        return ESP_CODEC_DEV_INVALID_ARG;
    }
    if (codec->is_open == false) {
        return ESP_CODEC_DEV_WRONG_STATE;
    }
    //-------------------------------------
    db_value -= codec->hw_gain;
    int reg = esp_codec_dev_vol_calc_reg(&vol_range, db_value);
    ESP_LOGD(TAG, "Set volume reg:%x db:%d", reg, (int) db_value);
    return es8311_write_reg(codec, ES8311_DAC_REG32, (uint8_t) reg);
}

从分割线之后开始,参数 db_value 是想要设置的音量响度。
以期望得到的音量减去硬件电路增益,也就是刚才的17.39,得到校准后的增益值;
比如我们希望实现最终45dB的输出,那实际要设的寄存器值是45-17.39=27.61;
调用esp_codec_dev_vol_calc_reg()函数,把这个27.61映射成寄存器值;
最后写入寄存器0x32中。

esp_codec_dev_vol_calc_reg()函数把27.61映射寄存器值的过程如下:

int esp_codec_dev_vol_calc_reg(const esp_codec_dev_vol_range_t *vol_range, float db)
{
    if (vol_range->max_vol.db_value == vol_range->min_vol.db_value) {
        return vol_range->max_vol.vol;
    }
    if (db >= vol_range->max_vol.db_value) {
        return vol_range->max_vol.vol;
    }
    if (db <= vol_range->min_vol.db_value) {
        return vol_range->min_vol.vol;
    }
    float ratio =
        (vol_range->max_vol.vol - vol_range->min_vol.vol) / (vol_range->max_vol.db_value - vol_range->min_vol.db_value);
    return (int) ((db - vol_range->min_vol.db_value) * ratio + vol_range->min_vol.vol);
}

参数 vol_range是提前预设值,就是0x32寄存器能提供的最大范围。
ES8311提供了1-7位,范围是0至255,对应-95.5至32dB。
函数首先限制了dB的范围,也就是说你要设的27.61在-95.5至32这个范围里,从这里可以看出,按例子的参数,最大响度也就输出32+27.61=57.61dB;
然后计算出寄存器值与dB的映射系数是2,即每加减0.5dB,寄存器加减1。
最后计算寄存器值。说真的,真是很烦看到一个数学式子用了这么长的名字来代替,实在看不下去,但这个式子可以通用,将来换个寄存器和DB值是其他对应方式的,改vol_range值就行了,计算公式还可以用。
要是专门优化成ES8311用,核心就一句:

    return (int) ((db + 95.5) * 2);  

寄存器值应该是246

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐