轉載自http://blog.csdn.net/u012440406/article/details/51090495
上一篇我們簡單地說了一下Android java層的基本框架。接下來我們就來聊一下在android中音量控制的相關內容。
1.音量定義
在Android中,音量的控制與流類型是密不可分的,每種流類型都獨立地擁有自己的音量設置,各種流類型的音量是互不干擾的,例如音樂音量、通話音量就是相互獨立的。Andorid當前在AudioSystem.java默認有10種流類型(見下表列二)。既然Android當中有10種流類型,每種流類型的音量都是相互獨立的,所以在默認音量方面,Android也給每種流類型初始化了一個默認的音量(下表列二、三)和最大音量(下表列四)。雖然Android中擁有10種流類型,但是爲了便於使用,android通過判斷設備的類型,去映射具體流類型。當前Android在AudioSystem.java中提供了3個設備(DEFAULT,VOICE,TELEVISION)作爲可選擇項,分別去映射我們具體的音頻流類型。其中,DEFAULT和VOICE類型的音頻映射是一致的。
所以,從上表中可以看出,在手機設備當中,我們當前可調控的流類型音量其實只有5個,當你想調節STREAM_SYSTEM,STREAM_NOTIFICATION等流類型的音量時,實際上是調節了STREAM_RING的音量。當前可控的流類型可以通過下表更直觀地顯示:
每次手機開機,在Android6.0之前,SettingsProvide的內容提供者會將流類型默認值寫入到數據庫settings.db裏面,當然,這是個SQLite數據庫,如果數據庫已存在則不再進行寫入。如下圖:
但是,從Andorid6.0開始,google將設置部分相關的內容從settings.db轉移出來,轉爲以xml形式異步保存在/data/system/user/0(用戶名)/目錄下。當前負責存儲音量和鈴聲相關的文件爲settings_system.xml。形式如下:
- <setting id="4" name="volume_alarm" value="6" package="android" />
- <setting id="0" name="volume_music" value="11" package="android" />
- <setting id="3" name="volume_voice" value="4" package="android" />
- <setting id="32" name="ringtone" value="content://media/internal/audio/media/180" package="com.android.providers.media" />
- <setting id="13" name="hearing_aid" value="0" package="android" />
- <setting id="1" name="volume_ring" value="5" package="android" />
當前Android6.0將這些設置轉移出來,是爲了便於執行並提高性能。更改一個設置可以從原本的400ms左右變爲10ms左右。這大大地提高了讀寫的效率。另一方面,它爲每個用戶都會新建一個這樣的表從而避免了多用戶的設置的衝突。用戶體驗更好,設置更人性化。
2.音量調整
在Android手機上有兩種改變系統音量的方式。最直接的做法就是通過手機的音量鍵進行音量調整,還有就是從設置界面中調整某一種類型音頻的音量。他們都是都是通過AudioService進行的。
2.1 音量鍵的處理流程
音量鍵被按下後,Android輸入系統將該事件一路派發給Activity,如果無人截獲這個事件,承載當前Activity的顯示的PhoneWindow類的onKeyDown()或onKeyUp()函數將會將其處理,從而開始了通過音量鍵調整音量的處理流程。需要注意的是,按照Android的輸入事件派發策略,Window對象在事件的派發隊列中排在Activity的後面(應該說排在隊尾比較合適),所以應用程序可以重寫自己的onKeyDown()函數,將音量鍵用作其他的功能。
PhoneWindow的onKeyDown()函數實現如下(省略部分代碼):
- ……
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_MUTE: {
- ……
- /*
- 在這裏,先判斷mMediaController是否爲空(顯示的音量調整UI是否還存在),假如存在,就直接調用mMediaController的adjustVolume進行調整音量。不存在就通過MediaSession去創建UI並調整。
- */
- if (mMediaController != null) {
- mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
- } else {
- MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
- mVolumeControlStreamType, direction,
- AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
- | AudioManager.FLAG_FROM_KEY);
- }
- return true;
- }
- ……
上面的代碼顯示,PhoneWindow接收onKeyDown()事件處理時,先判斷顯示的音量調整UI是否存在,假如存在,就直接在當前的流類型上進行調整音量。假如不存在就通過MediaSession去創建UI並調整音量。
在這裏Android從5.0開始使用MediaSession對音量進行控制。通過MeidaSession相關的類,最終在MeidaSessionService中調用AudioService的adjustSuggestedStreamVolume()進行真正的音量設置的初步處理。
AudioService的adjustSuggestedStreamVolume()實現如下(省略部分代碼):
- ……
- int streamType;
- boolean isMute = isMuteAdjust(direction);
- //在這裏也可以更改需要修改的流類型
- if (mVolumeControlStream != -1) {
- streamType = mVolumeControlStream;
- } else {
- //通過getActiveStreamType()函數獲取要控制的流類型
- streamType = getActiveStreamType(suggestedStreamType);
- }
- ensureValidStreamType(streamType);
- final int resolvedStream = mStreamVolumeAlias[streamType];
- ……
- // For notifications/ring, show the ui before making any adjustments
- // Don't suppress mute/unmute requests
- if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
- direction = 0;
- flags &= ~AudioManager.FLAG_PLAY_SOUND;
- flags &= ~AudioManager.FLAG_VIBRATE;
- if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
- }
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
- ……
adjustSuggestedStreamVolume()負責接收MeidaSessionService傳入的信息,然後針對要修改流類型獲取相應的映射,更改是否顯示ui的標誌,然後將具體的調整音量操作交給adjustStreamVolume()去完成。
另外,關於這個adjustSuggestedStreamVolume()有點是需要特別說明一下。它剛開始的時候有一個判斷,條件是一個名爲mVolumeControlStream的整型變量是否等於-1,從這塊代碼來看,mVolumeControlStream比參數傳入的suggestedStreamType厲害多了,只要它不是-1,那麼要調整音量的流類型就是它。那這麼厲害的控制手段,是做什麼用的呢?其實,mVolumeControlStream是VolumePanel通過forceVolumeControlStream()函數設置的。什麼是VolumePanel呢?就是我們按下音量鍵後的那個音量條提示框了。VolumePanel在顯示時會調用forceVolumeControlStream強制後續的音量鍵操作固定爲促使它顯示的那個流類型。並在它關閉時取消這個強制設置,即置mVolumeControlStream爲-1。
AudioService的adjustStreamVolume()實現如下(省略部分代碼):
- ……
- //確認一下調整的音量方向和流類型
- ensureValidDirection(direction);
- ensureValidStreamType(streamType);
- // 首先還是獲取streamType映射到的流類型。
- int streamTypeAlias = mStreamVolumeAlias[streamType];
- VolumeStreamState streamState = mStreamStates[streamTypeAlias];
- final int device = getDeviceForStream(streamTypeAlias);
- //然後獲取這個streamType的當前音量
- int aliasIndex = streamState.getIndex(device);
- boolean adjustVolume = true;
- int step;
- ……
- //確定當前流類型的音量等級
- if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
- ((device & mFixedVolumeDevices) != 0)) {
- flags |= AudioManager.FLAG_FIXED_VOLUME;
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
- (device & mSafeMediaVolumeDevices) != 0) {
- step = mSafeMediaVolumeIndex;
- } else {
- step = streamState.getMaxIndex();
- }
- if (aliasIndex != 0) {
- aliasIndex = step;
- }
- } else {
- step = rescaleIndex(10, streamType, streamTypeAlias);
- }
- ……
- //判斷是否該改變情景模式。例如當從震動轉換成響鈴時,不需要更改音量。adjustVolume作爲一個控制量,控制是否需要更改音量。
- final int result = checkForRingerModeChange(aliasIndex, direction, step,
- streamState.mIsMuted);
- adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
- ……
- //調用adjustIndex()更改VolumeStreamState對象中保存的音量值
- } else if (streamState.adjustIndex(direction * step, device, caller)
- || streamState.mIsMuted) {
- //發送消息給AudioHandle,更改音量。
- sendMsg(mAudioHandler,
- MSG_SET_DEVICE_VOLUME,
- SENDMSG_QUEUE,
- device,
- 0,
- streamState,
- 0);
- }
- ……
- //最後通過sendVolumeUpdate去通知音量已經發生變化了。
- int index = mStreamStates[streamType].getIndex(device);
- sendVolumeUpdate(streamType, oldIndex, index, flags);
- }
AudioService的adjustStreamVolume ()針對音量設置做了很多的操作,所以在這裏簡單地總結一下這個函數都作了什麼:
1) 準備工作。計算按下音量鍵的音量步進值。主要通過rescaleIndex()函數的實現。
2) 檢查是否需要改變情景模式。checkForRingerModeChange()和情景模式有關。調用adjustIndex()更改VolumeStreamState對象中保存的音量值。
3) 通過sendMsg()發送消息MSG_SET_DEVICE_VOLUME到mAudioHandler。
4) 調用sendVolumeUpdate()函數,通知外界音量發生了變化。
VolumeStreamState是AudioService的一個內部類,當進行音量或者鈴聲模式管理時,需要鎖定這個對象,避免順序出錯。
下面是VolumeSteramState獲取音量和保存音量的操作:
- ......
- public void readSettings() {
- //先鎖定,避免出錯
- synchronized (VolumeStreamState.class) {
- ……
- String name = getSettingNameForDevice(device);
- int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ?
- AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType] : -1;
- //通過讀取setting數據庫去獲取值
- int index = Settings.System.getIntForUser(
- mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
- ……
- }
- ......
- public boolean setIndex(int index, int device, String caller) {
- ……
- //先鎖定,避免出錯
- synchronized (VolumeStreamState.class) {
- oldIndex = getIndex(device);
- index = getValidIndex(index);
- ……
- // 首先是在mIndexMap中保存設置的音量值
- mIndexMap.put(device, index);
- changed = oldIndex != index;
- if (changed) {
- // 同時設置所有映射到當前流類型的其他流的音量 boolean currentDevice = (device == getDeviceForStream(mStreamType));
- int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType) {
- ……
- }
- // 發送通知
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
- mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
- mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
- mStreamVolumeAlias[mStreamType]);
- sendBroadcastToAll(mVolumeChanged);
- }
- return changed;
- }
VolumeSteramState可以直接通過調用Settings.System.getIntForUser()去獲取數據庫中的音量,但是,在更新音量時,它只是更新了內部保存的音量而沒有做更多的處理。所以,真正的更新音量的操作應該由mAudioHandler去處理。
從AudioService的adjustStreamVolume ()可以知道,adjustStreamVolume()給AudioHandler發送了帶有“MSG_SET_DEVICE_VOLUME”的消息。AudioHandler根據此消息會進行setDeviceVolume()處理。
以下是setDeviceVolume()的主要內容:
- ……
- private void setDeviceVolume(VolumeStreamState streamState, int device) {
- //先鎖定,避免出錯
- synchronized (VolumeStreamState.class) {
- //通過VolumeStreamState調用AudioSystem的setStreamVolumeIndex()設置音量到底層的AudioFlinger裏面去
- streamState.applyDeviceVolume_syncVSS(device);
- ……
- //繼續給AudioHandler發送信息,調用persistVolume(),通過System.putIntForUser()將目標音量保存在Setting數據庫中
- sendMsg(mAudioHandler,
- MSG_PERSIST_VOLUME,
- SENDMSG_QUEUE,
- device,
- 0,
- streamState,
- PERSIST_DELAY);
- }
從上面代碼可以看出,AudioService通過setDeviceVolume()真正地更改了音量。setDeviceVolume()先用過VolumeStremState調用AudioSystem的setStreamVolumeIndex()設置音量到底層的AudioFlinger裏面去,然後在通過AudioHandler.persistVolume()將音量真正保存起來。這樣就完成了大部分的音量調整了。
之後,AudioService通過sendVolumeUpdate()去更新界面。sendVolumeUpdate()會通過AIDL去調用VolumeDialogController.java中的onVolumeChangedW()方法,從而顯示界面調整。在這裏就不細說了。
總的來說,通過音量鍵去調整音量的序列圖如下:
2.2 通過設置調整音量
在Android中,除了通過音量鍵直接調節音量之外,還可以在系統設置進行音量的調整。在當前系統設置應用當中,Android主要通過調用SeekBarVolumizer去顯示界面並調整音量的。SeekBarVolumizer是一個控件,當我們觸動這個控件的時候,控件會根據當初的音量和模式去調用AudioManager的adjustStreamVolume(靜音或震動模式)或setStreamVolume(普通模式)去調整相對應的音量。具體的調整音量方式其實是大致一樣的,這裏就不細說了。
普通模式下通過設置調整音量的序列圖如下:
2.3 靜音與震動
靜音與震動是另外的2種響鈴模式。響鈴模式的調整其實與上面音量調整的設置的思想和流程大部分是一致的。當前外部程序設置爲靜音或震動的流程爲:先通過調用AudioManager去進行響鈴模式的調整。實際的響鈴模式調整發生在AudioService。AudioService設置並保存流類型的模式。稍微不同的是當前AudioService並沒有調用AudioSystem去保存RingerMode,而是直接通過persistRingerMode()將其保存在數據庫當中。然後Android6.0調整當前設置模式的序列圖爲:
在音頻中,設置音量等等流程上是挺簡單的。只需要我們細心點去一步一步往下走,就能找到我們想要的東西。