android 音量调节

安卓音频数据的最终音量由三部分组成,分别是master volume(全局音量,对整个系统所有的音频数据生效),stream volume(流音量,只针对特定类型的音频数据生效)和track volume(track音量,只针对某个audiotrack的数据生效)。

音频数据音量大小公式:

final_volume= master_volume * stream_volume * track_volume;playbackthread负责这个具体值的计算并设置到audiomixer中生效。

其中master_volume,stream_volume和track_volume都是百分比,1表示音量调到最大;

音量最大分贝是0db,表示没有衰减,也就是音源音量;

stream volume

VolumeStreamState(audioservice用来管理系统音量引入的概念)

android系统定义了11种Stream(从0到10),每个stream都用VolumeStreamState来封装:

markup 复制代码
VolumeStreamState[] mStreamStates = new int[] {0,1,2,3,4,5,6,7,8,9,10};//0,1,2...这些数字表示stream类型,分别对应default,voice call,ring等;
markup 复制代码
VolumeStreamState{//用来管理一个流类型所有的音量信息
    /*stream类型*/
    int mStreamType;
 
    /*volume最小索引,只有0和1*/
    int mIndexMin;
 
    /*volume最大索引,7,15等*/
    int mIndexMax;//
 
    boolean mIsMuted;
 
    /*VolumeStreamState的名字,用来对系统setting进行查询和持久化*/
    String mVolumeIndexSettingName;
 
    int mObservedDevices;
 
    /*map中的key为device,value为音量值bvolume*/
    SparseIntArray mIndexMap = new SparseIntArray(8);
 
    /*当音量发生改变时,发送广播AudioManager.VOLUME_CHANGED_ACTION*/
    Intent mVolumeChanged;
 
    Intent mStreamDevicesChanged;
}

alias流别名

android系统定义了11种Stream(从0到10),如果用一个数组来表示,它们与mStreamStates数组中的元素一一对应:

markup 复制代码
int[] STREAM_VOLUME_DEFAULT = new int[] {0,1,2,3,4,5,6,7,8,9,10};//系统默认

android定义了这么多的streamtype,但目前android设备的并不支持这么多的stream,比如点击手机音量键调节某一个Stream音量时,android系统只会出现5个滑动条,也就是手机设备只有5类stream,又比如机顶盒只支持music stream这1类,所以对于不同的平台,需要将这些stream进行分组,把具有相同属性stream分为一类,在Android源码中称之为"别名", 即alias;

下面就是android源码11种stream的分组结果:

markup 复制代码
int[] STREAM_VOLUME_ALIAS_VOICE      = new int[] {0,2,2,3,4,5,6,2,2,3,3};//手机
int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {3,3,3,3,3,3,3,3,3,3,3};//机顶盒
int[] STREAM_VOLUME_ALIAS_DEFAULT    = new int[] {0,2,2,3,4,2,6,2,2,3,3};//默认

以手机为例,手机只支持2,3,4,5,6(0暂时忽略)这5种stream,由于手机的3,9,10归类到了3,也就是别名alias为3,所以当手机调节3,9,10这3种stream时,实际上调节的是3(music stream)。

系统提供的调节stream volume的api有2个,分别是adjustVolume()和 setStreamVolume(),我们看adjustVolume():

markup 复制代码
adjustVolume()
//java层:
//AudioService.java
adjustSuggestedStreamVolume()
    //确定streamType
    final int streamType;
    if (mUserSelectedVolumeControlStream) {
        streamType = mVolumeControlStream;
    }else{
        ......
    }
 
    adjustStreamVolume()
        int streamTypeAlias = mStreamVolumeAlias[streamType];//将streamType转化为对应平台的streamTypeAlias
        VolumeStreamState streamState = mStreamStates[streamTypeAlias];
        final int device = getDeviceForStream(streamTypeAlias);//得到当前的device
        int aliasIndex = streamState.getIndex(device);//得到该device在当前stream上的音量
 
        int step;
        step = rescaleIndex(10, streamType, streamTypeAlias);//音量步进转化
        /*安全音量相关*/
        ......
        
        if(streamState.adjustIndex()){//调节音量,设置新的index值并发送音量改变的广播
            sendMsg(...,MSG_SET_DEVICE_VOLUME,...);//设置index到底层,并且将index保存到系统settings
                setDeviceVolume();
                    streamState.applyDeviceVolume_syncVSS(device);//设置index到底层,一直想不明白VSS是个啥缩写,今天突然明白了是VolumeStreamState的首字母缩写!!!!!!!!!!!!!!!!!!!!!!!1
                        int index;
                        index = ......;//确定index的最终值
                        AudioSystem.setStreamVolumeIndex(mStreamType, index, device);//将index设置到底层
                
                    for(......){//处理mStreamVolumeAlias相关
                        mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
                    }
            
                    sendMsg(...,MSG_SET_DEVICE_VOLUME,...);//将index保存到系统settings
                        persistVolume();
                            System.putIntForUser(......);//将index保存到系统settings
        }
 
        sendVolumeUpdate(streamType, oldIndex, index, flags);//刷新音量条(UI)
 
 
        //看看streamState.adjustIndex()
        streamState.adjustIndex()
            setIndex()//设置新的index值并发送音量改变的广播
                oldIndex = getIndex(device);//volume改变之前的index
                mIndexMap.put(device, index);//保存volume改变之后的index
                changed = oldIndex != index;//如果volume改变前后的index不相同
 
                for(...){//处理alias相关
                    VolumeStreamState aliasStreamState = mStreamStates[streamType];
                    aliasStreamState.setIndex(scaledIndex, device, caller);
                }
 
                if(changed){//当音量发生改变时,发送广播AudioManager.VOLUME_CHANGED_ACTION
                    sendBroadcastToAll(mVolumeChanged);
                }

