高通平台音频录音EC_Ref参考信号简析
—— Android + SA8155 + 语音唤醒架构分析

1️⃣ 背景说明
在 Android Automotive + SA8155 平台中,语音唤醒(Voice Activation)通常运行在低功耗DSP(Hexagon ADSP)侧,而主系统(AP)负责业务逻辑。
在车载场景中:
- 正在播放媒体
- 同时进行语音唤醒监听
- 麦克风会采集到扬声器声音
如果没有 EC(Echo Cancellation):
- 唤醒率下降
- 误唤醒
- 唤醒词污染
而 EC_Ref(Echo Reference) 是整个回声消除算法的关键输入。
2️⃣ SA8155 音频整体架构
🔹 2.1 SoC 架构核心模块
SA8155 内部包含:
- AP (Kryo CPU) — 运行 Android
- ADSP (Hexagon DSP) — 音频算法
- Audio HW (Codec + TDM + MI2S)
- AFE(Audio Front End)
DSP承担:
- EC
- NS
- AGC
- Voice Wakeup
- Fluence算法
🔹 2.2 Android → DSP 音频路径
典型播放路径:
App
↓
AudioTrack
↓
AudioFlinger
↓
Audio HAL
↓
ASM (Audio Stream Manager)
↓
ADM (Audio Device Manager)
↓
AFE Port
↓
Codec → Speaker
录音路径:
Mic → Codec → AFE → ADM → ASM → HAL → App
3️⃣ 语音唤醒路径(Low Power 场景)
在 8155 语音唤醒中,常见架构:
Mic
↓
AFE
↓
Voice Processing Topology
├── EC
├── NS
├── Beamforming
└── Wakeup Engine
↓
Keyword Detection
此时EC的两个输入:
| 输入 | 来源 |
|---|---|
| Near-End | 麦克风信号 |
| Far-End | EC_Ref |
4️⃣ EC_Ref 在 8155 中的来源分析
🔹 4.1 EC_Ref 本质
EC_Ref = 当前播放到扬声器的PCM数据
它必须:
- 与实际Speaker输出一致
- 与Mic采集保持时间同步
- 在DSP侧可获取
🔹 4.2 在8155中实现方式
方案一:AFE Loopback Tap(最常见)
RX Playback
↓
AFE RX Port
├── Speaker
└── EC_REF Port (Loopback)
优点:
- 硬件级同步
- 延迟可控
- 推荐方案
方案二:ADM内部复制 (不推荐)
ASM → ADM
├── Speaker
└── EC_Ref
风险:
- 延迟偏差
- 多zone时Ref错乱
6️⃣ Android HAL 侧配置点
关键文件及代码:
audio_platform_info.xml
mixer_paths.xml
方案一:全局EC Reference
<!-- 多媒体通道的mixer -->
<path name="media-playback">
<ctl name="QUAT_TDM_RX_0 Channels" value="Eight" />
<ctl name="QUAT_TDM_RX_0 Audio Mixer MultiMedia1" value="1" />
</path>
<!-- 录音通道的mixer -->
<path name="speaker-qmic">
<ctl name="QUIN_TDM_TX_0 Channels" value="Eight" />
<ctl name="MultiMedia1 Mixer QUIN_TDM_TX_0" value="1" />
</path>
<path name="quad-mic">
<ctl name="AUDIO_REF_EC_UL1 MUX" value="QUAT_TDM_RX_0" />
<ctl name="EC Reference Channels" value="Four" />
<ctl name="EC Reference Bit Format" value="S16_LE" />
<ctl name="EC Reference SampleRate" value="48000" />
<path name="speaker-qmic" />
</path>
HAL代码
// lagvm/lagvm/LINUX/android/vendor/qcom/opensource/audio-hal/primary-hal/hal/audio_hw.c
--> enable_snd_device()
----> audio_route_apply_and_update_path()
驱动源码
AUDIO_REF_EC_UL1 MUX
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-routing-v2.c
--> msm_routing_ec_ref_rx_put()
----> msm_ec_ref_port_id = get_ec_ref_port_id(value)
----> adm_ec_ref_rx_id(msm_ec_ref_port_id)
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/dsp/q6adm.c
------> this_adm.ec_ref_rx = port_id;
EC Reference Channels
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-routing-v2.c
--> msm_ec_ref_ch_put()
----> adm_num_ec_ref_rx_chans(msm_ec_ref_ch)
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/dsp/q6adm.c
------> this_adm.num_ec_ref_rx_chans = num_chans
EC Reference Bit Format
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-routing-v2.c
--> msm_ec_ref_ch_put()
----> adm_ec_ref_rx_bit_width(value)
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/dsp/q6adm.c
------> this_adm.ec_ref_rx_bit_width = bit_width
EC Reference SampleRate
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-routing-v2.c
--> msm_ec_ref_ch_put()
----> adm_ec_ref_rx_sampling_rate(value)
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/dsp/q6adm.c
------> this_adm.ec_ref_rx_sampling_rate = sampling_rate
方案二:单个音频流EC Ref ChMixer
<custom_mtmx_ec_ref_params param_id="0" in_channel_count="8" out_channel_count="4" usecase="USECASE_AUDIO_RECORD" snd_device="SND_DEVICE_IN_QUAD_MIC">
<custom_mtmx_ec_ref_param_coeffs out_channel_index="0" values="5461 0 5461 0 5461 0 0 0"/>
<custom_mtmx_ec_ref_param_coeffs out_channel_index="1" values="0 5461 0 5461 5461 0 0 0"/>
<custom_mtmx_ec_ref_param_coeffs out_channel_index="2" values="0 0 0 0 0 0 0 0"/>
<custom_mtmx_ec_ref_param_coeffs out_channel_index="3" values="0 0 0 0 0 0 0 0"/>
</custom_mtmx_ec_ref_params>
HAL代码
// lagvm/lagvm/LINUX/android/vendor/qcom/opensource/audio-hal/primary-hal/hal/audio_hw.c
start_input_stream()
--> select_devices()
----> enable_audio_route()
// lagvm/lagvm/LINUX/android/vendor/qcom/opensource/audio-hal/primary-hal/hal/audio_extn/audio_extn.c
------> audio_extn_set_custom_mtmx_ec_ref_params_v2()
--------> pcm_device_id = platform_get_pcm_device_id()
--------> platform_get_custom_mtmx_ec_ref_params() //从上面的xml配置中获取参数
--------> set_custom_mtmx_mixer_channel_map() // input
----------> snprintf(mixer_name_prefix, sizeof(mixer_name_prefix), "AudStr Cap %d EC Ref ChMixer Input Map", pcm_device_id);
----------> mixer_ctl_set_array()
--------> set_custom_mtmx_mixer_channel_map() // output
----------> snprintf(mixer_name_prefix, sizeof(mixer_name_prefix), "AudStr Cap %d EC Ref ChMixer Output Map", pcm_device_id);
----------> mixer_ctl_set_array()
--------> update_custom_mtmx_ec_ref_coefficients_v2()
----------> snprintf(mixer_name_prefix, sizeof(mixer_name_prefix), "AudStr Cap %d EC Ref ChMixer Weight Ch", pcm_device_id);
----------> mixer_ctl_set_array()
--------> set_custom_mtmx_ec_ref_params_v2()
----------> snprintf(mixer_name_prefix, sizeof(mixer_name_prefix), "AudStr Cap %d EC Ref ChMixer Cfg", pcm_device_id);
----------> mixer_ctl_set_array()
--> audio_extn_ext_hw_plugin_usecase_start()
--> pcm_open()
--> pcm_prepare()
--> pcm_start()
驱动源码
EC Ref ChMixer Input Map
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-q6-v2.c
--> msm_pcm_chmixer_ec_ref_input_map_ctl_put()
----> struct msm_pcm_channel_mixer *chmixer_ec_ref = NULL;
----> chmixer_ec_ref = pdata->chmixer_ec_ref[fe_id];
----> chmixer_ec_ref->override_in_ch_map = true;
----> chmixer_ec_ref->in_ch_map[i] = value[i];
EC Ref ChMixer Output Map
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-q6-v2.c
--> msm_pcm_chmixer_ec_ref_output_map_ctl_put()
----> struct msm_pcm_channel_mixer *chmixer_ec_ref = NULL;
----> chmixer_ec_ref = pdata->chmixer_ec_ref[fe_id];
----> chmixer_ec_ref->override_out_ch_map = true;
----> chmixer_ec_ref->out_ch_map[i] = value[i];
EC Ref ChMixer Weight Ch
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-q6-v2.c
--> msm_pcm_chmixer_ec_ref_weight_ctl_put()
----> struct msm_pcm_channel_mixer *chmixer_ec_ref = NULL;
----> chmixer_ec_ref = pdata->chmixer_ec_ref[fe_id];
----> chmixer_ec_ref->channel_weight[channel][i] = value[i];
EC Ref ChMixer Cfg
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-q6-v2.c
--> msm_pcm_chmixer_ec_ref_cfg_ctl_put()
----> struct msm_pcm_channel_mixer *chmixer_ec_ref = NULL;
----> chmixer_ec_ref = pdata->chmixer_ec_ref[fe_id];
----> chmixer_ec_ref->input_channel = ucontrol->value.integer.value[0];
----> chmixer_ec_ref->output_channel = ucontrol->value.integer.value[1];
----> msm_pcm_routing_set_stream_ec_ref_chmix_cfg(fe_id, chmixer_ec_ref)
------> ec_ref_chmix_cfg[fedai_id].input_channel =cfg_data->input_channel;
------> ec_ref_chmix_cfg[fedai_id].output_channel =cfg_data->output_channel;
------> ec_ref_chmix_cfg[fedai_id].override_in_ch_map =cfg_data->override_in_ch_map;
------> ec_ref_chmix_cfg[fedai_id].in_ch_map[i] =cfg_data->in_ch_map[i];
------> ec_ref_chmix_cfg[fedai_id].override_out_ch_map =cfg_data->override_out_ch_map;
------> ec_ref_chmix_cfg[fedai_id].out_ch_map[i] =cfg_data->out_ch_map[i];
6️⃣ Android 驱动实际使用点
// lagvm/lagvm/LINUX/android/vendor/qcom/opensource/audio-hal/primary-hal/hal/audio_hw.c
pcm_prepare()
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/asoc/msm-pcm-q6-v2.c
--> msm_pcm_prepare()
----> msm_pcm_capture_prepare()
------> q6asm_open_read_with_retry()
------> msm_pcm_routing_reg_phy_stream_v2()
--------> msm_pcm_routing_reg_phy_stream()
// lagvm/lagvm/LINUX/android/kernel/msm-5.4/techpack/audio/dsp/q6adm.c
----------> copp_idx = adm_open_v2(port_id, path_type,
sample_rate, channels, topology,
perf_mode, bits_per_sample,
app_type, acdb_dev_id,
session_type, passthr_mode,
copp_token,
&ec_ref_port_cfg,
&ec_ref_chmix_cfg[fedai_id])
------------> int ec_ref_port_id = ec_ref_port_cfg ? ec_ref_port_cfg->port_id :this_adm.ec_ref_rx;
------------> port_id = q6audio_convert_virtual_to_portid(port_id)
------------> port_idx = adm_validate_and_get_port_index(port_id)
------------> copp_idx = adm_get_idx_if_copp_exists(port_idx, topology,perf_mode,rate, bit_width,app_type, session_type,copp_token)
------------> copp_idx = adm_get_next_available_copp(port_idx)
------------> send_adm_custom_topology()
--------------> apr_send_pkt(this_adm.apr, (uint32_t *)&adm_top)
------------> adm_arrange_mch_map(&open, path, channel_mode,port_idx)
------------> adm_arrange_mch_ep2_map(&open_v6,open_v6.dev_num_channel_eid2)
------------> adm_apr_send_pkt((uint32_t *) &open_v6,&this_adm.copp.wait[port_idx][copp_idx],port_idx, copp_idx, open_v6.hdr.opcode)
------------> adm_copp_set_ec_ref_mfc_cfg_v2(port_id, copp_idx,rate, bit_width, ec_ref_chmix_cfg)
--------------> adm_pack_and_set_one_pp_param(port_id, copp_idx, param_hdr, (uint8_t *) &mfc_cfg)
--------------> adm_pack_and_set_one_pp_param(port_id, copp_idx, param_hdr, (uint8_t *) chmixer_params)
----------> adm_matrix_map(fedai_id, path_type, payload, perf_mode, passthr_mode)
----------> msm_pcm_routng_cfg_matrix_map_pp(payload, path_type, perf_mode)
----------> msm_pcm_routing_channel_mixer(fedai_id, perf_mode,dspst_id, stream_type)