車載音頻(CarAudio)

1.概覽

??Android Automotive OS (AAOS) 是在核心 Android 音頻堆棧的基礎(chǔ)之上打造而成,以支持用作車輛信息娛樂系統(tǒng)的用例。AAOS 負責實現(xiàn)信息娛樂聲音(即媒體、導(dǎo)航和通訊聲音),但不直接負責具有嚴格可用性和計時要求的鈴聲和警告。雖然 AAOS 提供了信號和機制來幫助車輛管理音頻,但最終還是由車輛來決定應(yīng)為駕駛員和乘客播放什么聲音,從而確保對保障安全至關(guān)重要的聲音和監(jiān)管聲音能被確切聽到,而不會中斷。

??當 Android 管理車輛的媒體體驗時,應(yīng)通過應(yīng)用來代表外部媒體來源(例如電臺調(diào)諧器),這類應(yīng)用可以處理該來源的音頻焦點和媒體鍵事件。

1.1 Android 聲音和聲音流

汽車音頻系統(tǒng)可以處理以下聲音和聲音流:

image.png

??Android 管理來自 Android 應(yīng)用的聲音,同時控制這些應(yīng)用,并根據(jù)其聲音類型將聲音路由到 HAL 中的輸出設(shè)備:

  • 邏輯聲音流:在核心音頻命名法中稱為“聲源”,使用音頻屬性進行標記。
  • 物理聲音流:在核心音頻命名法中稱為“設(shè)備”,在混音后沒有上下文信息。

??為了確??煽啃?,外部聲音(來自獨立聲源,例如安全帶警告鈴聲)在 Android 外部(HAL 下方,甚至是在單獨的硬件中)進行管理。系統(tǒng)實現(xiàn)者必須提供一個混音器,用于接受來自 Android 的一個或多個聲音輸入流,然后以合適的方式將這些聲音流與車輛所需的外部聲源組合起來。

??HAL 實現(xiàn)和外部混音器負責確保對保障安全至關(guān)重要的外部聲音能夠被用戶聽到,而且負責在 Android 提供的聲音流中進行混音,并將混音結(jié)果路由到合適的音響設(shè)備。

1.1.1 Android 聲音

??應(yīng)用可以有一個或多個通過標準 Android API(如用于控制焦點的 AudioManager 或用于在線播放的 MediaPlayer)交互的播放器,以便發(fā)出一個或多個音頻數(shù)據(jù)邏輯聲音流。這些數(shù)據(jù)可能是單聲道聲音,也可能是 7.1 環(huán)繞聲,但都會作為單個聲源進行路由和處理。應(yīng)用聲音流與 AudioAttributes(可向系統(tǒng)提供有關(guān)應(yīng)如何表達音頻的提示)相關(guān)聯(lián)。

??邏輯聲音流通過 AudioService 發(fā)送,并路由到一個(并且只有一個)可用的物理輸出聲音流,其中每個聲音流都是混音器在 AudioFlinger 內(nèi)的輸出。音頻屬性在混合到物理聲音流后將不再可用。

??接下來,每個物理聲音流都會傳輸?shù)揭纛l HAL,以在硬件上呈現(xiàn)。在汽車應(yīng)用中,呈現(xiàn)硬件可能是本地編解碼器(類似于移動設(shè)備),也可能是車輛物理網(wǎng)絡(luò)中的遠程處理器。無論是哪種情況,音頻 HAL 實現(xiàn)都需要提供實際樣本數(shù)據(jù)并使其能被用戶聽見。

1.1.2 外部聲音流.

??如果聲音流因認證或計時原因而不應(yīng)經(jīng)由 Android,則可以直接發(fā)送到外部混音器。從 Android 11 開始,HAL 現(xiàn)在能夠針對這些外部聲音請求焦點,以通知 Android,使其能夠采取適當措施(例如暫停媒體或阻止其他人獲得焦點)。

??如果外部聲音流是應(yīng)與 Android 正在生成的聲音環(huán)境交互的媒體源(例如,當外部調(diào)諧器處于開啟狀態(tài)時,停止 MP3 播放),則那些外部聲音流應(yīng)由 Android 應(yīng)用表示。此類應(yīng)用將代表媒體來源(而非 HAL)請求音頻焦點,并根據(jù)需要通過啟動/停止外部聲音源來響應(yīng)焦點通知,以符合 Android 音頻焦點政策規(guī)定。應(yīng)用還負責處理媒體鍵事件,例如播放/暫停。如需控制此類外部設(shè)備,建議使用的一種機制是 HwAudioSource。

