AudioService音量服務(wù)

本篇文章基于Android11源碼分析后雷,本篇文章的源碼均在frameworks目錄下

1. 調(diào)整音量的方式:

在學(xué)習(xí)AudioService源碼服務(wù)之前吧史,我們看一下在應(yīng)用層如何調(diào)節(jié)音量的增加、減小蕴轨、靜音港谊、非靜音示例:

   private AudioManager audioManager;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.checkout_main);
        audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        //.....
     }

    /**
     * 增加音頻流為STREAM_MUSIC的音量
     */
    private void increaseVolume(){
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        Log.d(TAG,"當(dāng)前音量大小 = "+ currentVolume);
        if (currentVolume < maxVolume){
            audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_RAISE,FLAG_SHOW_UI | FLAG_PLAY_SOUND);
        }
        int currentIncreaseVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        Log.d(TAG,"增加后的音量大小 = "+ currentIncreaseVolume);
    }

    /**
     * 減少音頻流為STREAM_MUSIC的音量
     */
    private void decreaseVolume(){
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        Log.d(TAG,"當(dāng)前音量大小 = "+ currentVolume);
        if (currentVolume > 0 ){
            audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_LOWER,FLAG_SHOW_UI);
        }
        int currentDecreaseVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        Log.d(TAG,"減小后的音量大小 = "+ currentDecreaseVolume);
    }

    /**
     * 使頻流為STREAM_MUSIC的靜音
     */
    private void muteVolume() {
        Log.d(TAG,"muteVolume ");
        previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_MUTE, FLAG_SHOW_UI);
    }

    /**
     * 使頻流為STREAM_MUSIC的關(guān)閉靜音
     */
    private void unmuteVolume() {
        Log.d(TAG,"unmuteVolume ");
        if (previousVolume > 0) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,AudioManager.ADJUST_UNMUTE, FLAG_SHOW_UI);
        }
    }

應(yīng)用層可以通過AudioManager.adjustStreamVolume(int streamType, int direction, int flags)AudioManager.setStreamVolume(int streamType, int index, int flags)其中streamType是音頻流,direction是表示操作的描述如增加/減少/靜音/非靜音橙弱。
其中streamType類型有:

    /**
     * Increase the ringer volume.
     */
    public static final int ADJUST_RAISE = 1;

    /**
     * Decrease the ringer volume.
     */
    public static final int ADJUST_LOWER = -1;

    /**
     * Maintain the previous ringer volume. This may be useful when needing to
     * show the volume toast without actually modifying the volume.
     */
    public static final int ADJUST_SAME = 0;

    /**
     * Mute the volume. Has no effect if the stream is already muted.
     */
    public static final int ADJUST_MUTE = -100;

    /**
     * Unmute the volume. Has no effect if the stream is not muted.
     */
    public static final int ADJUST_UNMUTE = 100;

    /**
     * Toggle the mute state. If muted the stream will be unmuted. If not muted
     * the stream will be muted.
     */
    public static final int ADJUST_TOGGLE_MUTE = 101;

在Android中有兩種方式來控制音量歧寺,一種是通過代碼來調(diào)整音量、一種是通過音量鍵來控制音量棘脐。

如下我們看下通過音量鍵是如何調(diào)整音量的:
在按下音量鍵的時候斜筐,會先經(jīng)過PhoneWindowManager的處理是否攔截:

