音量调节调研报告

文章目录

声音的基本概念

声音是介质振动在听觉系统中产生的反应。
声音总可以被分解为不同频率不同强度正弦波的叠加(傅里叶变换)。

分贝

分贝(deciBel),通常表示两个声音信号或电力信号在功率或强度方面的相对差别的单位,相当于两个水平的比率的常用对数的十倍。转换成公式表示如下:
dB = 10logX (乘10是为了计算方便,未乘10之前的单位叫贝尔,它可以把一个非常小的数通过对数变换转换成一个可读性较高的一个数)

应用于声音领域的分贝单位,就是在上面的基础上,根据人耳对声音的特性,做了进一步的简化:

变为20的原因是因为计量单位换成帕斯卡,所以多了一个开方的操作;其中的pref是标准参考声压值20微帕

而适用于数字音频的转换关系如下(以16bit的位深进行举例):
**dBFS = 20 * log (采样信号 / 1111 1111 1111 1111) **

模拟音频

模拟音频(Analogous Audio),用连续的电流或电压表示的音频信号,在时间和振幅上是连续。 在过去记录声音记录的都是模拟音频,比如机械录音(以留声机、机械唱片为代表)、光学录音(以电影胶片为代表)、磁性录音(以磁带录音为代表)等模拟录音方式。

周期(频率):音调
振幅:音量
人耳可以识别到频率范围:

数字音频

数字音频(Digital Audio),通过采样和量化技术获得的离散性(数字化)音频数据。计算机内部处理的是二进制数据,处理的都是数字音频,所以需要将模拟音频通过采样、量化转换成有限个数字表示的离散序列(即实现音频数字化)。

采样频率:周期的倒数,至少是信号频率的两倍(香农采样定理);常见采样率:48kHz,44.1kHz
量化深度:也叫位深,表示样本的比特数;可以把它理解成幅度的量化精度,比如:8bit 就可以把幅度量化成256级,bit数越高音乐质量越好
声道:Mono(单声道)和Stereo(双声道、立体声)

PCM(Pulse Code Modulation)

PCM(Pulse Code Modulation)编码,即通过脉冲编码调制方法生成数字音频数据的技术或格式,是一种无损编码格式,是音频模拟信号数字化的一种方法,需要经过采样、量化和编码过程,以实现音频模拟信号数字化。
数字表现形式:

root@RainOS:~# ffmpeg -formats | grep PCM
 DE alaw PCM A-law
 DE f32be PCM 32-bit floating-point big-endian
 DE f32le PCM 32-bit floating-point little-endian
 DE f64be PCM 64-bit floating-point big-endian
 DE f64le PCM 64-bit floating-point little-endian
 DE mulaw PCM mu-law
 DE s16be PCM signed 16-bit big-endian
 DE s16le PCM signed 16-bit little-endian
 DE s24be PCM signed 24-bit big-endian
 DE s24le PCM signed 24-bit little-endian
 DE s32be PCM signed 32-bit big-endian
 DE s32le PCM signed 32-bit little-endian
 DE s8 PCM signed 8-bit
 DE u16be PCM unsigned 16-bit big-endian
 DE u16le PCM unsigned 16-bit little-endian
 DE u24be PCM unsigned 24-bit big-endian
 DE u24le PCM unsigned 24-bit little-endian
 DE u32be PCM unsigned 32-bit big-endian
 DE u32le PCM unsigned 32-bit little-endian
 DE u8 PCM unsigned 8-bit

在日常生活中常见的WAV格式音频,就是对PCM做一个简单的封装(添加文件头信息,比如:采样率,通道数,文件大小等)。
所以当我们手动播放PCM或这RAW裸数据时需要手动指定裸数据的基本信息。

音量调节

在前面我们已经了解了分贝和PCM的相关知识,且PCM波形中的幅度就代表着音量大小。
基于这些,我们就可以对音量调节的原理做一个展开。
由上面的分贝公式我们可以推算出位深为8bit的动态范围:
**dB = -20 * log(256) ** ==>> -48 - 0 (dB)
位深为16bit的动态范围
**dB = -20*log(65536) ** ==>> -96 - 0 (dB)
由上我们可以得出两种音量变化曲线:
1)音量滑块与声音增幅大小线性变化

上述左图中,音量滑块位置与声音振幅为线性增长关系,右图是我们人耳感受的音量大小与滑块位置关系。可知,在左侧移动相同距离的滑块,感知到的声音变化范围很大,在右侧接近声音最大值移动相同距离滑块,感知到的声音大小变化就很小了。
2)音量滑块与声音振幅大小对数关系变化

上述左图中,音量滑块位置与声音振幅对数关系增长。右图中无论哪个位置,移动相同距离滑块,感知到的声音变化都是相同的。
了解了这些基本的概念之后,我们就可以看看Android是如何实现这个音量调节的,而且可以做到刻度的线性变化。

Android的实现

// convert volume steps to natural log scale

// change this value to change volume scaling
static const float dBPerStep = 0.5f;
// shouldn't need to touch these
static const float dBConvert = -dBPerStep * 2.302585093f / 20.0f;     // ln(10) = 2.302585093f 
static const float dBConvertInverse = 1.0f / dBConvert;

