1. 程式人生 > >Android音量系統分析

Android音量系統分析

原文連結:http://www.10tiao.com/html/223/201705/2651232467/1.html

最近在處理一個藍芽裝置播放沒有聲音問題時,發現是設定音量的問題,順便學習了一下Android系統的音量構架原理及設定方法。這裡主要參考了rinswindqin同學寫的有關音訊及音量分析的文章,加了一些自己的理解及原始碼分析。下面以Android 6.0為例來說明。

一、音訊流、音訊裝置、音量三角關係

要了解Android系統的音量構架原理,我們先要了解一下Android系統的音訊流有哪些。下面是在AudioSystem.java中定義的音訊流格式:

int STREAM_VOICE_CALL = 0
;    電話 int STREAM_SYSTEM = 1;   系統 int STREAM_RING = 2;  響鈴和訊息 int STREAM_MUSIC = 3;   音樂 int STREAM_ALARM = 4;  鬧鐘 int STREAM_NOTIFICATION = 5;  通知 int STREAM_BLUETOOTH_SCO = 6;  藍芽 int STREAM_SYSTEM_ENFORCED = 7;   強制系統聲音 int STREAM_DTMF = 8;  雙音多頻 int STREAM_TTS = 9;  語音

總共10種音訊流,因Android版本不同可能存在差異。在AudioManager.java中也定義了,但它是引用了AudioSystem.java的定義。
音量與音訊流是息息相關的。每種音訊流至少對應一種音量,當然也可以多種音訊流對應一種音量。在AudioService.java中定義了這種對應關係,具體如下:

private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
   // STREAM_VOICE_CALL    AudioSystem.STREAM_VOICE_CALL,
    // STREAM_SYSTEM    AudioSystem.STREAM_RING,
   // STREAM_RING    AudioSystem.STREAM_RING,
   // STREAM_MUSIC    AudioSystem.STREAM_MUSIC, 
   // STREAM_ALARM    AudioSystem.STREAM_ALARM,
    // STREAM_NOTIFICATION
   AudioSystem.STREAM_RING,
   // STREAM_BLUETOOTH_SCO    AudioSystem.STREAM_BLUETOOTH_SCO,
    // STREAM_SYSTEM_ENFORCED    AudioSystem.STREAM_RING,
    // STREAM_DTMF    AudioSystem.STREAM_RING, 
    // STREAM_TTS    AudioSystem.STREAM_MUSIC      };

從上面定義可以看到系統音訊流,響鈴與訊息音訊流,通知音訊流,強制聲音音訊流,DTMF這五種音訊流共用一個音量,音樂與語音是共用一個音量。上面這個定義是用於通話的Android平臺上的(比如手機),Android還定義了兩種,分別用在電視或者機頂盒上的定義:

private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
   // STREAM_VOICE_CALL    AudioSystem.STREAM_MUSIC,
   // STREAM_SYSTEM    AudioSystem.STREAM_MUSIC,
   // STREAM_RING    AudioSystem.STREAM_MUSIC,
   // STREAM_MUSIC    AudioSystem.STREAM_MUSIC,
   // STREAM_ALARM    AudioSystem.STREAM_MUSIC,
   // STREAM_NOTIFICATION    AudioSystem.STREAM_MUSIC, 
    // STREAM_BLUETOOTH_SCO    AudioSystem.STREAM_MUSIC,
   // STREAM_SYSTEM_ENFORCED    AudioSystem.STREAM_MUSIC,
   // STREAM_DTMF    AudioSystem.STREAM_MUSIC,
     // STREAM_TTS    AudioSystem.STREAM_MUSIC       };

和是其它裝置的定義:

private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
       // STREAM_VOICE_CALL        AudioSystem.STREAM_VOICE_CALL,
       // STREAM_SYSTEM        AudioSystem.STREAM_RING,
       // STREAM_RING        AudioSystem.STREAM_RING,  
        // STREAM_MUSIC        AudioSystem.STREAM_MUSIC,
        // STREAM_ALARM        AudioSystem.STREAM_ALARM,  
       // STREAM_NOTIFICATION        AudioSystem.STREAM_RING,    
        // STREAM_BLUETOOTH_SCO        AudioSystem.STREAM_BLUETOOTH_SCO,
       // STREAM_SYSTEM_ENFORCED        AudioSystem.STREAM_RING,    
       // STREAM_DTMF        AudioSystem.STREAM_RING,
       // STREAM_TTS        AudioSystem.STREAM_MUSIC        };

系統通過mStreamVolumeAlias來儲存當前是那種平臺。

我們知道在使用手機揚聲器播放音樂時調整音量後,如果插入耳機,從耳機聽到的音量並沒有變化。在Android系統中,定義了一系統輸入和輸出裝置,針對每個輸入與輸出裝置的音量也是不一樣的。下面是Android系統在audio.h定義的部份音訊裝置。
輸出裝置:

AUDIO_DEVICE_OUT_EARPIECE                  = 0x1,// 聽筒
AUDIO_DEVICE_OUT_SPEAKER                   = 0x2,// 揚聲器
AUDIO_DEVICE_OUT_WIRED_HEADSET             = 0x4,//線控耳機
AUDIO_DEVICE_OUT_WIRED_HEADPHONE           = 0x8,//普通耳機
AUDIO_DEVICE_OUT_BLUETOOTH_SCO             = 0x10,//單聲道藍芽耳機
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET     = 0x20,//藍芽電話 
AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT      = 0x40, //車載擴音藍芽裝置
AUDIO_DEVICE_OUT_BLUETOOTH_A2DP                 = 0x80, //立體聲藍芽耳機

輸入裝置,比如:

AUDIO_DEVICE_IN_BUILTIN_MIC     = AUDIO_DEVICE_BIT_IN | 0x4, //手機自帶MIC
AUDIO_DEVICE_IN_VOICE_CALL     = AUDIO_DEVICE_BIT_IN| 0x40,//電話MIC

可以說每個音訊流對應到每種裝置都有一個音量。比如,對於同一個STREAM_MUSIC流,對揚聲器和耳機的音量是分開儲存的。不考慮相同的情況,音量個數=音訊流*音訊裝置。

二、音量的快取與持久化

音量的快取是通過AudioService.java的內部類VolumeStreamState來設定。mStreamType屬性指示哪個音訊流,通過定義:

private final SparseIntArray mIndexMap = new SparseIntArray(8);

按照device = index的鍵值對,每個元素對應一個裝置的音量,將用於播放這種音訊流的裝置的音量儲存在其中。
在AudioService中定義了所有音訊流及所對應的裝置的音量,具體如下:

private VolumeStreamState[] mStreamStates;

我們在使用手機調整音量後,關機後再開機,發現音量是我們最後調整的音量。那麼這說明音量已經持久化了。音量的持久化在Android 6.0以前是儲存到設定資料庫setting.db的System表中,具體如下:

上圖中字尾為headset的就是耳機相關音量,比如:耳機鈴聲音量,耳機MIC音量。
在Android 6.0及以後的版本為了加快響應速度,採用了xml的形式來儲存的。
在使用者做音量調整時,會儲存到資料庫或者xml中以實現音量的持久化。如果Android系統沒有使用過音量,音量的初始值是什麼呢?在AudioSystem.java中定義了各種音訊流的預設音量,如下:

public static int[] DEFAULT_STREAM_VOLUME = new int[] {
        4,  // STREAM_VOICE_CALL  
        7,  // STREAM_SYSTEM
        5,  // STREAM_RING      
        11, // STREAM_MUSIC     
        6,  // STREAM_ALARM     
        5,  // STREAM_NOTIFICATION 
        7,  // STREAM_BLUETOOTH_SCO 
        7,  // STREAM_SYSTEM_ENFORCED 
        11, // STREAM_DTMF
        11  // STREAM_TTS
};

同樣在AudioService.java中定義了每種流的最大音量與最小音量:

/** Maximum volume index values for audio streams */
private 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
};
/** Minimum volume index values for audio streams */
private static int[] MIN_STREAM_VOLUME = new int[] {
    1,  // STREAM_VOICE_CALL
    0,  // STREAM_SYSTEM
    0,  // STREAM_RING
    0,  // STREAM_MUSIC
    0,  // STREAM_ALARM
    0,  // STREAM_NOTIFICATION
    1,  // STREAM_BLUETOOTH_SCO
    0,  // STREAM_SYSTEM_ENFORCED
    0,  // STREAM_DTMF
    0   // STREAM_TTS
};

通過以上兩個陣列來控制各種流音量的最大最小值。

三、音量的設定流程

設定音量通常有以下方法:
通過AudioManager來設定
通過AudioTrack/MediaPlayer來設定

1.通過AudioManager來設定

我們先看一下AudioManager音量的設定過程

圖3.1AudioManager音量設定流程
AudioManager只是一個輕量級的封裝類,由Context建立,工作在APK程序中,通過IBinder的機制,負責與JAVA層的音訊服務AudioService進行互動。
AudioManager類提供了setStreamVolume方法來對一種stream type對應的音量進行設定:

public void setStreamVolume(int streamType, int index, int flags) {
    IAudioService service = getService();
    try {
        service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());
    } catch (RemoteException e) {
        Log.e(TAG, "Dead object in setStreamVolume", e);
    }
}

從程式碼中可以看出,setStreamVolume就是通過IPC呼叫AudioService的方法,用一個類圖來表示AudioManager和AudioService的關係:

圖3.2 AudioManager & AudioService
AudioManager通過代理物件訪問工作在SystemServer中的AudioService服務,呼叫其setStreamVolume方法來設定音量。
上面說過AudioService通過VolumeStreamState來快取各種音訊流的音量,並且通過mStreamStates來記錄各種音訊流的音量。設定音量最終是通過 VolumeStreamState. applyDeviceVolume_syncVSS函式呼叫AudioSystem.setStreamVolumeIndex函式來傳入device型別、音量index以及stream型別,告知音訊系統,“使用這種device播放這種stream型別的音訊播放操作,都將使用這個音量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);
}

AudioSystem主要對AudioPolicyService進行了封裝,所以接下來的操作都是由AudioPolicyService來完成的。
setStreamVolumeIndex是AudioSystem通過IBinder呼叫了AudioPolicyService的setStreamVolumeIndex函式,AudioPolicyService繼承了AudioPolicyClientInterface類,他有一個AudioPolicyInterface類的成員指標mpPolicyManager,實際上就是指向了AudioPolicyManager,最終是呼叫了AudioPolicyManager的setStreamVolumeIndex函式。(實際上AudioPolicyService是通過成員指標mpPolicyManager訪問AudioPolicyManager,而AudioPolicyManager則通過AudioPolicyClientInterface(mpClientInterface)訪問AudioPolicyService)。
AudioPolicyManager呼叫setStreamVolumeIndex後會引發AudioPolicyService執行一個SET_VOLUME的CommandThread,在這個CommandThread中呼叫了AudioSystem的靜態方法setStreamVolume,具體如下:

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

在這個函式裡呼叫了AudioFlinger的setStreamVolume。在AudioFlinger的setStreamVolume中呼叫了PlaybackThread的setStreamVolume.
AudioFlinger通過checkPlaybachTread方法,通過AudioPolicy傳入IO控制代碼(audio_io_handle_t),來定位到具體的PlaybackThread,呼叫其setStreamVolume方法,這個方法將音量值快取到stream對應的stream_type_t物件中,這樣,PlaybackThread便知道每種stream對應的音量了。具體如下:

status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,
        audio_io_handle_t output)
{
    ......
    if (thread == NULL) {
        for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
            mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value);
        }
    } else {
        thread->setStreamVolume(stream, value);
    }

    return NO_ERROR;
}

在PlaybackThread的setStreamVolume中只是儲存當前音量值,然後傳送通知在輸出音訊時按新的音量計