// PhoneWindowManager

 @Override
    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
            int policyFlags) {

         //......
         else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP
                || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
            if (mUseTvRouting || mHandleVolumeKeysInWM) {
                // On TVs or when the configuration is enabled, volume keys never
                // go to the foreground app.
                 // 調(diào)用此方法
                dispatchDirectAudioEvent(event);
              }
                return -1;
            }
  
    private void dispatchDirectAudioEvent(KeyEvent event) {
        //....
        try {
            //  調(diào)用AudioService.handleVolumeKey()方法
            getAudioService().handleVolumeKey(event, mUseTvRouting,
                    mContext.getOpPackageName(), TAG);
        } catch (Exception e) {
            Log.e(TAG, "Error dispatching volume key in handleVolumeKey for event:"
                    + event, e);
        }

    }


    // AudioService

    public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
            @NonNull String callingPackage, @NonNull String caller) {
        int keyEventMode = VOL_ADJUST_NORMAL;
        if (isOnTv) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                keyEventMode = VOL_ADJUST_START;
            } else { // may catch more than ACTION_UP, but will end vol adjustement
                // the vol key is either released (ACTION_UP), or multiple keys are pressed
                // (ACTION_MULTIPLE) and we don't know what to do for volume control on CEC, end
                // the repeated volume adjustement
                keyEventMode = VOL_ADJUST_END;
            }
        } else if (event.getAction() != KeyEvent.ACTION_DOWN) {
            return;
        }

        int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
                | AudioManager.FLAG_FROM_KEY;


        // 調(diào)用adjustSuggestedStreamVolume()方法
        switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_VOLUME_UP:
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
                            Binder.getCallingUid(), true, keyEventMode);
                break;
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
                            Binder.getCallingUid(), true, keyEventMode);
                break;
            case KeyEvent.KEYCODE_VOLUME_MUTE:
                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                    adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
                            Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
                }
                break;
            default:
                Log.e(TAG, "Invalid key code " + event.getKeyCode() + " sent by " + callingPackage);
                return; // not needed but added if code gets added below this switch statement
        }
    }

    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
            String callingPackage, String caller) {
        boolean hasModifyAudioSettings =
                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
                == PackageManager.PERMISSION_GRANTED;
        adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
                caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
    }

    private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
            String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
            int keyEventMode) {
       // ....;
       // 最終也是調(diào)用到adjustStreamVolume()方法
        adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
                hasModifyAudioSettings, keyEventMode);
    }

音量按鍵在PhoneWindowManager的時候最終會調(diào)用dispatchDirectAudioEvent()方法,此方法又調(diào)用AudioService.handleVolumeKey()方法蛀缝,在AudioService最終還是調(diào)用adjustStreamVolume()方法進行音量的調(diào)整顷链。

接下來將看adjustStreamVolume()方法源碼是如何控制音量的。在講源碼之前屈梁,先講講音頻流和設(shè)備嗤练。

2. 音頻流和設(shè)備:

在android11中工定義了12中音頻流類型,在AudioSystem中有相關(guān)定義:

public class AudioSystem
{

    /**電話 */
    public static final int STREAM_VOICE_CALL = 0;
    /** 系統(tǒng) */
    public static final int STREAM_SYSTEM = 1;
    /** 響鈴和消息 */
    public static final int STREAM_RING = 2;
    /**  音樂 */
    public static final int STREAM_MUSIC = 3;
    /**  鬧鐘 */
    public static final int STREAM_ALARM = 4;
    /**  通知 */
    public static final int STREAM_NOTIFICATION = 5;
    /**  藍牙 */
    public static final int STREAM_BLUETOOTH_SCO = 6;
    /**  強制系統(tǒng)聲音 */
    @UnsupportedAppUsage
    public static final int STREAM_SYSTEM_ENFORCED = 7;
    /** 雙音多頻 */
    public static final int STREAM_DTMF = 8;
    /**  語音 */
    public static final int STREAM_TTS = 9;
    /**  輔助功能*/
    public static final int STREAM_ACCESSIBILITY = 10;
    /**  助手*/
    public static final int STREAM_ASSISTANT = 11;

    private static final int NUM_STREAM_TYPES = 12;

}

一個或多個音頻流共享一個音量:

public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {

    // 多種音頻流對應(yīng)一種音量,系統(tǒng)音頻流在讶,響鈴與消息音頻流煞抬,通知音頻流,強制聲音音頻流构哺,DTMF這五種音頻流共用一個音量
    private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
        AudioSystem.STREAM_RING,            // STREAM_SYSTEM
        AudioSystem.STREAM_RING,            // STREAM_RING
        AudioSystem.STREAM_MUSIC,           // STREAM_MUSIC
        AudioSystem.STREAM_ALARM,           // STREAM_ALARM
        AudioSystem.STREAM_RING,            // STREAM_NOTIFICATION
        AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
        AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
        AudioSystem.STREAM_RING,            // STREAM_DTMF
        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
        AudioSystem.STREAM_MUSIC,           // STREAM_ACCESSIBILITY
        AudioSystem.STREAM_MUSIC            // STREAM_ASSISTANT
    };
    // 電視的
    private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
        AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM
        AudioSystem.STREAM_RING,       // STREAM_RING
        AudioSystem.STREAM_MUSIC,       // STREAM_MUSIC
        AudioSystem.STREAM_ALARM,       // STREAM_ALAR
        AudioSystem.STREAM_MUSIC,       // STREAM_NOTIFICATION
        AudioSystem.STREAM_BLUETOOTH_SCO,       // STREAM_BLUETOOTH_SCO
        AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
        AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
        AudioSystem.STREAM_MUSIC,       // STREAM_TTS
        AudioSystem.STREAM_MUSIC,       // STREAM_ACCESSIBILITY
        AudioSystem.STREAM_MUSIC        // STREAM_ASSISTANT
    };
    private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
        AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
        AudioSystem.STREAM_RING,            // STREAM_SYSTEM
        AudioSystem.STREAM_RING,            // STREAM_RING
        AudioSystem.STREAM_MUSIC,           // STREAM_MUSIC
        AudioSystem.STREAM_ALARM,           // STREAM_ALARM
        AudioSystem.STREAM_RING,            // STREAM_NOTIFICATION
        AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
        AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
        AudioSystem.STREAM_RING,            // STREAM_DTMF
        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
        AudioSystem.STREAM_MUSIC,           // STREAM_ACCESSIBILITY
        AudioSystem.STREAM_MUSIC            // STREAM_ASSISTANT
    };
    // 存儲多種音頻流對應(yīng)一個音量
    protected static int[] mStreamVolumeAlias;


}