1.2 輸出設(shè)備

??在音頻 HAL 級別,設(shè)備類型 AUDIO_DEVICE_OUT_BUS 提供用于車載音頻系統(tǒng)的通用輸出設(shè)備??偩€設(shè)備支持可尋址端口(其中每個端口都是一個物理聲音流的端點),并且應(yīng)該是車輛內(nèi)唯一受支持的輸出設(shè)備類型。

??系統(tǒng)實現(xiàn)可以針對所有 Android 聲音使用一個總線端口,在這種情況下,Android 會將所有聲音混合在一起,并將混音結(jié)果作為一個聲音流進行傳輸。此外,HAL 可以分別為每個 CarAudioContext 提供一個總線端口,以允許并發(fā)傳輸任何聲音類型。這樣一來,HAL 實現(xiàn)就可以根據(jù)需要混合和閃避不同的聲音。

??音頻上下文到輸出設(shè)備的分配是通過 car_audio_configuration.xml 完成的。

1.3 麥克風輸入

??在捕獲音頻時,音頻 HAL 會收到 openInputStream 調(diào)用,其中包含指示應(yīng)如何處理麥克風輸入的 AudioSource 參數(shù)。

??VOICE_RECOGNITION 源(尤其是 Google 助理)需要一個符合以下條件的立體聲麥克風流:具有回聲消除效果(如果有),但不應(yīng)用任何其他處理。波束成形應(yīng)由 Google 助理來完成。

1.3.1 多聲道麥克風輸入

??若要從具有兩個以上聲道(立體聲)的設(shè)備捕獲音頻,請使用聲道索引掩碼,而不是定位索引掩碼(例如 CHANNEL_IN_LEFT)。例如:

final AudioFormat audioFormat = new AudioFormat.Builder()
    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
    .setSampleRate(44100)
    .setChannelIndexMask(0xf /* 4 channels, 0..3 */)
    .build();
final AudioRecord audioRecord = new AudioRecord.Builder()
    .setAudioFormat(audioFormat)
    .build();
audioRecord.setPreferredDevice(someAudioDeviceInfo);

??如果 setChannelMask 和 setChannelIndexMask 均已設(shè)置,則 AudioRecord 僅使用由 setChannelMask 設(shè)置的值(最多兩個聲道)。

1.3.2 并發(fā)捕獲

??從 Android 10 開始,Android 框架支持并發(fā)捕獲輸入,但具有保護用戶隱私的限制。作為這些限制的一部分,AUDIO_SOURCE_FM_TUNER 等虛擬來源會被忽略,因此可以與常規(guī)輸入(例如麥克風)同時捕獲。HwAudioSources 也不會被納入并發(fā)捕獲限制。

??旨在與 AUDIO_DEVICE_IN_BUS 設(shè)備或輔助 AUDIO_DEVICE_IN_FM_TUNER 設(shè)備結(jié)合使用的應(yīng)用必須依賴于以下功能:明確識別這些設(shè)備,以及使用 AudioRecord.setPreferredDevice() 繞過 Android 默認聲源選擇邏輯。

1.4 音頻用法

??AAOS 主要使用 AudioAttributes.AttributeUsages 進行路由、音量調(diào)整和焦點管理。用法用于表示播放聲音流的“原因”。因此,所有聲音流和音頻焦點請求都應(yīng)為其音頻播放指定用法。如果在構(gòu)建 AudioAttributes 對象時未明確設(shè)置,則用法將默認為 USAGE_UNKOWN。雖然目前會對此用法采取與 USAGE_MEDIA 一樣的處理,但不應(yīng)依賴此行為進行媒體播放。

1.4.1 系統(tǒng)用法

??Android 11 中引入了系統(tǒng)用法。這些用法的行為與之前確立的用法類似,不同之處在于它們需要使用系統(tǒng) API 以及 android.permission.MODIFY_AUDIO_ROUTING。新的系統(tǒng)用法如下:

  • USAGE_EMERGENCY
  • USAGE_SAFETY
  • USAGE_VEHICLE_STATUS
  • USAGE_ANNOUNCEMENT

??若要通過系統(tǒng)用法構(gòu)造 AudioAttributes,請使用 AudioAttributes.Builder#setSystemUsage,而不是 setUsage。如果要通過非系統(tǒng)用法調(diào)用此方法,就會導(dǎo)致系統(tǒng)拋出 IllegalArgumentException。此外,如果同時在構(gòu)建器上設(shè)置了系統(tǒng)用法和非系統(tǒng)用法,則在構(gòu)建時將會拋出 IllegalArgumentException。

