通过pcm音频数据计算分贝

本文介绍了一款开源的Android音频播放库《wlmusic》,并详细讲解了如何利用该库动态显示声音的波形。通过计算声音采样点的振幅和分贝值,可以实现音频播放时的实时声音强度可视化。文章提供了具体的计算公式和算法,包括平均值、均方根和最大振幅值的计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近开源了一个Android音频播放库《wlmusic》可以直接在gradle中导入集成。已经实现了音频播放所有逻辑,除了播放正常的网络音频和本地音频外,还可以播放raw和assets文件中的音频和循环不间断播放短音频等功能。这些做完了之后发现一个问题,怎么能动态显示声音的波形?于是想到了一个声音的关键词“分贝”,然后百度、谷歌了一番找到了一个有用的公式:

参数:Pref:就是声音总的振幅最大值;Prms:就是当前声音的振幅值;Lp:就是我们需要的声音分贝值了。

比如:我们声音是无符号16bit深度的,那么其每个采样点的值应该在(0~2^16-1既:0~65535)范围内,带入公式我们可以计算到(不用除以最大振幅值):20*log(65535)=96.32db,所以根据这个我们只要拿到某个采样点的振幅值,也就是当前声音采样点转成16bit后的值就可以计算出相应的分贝值了。那么怎么求声音采样点的振幅呢?这是一个问题,不过也有解决办法了。

获取pcm声音采样点的振幅:

        这里以我项目中用OpenSL来播放FFmpeg重采样生成的PCM声音为例,PCM声音是重采样为无符号16bit的深度的,然后我们需要得到某一时间(一般是零点几毫秒)PCM所在内存的地址和PCM声音的大小,而16bit也就是16bit/8bit=2byte,在c语言中2byte用short int来表示,因此我们可以从PCM所在地址里面按顺序取出2个byte的数据然后转化成short int的值就可以拿到当前采样点的振幅了,获取的方式是用c语言中的memcpy拷贝2个字节的数据求值就可以了。(注:因为采用点很密集,如果每个采用点都计算一下分贝的话,会消耗一定的性能或者导致声音播放不连贯,所这里采用取其绝对值和的平均值就可以了,因为在这段时间内,我们看不出任何的区别。)

/**
* 获取所有振幅之平均值 计算db (振幅最大值 2^16-1 = 65535 最大值是 96.32db)
* 16 bit == 2字节 == short int
* 无符号16bit:96.32=20*lg(65535);
*
* @param pcmdata 转换成char类型,才可以按字节操作
* @param size pcmdata的大小
* @return
*/

int Audio::getPcmDB(const unsigned char *pcmdata, size_t size) {

    int db = 0;
    short int value = 0;
    double sum = 0;

    for(int i = 0; i < size; i += 2)
    {
        memcpy(&value, pcmdata+i, 2); //获取2个字节的大小(值)
        sum += abs(value); //绝对值求和
    }
    sum = sum / (size / 2); //求平均值(2个字节表示一个振幅,所以振幅个数为:size/2个)

    if(sum > 0)
    {
        db = (int)(20.0*log10(sum));
    }
    return db;

}

很多场合我们需要动态显示实时声音分贝,下面列举三种计算分贝的算法。(以双声道为例,也就是一个short类型,最大能量值为32767)

1:计算分贝 音频数据与大小

首先我们分别累加每个采样点的数值,除以采样个数,得到声音平均能量值。
然后再将其做100与32767之间的等比量化。得到1-100的量化值。
通常情况下,人声分布在较低的能量范围,这样就会使量化后的数据大致分布在1-20的较小区间,不能够很敏感的感知变化。
所以我们将其做了5倍的放大,当然计算后大于100的值,我们将其赋值100.

//参数为数据,采样个数
//返回值为分贝
#define VOLUMEMAX   32767
int SimpleCalculate_DB(short* pcmData, int sample)
{
    signed short ret = 0;
    if (sample > 0){
        int sum = 0;
        signed short* pos = (signed short *)pcmData;
        for (int i = 0; i < sample; i++){
            sum += abs(*pos);
            pos++;
        }
        ret = sum * 500.0 / (sample * VOLUMEMAX);
        if (ret >= 100){
            ret = 100;
        }
   }
   return ret;
}

2:计算均方根(RMS) 即能量值

static const float kMaxSquaredLevel = 32768 * 32768;
constexpr float kMinLevel = 30.f;

void Process(const int16_t* data, size_t length) 
{
    float sum_square_ = 0;
    size_t sample_count_ = 0;
    for (size_t i = 0; i < length; ++i) {
        sum_square_ += data[i] * data[i];
    }
    sample_count_ += length;.
    float rms = sum_square_ / (sample_count_ * kMaxSquaredLevel);
    //20log_10(x^0.5) = 10log_10(x)
    rms = 10 * log10(rms);
    if (rms < -kMinLevel)
        rms = -kMinLevel;
    rms = -rms;
    return static_cast<int>(rms + 0.5);
}

3:获取音频数据最大的振幅(即绝对值最大)(0-32767),除以1000,得到(0-32)。从数组中获取相应索引所对应的分贝值。(提取自webrtc)

const int8_t permutation[33] =
    {0,1,2,3,4,4,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,9,9,9,9,9,9,9,9,9,9,9};
    
int16_t WebRtcSpl_MaxAbsValueW16C(const int16_t* vector, size_t length) 
{
    size_t i = 0;
    int absolute = 0, maximum = 0;
    for (i = 0; i < length; i++) {
        absolute = abs((int)vector[i]);
        if (absolute > maximum) {
            maximum = absolute;
        }
    }
    if (maximum > 32767) {
        maximum = 32767;
    }
    return (int16_t)maximum;
}

void ComputeLevel(const int16_t* data, size_t length)
{
    int16_t _absMax = 0;
    int16_t _count = 0;
    int8_t _currentLevel = 0;
    int16_t absValue(0);
    absValue = WebRtcSpl_MaxAbsValueW16(data,length);
    if (absValue > _absMax)
        _absMax = absValue;
    if (_count++ == 10) {
        _count = 0;
        int32_t position = _absMax/1000;
        if ((position == 0) && (_absMax > 250)){
            position = 1;
        }
        _currentLevel = permutation[position];
        _absMax >>= 2;
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值