一、引言:
Android的音量控制是典型的audiopolicy和audioflinger协作的例子,博文针对音量调节进行详细的解析.音量控制主要分成两大部分,一部分是java层完成的操作,用于响应音量调节,记录音量值,更新UI等操作,另一部分是native层完成,用于计算音量并执行。需要提一句的是,音量控制是设备厂商的适配重点,通常分为软音量和硬件音量,所谓软音量,就是Android原生针对audiotrack中的数据进行设置,而硬件音量则是设置对应芯片的寄存器,两者结合为Android音量的最终体现,博文最后为Android原生音量设置的概括图,嫌代码麻烦的可先看博文最后的总结图。
二、代码分析:
1. java层分析:
我们按下音量键或者触屏音量调节之后,会由Android系统响应按键,由于不同系统和每个公司的策略做的不一样,所以,在响应上逻辑上,不尽相同,但是,最终都会调入到AudioManager.java中:
handleKeyDown@AudioManager.java
public void handleKeyDown(KeyEvent event, int stream) {
...
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
...
adjustSuggestedStreamVolume(...);
...
}
}
响应的函数是adjustSuggestedStreamVolume,首先会获取java层的binder服务,然后调入到AudioService.java中:
adjustStreamVolume@AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, int uid) {
...
/* 确定streamType对应的Alias组别 */
int streamTypeAlias = mStreamVolumeAlias[streamType];
...
/* 获取对应的device */
final int device = getDeviceForStream(streamTypeAlias);
...
/* java层消息机制:调节音量 */
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
...
/* UI更新相关 */
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
因为不同的streamtype可能有相同的策略,所以,这里要先去匹配Alias组别,然后去获取到device,之后我们看到是使用了java中的消息机制,通知需要调节音量,代码最后跟UI更新相关,这里不去重点关注,我们主要看消息机制这里,handler发送了消息之后,处理是在handleMessage中,调用的是setDeviceVolume方法:
private void setDeviceVolume(VolumeStreamState streamState, int device) {
...
synchronized (VolumeStreamState.class) {
streamState.applyDeviceVolume_syncVSS(device);
...
}
}
这里可以看到继续调用的applyDeviceVolume_syncVSS,需要注意在调节了当前streamtype的音量之后,还会去调节其他的类型,因此在调试中,会看到很多类型的打印,我们只关注streamType == 3,applyDeviceVolume_syncVSS会去计算index值,这个index值指UI的刻度值,比如music的话共15个进度,不同的码流类型进度总值可能不一样,方法重点是去调用了AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
三个参数,参数一为流类型,参数二为index(因为native也需要记录这个值),参数三为输出的设备,这是个native方法,接下来,将正式进入native分析。
2.native层分析:
native层的策略是首先根据index计算出真正的音量值,然后再去调用audioflinger执行,先看AudioSystem:
status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,
int index,
audio_devices_t device)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return PERMISSION_DENIED;
return aps->setStreamVolumeIndex(stream, index, device);
}
之前的博文已经分析了,这里是通过IAudioPolicyService去进一步调用,IAudioPolicyService的Bn端在AudioPolicyInterfaceImpl.cpp中:
status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream,
int index,
audio_devices_t device)
{
...
return mAudioPolicyManager->setStreamVolumeIndex(stream,
index,
device);
}
前面博文说过,AudioPolicyManager是audiopolicyservice的持有者,APS不会直接与AF交互,我们看下APM中做了什么:
status_t AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t stream,
int index,
audio_devices_t device)
{
...
/* 记录当前流类型对应的设备和index值 */
mStreams[stream].mIndexCur.add(device, index);
/* 获取设备策略 */
audio_devices_t strategyDevice = getDeviceForStrategy(getStrategy(stream), true /*fromCache*/);
...
/* 检查并准备往AF中设置了 */
status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), curDevice);
}
java层分析的时候说过,index值也会传入到native层,这里便是记录的地方,我们重点关注checkAndSetVolume函数,很多厂商自己的适配也是在这里做的:
status_t AudioPolicyManager::checkAndSetVolume(audio_stream_type_t stream,
int index,
audio_io_handle_t output,
audio_devices_t device,
int delayMs,
bool force)
{
...
float driverVol[6]= {0.00,0.02,0.03,0.04,0.05,0.06};
/* 计算音量值 */
float volume = computeVolume(stream, index, output, device);
/* 如果index值为前六个,则重新赋值 */
if( index < 6)
{
volume = driverVol[index];
}
...
/* 这里最终会调入到AF中 */
mpClientInterface->setStreamVolume(stream, volume, output, delayMs);
}
我所使用的平台是海思厂商,因为计算出来的音量值是绝对音量,所以如果index值太小的话,音量的变化并不会很明显,因此,这里对前六的index值进行了重新赋值,computeVolume为每个厂商自己的计算方法,不尽相同,海思的如下:
float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 )
实际上整个函数目的很简单,由index换算成真正的音量值,然后启动AF去设置,代码最后看起来跟AF没有关系,但实际上却是会调入到AF中,这里需要追一下代码:mpClientInterface对应的类型是AudioPolicyClientInterface,在AudioPolicyClientImpl.cpp中能找到到对应的实现:
status_t AudioPolicyService::AudioPolicyClient::setStreamVolume(audio_stream_type_t stream,
float volume, audio_io_handle_t output,
int delay_ms)
{
return mAudioPolicyService->setStreamVolume(stream, volume, output,
delay_ms);
}
这里又会调入到AudioPolicyService中,回想一下前面,AudioPolicyService是通过index到下面的,现在由index确定了真正的音量值之后,又返回了回来,我们看下AudioPolicyService下一步又会怎么调用:
setStreamVolume@AudioPolicyService.cpp
int AudioPolicyService::setStreamVolume(audio_stream_type_t stream,
float volume,
audio_io_handle_t output,
int delayMs)
{
return (int)mAudioCommandThread->volumeCommand(stream, volume,
output, delayMs);
}
这段代码初看有点懵,mAudioCommandThread是什么鬼?这是一个audio的命令接收线程,那么,这个线程在什么时候创建的呢?其实就是在第一次引用AudioPolicyService的强指针时候创建的,我们看下AudioPolicyService::onFirstRef():
void AudioPolicyService::onFirstRef()
{
...
// start tone playback thread
mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
// start audio commands thread
mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
// start output activity command thread
mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);
...
}
可以看到这里一共创建了三个命令线程,AudioCommandThread的构造函数并没有做什么实质性的事,联想前几篇博客的分析,我们看一下AudioCommandThread的onFirstRef函数:
void AudioPolicyService::AudioCommandThread::onFirstRef()
{
run(mName.string(), ANDROID_PRIORITY_AUDIO);
}
果然,当第一次引用AudioCommandThread的强指针时,线程就会开始转起来,不停地接受指令了,那么,哪个地方是第一次引用强指针的呢?搜了下代码,发现只有一个地方使用了AudioCommandThread的强指针,就是AudioPolicyClient声明了此成员:
class AudioPolicyClient : public AudioPolicyClientInterface
{
...
sp<AudioCommandThread> mAudioCommandThread; // audio commands thread
sp<AudioCommandThread> mTonePlaybackThread; // tone playback thread
sp<AudioCommandThread> mOutputCommandThread;
...
}
基本我们就可以确认了,在第一次实例化AudioPolicyClient对象的时候,就会为成员变量分配指针空间,这里就相当于第一次引用了AudioCommandThread的强指针,接收命令的线程也就转起来了。
花了比较大的工夫分析了mAudioCommandThread线程的创建过程,我们去看下它的volumeCommand:
status_t AudioPolicyService::AudioCommandThread::volumeCommand(audio_stream_type_t stream,
float volume,
audio_io_handle_t output,
int delayMs)
{
sp<AudioCommand> command = new AudioCommand();
command->mCommand = SET_VOLUME;
sp<VolumeData> data = new VolumeData();
data->mStream = stream;
data->mVolume = volume;
data->mIO = output;
command->mParam = data;
command->mWaitStatus = true;
ALOGV("AudioCommandThread() adding set volume stream %d, volume %f, output %d",
stream, volume, output);
return sendCommand(command, delayMs);
}
封装数据,返回sendCommand,到这里似乎就跟不下去了,但不要忘了mAudioCommandThread是一个线程,并且已经run起来了,那么我们需要去看下它的threadloop干了什么:
bool AudioPolicyService::AudioCommandThread::threadLoop()
{
...
while (!exitPending())
{
...
switch (command->mCommand) {
...
case SET_VOLUME: {
VolumeData *data = (VolumeData *)command->mParam.get();
ALOGV("AudioCommandThread() processing set volume stream %d, \
volume %f, output %d", data->mStream, data->mVolume, data->mIO);
command->mStatus = AudioSystem::setStreamVolume(data->mStream,
data->mVolume,
data->mIO);
...
}
}
...
}
一切如我们所想的,这里面的case语句指引了我们,饶了一大圈,还是到AudioSystem了,但是,看到AudioSystem就应该兴奋起来了,因为马上要到audioflinger了,看吧:
status_t AudioSystem::setStreamVolume(audio_stream_type_t stream, float value,
audio_io_handle_t output)
{
if (uint32_t(stream) >= AUDIO_STREAM_CNT) return BAD_VALUE;
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
af->setStreamVolume(stream, value, output);
return NO_ERROR;
}
剥丝抽茧,果然还是会由audioflinger来完成,看下audioflinger又会做什么:
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
audio_io_handle_t output)
{
...
/* 1.根据output找到thread */
PlaybackThread *thread = NULL;
if (output != AUDIO_IO_HANDLE_NONE) {
thread = checkPlaybackThread_l(output);
if (thread == NULL) {
return BAD_VALUE;
}
}
/* 2.记录当前的流类型的音量值 */
mStreamTypes[stream].volume = value;
...
/* 3.进入到thread中去设置音量 */
thread->setStreamVolume(stream, value);
/* 4.厂商定制化:设置硬件音量 */
#if defined (PRODUCT_STB)
for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
AudioHwDevice *dev = mAudioHwDevs.valueAt(i);
mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
ALOGE("setStreamVolume by set_master_volume");
if(stream == 3)//music type,only this type can adjust hardware volum
dev->hwDevice()->set_master_volume(dev->hwDevice(),value);
mHardwareStatus = AUDIO_HW_IDLE;
}
ALOGV("setStreamVolume value over");
#endif
}
audioflinger中的setStreamVolume真是信息量巨大,首先,它会通过output找到对应的thread,因为之前分析audioflinger和audiotrack的博客时,已经知道,audioflinger会创建两个线程,一个是PlaybackThread,一个是RecordThread,而PlaybackThread的子类有MixerThread,DirectOutputThread等,所以,这里首先需要根据output确认到底是哪一个thread,之后,记录当前的流类型的音量值,这个值,后面会使用到,注释三为什么会到thread中去设置呢?我们这里需要清楚,thread掌管的是track,其实就是到track里面去设置音量,也就是说,这里设置音量实际上改变的是数据,简单地说,对数据进行幅值强弱处理达到音量调节的目的,这也正是我开篇所说的软音量调节, 因为是对数据进行处理,并没有真正地设置到硬件中去,但注释四就是设置到硬件中了,可以看到,这里是芯片厂商自己加的,通过hal层直接设置到芯片的sdk,然后设置到寄存器中,海思的硬件音量设置是在audioflinger中做的,各个厂商的实现策略可能不一样,但大家一定要区别设置软音量和硬件音量的区别。
硬件音量的设置我们就先不分析了,我们看软件音量上又是怎么走的,去到setStreamVolume:
void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
{
Mutex::Autolock _l(mLock);
mStreamTypes[stream].volume = value;
broadcast_l();
}
其实这里跟前面的audioflinger中做了重复工作,多赋值一次,前面audioflinger中的赋值应该是可以去掉的(从binder机制上我觉得是可以的),言归正传,似乎路再一次的堵死了,这里面通过广播告诉thread有事情要处理,我们需要到threadloop里面去看看,以mixthread为例,我们关注下它的threadloop,threadloop“三把斧”的第一把prepareTracks_l就告诉了我们答案:
AudioFlinger::MixerThread::prepareTracks_l@Threads.cpp
{
...
float typeVolume = mStreamTypes[track->streamType()].volume;
float v = masterVolume * typeVolume;
}
这里的typeVolume 就是前面赋值的音量值,下面一句话告诉了我们软音量的总值为masterVolume 与设置index计算出来的音量之积,所以,在实际问题的处理中,如果音量调节全程都很小,请注意是否是masterVolume 太小导致的,masterVolume 也是通过上层java接口来设置的。计算了总音量之后,后续就会去mixer中进行混音了,软音量的设置也完成了。
至此,音量设置的分析基本就完成了,回顾一下,音量设置首先是java层计算,存储index值,更新UI等操作,然后将流类型和index传入native层,native层通过audiopolicy进行计算之后,转交由audioflinger去设置,而原生audioflinger中是通过处理track数据来达到音量调节的目的。
三、总结:
Android原生音量调节略微复杂,这里做了一张图来比较完成的概括一下(原图较大,可自行下载保存):
1.不同的厂商按键响应策略不一样,但最终都会调入到AudioService中;
2.AudioPolicyService在音量值计算的过程中承载的工作比较大,首先是通过binder服务调用AudioPolicyManager去计算音量值,然后是通过自己创建的AudioCommandThread去接收audio音量调节的指令;
3.AudioPolicyManager如何设置到AudioFlinger略微有点绕,因为中间有一个AudioPolicyService自己创建的AudioCommandThread线程;
4.audioflinger中原生场景会去设置软件音量,不同的厂商硬件音量的策略不同,所以红色虚线圈起来的为硬件音量设置,需视平台而定,但是一定要区分软件音量与硬件音量的区别;
5.软件音量是通过改变track中数据实现的音量调节,硬件音量是通过修改寄存器值实现的调节;
6.原生中所说的二级音量设置就是prepareTracks_l中的masterVolume*typeVolume,因为软件总音量是二者之积,所以,音量值太小时请确认软件总音量值;