??如需查看與 AudioAttributes 實例關(guān)聯(lián)的用法,請調(diào)用 AudioAttributes#getSystemUsage。這將返回關(guān)聯(lián)的用法或系統(tǒng)用法。

1.4.2 音頻上下文

??為了簡化 AAOS 音頻的配置,類似用法均已歸入 CarAudioContext。這些音頻上下文會在整個 CarAudioService 中使用,以定義路由、音量組和音頻焦點管理。

Android 11 中的音頻上下文包括:

image.png

??音頻上下文和用法之間的映射關(guān)系。突出顯示的行用于新的系統(tǒng)用法。

1.5 多區(qū)音頻

??在汽車領(lǐng)域,圍繞多個用戶同時與平臺互動并且每個用戶都希望使用單獨媒體的需求,出現(xiàn)了一系列新的用例。例如,后座上的乘客在后座顯示屏上觀看 YouTube 視頻時,司機可以在駕駛艙中播放音樂。多區(qū)音頻通過允許不同的音頻源在車輛的不同音頻區(qū)同時進行播放來實現(xiàn)此目的。

??從 Android 10 開始提供的多區(qū)音頻讓原始設(shè)備制造商 (OEM) 能夠?qū)⒁纛l配置到單獨的音頻區(qū)。每個音頻區(qū)由車輛內(nèi)的一組設(shè)備組成,并且有各自的音量組、用于上下文的路由配置以及焦點管理。通過這種方式,可以將主駕駛艙配置為一個音頻區(qū),而將后座顯示屏的耳機插孔配置為第二個音頻區(qū)。

??這些音頻區(qū)被定義為 car_audio_configuration.xml 的一部分。然后,CarAudioService 讀取該配置,并幫助 AudioService 根據(jù)關(guān)聯(lián)的音頻區(qū)路由音頻流。每個音頻區(qū)仍會根據(jù)上下文和應(yīng)用 UID 定義路由規(guī)則。創(chuàng)建播放器時,CarAudioService 會確定播放器與哪個音頻區(qū)相關(guān)聯(lián),然后根據(jù)用法確定 AudioFlinger 應(yīng)將音頻路由到哪個設(shè)備。

??每個音頻區(qū)的焦點也是單獨維護的。這使得不同音頻區(qū)中的應(yīng)用可以單獨生成音頻,而不會彼此干擾,同時讓應(yīng)用保持關(guān)注其所在音頻區(qū)內(nèi)焦點的變化。CarAudioService 內(nèi)中的 CarZonesAudioFocus 負責管理每個音頻區(qū)的焦點。

image.png

1.6 音頻 HAL

車載音頻實現(xiàn)依賴標準 Android 音頻 HAL,其中包括以下內(nèi)容:

  • IDevice.hal:負責創(chuàng)建輸入聲音流和輸出聲音流、處理主音量和靜音操作,以及使用:
  • createAudioPatch:在設(shè)備之間創(chuàng)建外部-外部音頻通路。
  • IDevice.setAudioPortConfig():為各個物理聲音流提供音量。
  • IStream.hal:連同輸入變體和輸出變體一起管理進出硬件的樣本音頻流。

1.6.1 車載設(shè)備類型

以下設(shè)備類型與車載平臺相關(guān):

image.png

1.6.2 配置音頻設(shè)備

Android 可見的音頻設(shè)備必須在 /audio_policy_configuration.xml 中進行定義,其中包括以下組件:

  • 模塊名稱:支持“primary”(用于汽車用例)、“A2DP”、“remote_submix”和“USB”。模塊名稱和相應(yīng)音頻驅(qū)動程序應(yīng)編譯到 audio.primary.$(variant).so 中。

  • devicePorts:包含可從此模塊訪問的所有輸入和輸出設(shè)備(包括永久連接的設(shè)備和可移除設(shè)備)的設(shè)備描述符列表。

    • 對于每種輸出設(shè)備,您可以定義增益控制(包含以 millibel 為單位的 min/max/default/step 值,其中 1 millibel = 1/100 dB = 1/1000 bel)。
    • 即使有多個設(shè)備的設(shè)備類型為 AUDIO_DEVICE_OUT_BUS,也可以使用 devicePort 實例上的地址屬性查找設(shè)備。
  • mixPorts:包含由音頻 HAL 提供的所有輸出聲音流和輸入聲音流的列表。每個 mixPort 實例都可被視為傳輸?shù)?Android AudioService 的物理聲音流。

  • routes:定義輸入和輸出設(shè)備之間或聲音流和設(shè)備之間可能存在的連接的列表。

??以下示例定義了輸出設(shè)備 bus0_phone_out,其中所有 Android 音頻流都通過 mixer_bus0_phone_out 完成混音。該路由會將 mixer_bus0_phone_out 的輸出聲音流傳遞到設(shè)備 bus0_phone_out。

<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
    <modules>
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>bus0_phone_out</item>
<defaultOutputDevice>bus0_phone_out</defaultOutputDevice>
            <mixPorts>
                <mixPort name="mixport_bus0_phone_out"
                         role="source"
                         flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                </mixPort>
            </mixPorts>
            <devicePorts>
                <devicePort tagName="bus0_phone_out"
                            role="sink"
                            type="AUDIO_DEVICE_OUT_BUS"
                            address="BUS00_PHONE">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    <gains>
                        <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                                minValueMB="-8400"
                                maxValueMB="4000"
                                defaultValueMB="0"
                                stepValueMB="100"/>
                    </gains>
                </devicePort>
            </devicePorts>
            <routes>
                <route type="mix" sink="bus0_phone_out"
                       sources="mixport_bus0_phone_out"/>
            </routes>
        </module>
    </modules>
</audioPolicyConfiguration>

2.音頻HAL

車載音頻實現(xiàn)依賴標準 Android 音頻 HAL,其中包括以下內(nèi)容:

  • IDevice (hardware/interfaces/audio/2.0/IDevice.hal)。負責創(chuàng)建輸入流和輸出流、處理主音量和靜音操作,以及使用:

    • createAudioPatch 在設(shè)備之間創(chuàng)建 external-external 補丁程序。
    • IDevice.setAudioPortConfig() 為各個物理音頻流提供音量。
  • IStream (hardware/interfaces/audio/2.0/IStream.hal)。連同輸入變體和輸出變體一起管理進出硬件的樣本音頻流。

2.1 車載設(shè)備類型

以下設(shè)備類型與車載平臺相關(guān):


image.png

2.2 路由音頻源

??應(yīng)使用 AudioRecord 或相關(guān) Android 機制捕獲大多數(shù)音頻源。接下來,可為數(shù)據(jù)分配 AudioAttributes 并通過 AndroidTrack 播放數(shù)據(jù),只需依賴默認的 Android 路由邏輯或通過對 AudioRecord 或 AudioTrack 對象顯式調(diào)用 setPreferredDevice() 即可。

??對于與外部混音器之間有專用硬件連接的來源或具有極為嚴苛的延遲要求的來源,您可以使用 createAudioPatch() 和 releaseAudioPatch() 來啟用和停用外部設(shè)備之間的路由(在樣本傳輸過程中無需使用 AudioFlinger)。

2.3 配置音頻設(shè)備

Android 可見的音頻設(shè)備必須在 /audio_policy_configuration.xml 中進行定義,其中包括以下組件:

  • 模塊名稱:支持“primary”(用于汽車用例)、“A2DP”、“remote_submix”和“USB”。模塊名稱和相應(yīng)音頻驅(qū)動程序應(yīng)編譯到 audio.primary.$(variant).so 中。

  • devicePorts:包含可從此模塊訪問的所有輸入和輸出設(shè)備(包括永久連接的設(shè)備和可移除設(shè)備)的設(shè)備描述符列表。

    • 對于每種輸出設(shè)備,您可以定義增益控制(包含以 millibel 為單位的 min/max/default/step 值,其中 1 millibel = 1/100 dB = 1/1000 bel)。
    • 即使有多個設(shè)備的設(shè)備類型為 AUDIO_DEVICE_OUT_BUS,也可以使用 devicePort 實例上的地址屬性查找設(shè)備。
  • mixPorts:包含由音頻 HAL 提供的所有輸出聲音流和輸入聲音流的列表。每個 mixPort 實例都可被視為傳輸?shù)?Android AudioService 的物理聲音流。

  • routes:定義輸入和輸出設(shè)備之間或聲音流和設(shè)備之間可能存在的連接的列表。

??以下示例定義了輸出設(shè)備 bus0_phone_out,其中所有 Android 音頻流都通過 mixer_bus0_phone_out 完成混音。該路由會將 mixer_bus0_phone_out 的輸出聲音流傳遞到設(shè)備 bus0_phone_out。

<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
    <modules>
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>bus0_phone_out</item>
<defaultOutputDevice>bus0_phone_out</defaultOutputDevice>
            <mixPorts>
                <mixPort name="mixport_bus0_phone_out"
                         role="source"
                         flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                </mixPort>
            </mixPorts>
            <devicePorts>
                <devicePort tagName="bus0_phone_out"
                            role="sink"
                            type="AUDIO_DEVICE_OUT_BUS"
                            address="BUS00_PHONE">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    <gains>
                        <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                                minValueMB="-8400"
                                maxValueMB="4000"
                                defaultValueMB="0"
                                stepValueMB="100"/>
                    </gains>
                </devicePort>
            </devicePorts>
            <routes>
                <route type="mix" sink="bus0_phone_out"
                       sources="mixport_bus0_phone_out"/>
            </routes>
        </module>
    </modules>
</audioPolicyConfiguration>

2.4 指定 devicePorts

??車載平臺應(yīng)該為每個輸入到 Android 和從 Android 輸出的物理音頻流指定 devicePort 實例。對于輸出音頻流,每個 devicePort 實例的類型都應(yīng)該為 AUDIO_DEVICE_OUT_BUS,并采用整數(shù)(即總線 0、總線 1 等)編址。mixPort 實例與 devicePort 實例的數(shù)量之比應(yīng)為 1:1,并應(yīng)允許指定可以路由到每條總線的數(shù)據(jù)格式。

??車載實現(xiàn)可以使用多個輸入設(shè)備類型,包括 FM_TUNER(保留以用于廣播無線裝置輸入)、MIC 設(shè)備(用于處理麥克風輸入)和 TYPE_AUX_LINE(用于表示模擬線路輸入)。所有其他輸入流都會分配到 AUDIO_DEVICE_IN_BUS 并在通過 AudioManager.getDeviceList() 調(diào)用枚舉設(shè)備時被發(fā)現(xiàn)。各個來源可根據(jù) AudioDeviceInfo.getProductName() 進行區(qū)分。

??還可以將外部設(shè)備定義為端口,然后通過音頻 HAL 的 IDevice::createAudioPatch 方法(通過新的 CarAudioManager 入口點提供)使用這些端口與外部硬件互動。如果存在基于總線的音頻驅(qū)動程序,必須將 audioUseDynamicRouting 標志設(shè)置為 true:

<resources>
    <bool name="audioUseDynamicRouting">true</bool>
</resources>

如需了解詳情,請參閱 device/generic/car/emulator/audio/overlay/packages/services/Car/service/res/values/config.xml。

3.多區(qū)

3.1 整體介紹

??在汽車領(lǐng)域,圍繞多個用戶同時與平臺互動并且每個用戶都希望使用單獨媒體的需求,出現(xiàn)了一系列新的用例。例如,后座上的乘客在后座顯示屏上觀看 YouTube 視頻時,司機可以在駕駛艙中播放音樂。多可用區(qū)音頻允許不同的音頻源通過車輛的不同可用區(qū)同時進行播放,從而實現(xiàn)此功能。

??Android 10 中的多可用區(qū)音頻使原始設(shè)備制造商 (OEM) 能夠?qū)⒁纛l配置為不同的可用區(qū)。每個可用區(qū)由車輛內(nèi)的一組設(shè)備組成,并且有各自的音量組、用于上下文的路由配置以及焦點管理。這樣,主駕駛艙可配置為一個音頻可用區(qū),而后座顯示屏的耳機插孔可配置為第二個可用區(qū)。

??可用區(qū)定義為 car_audio_configuration.xml 的一部分。然后,CarAudioService 會讀取該配置,并幫助 AudioService 根據(jù)關(guān)聯(lián)可用區(qū)路由音頻流。每個可用區(qū)仍會根據(jù)上下文和應(yīng)用 UID 定義路由規(guī)則。創(chuàng)建播放器時,CarAudioService 會確定播放器與哪個可用區(qū)相關(guān)聯(lián),然后根據(jù)使用情況,確定 AudioFlinger 應(yīng)將音頻路由到哪個設(shè)備。