float AudioSystem::linearToLog(int volume)
{
    // float v = volume ? exp(float(100 - volume) * dBConvert) : 0;
    // ALOGD("linearToLog(%d)=%f", volume, v);
    // return v;
    return volume ? exp(float(100 - volume) * dBConvert) : 0;
}

int AudioSystem::logToLinear(float volume)
{
    // int v = volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0;
    // ALOGD("logTolinear(%d)=%f", v, volume);
    // return v;
    return volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0;
}

这边省略前面的一些调用流程,直接从关键部分进行分析

system/hardware/rk29/audio/AudioPolicyManagerBase.cpp

float AudioPolicyManagerBase::volIndexToAmpl(audio_devices_t device, const StreamDescriptor& streamDesc,
        int indexInUi)
{
    device_category deviceCategory = getDeviceCategory(device);
    const VolumeCurvePoint *curve = streamDesc.mVolumeCurve[deviceCategory];

    // the volume index in the UI is relative to the min and max volume indices for this stream type
    int nbSteps = 1 + curve[VOLMAX].mIndex -
            curve[VOLMIN].mIndex;   // 计算预置的曲线区间的范围,这里是(1-100)
    int volIdx = (nbSteps * (indexInUi - streamDesc.mIndexMin)) /
            (streamDesc.mIndexMax - streamDesc.mIndexMin);  //(由传进来的UIIndex计算百分比的index,比如现在是第一级 100*(1-0)/(15-0)=6)

    // find what part of the curve this index volume belongs to, or if it's out of bounds
    int segment = 0;
    if (volIdx < curve[VOLMIN].mIndex) { // out of bounds
        return 0.0f;
    } else if (volIdx < curve[VOLKNEE1].mIndex) {
        segment = 0;
    } else if (volIdx < curve[VOLKNEE2].mIndex) {
        segment = 1;
    } else if (volIdx <= curve[VOLMAX].mIndex) {
        segment = 2;
    } else { // out of bounds
        return 1.0f;
    }
//第一极6是在区间VOLKNEE1之间,其区间表是在AudioPolicyManager初始化的时候就已经加载,因此它对应的segment为0
    // linear interpolation in the attenuation table in dB
    float decibels = curve[segment].mDBAttenuation +
            ((float)(volIdx - curve[segment].mIndex)) *
                ( (curve[segment+1].mDBAttenuation -
                        curve[segment].mDBAttenuation) /
                    ((float)(curve[segment+1].mIndex -
                            curve[segment].mIndex)) );
  //计算衰减分贝数 curve[0].db + 该区间每一级index对应的db*index数 
    float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 ) 
  //由指数公式计算出音量amplification db = 20log(V/Vmax) linearToLog Vmax是一个参考值
    ALOGVV("VOLUME vol index=[%d %d %d], dB=[%.1f %.1f %.1f] ampl=%.5f",
            curve[segment].mIndex, volIdx,
            curve[segment+1].mIndex,
            curve[segment].mDBAttenuation,
            decibels,
            curve[segment+1].mDBAttenuation,
            amplification);

    return amplification;
}

这个函数主要作用就是:将UI上的音量值换算成(0-1)的音量系数,并把该系数更新给AudioFlinger;最后在混音的时候将pcm数据(共享内存)乘上该系数喂给声卡。
下面这个函数,是描述将pcm数据送到共享内存的一个过程。
我们可以看到,对于解析出来的pcm流数据,都会对它乘以一个音量系数,然后在把数据送往共享内存去;最后由threadLoop_write完成数据的写入

system/frameworks/av/services/audioflinger/AudioMixer.cpp

void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
        int32_t* aux)
{
    const int16_t *in = static_cast<const int16_t *>(t->in);

    if (CC_UNLIKELY(aux != NULL)) {
        int32_t l;
        int32_t r;
        // ramp gain
        if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
            int32_t vl = t->prevVolume[0];
            int32_t vr = t->prevVolume[1];
            int32_t va = t->prevAuxLevel;
            const int32_t vlInc = t->volumeInc[0];
            const int32_t vrInc = t->volumeInc[1];
            const int32_t vaInc = t->auxInc;
            // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
            // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
            // (vl + vlInc*frameCount)/65536.0f, frameCount);

            do {
                l = (int32_t)*in++;
                r = (int32_t)*in++;
                *out++ += (vl >> 16) * l;
                *out++ += (vr >> 16) * r;
                *aux++ += (va >> 17) * (l + r);
                vl += vlInc;
                vr += vrInc;
                va += vaInc;
            } while (--frameCount);

            t->prevVolume[0] = vl;
            t->prevVolume[1] = vr;
            t->prevAuxLevel = va;
            t->adjustVolumeRamp(true);
        }

        // constant gain
........

参考文档:
多媒体基础知识之PCM数据
Android Audio System线性音量和对数音量的转换
Android音量控制曲线
Android多媒体:AudioMixer
Android音频子系统,音量的调节控制(七)
audiomixer分析

上一篇:inodes过载问题解决 :Free inodes is less than 20% on volume /


下一篇:listagg within group