首页 >> 大全

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

2023-10-07 大全 22 作者:考证青年

一、引言:

音量控制是典型的和协作的例子,博文针对音量调节进行详细的解析.音量控制主要分成两大部分,一部分是java层完成的操作,用于响应音量调节,记录音量值,更新UI等操作,另一部分是层完成,用于计算音量并执行。需要提一句的是,音量控制是设备厂商的适配重点,通常分为软音量和硬件音量,所谓软音量,就是原生针对中的数据进行设置,而硬件音量则是设置对应芯片的寄存器,两者结合为音量的最终体现,博文最后为原生音量设置的概括图,嫌代码麻烦的可先看博文最后的总结图。

二、代码分析:

1. java层分析:

我们按下音量键或者触屏音量调节之后,会由系统响应按键,由于不同系统和每个公司的策略做的不一样,所以,在响应上逻辑上,不尽相同,但是,最终都会调入到.java中:

handleKeyDown@AudioManager.java
public void handleKeyDown(KeyEvent event, int stream) {...switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_DOWN:...adjustSuggestedStreamVolume(...);...}
}

响应的函数是,首先会获取java层的服务,然后调入到.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);	
}

因为不同的可能有相同的策略,所以,这里要先去匹配Alias组别,然后去获取到,之后我们看到是使用了java中的消息机制,通知需要调节音量,代码最后跟UI更新相关,这里不去重点关注,我们主要看消息机制这里,发送了消息之后,处理是在中,调用的是方法:

private void setDeviceVolume(VolumeStreamState streamState, int device) {...synchronized (VolumeStreamState.class) {streamState.applyDeviceVolume_syncVSS(device);...}
}

这里可以看到继续调用的ncVSS,需要注意在调节了当前的音量之后,还会去调节其他的类型,因此在调试中,会看到很多类型的打印,我们只关注 == 3,ncVSS会去计算index值,这个index值指UI的刻度值,比如music的话共15个进度,不同的码流类型进度总值可能不一样,方法重点是去调用了.(, index, );

三个参数,参数一为流类型,参数二为index(因为也需要记录这个值),参数三为输出的设备,这是个方法,接下来,将正式进入分析。

2.层分析:

层的策略是首先根据index计算出真正的音量值,然后再去调用执行,先看:

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);
}

之前的博文已经分析了,这里是通过去进一步调用,的Bn端在Impl.cpp中:

status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream,int index,audio_devices_t device)
{...return mAudioPolicyManager->setStreamVolumeIndex(stream,index,device);	
}

前面博文说过,是的持有者,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值也会传入到层,这里便是记录的地方,我们重点关注函数,很多厂商自己的适配也是在这里做的:

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值进行了重新赋值,为每个厂商自己的计算方法,不尽相同,海思的如下:

float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 )

实际上整个函数目的很简单,由index换算成真正的音量值,然后启动AF去设置,代码最后看起来跟AF没有关系,但实际上却是会调入到AF中,这里需要追一下代码:对应的类型是,在l.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);
}

这里又会调入到中,回想一下前面,是通过index到下面的,现在由index确定了真正的音量值之后,又返回了回来,我们看下下一步又会怎么调用:

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);
}

这段代码初看有点懵,是什么鬼?这是一个audio的命令接收线程,那么,这个线程在什么时候创建的呢?其实就是在第一次引用的强指针时候创建的,我们看下::():

void AudioPolicyService::onFirstRef()
{...// start tone playback threadmTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);// start audio commands threadmAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);// start output activity command threadmOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);	...
}

可以看到这里一共创建了三个命令线程,的构造函数并没有做什么实质性的事,联想前几篇博客的分析,我们看一下的函数:

void AudioPolicyService::AudioCommandThread::onFirstRef()
{run(mName.string(), ANDROID_PRIORITY_AUDIO);
}

果然,当第一次引用的强指针时,线程就会开始转起来,不停地接受指令了,那么,哪个地方是第一次引用强指针的呢?搜了下代码,发现只有一个地方使用了的强指针,就是声明了此成员:

class AudioPolicyClient : public AudioPolicyClientInterface
{...sp<AudioCommandThread> mAudioCommandThread;     // audio commands threadsp<AudioCommandThread> mTonePlaybackThread;     // tone playback threadsp<AudioCommandThread> mOutputCommandThread;	...
}

基本我们就可以确认了,在第一次实例化对象的时候,就会为成员变量分配指针空间,这里就相当于第一次引用了的强指针,接收命令的线程也就转起来了。

花了比较大的工夫分析了线程的创建过程,我们去看下它的:

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);	
}

封装数据,返回,到这里似乎就跟不下去了,但不要忘了是一个线程,并且已经run起来了,那么我们需要去看下它的干了什么:

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语句指引了我们,饶了一大圈,还是到了,但是,看到就应该兴奋起来了,因为马上要到了,看吧:

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;
}

剥丝抽茧,果然还是会由来完成,看下又会做什么:

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 volumdev->hwDevice()->set_master_volume(dev->hwDevice(),value);mHardwareStatus = AUDIO_HW_IDLE;}ALOGV("setStreamVolume value over");
#endif	
}

中的真是信息量巨大,首先,它会通过找到对应的,因为之前分析和的博客时,已经知道,会创建两个线程,一个是,一个是,而的子类有,等,所以,这里首先需要根据确认到底是哪一个,之后,记录当前的流类型的音量值,这个值,后面会使用到,注释三为什么会到中去设置呢?我们这里需要清楚,掌管的是track,其实就是到track里面去设置音量,也就是说,这里设置音量实际上改变的是数据,简单地说,对数据进行幅值强弱处理达到音量调节的目的,这也正是我开篇所说的软音量调节, 因为是对数据进行处理,并没有真正地设置到硬件中去,但注释四就是设置到硬件中了,可以看到,这里是芯片厂商自己加的,通过hal层直接设置到芯片的sdk,然后设置到寄存器中,海思的硬件音量设置是在中做的,各个厂商的实现策略可能不一样,但大家一定要区别设置软音量和硬件音量的区别。

硬件音量的设置我们就先不分析了,我们看软件音量上又是怎么走的,去到:

void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
{Mutex::Autolock _l(mLock);mStreamTypes[stream].volume = value;broadcast_l();
}

其实这里跟前面的中做了重复工作,多赋值一次,前面中的赋值应该是可以去掉的(从机制上我觉得是可以的),言归正传,似乎路再一次的堵死了,这里面通过广播告诉有事情要处理,我们需要到里面去看看,以为例,我们关注下它的,“三把斧”的第一把就告诉了我们答案:

AudioFlinger::MixerThread::prepareTracks_l@Threads.cpp{...float typeVolume = mStreamTypes[track->streamType()].volume;float v = masterVolume * typeVolume;	
}

这里的 就是前面赋值的音量值,下面一句话告诉了我们软音量的总值为 与设置index计算出来的音量之积,所以,在实际问题的处理中,如果音量调节全程都很小,请注意是否是 太小导致的, 也是通过上层java接口来设置的。计算了总音量之后,后续就会去mixer中进行混音了,软音量的设置也完成了。

至此,音量设置的分析基本就完成了,回顾一下,音量设置首先是java层计算,存储index值,更新UI等操作,然后将流类型和index传入层,层通过进行计算之后,转交由去设置,而原生中是通过处理track数据来达到音量调节的目的。

三、总结:

原生音量调节略微复杂,这里做了一张图来比较完成的概括一下(原图较大,可自行下载保存):

1.不同的厂商按键响应策略不一样,但最终都会调入到中;

2.在音量值计算的过程中承载的工作比较大,首先是通过服务调用去计算音量值,然后是通过自己创建的去接收audio音量调节的指令;

3.如何设置到略微有点绕,因为中间有一个自己创建的线程;

4.中原生场景会去设置软件音量,不同的厂商硬件音量的策略不同,所以红色虚线圈起来的为硬件音量设置,需视平台而定,但是一定要区分软件音量与硬件音量的区别;

5.软件音量是通过改变track中数据实现的音量调节,硬件音量是通过修改寄存器值实现的调节;

6.原生中所说的二级音量设置就是中的*,因为软件总音量是二者之积,所以,音量值太小时请确认软件总音量值;

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了