??系統(tǒng)為每個音頻可用區(qū)單獨維護焦點。這使得不同可用區(qū)中的應(yīng)用可以單獨生成音頻,而不會彼此干擾,同時讓應(yīng)用保持關(guān)注其所在可用區(qū)內(nèi)焦點的變化。CarAudioService 內(nèi)的 CarZonesAudioFocus 負責管理每個可用區(qū)的焦點。

image.png

3.1.2 配置多個可用區(qū)

??在 Android 10 中,car_audio_configuration.xml 取代了 car_volumes_groups.xml 和 IAudioControl.getBusForContext。新的配置文件中定義了可用區(qū)列表。每個可用區(qū)都有一個或多個音量組及其關(guān)聯(lián)設(shè)備,而每臺設(shè)備都有其應(yīng)在該可用區(qū)內(nèi)進行路由的上下文。所有上下文都必須在各個可用區(qū)進行表示。

??audio_policy 通常位于 vendor 分區(qū)中,表示主板的音頻硬件配置。car_audio_configuration.xml 中引用的所有設(shè)備都將在 audio_policy_configuration.xml 中進行定義。

  1. 啟用多可用區(qū)支持

如果存在多可用區(qū)音頻,則必須將 audioUseDynamicRouting 標志設(shè)置為 true:

<resources>
    <bool name="audioUseDynamicRouting">true</bool>
</resources>

如果設(shè)置為 false,CarAudioService 將回退為使用 car_volumes_groups.xml 和 IAudioControl.getBusForContext。雖然 HAL 層不需要進行任何更改,但 car_audio_configuration.xml 文件定義了設(shè)備與上下文的關(guān)系,因此在 Android 10 中已棄用 IAudioControl.getBusForContext。

  1. 主可用區(qū)audioUseDynamicRouting

??主可用區(qū)是系統(tǒng)默認向其路由所有音頻的位置。只能有一個主可用區(qū),該可用區(qū)在配置中通過屬性 isPrimary="true" 進行指示。如果未指定主可用區(qū),則默認情況下,系統(tǒng)會推薦使用列表中的第一個可用區(qū)。

  1. 示例配置

??對于之前定義的示例用例,車輛需要兩個可用區(qū),即一個主可用區(qū)和一個后座娛樂系統(tǒng)可用區(qū)。對于該配置,可能的 car_audio_configuration.xml 的定義如下:

<audioZoneConfiguration version="1.0">
       <zone name="primary zone" isPrimary="true">
           <volumeGroups>
               <group>
                   <device address="bus0_media_out">
                       <context context="music"/>
                   </device>
                   <device address="bus3_call_ring_out">
                       <context context="call_ring"/>
                   </device>
                   <device address="bus6_notification_out">
                       <context context="notification"/>
                   </device>
                   <device address="bus7_system_sound_out">
                       <context context="system_sound"/>
                   </device>
               </group>
               <group>
                   <device address="bus1_navigation_out">
                       <context context="navigation"/>
                   </device>
                   <device address="bus2_voice_command_out">
                       <context context="voice_command"/>
                   </device>
               </group>
               <group>
                   <device address="bus4_call_out">
                       <context context="call"/>
                   </device>
               </group>
               <group>
                   <device address="bus5_alarm_out">
                       <context context="alarm"/>
                   </device>
               </group>
           </volumeGroups>
           <displays>
                <display port="0"/>
           </displays>
       </zone>
        <zone name="rear seat zone">
           <volumeGroups>
               <group>
                   <device address="bus100_rear_seat">
                       <context context="music"/>
                       <context context="navigation"/>
                       <context context="voice_command"/>
                       <context context="call_ring"/>
                       <context context="call"/>
                       <context context="alarm"/>
                       <context context="notification"/>
                       <context context="system_sound"/>
                   </device>
               </group>
           </volumeGroups>
           <displays>
               <display port="1"/>
           </displays>
    </zones>
</audioZoneConfiguration>

??在這里,主可用區(qū)將上下文劃分至各個設(shè)備。這樣一來,HAL 便可使用車輛的硬件,在各個設(shè)備輸出中應(yīng)用不同的后處理效果和混音。設(shè)備已劃分為四個音量組:媒體、導(dǎo)航、通話和鬧鐘。如果系統(tǒng)配置為 useFixedVolume,則每個組的音量級別都將傳遞到 HAL,以應(yīng)用于這些設(shè)備的輸出。

??對于次要可用區(qū),應(yīng)通過單個耳機插孔進行輸出。在此示例中,所有使用請求都將路由到單臺設(shè)備和音量組,以簡化操作。

