Android音频系统之音量控制详解(Android 5.1)

一、引言:
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原生音量调节略微复杂,这里做了一张图来比较完成的概括一下(原图较大,可自行下载保存):
Android音频系统之音量控制详解(Android 5.1)_第1张图片
1.不同的厂商按键响应策略不一样,但最终都会调入到AudioService中;
2.AudioPolicyService在音量值计算的过程中承载的工作比较大,首先是通过binder服务调用AudioPolicyManager去计算音量值,然后是通过自己创建的AudioCommandThread去接收audio音量调节的指令;
3.AudioPolicyManager如何设置到AudioFlinger略微有点绕,因为中间有一个AudioPolicyService自己创建的AudioCommandThread线程;
4.audioflinger中原生场景会去设置软件音量,不同的厂商硬件音量的策略不同,所以红色虚线圈起来的为硬件音量设置,需视平台而定,但是一定要区分软件音量与硬件音量的区别;
5.软件音量是通过改变track中数据实现的音量调节,硬件音量是通过修改寄存器值实现的调节;
6.原生中所说的二级音量设置就是prepareTracks_l中的masterVolume*typeVolume,因为软件总音量是二者之积,所以,音量值太小时请确认软件总音量值;

你可能感兴趣的:(Android音频)