//native层

//AudioSystem.java

AudioSystem.setStreamVolumeIndex()

//AudioSystem.cpp

AudioSystem::setStreamVolumeIndex()

//AudioPolicyManager.cpp

AudioPolicyManager::setStreamVolumeIndex()

for (size_t i = 0; i < mOutputs.size(); i++) {

checkAndSetVolume();//设置每个输出设备的音量

float volumeDb = computeVolume(stream, index, device);//计算音量

volumeDB = mVolumeCurves->volIndexToDb(stream, Volume::getDeviceCategory(device), index);

//VolumeCurve.cpp

VolumeCurve::volIndexToDb()//根据音量曲线计算出音量

outputDesc->setVolume(volumeDb, stream, device, delayMs, force);

//AudioOutputDescriptor.cpp

SwAudioOutputDescriptor::setVolume()

bool changed = AudioOutputDescriptor::setVolume(volume, stream, device, delayMs, force);

AudioOutputDescriptor::setVolume()

mCurVolume[stream] = volume;

mClientInterface->setStreamVolume(stream, volume, mIoHandle, delayMs);

//AudioPolicyService.cpp

AudioPolicyService::setStreamVolume()

mAudioCommandThread->volumeCommand()//AudioPolicyService::AudioCommandThread::volumeCommand()

sp command = new AudioCommand();

command->mCommand = SET_VOLUME;

sendCommand(command, delayMs);//AudioPolicyService::AudioCommandThread::sendCommand()

insertCommand_l(command, delayMs);//插入命令,执行AudioPolicyService::AudioCommandThread::threadLoop()

AudioSystem::setStreamVolume();//AudioSystem::setStreamVolume()

//AudioSystem.cpp

af->setStreamVolume(stream, value, output);//AudioFlinger::setStreamVolume()

//AudioFlinger.cpp

VolumeInterface *volumeInterface = getVolumeInterface_l(output);

volumeInterface->setStreamVolume(stream, value);//AudioFlinger::PlaybackThread::setStreamVolume()

//Threads.cpp

mStreamTypes[stream].volume = value;//保存volume值

broadcast_l();//唤醒PlaybackThread线程

AudioFlinger::PlaybackThread::threadLoop()

mMixerStatus = prepareTracks_l(&tracksToRemove);//不同类型的Thread对prepareTracks_l有不同的实现

AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l()//计算每个track数据最终的音量

...

}

音量曲线:我们从上面的代码中可以看到在audioservice中音量都是整形的index。而用于音量算法处理的是DB。音量曲线的作用就是将index转化为对应的DB 。这个音量曲线是可以通过配置文件修改。配置文件会定义一些点,audiopolicymanager会根据配置文件的点进行拟合,获得一条从最小index到最大index隐射到-∞DB到0DB的曲线。

auditorack音量设置:

markup 复制代码
//AudioTrack.cpp
AudioTrack::setVolume(float left, float right)
    /*传入的音量值保存在mVolume数组中*/
    mVolume[AUDIO_INTERLEAVE_LEFT] = left;
    mVolume[AUDIO_INTERLEAVE_RIGHT] = right;
    /*setVolumeLR会把做声道与右声道的值,组装成一个数*/
    mProxy->setVolumeLR(gain_minifloat_pack(gain_from_float(left), gain_from_float(right)));
        //AudioTrackShared.h
        /*mCblk表示共享内存的头部,也就是说这个音量值会保存到共享内存的头部*/
        mCblk->mVolumeLR = volumeLR;
 
播放声音时需要AudioMixer进行混音,继续分析AudioFlinger::MixerThread::prepareTracks_l():
//Threads.cpp
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l()
    /*取出硬件声音*/
    float masterVolume = mMasterVolume;
    bool masterMute = mMasterMute;
    
    /*把所有活跃的Tracks取出来*/
    for (size_t i=0 ; i<count ; i++) {
        ......
        //这就是stream volume
        float typeVolume = mStreamTypes[track->streamType()].volume;
        float v = masterVolume * typeVolume;
        
        /*从proxy中取出取出音量,其就是通过头部保存的音量,其含有左右声道的音量*/
        gain_minifloat_packed_t vlr = proxy->getVolumeLR();
        /*提取左右声道的值*/
        vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
        vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
        
        /*都与之前的V进行相乘*/
        vlf *= v * vh;
        vrf *= v * vh;
        
        /*把vlf与vrf传入给AudioMixer*/
        mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, &vlf);
        mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, &vrf);
    }
相关推荐
Lei活在当下19 小时前
【业务场景架构实战】7. 多代智能手表适配:Android APP 表盘编辑页的功能驱动设计
android·设计模式·架构
手机不死我是天子1 天前
《Android 核心组件深度系列 · 第 2 篇 Service》
android
前行的小黑炭1 天前
Compose页面切换的几种方式:Navigation、NavigationBar+HorizontalPager,会导致LaunchedEffect执行?
android·kotlin·app
前行的小黑炭1 天前
Android :Comnpose各种副作用的使用
android·kotlin·app
BD_Marathon2 天前
【MySQL】函数
android·数据库·mysql
西西学代码2 天前
安卓开发---耳机的按键设置的UI实例
android·ui
maki0772 天前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架2 天前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid2 天前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl2 天前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea