天天看點

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

在前面兩個小節小節中,已經講解了音量調節的最重要部分,該小節講解一下使用音量鍵,或者在Setting界面調節滑動條調節音量時,其處理流程是怎麼樣的。

使用音量鍵控制音量會涉及兩個系統,一個是輸入系統,一個是音頻系統,我們需要從源頭開始分析,我們先來看看輸入系統,在以前我們講解輸入系統時,我們曾經說過,對于輸入事件,我們會分為很多個stage進行處理:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

分為輸入法之前的處理以及輸入法之後的處理,我們打開ViewRootImpl.java檔案,對于音量調節鍵,我們關心的是在輸入法之後的處理:

處理過程,他會調用該類中的onProcess函數,在這之前我們先來梳理一下,他的處理流程:輸入系統會把輸入事件發送給位于最前面的APP(activity),activity收到輸入事件之後,會把他發送給自己wind,wind發送給deocview,deocview再把他發送給輸入焦點,所謂的輸入焦點就是一個控件,這個控件位于最前面,他接受使用者的輸入:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

如果其上的wind,deocview,以及輸入焦點都不能處理這個事件,我們還有一個兜底工作,對于音量鍵,就是由兜底工作完成的,當然,我們也可以在輸入焦點上重寫輸入函數,對音量鍵進行處理,這樣的話,如果重寫的APP位于最前面,我們按下音量鍵,接不能操控聲音了,具體行為按照編寫的程式為準。一般來說,我們不會重新音量鍵程式。也就是說,在兜底的工作中,實作對音量鍵對音量的控制。下面是調用的時序圖:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

現在我們檢視onProces函數:

protected int onProcess(QueuedInputEvent q) {
	/*處理案件事件*/
	processKeyEvent(q);
		/*該就是我們前面提到的處理流程*/
        // Deliver the key to the view hierarchy.
        mView.dispatchKeyEvent(event)
            // Apply the fallback event policy.
            if (mFallbackEventHandler.dispatchKeyEvent(event)) {		
           

其上dispatchKeyEvent就是我們的音量處理相關函數,其實作在PhoneFallbackEventHandler.cpp中實作:

public boolean dispatchKeyEvent(KeyEvent event) {
	/*如果按下音量鍵*/
    if (action == KeyEvent.ACTION_DOWN) {
        return onKeyDown(keyCode, event);
    } else {//如果松開音量鍵
        return onKeyUp(keyCode, event);
    }
           

其處理流程如下:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

這裡就不一步一步對源碼進行追蹤了,下面是一些文字的總結:

a. 音量鍵處理流程

音量鍵:

如果APP沒有重寫它的處理函數,

音量鍵的處理将交給PhoneFallbackEventHandler來處理,

它會調用AudioService.adjustSuggestedStreamVolume( 調整"推薦的流"的音量):

根據時序,我們打開AudioService.java中的adjustSuggestedStreamVolume函數:

/*
direction:增加還是減少音量
suggestedStreamType:推薦的StreamType(最後的選擇)
*/
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid) {
	/*獲得目前活躍的StreamType,如果沒有活躍的,則使用推薦值*/
	streamType = getActiveStreamType(suggestedStreamType);
		/*手機*/
		case AudioSystem.PLATFORM_VOICE:
			/*如果正在通話中*/
			if (isInCommunication()) {
				/*并且使用藍牙裝置*/
				if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
					/*傳回藍牙的StreamType類型*/
	                return AudioSystem.STREAM_BLUETOOTH_SCO;
				} else {//沒有使用藍牙裝置
                    return AudioSystem.STREAM_VOICE_CALL;
                }
            /*如果推薦之後為USE_DEFAULT_STREAM_TYPE*/
            } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
		/*電視,如機頂盒*/
				/*如果最近聽過音樂*/
				if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
					/*傳回音樂STREAM_MUSIC*/
					return AudioSystem.STREAM_MUSIC;
		case AudioSystem.PLATFORM_TELEVISION:
			/*永遠方傳回音樂*/		
			return AudioSystem.STREAM_MUSIC;
		default://平闆之類的
			//和電話處理類似
           

這樣我們就解答了前面的問題,如何獲得推薦的StreamType, stream = getActiveStreamType(…)。

為了減少使用者的設定難度,我們把Stream歸為幾類:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

我們設定StreamType時,他會導緻好幾種音量被設定,這就是分組,或者别名alias。我們來看看這别名分組是怎麼起作用單的?其調用流程如下:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

根據前面getActiveStreamType函數,已經獲得了活躍的StreamType,在根據流程,執行到:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

打開:

private class AudioHandler extends Handler {
	private void setDeviceVolume(VolumeStreamState streamState, int device) {
        synchronized (VolumeStreamState.class) {
            // Apply volume
            streamState.applyDeviceVolume_syncVSS(device);

            // Apply change to all streams using this one as alias
            int numStreamTypes = AudioSystem.getNumStreamTypes();
            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                if (streamType != streamState.mStreamType &&
                        mStreamVolumeAlias[streamType] == streamState.mStreamType) {
                    // Make sure volume is also maxed out on A2DP device for aliased stream
                    // that may have a different device selected
                    int streamDevice = getDeviceForStream(streamType);
                    if ((device != streamDevice) && mAvrcpAbsVolSupported &&
                            ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
                    }
                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
                }
            }
        }
        // Post a persist volume msg
        sendMsg(mAudioHandler,
                MSG_PERSIST_VOLUME,
                SENDMSG_QUEUE,
                device,
                0,
                streamState,
                PERSIST_DELAY);

    }
	
           

注釋比較詳細,就不進行講解了,小結如下:

a.2 音量設定的"alias"如何起作用: 
     set volume for stream; // 設定"推薦的流"的音量
	 
	 if (mStreamVolumeAlias[other stream] == stream)
	     set volume for other stream;  // 設定同屬一個alias的其他流的音量
		 
	 對于不同的裝置(電話、TV、平闆), mStreamVolumeAlias指向不同的數組
           

下面我們看看怎麼去設定stream的音量,根據時序圖,其最終版會調用到:

08.音頻系統:第006課_音頻系統HAL分析:003_音量鍵和Setting界面調節音量流程

我們打開AudioFlinger找到找到:

status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,audio_io_handle_t output)
	/*設定mStreamTypes的值*/
	mStreamTypes[stream].volume = value;
	/*設定其他線程的StreamVolume*/
	if (thread == NULL) {
        for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
            mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value);
        }
    } else {
        thread->setStreamVolume(stream, value);
    }
           

小節如下:

a.3 怎麼設定流的音量:
    設定audioflinger中的數組:   mStreamTypes[stream].volume = value;
	設定PlaybackThread中的數組: mStreamTypes[stream].volume = value;
           

下面我們分析音量滑動條的流程。

音量滑動條

音量滑動條是一個seekBar控件,打開seekBar.java,

@Override
void onProgressRefresh(float scale, boolean fromUser, int progress) {
        super.onProgressRefresh(scale, fromUser, progress);
           

當我們滑動滑動條的時候onProgressRefresh函數會被調用,我們怎麼去設定音量呢?有兩種方法:

1.重寫onProgressRefresh函數,在裡面去設定音量。

2.添加Listener。

為了大家友善,文字描述第二種方法:

b. 音量滑動條處理流程
b.1 通過下面檔案定義音量滑動條:
packages/apps/Settings/res/xml/notification_settings.xml
   該檔案定義了多個VolumeSeekBarPreference
   每個VolumeSeekBarPreference要跟一個SeekBar綁定

b.2 在VolumeSeekBarPreference的綁定函數onBindView中,
   設定了對應的SeekBar的SeekBarChangeListener (一個SeekBarVolumizer對象)

b.3 當SeekBar被滑動時, 它的onProgressRefresh被調用
   該函數會調用 mOnSeekBarChangeListener.onProgressChanged

b.4 mOnSeekBarChangeListener.onProgressChanged去設定音量

b.5 每一個SeekBar對應一個stream,
    滑動SeekBar時會設定該stream的音量,
	也會去設定同屬一個alias(同一分組)的其他stream的音量
           

(後兩分鐘無記錄)

繼續閱讀