STREAM_VOLUME_ALIAS_VOICE代表的是具有語音功能的設(shè)備如手機革答。
STREAM_VOLUME_ALIAS_TELEVISION代表電視或機頂盒的設(shè)備。
STREAM_VOLUME_ALIAS_DEFAULT代表其他的設(shè)備曙强。
STREAM_VOLUME_ALIAS_VOICE中統(tǒng)音頻流残拐,響鈴與消息音頻流,通知音頻流旗扑,強制聲音音頻流蹦骑,DTMF音頻流就和響鈴與消息音頻流共享音量,他們的音量是一致的臀防,每當(dāng)STREAM_RING音量發(fā)生改變時眠菇,其他的與之共享音量的音頻流也要對應(yīng)的發(fā)生改變边败。

其中mStreamVolumeAlias數(shù)組變量就是就是設(shè)置為對應(yīng)的變量。

  • 最大最小音量
    每種音頻流都有自己的最大最小的音量值捎废,定義在AudioService的兩個數(shù)組變量中:
public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {


 //每種流的最小音量
    protected static int[] MAX_STREAM_VOLUME = new int[] {
        5,  // STREAM_VOICE_CALL
        7,  // STREAM_SYSTEM
        7,  // STREAM_RING
        15, // STREAM_MUSIC
        7,  // STREAM_ALARM
        7,  // STREAM_NOTIFICATION
        15, // STREAM_BLUETOOTH_SCO
        7,  // STREAM_SYSTEM_ENFORCED
        15, // STREAM_DTMF
        15, // STREAM_TTS
        15, // STREAM_ACCESSIBILITY
        15  // STREAM_ASSISTANT
    };

    /** Minimum volume index values for audio streams */
    //每種流的最小音量
    protected static int[] MIN_STREAM_VOLUME = new int[] {
        1,  // STREAM_VOICE_CALL
        0,  // STREAM_SYSTEM
        0,  // STREAM_RING
        0,  // STREAM_MUSIC
        1,  // STREAM_ALARM
        0,  // STREAM_NOTIFICATION
        0,  // STREAM_BLUETOOTH_SCO
        0,  // STREAM_SYSTEM_ENFORCED
        0,  // STREAM_DTMF
        0,  // STREAM_TTS
        1,  // STREAM_ACCESSIBILITY
        0   // STREAM_ASSISTANT
    };

}
  • 輸出設(shè)備
    音頻的輸出設(shè)備有很多種笑窜,一種音頻流有多種輸出設(shè)備,而且每種設(shè)備對應(yīng)的音量不一定相同登疗,比如插入耳機時是另一種音量大小排截。
AUDIO_DEVICE_OUT_EARPIECE: 聽筒,用于通話時將聲音傳遞到耳朵辐益。

AUDIO_DEVICE_OUT_WIRED_HEADSET: 有線耳機/耳麥断傲,通過耳機插孔連接。

AUDIO_DEVICE_OUT_WIRED_HEADPHONE: 有線普通耳機智政,通過耳機插孔連接认罩。

AUDIO_DEVICE_OUT_BLUETOOTH_SCO: 單聲道藍牙耳機,用于通話续捂。

AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET: 藍牙電話耳機垦垂,用于通話。

AUDIO_DEVICE_OUT_BLUETOOTH_A2DP: 藍牙立體聲耳機牙瓢。

AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES: 藍牙立體聲耳機劫拗。

AUDIO_DEVICE_OUT_USB_HEADSET: USB 耳機/耳麥。

AUDIO_DEVICE_OUT_HEARING_AID: 助聽器矾克,用于助聽設(shè)備页慷。

AUDIO_DEVICE_OUT_LINE: 線級輸出。

AUDIO_DEVICE_OUT_AUX_DIGITAL: 數(shù)字輔助輸出聂渊,例如 HDMI差购。

AUDIO_DEVICE_OUT_USB_DEVICE: USB 設(shè)備。

AUDIO_DEVICE_OUT_SPEAKER: 揚聲器汉嗽,外部設(shè)備,通常是手機的內(nèi)置揚聲器找蜜。

AUDIO_DEVICE_OUT_SPEAKER_SAFE: 安全揚聲器饼暑,與揚聲器類似。

AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT: 車載藍牙免提設(shè)備洗做。

AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER: 藍牙 A2DP 揚聲器弓叛。

AUDIO_DEVICE_OUT_USB_ACCESSORY: USB 附件。

AUDIO_DEVICE_OUT_REMOTE_SUBMIX: 遠程混音輸出诚纸。
  • VolumeStreamState
    在AudioService中如何去管理這個復(fù)雜的音頻流和輸出設(shè)備間的音量大小關(guān)系呢撰筷?其通過一個內(nèi)部類VolumeStreamState,每個音頻流都有一個VolumeStreamState類存儲著當(dāng)前音頻流的相關(guān)信息:
public class AudioService extends IAudioService.Stub
        implements AccessibilityManager.TouchExplorationStateChangeListener,
            AccessibilityManager.AccessibilityServicesStateChangeListener {

    // 所有音頻流對應(yīng)的VolumeStreamState
    private VolumeStreamState[] mStreamStates;

    private void createStreamStates() {
        // 音頻流的總數(shù)
        int numStreamTypes = AudioSystem.getNumStreamTypes();
        // 賦值mStreamStates數(shù)組大小為音頻流的總數(shù)
        VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];

        for (int i = 0; i < numStreamTypes; i++) {
            // 為每個音頻流創(chuàng)建VolumeStreamState實例
            streams[i] =
                    new VolumeStreamState(System.VOLUME_SETTINGS_INT[mStreamVolumeAlias[i]], i);
        }

    }

    private class VolumeStreamState {
        private final int mStreamType; // 指示哪個音頻流
        private int mIndexMin; // 對應(yīng)的最小音量值
        // min index when user doesn't have permission to change audio settings
        private int mIndexMinNoPerm;
        private int mIndexMax; // 對應(yīng)的最大音量值

        private boolean mIsMuted;
        private boolean mIsMutedInternally;
        private String mVolumeIndexSettingName;
        private int mObservedDevices;

        // 保存每種設(shè)備的音量值
        private final SparseIntArray mIndexMap = new SparseIntArray(8) {
            @Override
            public void put(int key, int value) {
                super.put(key, value);
                record("put", key, value);
            }
            @Override
            public void setValueAt(int index, int value) {
                super.setValueAt(index, value);
                record("setValueAt", keyAt(index), value);
            }
        };

}

1. adjustStreamVolume()

按鍵或代碼設(shè)置都可以通過adjustStreamVolume()去調(diào)整音量大小:

   // 設(shè)置音量API之一
    public void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage) {
        if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
            Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
                    + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
            return;
        }
        final boolean hasModifyAudioSettings =
                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
                        == PackageManager.PERMISSION_GRANTED;
        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                direction/*val1*/, flags/*val2*/, callingPackage));
        adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
    }

    protected void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
            int keyEventMode) {
       
        ensureValidDirection(direction);
        ensureValidStreamType(streamType);

        boolean isMuteAdjust = isMuteAdjust(direction);
        //....
        if (adjustVolume
                && (direction != AudioManager.ADJUST_SAME) && (keyEventMode != VOL_ADJUST_END)) {
            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
            if (isMuteAdjust) {
                
                for (int stream = 0; stream < mStreamStates.length; stream++) {
                    if (streamTypeAlias == mStreamVolumeAlias[stream]) {
                        if (!(readCameraSoundForced()
                                    && (mStreamStates[stream].getStreamType()
                                        == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                            // 調(diào)用VolumeStreamState.mute()去設(shè)置靜音/非靜音
                            mStreamStates[stream].mute(state);
                        }
                    }
                }
            } else if (!isInVolumePassthrough()
                    && (streamState.adjustIndex(direction * step, device, caller,
                            hasModifyAudioSettings)
                            || streamState.mIsMuted)) {
                // 去設(shè)置音量
                sendMsg(mAudioHandler,
                        MSG_SET_DEVICE_VOLUME,
                        SENDMSG_QUEUE,
                        device,
                        0,
                        streamState,
                        0);
            }

            int newIndex = mStreamStates[streamType].getIndex(device);
         
        // 更新UI
         sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device);
     
    }

adjustStreamVolume方法中代碼中有很多畦徘,這里挑出主要的步驟:

  • 如果是傳入的direction是ADJUST_MUTE毕籽、ADJUST_UNMUTE靜音的屬性抬闯,則會調(diào)用VolumeStreamState.mute()方法,其相關(guān)執(zhí)行后面在講关筒,大致流程都差不多溶握。
  • 通過VolumeStreamState.adjustIndex設(shè)置Index的大小
  • 發(fā)送AudioHandler. MSG_SET_DEVICE_VOLUME消息去執(zhí)行音量的設(shè)置
  • 通過sendVolumeUpdate()方法回調(diào)給SystemUI中的VolumeUI去展示對應(yīng)的UI通知。

VolumeStreamState.adjustIndex

     // AudioService
    public void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage) {

    //....
           // 音頻流是音樂且是固定音量設(shè)備
        // 計算step為音量增加或較少的單位
        if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
            flags |= AudioManager.FLAG_FIXED_VOLUME;

            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                    mSafeMediaVolumeDevices.contains(device)) {
                step = safeMediaVolumeIndex(device);
            } else {
                step = streamState.getMaxIndex();
            }
            if (aliasIndex != 0) {
                aliasIndex = step;
            }
        } else {
            // 一般走這里蒸播,內(nèi)部單元值睡榆,即增加或減少一個音量的單元值是多少 
            step = rescaleStep(10, streamType, streamTypeAlias);
        }
    //...
     // direction 就是1,-1袍榆,100胀屿,-100,剛好*單元值后就是index要設(shè)置的選項
     else if ( (streamState.adjustIndex(direction * step, device, caller, hasModifyAudioSettings)) 
   }


    private int rescaleStep(int step, int srcStream, int dstStream) {
        // 源的最大值-最小值
        int srcRange = getIndexRange(srcStream);
        // 最大值-最小值
        int dstRange = getIndexRange(dstStream);
        // 錯誤
        if (srcRange == 0) {
            Log.e(TAG, "rescaleStep : index range should not be zero");
            return 0;
        }
        // 轉(zhuǎn)換后的布值
        return ((step * dstRange + srcRange / 2) / srcRange);
    }

}

// AudioManager
    public static final int ADJUST_RAISE = 1;
    public static final int ADJUST_LOWER = -1;
    public static final int ADJUST_MUTE = -100;
    public static final int ADJUST_UNMUTE = 100;

// VolumeStreamState 
    private class VolumeStreamState {
   
        public boolean adjustIndex(int deltaIndex, int device, String caller,
                boolean hasModifyAudioSettings) {
            return setIndex(getIndex(device) + deltaIndex, device, caller,
                    hasModifyAudioSettings);
        }

        public boolean setIndex(int index, int device, String caller,
                boolean hasModifyAudioSettings) {
            boolean changed;
            int oldIndex;
            synchronized (mSettingsLock) {
                synchronized (VolumeStreamState.class) {
                    oldIndex = getIndex(device);
                    // 檢查有效值包雀,不超過最小最大值
                    index = getValidIndex(index, hasModifyAudioSettings);
                    if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                        index = mIndexMax;
                    }
                    // 將對應(yīng)的設(shè)備設(shè)置到對應(yīng)的新值
                    mIndexMap.put(device, index);

                    changed = oldIndex != index;
                    final boolean isCurrentDevice = (device == getDeviceForStream(mStreamType));
                    // 同時也要更新與之共享音量的其他音頻流的VolumeStreamState中的對應(yīng)device的新值
                    final int numStreamTypes = AudioSystem.getNumStreamTypes();
                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                        final VolumeStreamState aliasStreamState = mStreamStates[streamType];

                        if (streamType != mStreamType &&
                                mStreamVolumeAlias[streamType] == mStreamType &&
                                (changed || !aliasStreamState.hasIndexForDevice(device))) {
                            final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
                            aliasStreamState.setIndex(scaledIndex, device, caller,
                                    hasModifyAudioSettings);
                            if (isCurrentDevice) {
                                aliasStreamState.setIndex(scaledIndex,
                                        getDeviceForStream(streamType), caller,
                                        hasModifyAudioSettings);
                            }
                        }
                    }
                    // Mirror changes in SPEAKER ringtone volume on SCO when
                    if (changed && mStreamType == AudioSystem.STREAM_RING
                            && device == AudioSystem.DEVICE_OUT_SPEAKER) {
                        for (int i = 0; i < mIndexMap.size(); i++) {
                            int otherDevice = mIndexMap.keyAt(i);
                            if (AudioSystem.DEVICE_OUT_ALL_SCO_SET.contains(otherDevice)) {
                                mIndexMap.put(otherDevice, index);
                            }
                        }
                    }
                }
            }
            if (changed) {
              // 發(fā)送廣播
            }
            return changed;
        }

    }