??對于每個可用區(qū),都可以添加可選的顯示屏列表,以及與每個顯示屏關(guān)聯(lián)的實體顯示屏端口。這使某些應(yīng)用能夠查詢與其想要定位的特定顯示屏相關(guān)聯(lián)的可用區(qū)(見下文)。

3.1.3 新的隱藏 API(僅限 OEM 和系統(tǒng)應(yīng)用)

在 Android 10 中,向 CarAudioManager 引入了一系列隱藏 API,使應(yīng)用可以查詢并設(shè)置音頻可用區(qū)和焦點。

int[] getAudioZoneIds();
int getZoneIdForUid(int uid);
boolean setZoneIdForUid(int zoneId, int uid);
boolean clearZoneIdForUid(int uid);
int getZoneIdForDisplay(Display display);

更改應(yīng)用的可用區(qū)

默認情況下,所有音頻將路由到主可用區(qū)。如需更新應(yīng)用以路由到其他可用區(qū),請使用 setZoneIdForUid:

int zoneIdForDisplayId =
mCarAudioManager.getZoneIdForDisplay(selectedDisplay);

int uid = mContext.getPackageManager()
        .getApplicationInfo(mContext.getPackageName(), 0)
        .uid;

if (mCarAudioManager.setZoneIdForUid(zoneId, info.uid)) {
    Log.d(TAG, "Zone successfully updated");
} else {
    Log.d(TAG, "Failed to change zone");
}

注意:數(shù)據(jù)流無法動態(tài)地切換可用區(qū)。因此,必須停止播放才能更改可用區(qū)。

3.2 音頻焦點

??通常情況下,任何時候都只能由一個應(yīng)用在全局范圍內(nèi)持有音頻焦點。其他應(yīng)用請求獲得焦點時,前一個應(yīng)用會收到焦點丟失事件,新的應(yīng)用則會獲得焦點。但也有一些例外情況,例如,當通話應(yīng)用持有音頻焦點時,新的焦點請求會被拒。

3.2.1 新的上下文交互定義

??汽車音頻焦點的管理方式從 Android 9 開始有所變化。CarAudioFocus 引入了一個交互矩陣,該矩陣可在收到焦點請求時根據(jù)新的請求來源及當前焦點持有者的用途捕獲所需行為。交互類型分以下三種:

  1. 獨占交互

??在獨占應(yīng)用上下文交互中,一次只允許一個應(yīng)用持有焦點。如果兩個媒體播放器應(yīng)用都在播放媒體,則只有其中一個可以持有焦點。這是大多數(shù)焦點持有者和焦點請求者都請求 AudioManager.AUDIOFOCUS_GAIN 時的默認交互方式。在這種情況下,當焦點請求獲批時,系統(tǒng)會將 AudioManager.AUDIOFOCUS_LOSS 分派給當前的焦點持有者。除了 AudioManager.AUDIOFOCUS_GAIN 以外,獨占交互可能還會請求其他音頻焦點。交互仍是獨占模式,且系統(tǒng)會分派相應(yīng)類型的焦點丟失事件。

  1. 拒絕交互

??在拒絕上下文交互中,焦點請求者發(fā)出的請求會一直遭拒。嘗試從通知轉(zhuǎn)換為鬧鈴就是拒絕用途交互的一個示例。在本例中,如果播放通知鈴聲的應(yīng)用正持有音頻焦點,而另一個應(yīng)用要請求焦點以播放鬧鈴,則鬧鐘應(yīng)用發(fā)出的焦點請求會被拒。由于焦點請求遭拒,因此系統(tǒng)不會向當前焦點持有者分派任何類型的焦點丟失事件。

  1. 并發(fā)交互

??車載音頻用戶最感興趣的一個方面就是并發(fā)上下文交互。在這種交互模式下,請求音頻焦點的車載應(yīng)用可與其他應(yīng)用同時播放音頻。除了能夠進行車載音頻路由之外,原始設(shè)備制造商 (OEM) 還可以將音頻路由到汽車的其他部分。要實現(xiàn)并發(fā)交互,焦點請求者必須請求 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。setPauseWhenDucked(true) 在與 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 結(jié)合使用時會正常運行。系統(tǒng)將為并發(fā)焦點請求發(fā)送焦點丟失事件,使應(yīng)用暫停播放。后者通常用于有聲圖書應(yīng)用,可防止用戶錯過音頻。

