Android音频系统之音量控制详解(Android 5.1)
一、引言:
Android的音量控制是典型的audiopolicy和audioflinger协作的例子,博文针对音量调节进行详细的解析.音量控制主要分成两大部分,一部分是java层完成的操作,用于响应音量调节,记录音量值,更新UI等操作,另一部分是native层完成,用于计算音量并执行。需要提一句的是,音量控制是设备厂商的适配重点,通常分为软音量和硬件音量,所谓软音量,就是Android原生针对audiotrack中的数据进行设置,而硬件音量则是设置对应芯片的寄存器,两者结合为Android音量的最终体现,博文最后为Android原生音量设置的概括图,嫌代码麻烦的可先看博文最后的总结图。
二、代码分析:
1. java层分析:
我们按下音量键或者触屏音量调节之后,会由Android系统响应按键,由于不同系统和每个公司的策略做的不一样,所以,在响应上逻辑上,不尽相同,但是,最终都会调入到AudioManager.java中:
handleKeyDown@AudioManager.javapublic 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.javaprivate 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.cppint 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,因为软件总音量是二者之积,所以,音量值太小时请确认软件总音量值;
更多相关文章
- Android 驱动之旅: 第一章 在Android 内核源代码工程中编写硬件驱
- Android 开发艺术探索读书笔记 11 -- Android 的线程和线程池
- Android线程模型
- android开发——通过子线程更新界面UI
- 通杀所有系统的硬件漏洞?聊一聊Drammer,Android上的RowHammer攻击
- Android中AsyncTask(异步任务)和Handler(线程消息机制)的详解
- Android中的线程处理