在設(shè)置Index中主要操作是

  • 通過rescaleStep()方法計算出單元值step
  • 通過direction * step可以得到新值的大小碉纳,在AudioManager中direction在+/-時被巧妙的設(shè)置了1/-1,然后傳給VolumeStreamState.adjustIndex
  • adjustIndex后又調(diào)用setIndex()方法,首先對新值進行有效轉(zhuǎn)換馏艾,不超過最大最小值劳曹,然后通過mIndexMap存儲到對應(yīng)的設(shè)備和新值,并且也更新與之共享音量的音頻流的對應(yīng)的mIndexMap值琅摩,并且發(fā)送廣播铁孵。

AudioHandler. MSG_SET_DEVICE_VOLUME

private class AudioHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

                case MSG_SET_DEVICE_VOLUME:
                    // 執(zhí)行setDeviceVolume方法
                    setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
                    break;
       }
}

 void setDeviceVolume(VolumeStreamState streamState, int device) {

        synchronized (VolumeStreamState.class) {
            //調(diào)用VolumeStreamState.applyDeviceVolume_syncVSS方法
            streamState.applyDeviceVolume_syncVSS(device);

            // 其他與之共享音量的音頻流的也隨之設(shè)置之對應(yīng)設(shè)備的音量大小
            int numStreamTypes = AudioSystem.getNumStreamTypes();
            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                if (streamType != streamState.mStreamType &&
                        mStreamVolumeAlias[streamType] == streamState.mStreamType) {
                    int streamDevice = getDeviceForStream(streamType);
                    if ((device != streamDevice) && mAvrcpAbsVolSupported
                            && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
                    }
                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
                }
            }
        }
     

    }

//  VolumeStreamState 

       void applyDeviceVolume_syncVSS(int device) {
            int index;
            if (isFullyMuted()) {
                // 如果整個音頻流都被靜音,index 被設(shè)置為 0
                index = 0;
            } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
                    && mAvrcpAbsVolSupported) {
                //A2DP 設(shè)備集合并且支持絕對音量控制
                index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
            } else if (isFullVolumeDevice(device)) {
                //如果設(shè)備是全音量設(shè)備(如揚聲器)房资,index 被設(shè)置為最大音量索引的 1/10
                index = (mIndexMax + 5)/10;
            } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
               //如果設(shè)備是聽力輔助設(shè)備蜕劝,同樣將 index 設(shè)置為最大音量索引的 1/10
                index = (mIndexMax + 5)/10;
            } else {
                //其他,當(dāng)前值+5/10
                index = (getIndex(device) + 5)/10;
            }

            setStreamVolumeIndex(index, device);
        }

        private void setStreamVolumeIndex(int index, int device) {
            if (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0
                    && !isFullyMuted()) {
                index = 1;
            }
             //通過AudioSystem.setStreamVolumeIndexAS底層設(shè)置音量值
            AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
        }

MSG_SET_DEVICE_VOLUME消息主要是真正的設(shè)置音量值的新值轰异,主要操作有:

  • MSG_SET_DEVICE_VOLUME消息執(zhí)行setDeviceVolume方法岖沛,setDeviceVolume方法給音頻流和與之共享的音頻流調(diào)用VSS的applyDeviceVolume_syncVSS進行音量值設(shè)置
  • applyDeviceVolume_syncVSS將新值index進行轉(zhuǎn)換,并通過setStreamVolumeIndex方法交給AudioSystem.setStreamVolumeIndexAS進行真正音量值設(shè)置搭独。

