高通平台录音 EC_Ref 简析

 

高通平台音频录音EC_Ref参考信号简析

—— Android + SA8155 + 语音唤醒架构分析

Image


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)