??雖然并發(fā)交互適用于許多實用應(yīng)用,但原始設(shè)備制造商 (OEM) 必須在硬件級別跨輸出設(shè)備實現(xiàn)混音和降低音量。例如,同時提供導(dǎo)航和媒體播放服務(wù)時,原始設(shè)備制造商 (OEM) 可以暫時將駕駛員側(cè)播放媒體的音響靜音(降低媒體音量),改為播放導(dǎo)航提示。如果汽車的配置是將媒體和導(dǎo)航音頻傳送至不同的設(shè)備,則可通過在將數(shù)據(jù)傳送至導(dǎo)航設(shè)備時降低媒體的音量來實現(xiàn)這一點。在這種情況下,原始設(shè)備制造商 (OEM) 可在駕駛員側(cè)播放導(dǎo)航提示,在車廂的其余位置繼續(xù)播放媒體。但是,如果將兩個來源傳送至同一輸出設(shè)備,則不會降低音量。因此,建議您將同時播放的所有內(nèi)容路由到不同的設(shè)備,以便系統(tǒng)適當?shù)亟档鸵袅俊?/p>

  1. 交互矩陣

??下方的表 1 展示了 CarAudioFocus 中定義的交互矩陣。在該表中,行內(nèi)容表示當前正持有焦點的應(yīng)用,列內(nèi)容表示傳入的焦點請求者。

??如果某個音樂媒體應(yīng)用正持有音頻焦點,某導(dǎo)航應(yīng)用請求獲得焦點,則該矩陣表示這兩個交互可以同時進行。如果傳入的導(dǎo)航應(yīng)用請求的焦點為 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,則該應(yīng)用的焦點請求會獲批,且系統(tǒng)不會向媒體應(yīng)用發(fā)送焦點丟失事件。該矩陣允許同時存在多個焦點持有者。在這種情況下,系統(tǒng)會將傳入的焦點請求者與當前的各個焦點持有者進行比較,然后決定進行哪種轉(zhuǎn)換。

表 1. 汽車音頻焦點用途交互矩陣。

傳入焦點請求者的上下文:

image.png

說明:

  • REJ:拒絕
  • EXC:獨占
  • CON:并發(fā)

如需了解詳情,請參閱 packages/services/Car/service/src/com/android/car/audio/CarAudioFocus.java

3.2.2 多音頻區(qū)焦點管理

??Android 10 保留了 Android 9 中的應(yīng)用用途交互規(guī)則,但對每個音頻區(qū)進行了進一步劃分。因此,主車廂與后座娛樂系統(tǒng)的焦點可分開管理,以免某個音頻區(qū)的焦點發(fā)生變化時另一個音頻區(qū)的播放中斷。

??所有應(yīng)用的焦點均由 CarAudioService 自動管理且通過

??CarAudioManager.setZoneIdForUid API 進行設(shè)置。將 UID 映射到特定音頻區(qū)后,相應(yīng)音頻區(qū)會自動請求焦點。以下是焦點請求示例:

//Create focus
ret = mAudioManager
.requestAudioFocus(mFocusListener,
mMusicAudioAttrib,AudioManager.AUDIOFOCUS_GAIN, 0);

if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.i(TAG, "Got focus for usage " + mMusicAudioAttrib.getUsage());
           start();
} else {
           Log.i(TAG, "MediaPlayer denied focus for usage "
+ mMusicAudioAttrib.getUsage());
}

??該示例與第三方應(yīng)用請求焦點的方式?jīng)]有任何區(qū)別,因此第三方應(yīng)用仍可以使用音頻區(qū)功能,但請注意,音頻區(qū)必須由原始設(shè)備制造商 (OEM) 的應(yīng)用啟動器或其他管理應(yīng)用進行管理。

同時流式傳輸?shù)蕉鄠€音頻區(qū)

??如果將應(yīng)用流式傳輸?shù)絾蝹€音頻區(qū),從應(yīng)用的角度來看,音頻焦點運作方式?jīng)]有發(fā)生變化,且可通過使用 CarAudioManager.setZoneIdForUid 對音頻區(qū)的管理進行設(shè)置。但是,如果應(yīng)用需要同時在多個音頻區(qū)播放音頻,則必須在軟件包中包含 AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,以針對各個音頻區(qū)請求焦點。

//Create attribute with bundle and AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID
Bundle bundle = new Bundle();
bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
               zoneIdForDisplayId);

mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
     .setUsage(AudioAttributes.USAGE_MEDIA)
     .addBundle(bundle)
 .build();

//Create focus

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容