由于人耳对声音的听感具指数曲线型,也就是对小音量时比较敏感,随着声音的加大其听感随之变的不敏感,其变化近似指数函数曲线的形式。为了使听感变的近似直线的变化,人们在实践中就采用了音量变化近似对数式曲线型的电位器来实现这个目的。对比法产生音量控制曲线与最终扬声器输出的声压有关,当然您也可以根据扬声器的输出功率来进行比对,但功率终究不如电压来的方便。音量调节框的UI滑动条的刻度是线性的,这样就给我们生成音量控制曲线打下了很好的对比基础。下面我们就来通过一个音量调节的场景来分析Android是如何控制音量的。
首先,我们按音量调节键使得media音量逐级增加到最大。STREAM_MUSIC流的音量分为15级,通过AudioManger的handleKeyDown函数调用adjustSuggestedStreamVolume设置,一路找下去,发现在AudioService中adjustSuggestedStreamVolume然后调用adjustStreamVolume,通过消息MSG_SET_SYSTEM_VOLUME调用setSystemVolume,转到AudioSystem中的setStreamVolumeIndex,再通过jni层调用本地层的AudioSystem调用AudioPolicymanagerService,最后到AudiopolicyManagerBase的setStreamVolumeIndex,接下来的由checkAndSetVolume调用computeVolume,马上就要到真相大白的时候了。volIndexToAmpl函数时真正计算音量的地方,我们一起来分析这个函数
[mw_shl_code=java,true]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)
ALOGI("VOLUME vol indexInUi=%d, nbSteps=%d, mIndexMin=%d, mIndexMax=%d",indexInUi,nbSteps,streamDesc.mIndexMin,streamDesc.mIndexMax);
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是一个参考值
ALOGI("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;
}[/mw_shl_code]
然后通过以上函数计算后得到一个0-1之间的float音量值,最后通过mpClientInterface->setStreamVolume设置到audioflinger中的mStreamTypes[stream].value,在prepareTracks_l中将音量值传入到audiomixer中混合。至此音量调节的全过程介绍完毕,下面六个附表是在AudioPolicyManagerBase中预置的六个音量曲线db分布表
音量刻度 |
1 |
33 |
66 |
100 |
输出衰减量db |
-49.5 |
-33.5 |
-17.0 |
0.0 |
表1-1 default volume curve
音量刻度 |
1 |
20 |
60 |
100 |
输出衰减量db |
-58.0 |
-40.0 |
-17.0 |
0.0 |
表1-2 default media volume curve
音量刻度 |
1 |
20 |
60 |
100 |
输出衰减量db |
-56.0 |
-34.0 |
-11.0 |
0.0 |
表1-3 speaker media volume curve
音量刻度 |
1 |
33 |
66 |
100 |
输出衰减量db |
-29.7 |
-20.1 |
-10.2 |
0.0 |
表1-4 speaker sonification volume curve
音量刻度 |
1 |
33 |
66 |
100 |
输出衰减量db |
-24.0 |
-18.0 |
-12.0 |
-6.0 |
表1-5 default system volume curve
音量刻度 |
1 |
33 |
66 |
100 |
输出衰减量db |
-30.0 |
-26.0 |
-22.0 |
-18.0 |
表1-6 headset system volume curve
总结:通过调节音量键这一调节音量的场景,从java层,media本地层,AudioFlinger服务层,硬件抽象层等四层分析音量调节函数是如何完成一个音量调节任务的。总结了从线性UI的Index如何转化为对数关系的人耳的听觉转换的公式,以及预置的区间表,根据不同的硬件,我们可以自己预置适当的区间表,使得音量曲线更符合我们的听感。