sendVolumeUpdate

    protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags, int device)
    {
        streamType = mStreamVolumeAlias[streamType];

        if (streamType == AudioSystem.STREAM_MUSIC) {
            flags = updateFlagsForTvPlatform(flags);
            // The volume bar ui shows depends on whether the device is in passthrough mode.
            if (isInVolumePassthrough()) {
                Slog.d(TAG, "no volume bar");
                flags &= ~AudioManager.FLAG_SHOW_UI;
            }
        }
        mVolumeController.postVolumeChanged(streamType, flags);
    }


    public static class VolumeController {
        private static final String TAG = "VolumeController";

        private IVolumeController mController;

        // 設(shè)置IVolumeController
        public void setController(IVolumeController controller) {
            mController = controller;
            mVisible = false;
        }

        public void postVolumeChanged(int streamType, int flags) {
            if (mController == null)
                return;
            try {
                // 調(diào)用IVolumeController的volumeChanged方法
                mController.volumeChanged(streamType, flags);
            } catch (RemoteException e) {
                Log.w(TAG, "Error calling volumeChanged", e);
            }
        }

    }

通過IVolumeController.volumeChanged進行回調(diào)婴削,給SystemUi那邊進行UI的更新,那么這個IVolumeController是誰設(shè)置的呢牙肝?
在AudioService中有個setVolumeController()方法給設(shè)置VolumeController.setController


  private final VolumeController mVolumeController = new VolumeController();

   @Override
    public void setVolumeController(final IVolumeController controller) {
       //...
        mVolumeController.setController(controller);
        if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
    }

setVolumeController()這個方法又是誰設(shè)置的呢唉俗?這就需要看一下VolumeUI的啟動流程:

public class VolumeUI extends SystemUI {

 private VolumeDialogComponent mVolumeComponent;

    // 啟動會調(diào)用start()
    @Override
    public void start() {
        //....
        setDefaultVolumeController();
    }

    private void setDefaultVolumeController() {
        DndTile.setVisible(mContext, true);
        if (LOGD) Log.d(TAG, "Registering default volume controller");
        // 調(diào)用VolumeDialogComponent.register()
        mVolumeComponent.register();
    }

}

// VolumeDialogComponent
public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
        VolumeDialogControllerImpl.UserActivityListener{

    private final VolumeDialogControllerImpl mController;
    private VolumeDialog mDialog;

    // UI顯示的Dialog是VolumeDialogImpl
    protected VolumeDialog createDefault() {
        VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
        impl.setAutomute(true);
        impl.setSilentMode(false);
        return impl;
    }

    @Override
    public void register() {
        // 調(diào)用VolumeDialogControllerImpl.register()
        mController.register();
    }

}

// VolumeDialogControllerImpl
public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {

    protected final VC mVolumeController = new VC();

    public void register() {
        // 設(shè)置VolumeController
        setVolumeController();
    }

    protected void setVolumeController() {
        try {
            // 調(diào)用AudioService.setVolumeController將VC設(shè)置為VolumeController
            mAudio.setVolumeController(mVolumeController);
        } catch (SecurityException e) {
            Log.w(TAG, "Unable to set the volume controller", e);
            return;
        }
    }

  private final class VC extends IVolumeController.Stub {
    
        @Override
        public void volumeChanged(int streamType, int flags) throws RemoteException {
            if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
                    + " " + Util.audioManagerFlagsToString(flags));
            if (mDestroyed) return;
            // 發(fā)送VOLUME_CHANGED消息
            mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
        }
  }

}

VolumeUI開始的時候會調(diào)用start()方法,最后通過VolumeDialogControllerImplVC設(shè)置VolumeController配椭。之后AudioService于VC通信虫溜,而VC則通過與VolumeDialogImpl進行UI的展示。

AdjustStreamVolume小結(jié)

主要分為三大步驟:

  1. 通過adjustIndex()更新index的新值
  2. 發(fā)送MSG_SET_DEVICE_VOLUME消息股缸,通過AudioSystem設(shè)置新的音量值
  3. sendVolumeUpdate發(fā)送通知UI消息

2. setStreamVolume

