本文轉載自:https://blog.csdn.net/azloong/article/details/79383323
1. 音頻框圖概述
| Front End PCMs | SoC DSP | Back End DAIs | Audio devices |
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers/Earpiece
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Front End PCMs:音頻前端,一個前端對應着一個 PCM 設備
Back End DAIs:音頻后端,一個后端對應着一個 DAI 接口,一個 FE PCM 能夠連接到一個或多個 BE DAI
Audio Device:有 headset、speaker、earpiece、mic、bt、modem 等;不同的設備可能與不同的 DAI 接口連接,也可能與同一個 DAI 接口連接(如上圖,Speaker 和 Earpiece 都連接到 DAI1)
Soc DSP:本文范圍內實現路由功能:連接 FE PCMs 和 BE DAIs,例如連接 PCM0 與 DAI1:
*************
PCM0 <============> *<====++ * <----DAI0-----> Codec Headset
* || *
PCM1 <------------> * ++===>* <====DAI1=====> Codec Speakers/Earpiece
* *
PCM2 <------------> * * <----DAI2-----> MODEM
* DSP *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
1
2
3
4
5
6
7
8
9
10
11
12
13
高通 MSM8996 音頻框圖:
FE PCMs:
deep_buffer
low_latency
mutil_channel
compress_offload
audio_record
usb_audio
a2dp_audio
voice_call
BE DAIs:
SLIM_BUS
Aux_PCM
Primary_MI2S
Secondary_MI2S
Tertiary_MI2S
Quatermary_MI2S
2. HAL 中的 usecase 和 device
usecase 通俗表示音頻場景,對應着音頻前端,比如:
low_latency:按鍵音、觸摸音、游戲背景音等低延時的放音場景
deep_buffer:音樂、視頻等對時延要求不高的放音場景
compress_offload:mp3、flac、aac等格式的音源播放場景,這種音源不需要軟件解碼,直接把數據送到硬件解碼器(aDSP),由硬件解碼器(aDSP)進行解碼
record:普通錄音場景
record_low_latency:低延時的錄音場景
voice_call:語音通話場景
voip_call:網絡通話場景
/* These are the supported use cases by the hardware.
* Each usecase is mapped to a specific PCM device.
* Refer to pcm_device_table[].
*/
enum {
USECASE_INVALID = -1,
/* Playback usecases */
USECASE_AUDIO_PLAYBACK_DEEP_BUFFER = 0,
USECASE_AUDIO_PLAYBACK_LOW_LATENCY,
USECASE_AUDIO_PLAYBACK_MULTI_CH,
USECASE_AUDIO_PLAYBACK_OFFLOAD,
USECASE_AUDIO_PLAYBACK_ULL,
/* FM usecase */
USECASE_AUDIO_PLAYBACK_FM,
/* HFP Use case*/
USECASE_AUDIO_HFP_SCO,
USECASE_AUDIO_HFP_SCO_WB,
/* Capture usecases */
USECASE_AUDIO_RECORD,
USECASE_AUDIO_RECORD_COMPRESS,
USECASE_AUDIO_RECORD_LOW_LATENCY,
USECASE_AUDIO_RECORD_FM_VIRTUAL,
/* Voice usecase */
USECASE_VOICE_CALL,
/* Voice extension usecases */
USECASE_VOICE2_CALL,
USECASE_VOLTE_CALL,
USECASE_QCHAT_CALL,
USECASE_VOWLAN_CALL,
USECASE_VOICEMMODE1_CALL,
USECASE_VOICEMMODE2_CALL,
USECASE_COMPRESS_VOIP_CALL,
USECASE_INCALL_REC_UPLINK,
USECASE_INCALL_REC_DOWNLINK,
USECASE_INCALL_REC_UPLINK_AND_DOWNLINK,
USECASE_AUDIO_PLAYBACK_AFE_PROXY,
USECASE_AUDIO_RECORD_AFE_PROXY,
USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE,
AUDIO_USECASE_MAX
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
device 表示音頻端點設備,包括輸出端點(如 speaker、headphone、earpiece)和輸入端點(如 headset-mic、builtin-mic)。高通 HAL 對音頻設備做了擴展,比如 speaker 分為:
SND_DEVICE_OUT_SPEAKER:普通的外放設備
SND_DEVICE_OUT_SPEAKER_PROTECTED:帶保護的外放設備
SND_DEVICE_OUT_VOICE_SPEAKER:普通的通話免提設備
SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED:帶保護的通話免提設備
類似還有很多,詳見 platform.h 音頻設備定義,下面僅列舉一部分:
/* Sound devices specific to the platform
* The DEVICE_OUT_* and DEVICE_IN_* should be mapped to these sound
* devices to enable corresponding mixer paths
*/
enum {
SND_DEVICE_NONE = 0,
/* Playback devices */
SND_DEVICE_MIN,
SND_DEVICE_OUT_BEGIN = SND_DEVICE_MIN,
SND_DEVICE_OUT_HANDSET = SND_DEVICE_OUT_BEGIN,
SND_DEVICE_OUT_SPEAKER,
SND_DEVICE_OUT_HEADPHONES,
SND_DEVICE_OUT_HEADPHONES_DSD,
SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES,
SND_DEVICE_OUT_SPEAKER_AND_LINE,
SND_DEVICE_OUT_VOICE_HANDSET,
SND_DEVICE_OUT_VOICE_SPEAKER,
SND_DEVICE_OUT_VOICE_HEADPHONES,
SND_DEVICE_OUT_VOICE_LINE,
SND_DEVICE_OUT_HDMI,
SND_DEVICE_OUT_DISPLAY_PORT,
SND_DEVICE_OUT_BT_SCO,
SND_DEVICE_OUT_BT_A2DP,
SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
SND_DEVICE_OUT_AFE_PROXY,
SND_DEVICE_OUT_USB_HEADSET,
SND_DEVICE_OUT_USB_HEADPHONES,
SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET,
SND_DEVICE_OUT_SPEAKER_PROTECTED,
SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED,
SND_DEVICE_OUT_END,
/* Capture devices */
SND_DEVICE_IN_BEGIN = SND_DEVICE_OUT_END,
SND_DEVICE_IN_HANDSET_MIC = SND_DEVICE_IN_BEGIN, // 58
SND_DEVICE_IN_SPEAKER_MIC,
SND_DEVICE_IN_HEADSET_MIC,
SND_DEVICE_IN_VOICE_SPEAKER_MIC,
SND_DEVICE_IN_VOICE_HEADSET_MIC,
SND_DEVICE_IN_BT_SCO_MIC,
SND_DEVICE_IN_CAMCORDER_MIC,
SND_DEVICE_IN_END,
SND_DEVICE_MAX = SND_DEVICE_IN_END,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
擴展這么多是為了方便設置 acdb id,比如外放和通話免提雖然都用了同樣的喇叭設備,但是這兩種情景會使用不同的算法,因此需要設置不同的 acdb id 到 aDSP,區分 SND_DEVICE_OUT_SPEAKER 和 SND_DEVICE_OUT_VOICE_SPEAKER 是為了匹配到各自的 acdb id。
由於高通 HAL 定義的音頻設備與 Android Framework 定義的不一致,所以在高通 HAL 中會根據音頻場景對框架層傳入的音頻設備進行轉換,詳見:
platform_get_output_snd_device()
platform_get_input_snd_device()
在高通 HAL 中,我們只看到 usecase(即 FE PCM)和 device,那么上一個節中提到的 BE DAI 為什么沒有被提及?很簡單,device 和 BE DAI 是“多對一”的關系,device 連接着唯一的 BE DAI(反過來就不成立了,BE DAI 可能連接着多個 device),所以確定了 device 也就能確定所連接的 BE DAI。
3. 音頻通路連接
簡單描述下高通 HAL 層音頻通路的連接流程。如 音頻框圖概述 所示,音頻通路分為三大塊:FE PCMs、BE DAIs、Devices,這三塊均需要打開並串聯起來才能完成一個音頻通路的設置。
FE_PCMs <=> BE_DAIs <=> Devices
1
3.1. 打開 FE PCM
FE PCMs 是在音頻流打開時設置的,我們首先要了解一個音頻流對應着一個 usecase,具體細節請參考:Android 音頻系統:從 AudioTrack 到 AudioFlinger
AudioTrack、AudioFlinger Threads、AudioHAL Usecases、AudioDriver PCMs 的關系如下圖所示:
start_output_stream() 代碼分析:
// 根據 usecase 找到對應 FE PCM id
int platform_get_pcm_device_id(audio_usecase_t usecase, int device_type)
{
int device_id = -1;
if (device_type == PCM_PLAYBACK)
device_id = pcm_device_table[usecase][0];
else
device_id = pcm_device_table[usecase][1];
return device_id;
}
int start_output_stream(struct stream_out *out)
{
int ret = 0;
struct audio_usecase *uc_info;
struct audio_device *adev = out->dev;
// 根據 usecase 找到對應 FE PCM id
out->pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
if (out->pcm_device_id < 0) {
ALOGE("%s: Invalid PCM device id(%d) for the usecase(%d)",
__func__, out->pcm_device_id, out->usecase);
ret = -EINVAL;
goto error_open;
}
// 為這個音頻流新建一個 usecase 實例
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
if (!uc_info) {
ret = -ENOMEM;
goto error_config;
}
uc_info->id = out->usecase; // 音頻流對應的 usecase
uc_info->type = PCM_PLAYBACK; // 音頻流的流向
uc_info->stream.out = out;
uc_info->devices = out->devices; // 音頻流的初始設備
uc_info->in_snd_device = SND_DEVICE_NONE;
uc_info->out_snd_device = SND_DEVICE_NONE;
list_add_tail(&adev->usecase_list, &uc_info->list); // 把新建的 usecase 實例添加到鏈表中
// 根據 usecase、out->devices,為音頻流選擇相應的音頻設備
select_devices(adev, out->usecase);
ALOGV("%s: Opening PCM device card_id(%d) device_id(%d) format(%#x)",
__func__, adev->snd_card, out->pcm_device_id, out->config.format);
if (!is_offload_usecase(out->usecase)) {
unsigned int flags = PCM_OUT;
unsigned int pcm_open_retry_count = 0;
if (out->usecase == USECASE_AUDIO_PLAYBACK_AFE_PROXY) {
flags |= PCM_MMAP | PCM_NOIRQ;
pcm_open_retry_count = PROXY_OPEN_RETRY_COUNT;
} else if (out->realtime) {
flags |= PCM_MMAP | PCM_NOIRQ;
} else
flags |= PCM_MONOTONIC;
while (1) {
// 打開 FE PCM
out->pcm = pcm_open(adev->snd_card, out->pcm_device_id,
flags, &out->config);
if (out->pcm == NULL || !pcm_is_ready(out->pcm)) {
ALOGE("%s: %s", __func__, pcm_get_error(out->pcm));
if (out->pcm != NULL) {
pcm_close(out->pcm);
out->pcm = NULL;
}
if (pcm_open_retry_count-- == 0) {
ret = -EIO;
goto error_open;
}
usleep(PROXY_OPEN_WAIT_TIME * 1000);
continue;
}
break;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
語音通話的情景有所不同,它不是傳統意義的音頻流,流程大概是這樣的:
進入通話時,上層會先設置音頻模式為 AUDIO_MODE_IN_CALL(HAL 接口是 adev_set_mode()),再傳入音頻設備 routing=$device(HAL 接口是 out_set_parameters())
out_set_parameters() 中檢查音頻模式是否為 AUDIO_MODE_IN_CALL,是則調用 voice_start_call() 打開語音通話的 FE_PCM
3.2. 路由選擇
我們在 mixer_pahts.xml 中看到 usecase 相關的通路:
<path name="deep-buffer-playback speaker">
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
</path>
<path name="deep-buffer-playback headphones">
<ctl name="TERT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
</path>
<path name="deep-buffer-playback earphones">
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
</path>
<path name="low-latency-playback speaker">
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
</path>
<path name="low-latency-playback headphones">
<ctl name="TERT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
</path>
<path name="low-latency-playback earphones">
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia5" value="1" />
</path>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
這些通路其實就是連接 usecase、device 之間的路由。比如 “deep-buffer-playback speaker” 是連接 deep-buffer-playback FE PCM、speaker Device 之間的路由,打開 “deep-buffer-playback speaker”,則把 deep-buffer-playback FE PCM 和 speaker Device 連接起來;關閉 “deep-buffer-playback speaker”,則斷開 deep-buffer-playback FE PCM 和 speaker Device 的連接。
之前提到“device 連接着唯一的 BE DAI,確定了 device 也就能確定所連接的 BE DAI”,因此這些路由通路其實都隱含着 BE DAI 的連接:FE PCM 並非直接到 device 的,而是 FE PCM 先連接到 BE DAI,BE DAI 再連接到 device。這點有助於理解路由控件,路由控件面向的是 FE PCM 和 BE DAI 之間的連接,回放類型的路由控件名稱一般是: $BE_DAI Audio Mixer $FE_PCM,錄制類型的路由控件名稱一般是:$FE_PCM Audio Mixer $BE_DAI,這很容易分辨。
例如 “deep-buffer-playback speaker” 通路中的路由控件:
<ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
1
MultiMedia1:deep_buffer usacase 對應的 FE PCM
QUAT_MI2S_RX:speaker device 所連接的 BE DAI
Audio Mixer:表示 DSP 路由功能
value:1 表示連接,0 表示斷開連接
這個控件的意思是:把 MultiMedia1 PCM 與 QUAT_MI2S_RX DAI 連接起來。這個控件並沒有指明 QUAT_MI2S_RX DAI 與 speaker device 之間的連接,因為 BE DAIs 與 Devices 之間並不需要路由控件,如之前所強調”device 連接着唯一的 BE DAI,確定了 device 也就能確定所連接的 BE DAI“。
路由控件的開關不僅僅影響 FE PCMs、BE DAIs 的連接或斷開,同時會使能或禁用 BE DAIs,要深入理解這點的話需要去研究 ALSA DPCM(Dynamic PCM) 機制,這里稍作了解即可。
路由操作函數是 enable_audio_route()/disable_audio_route(),這兩個函數名稱很貼合,控制 FE PCMs 與 BE DAIs 的連接或斷開。
代碼流程很簡單,把 usecase 和 device 拼接起來就是路由的 path name 了,然后再調用 audio_route_apply_and_update_path() 來設置路由通路:
const char * const use_case_table[AUDIO_USECASE_MAX] = {
[USECASE_AUDIO_PLAYBACK_DEEP_BUFFER] = "deep-buffer-playback",
[USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = "low-latency-playback",
//...
};
const char * const backend_tag_table[SND_DEVICE_MAX] = {
[SND_DEVICE_OUT_HANDSET] = "earphones";
[SND_DEVICE_OUT_SPEAKER] = "speaker";
[SND_DEVICE_OUT_SPEAKER] = "headphones";
//...
};
void platform_add_backend_name(char *mixer_path, snd_device_t snd_device,
struct audio_usecase *usecase)
{
if ((snd_device < SND_DEVICE_MIN) || (snd_device >= SND_DEVICE_MAX)) {
ALOGE("%s: Invalid snd_device = %d", __func__, snd_device);
return;
}
const char * suffix = backend_tag_table[snd_device];
if (suffix != NULL) {
strlcat(mixer_path, " ", MIXER_PATH_MAX_LENGTH);
strlcat(mixer_path, suffix, MIXER_PATH_MAX_LENGTH);
}
}
int enable_audio_route(struct audio_device *adev,
struct audio_usecase *usecase)
{
snd_device_t snd_device;
char mixer_path[MIXER_PATH_MAX_LENGTH];
if (usecase == NULL)
return -EINVAL;
ALOGV("%s: enter: usecase(%d)", __func__, usecase->id);
if (usecase->type == PCM_CAPTURE)
snd_device = usecase->in_snd_device;
else
snd_device = usecase->out_snd_device;
strlcpy(mixer_path, use_case_table[usecase->id], MIXER_PATH_MAX_LENGTH);
platform_add_backend_name(mixer_path, snd_device, usecase);
ALOGD("%s: apply mixer and update path: %s", __func__, mixer_path);
audio_route_apply_and_update_path(adev->audio_route, mixer_path);
ALOGV("%s: exit", __func__);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
3.3. 打開 Device
Android 音頻框架層中,音頻設備僅表示輸入輸出端點,它不關心 BE DAIs 與 端點之間都經過了哪些部件(widget)。但我們做底層的必須清楚知道:從BE DAIs 到端點,整條通路經歷了哪些部件。如下圖的外放通路 :
為了使得聲音從 speaker 端點輸出,我們需要打開 AIF1、DAC1、SPKOUT 這些部件,並把它們串聯起來,這樣音頻數據才能順着這條路徑(AIF1>DAC1>SPKOUT>SPEAKER)一路輸出到 speaker。
在音頻硬件驅動中,定義各種控件用於部件的開關或連接,比如控件 “SPKL DAC1 Switch” 用於控制 SPKL、DAC1 的連接或斷開。具體細節請參考:Linux ALSA 音頻系統:物理鏈路篇
我們在 mixer_pahts.xml 中看到 speaker 通路:
<path name="speaker">
<ctl name="SPKL DAC1 Switch" value="1" />
<ctl name="DAC1L AIF1RX1 Switch" value="1" />
<ctl name="DAC1R AIF1RX2 Switch" value="1" />
</path>
1
2
3
4
5
這些設備通路由 enable_snd_device()/disable_snd_device() 設置:
int enable_snd_device(struct audio_device *adev,
snd_device_t snd_device)
{
int i, num_devices = 0;
snd_device_t new_snd_devices[SND_DEVICE_OUT_END];
char device_name[DEVICE_NAME_MAX_SIZE] = {0};
if (snd_device < SND_DEVICE_MIN ||
snd_device >= SND_DEVICE_MAX) {
ALOGE("%s: Invalid sound device %d", __func__, snd_device);
return -EINVAL;
}
// 設備引用計數累加
adev->snd_dev_ref_cnt[snd_device]++;
// 根據 snd_device 找到對應的 device_name
if(platform_get_snd_device_name_extn(adev->platform, snd_device, device_name) < 0 ) {
ALOGE("%s: Invalid sound device returned", __func__);
return -EINVAL;
}
// 設備已經被打開了,直接返回,不會重復打開設備
if (adev->snd_dev_ref_cnt[snd_device] > 1) {
ALOGV("%s: snd_device(%d: %s) is already active",
__func__, snd_device, device_name);
return 0;
}
// 如果是帶保護的設備,那么先停止校准操作
if (audio_extn_spkr_prot_is_enabled())
audio_extn_spkr_prot_calib_cancel(adev);
if (platform_can_enable_spkr_prot_on_device(snd_device) &&
audio_extn_spkr_prot_is_enabled()) {
// 檢查帶保護的設備有無合法的 acdb id,如果沒有合法的 acdb id,那么保護算法無法被調用的
if (platform_get_spkr_prot_acdb_id(snd_device) < 0) {
adev->snd_dev_ref_cnt[snd_device]--;
return -EINVAL;
}
audio_extn_dev_arbi_acquire(snd_device);
// 打開帶保護的設備,保護算法也開始運作
if (audio_extn_spkr_prot_start_processing(snd_device)) {
ALOGE("%s: spkr_start_processing failed", __func__);
audio_extn_dev_arbi_release(snd_device);
return -EINVAL;
}
} else if (platform_split_snd_device(adev->platform,
snd_device,
&num_devices,
new_snd_devices) == 0) {
// 鈴聲模式下,多設備分割:比如 SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES 先分割為
// SND_DEVICE_OUT_SPEAKER + SND_DEVICE_OUT_HEADPHONES,然后再一一打開 speaker
// 和 headphones 設備
for (i = 0; i < num_devices; i++) {
enable_snd_device(adev, new_snd_devices[i]);
}
} else {
ALOGD("%s: snd_device(%d: %s)", __func__, snd_device, device_name);
// A2DP:打開藍牙設備端
if ((SND_DEVICE_OUT_BT_A2DP == snd_device) &&
(audio_extn_a2dp_start_playback() < 0)) {
ALOGE(" fail to configure A2dp control path ");
return -EINVAL;
}
audio_extn_dev_arbi_acquire(snd_device);
// 設置設備通路
audio_route_apply_and_update_path(adev->audio_route, device_name);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
值得注意的點有:
設備引用計數:每個設備都有各自的引用計數 snd_dev_ref_cnt,引用計數在 enable_snd_device() 中累加,如果大於 1,則表示該設備已經被打開了,那么就不會重復打開該設備;引用計數在 disable_snd_device() 中累減,如果為 0,則表示沒有 usecase 需要該設備了,那么就關閉該設備。
帶保護的外放設備:帶 “audio_extn_spkr_prot” 前綴的函數是帶保護的外放設備的相關函數,這些帶保護的外放設備和其他設備不一樣,它雖然屬於輸出設備,但往往還需要打開一個 PCM_IN 作為 I/V Feedback,有了 I/V Feedback 保護算法才能正常運作。
多輸出設備的分割:多輸出設備,一般指鈴聲模式下,外放設備與其他設備同時輸出的情形;platform_split_snd_device() 把多輸出設備分割,比如 SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES 分割為 SND_DEVICE_OUT_SPEAKER + SND_DEVICE_OUT_HEADPHONES,然后再一一打開 speaker、headphones。為什么要把多輸出設備分割為 外放設備+其他設備 的形式?現在智能手機的外放設備一般都是帶保護的,需要跑喇叭保護算法,而其他設備如藍牙耳機也可能需要跑 aptX 算法,如果沒有分割的話,只能下發一個 acdb id,無法把喇叭保護算法和 aptX 算法都調度起來。多輸出設備分割時,還需要遵循一個規則:如果這些設備均連接到同一個 BE DAI,則無須分割。
int platform_split_snd_device(void *platform,
snd_device_t snd_device,
int *num_devices,
snd_device_t *new_snd_devices)
{
int ret = -EINVAL;
struct platform_data *my_data = (struct platform_data *)platform;
if (NULL == num_devices || NULL == new_snd_devices) {
ALOGE("%s: NULL pointer ..", __func__);
return -EINVAL;
}
/*
* If wired headset/headphones/line devices share the same backend
* with speaker/earpiece this routine returns -EINVAL.
*/
if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES &&
!platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HEADPHONES)) {
*num_devices = 2;
new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
new_snd_devices[1] = SND_DEVICE_OUT_HEADPHONES;
ret = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4. 音頻設備切換
回放場景,框架層回調 HAL 層接口 out_set_parameters(“routing=$device”) 來切換輸出設備
錄制場景,框架層回調 HAL 層接口 in_set_parameters(“routing=$device”) 來切換輸入設備
這兩個函數最終都是調用 select_device() 來實現設備切換的,select_device() 函數非常復雜,這里僅闡述下主干流程。
select_devices
disable_audio_route
disable_snd_device
check_usecases_codec_backend 檢查其他usecase是否也跟隨切換設備
platform_check_backends_match
disable_audio_route
disable_snd_device
enable_snd_device
enable_audio_route
enable_snd_device
enable_audio_route
1
2
3
4
5
6
7
8
9
10
11
4.1. 單 usecase 情景的設備切換
4.2. 多 usecase 情景的設備切換
–to be continued