代碼調(diào)整音量時衡楞,可以通過setStreamVolume方法進行調(diào)節(jié),所以它也是設(shè)置音量的入口之一敦姻。

 // 設(shè)置音量API之一
    private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
            String caller, int uid, boolean hasModifyAudioSettings) {

        ensureValidStreamType(streamType);  // 這里先判斷一下流類型這個參數(shù)的有效性
        // 對應(yīng)共享音量的音頻流
        int streamTypeAlias = mStreamVolumeAlias[streamType];
        // 對應(yīng)音頻流的VolumeStreamState
        VolumeStreamState streamState = mStreamStates[streamTypeAlias];

        // 根據(jù)音頻流確定輸出的設(shè)備
        final int device = getDeviceForStream(streamType);
        int oldIndex;

         //...權(quán)限判斷

        synchronized (mSafeMediaVolumeStateLock) {
            // reset any pending volume command
            mPendingVolumeCommand = null;

            // 獲取流當(dāng)前的音量
            oldIndex = streamState.getIndex(device);
            // 將原流類型下的音量值映射到目標流類型下的音量值
            // 因為不同流類型的音量值刻度不一樣瘾境,所以需要進行這個轉(zhuǎn)換
            index = rescaleIndex(index * 10, streamType, streamTypeAlias);
            //...
          // 調(diào)用setStreamVolumeInt()
         onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
         // 獲取設(shè)置的結(jié)果
        index = mStreamStates[streamType].getIndex(device);
        // 廣播通知
        sendVolumeUpdate(streamType, oldIndex, index, flags, device);
    }


    private void onSetStreamVolume(int streamType, int index, int flags, int device,
            String caller, boolean hasModifyAudioSettings) {
        final int stream = mStreamVolumeAlias[streamType];
        // 調(diào)用setStreamVolumeInt()
        setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
        //....
    }

    private void setStreamVolumeInt(int streamType,
                                    int index,
                                    int device,
                                    boolean force,
                                    String caller, boolean hasModifyAudioSettings) {
        // 獲取保存音量信息的VolumeStreamState對象
        VolumeStreamState streamState = mStreamStates[streamType];

        // 為什么還要判斷streamState.setIndex的返回值呢?
        // 因為如果音量值在setIndex之后并沒有發(fā)生變化歧杏,比如說達到了最大值,就不需要繼續(xù)后面的操作了
        // 或者force參數(shù)為true的話
        if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {

            // 通過MSG_SET_DEVICE_VOLUME去設(shè)置音量值
            sendMsg(mAudioHandler,
                    MSG_SET_DEVICE_VOLUME,
                    SENDMSG_QUEUE,
                    device,
                    0,
                    streamState,
                    0);
        }
    }
 

setStreamVolume方法的執(zhí)行流程跟adjustStreamVolume方法大同小異寄雀,都是先更新index值得滤,然后通過MSG_SET_DEVICE_VOLUME消息去設(shè)置音量值,最后通過sendVolumeUpdate去回調(diào)通知Ui盒犹。

總結(jié)

本篇文章主要介紹了AudioService調(diào)整音量大小的源碼懂更,主要在java層,真正的設(shè)置是通過的AudioSystem去調(diào)用底層的native去完成急膀。VolumeStreamState存儲著音頻流的所有關(guān)系沮协,包括最大最小值,device對應(yīng)的index值卓嫂。調(diào)整音量有兩個API:adjustStreamVolumesetStreamVolume它們的執(zhí)行流程都大概是先更新對應(yīng)device的index值慷暂,然后設(shè)置音量值,最后通知UI更新晨雳。

參考文章:
https://juejin.cn/post/6983977417173893128
https://wizardforcel.gitbooks.io/deepin-android-vol3/content/2.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末行瑞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子餐禁,更是在濱河造成了極大的恐慌血久,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帮非,死亡現(xiàn)場離奇詭異氧吐,居然都是意外死亡,警方通過查閱死者的電腦和手機末盔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門筑舅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陨舱,你說我怎么就攤上這事翠拣。” “怎么了隅忿?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵心剥,是天一觀的道長。 經(jīng)常有香客問我背桐,道長,這世上最難降的妖魔是什么蝉揍? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任链峭,我火速辦了婚禮,結(jié)果婚禮上又沾,老公的妹妹穿的比我還像新娘弊仪。我一直安慰自己熙卡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布励饵。 她就那樣靜靜地躺著驳癌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪役听。 梳的紋絲不亂的頭發(fā)上颓鲜,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音典予,去河邊找鬼甜滨。 笑死,一個胖子當(dāng)著我的面吹牛瘤袖,可吹牛的內(nèi)容都是我干的衣摩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼捂敌,長吁一口氣:“原來是場噩夢啊……” “哼艾扮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起占婉,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锐涯,沒想到半個月后磕诊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體升薯,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蝌诡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片九杂。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妈倔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤声搁,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布黑竞,位于F島的核電站,受9級特大地震影響疏旨,放射性物質(zhì)發(fā)生泄漏很魂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一檐涝、第九天 我趴在偏房一處隱蔽的房頂上張望莫换。 院中可真熱鬧霞玄,春花似錦骤铃、人聲如沸拉岁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喊暖。三九已至,卻和暖如春撕瞧,著一層夾襖步出監(jiān)牢的瞬間陵叽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工丛版, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留巩掺,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓页畦,卻偏偏與公主長得像胖替,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子豫缨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容