Qualcomm Audio HAL 音频通路设置 ---------Good

本文详细解析了高通MSM8996平台的音频系统架构,包括音频前端(FEPCMs)、后端(BEDAIs)、音频设备(AudioDevices)的概念与连接方式,以及音频通路的建立过程。介绍了音频场景(usecase)与设备(device)的映射关系,以及如何通过路由控件实现音频设备的切换。

come from : https://blog.youkuaiyun.com/azloong/article/details/79383323

1. 音频框图概述
| Front End PCMs    |  SoC DSP  | Back End DAIs | Audio devices |


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:

高通 MSM8996 音频框图: 

MSM8996_Audio
FE PCMs: use_case

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
};
 
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,
};
 
扩展这么多是为了方便设置 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

3.1. 打开 FE PCM
FE PCMs 是在音频流打开时设置的,我们首先要了解一个音频流对应着一个 usecase,具体细节请参考:Android 音频系统:从 AudioTrack 到 AudioFlinger

AudioTrack、AudioFlinger Threads、AudioHAL Usecases、AudioDriver PCMs 的关系如下图所示:

AudioUsecase

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;
        }
 
语音通话的情景有所不同,它不是传统意义的音频流,流程大概是这样的:

进入通话时,上层会先设置音频模式为 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>
 
这些通路其实就是连接 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" />

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;
}
 
3.3. 打开 Device
Android 音频框架层中,音频设备仅表示输入输出端点,它不关心 BE DAIs 与 端点之间都经过了哪些部件(widget)。但我们做底层的必须清楚知道:从BE DAIs 到端点,整条通路经历了哪些部件。如下图的外放通路 :

 WM8998_SPK

为了使得声音从 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>
 
这些设备通路由 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;
}
 
值得注意的点有:

设备引用计数:每个设备都有各自的引用计数 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;
 
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
 
4.1. 单 usecase 情景的设备切换
4.2. 多 usecase 情景的设备切换
–to be continued
 

<think>我们正在处理用户关于调试Qualcomm音频通路配置失败的问题。根据之前的对话,用户已经了解了音频通路配置的基本步骤,现在遇到配置失败需要调试。 首先,我们需要系统地分析可能的原因和调试方法。结合引用[2]中提到的DSP相关调试经验(修改内核代码调整音量),以及标准调试流程,我们可以从以下几个方面入手: 1. **检查日志**:这是最直接的调试手段,查看内核日志和Android日志中与音频相关的错误信息。 2. **混音器状态检查**:使用工具实时查看混音器控件的状态,确认配置是否生效。 3. **音频路由验证**:使用命令行工具强制切换路由,测试通路是否正常。 4. **配置文件验证**:检查配置文件的正确性,包括语法、控件名称和值范围。 5. **内核驱动检查**:如果涉及底层修改(如引用[2]中的音量调整),需要确认内核驱动是否支持。 具体步骤: ### 1. 获取系统日志 - 使用`adb logcat`查看Android系统日志,过滤音频相关标签: ```bash adb logcat | grep -E "Audio|audio|ACDB|sound|HAL|msm_" ``` - 使用`adb shell dmesg`查看内核日志,过滤音频相关消息: ```bash adb shell dmesg | grep -i "snd|audio|msm|dsp" ``` 重点查找错误(Error)、失败(Fail)等关键字。 ### 2. 使用tinymix检查混音器状态 - 列出所有混音器控件及其当前值: ```bash adb shell tinymix ``` - 对比修改后的`mixer_paths.xml`中的配置,检查相关控件的值是否正确。例如,如果配置了扬声器通路,则检查: - 音频流路由(如`SLIM RX0 MUX`)是否设置为预期的值(如`AIF1_PB`)。 - 开关控件(如`SPK DAC Switch`)是否已打开(值为1)。 - 音量值是否在合理范围内。 ### 3. 强制音频路由测试 - 使用`audiorouter`工具(如果系统支持)强制切换到特定通路: ```bash adb shell audiorouter -d <device_id> -p <path_name> ``` 其中`<device_id>`是设备ID(如0代表扬声器),`<path_name>`是混音器路径名(如"speaker")。然后播放音频测试是否正常。 ### 4. 验证配置文件语法和语义 - **XML语法检查**:确保修改后的`mixer_paths.xml`没有语法错误(如标签未闭合、属性值缺少引号等)。 - **控件存在性检查**:使用`tinymix -D <card number>`列出所有可用控件,确保配置文件中使用的控件名称在系统中存在。 - **值范围检查**:确认设置的值在控件允许的范围内(如音量控件可能有最小/最大值限制)。 ### 5. 内核驱动调试(如果涉及底层修改) 如引用[2]所示,有时需要修改内核代码来调整音频参数(如音量增益)。如果怀疑是驱动问题: - 检查内核配置(`/proc/config.gz`)确认相关驱动已启用。 - 如果修改了内核代码,确保重新编译并刷入正确的内核镜像。 - 使用`cat /proc/asound/cards`查看声卡状态,使用`amixer -c<card number>`(如果支持)获取更详细的混音器信息。 ### 6. 其他高级调试方法 - **ACDB加载检查**:Qualcomm平台使用ACDB(Audio Calibration Database)文件存储音频校准数据。检查日志中是否有ACDB加载失败的信息,并确认`/vendor/etc/acdbdata/`下的ACDB文件正确。 - **HAL层调试**:启用Audio HAL的调试日志(可能需要修改HAL代码并重新编译),查看音频策略管理器的决策过程。 - **使用strace跟踪**:对关键进程(如`audioserver`)进行系统调用跟踪: ```bash adb shell strace -p $(pidof audioserver) -f -o /data/local/tmp/audioserver.log ``` ### 常见问题及解决方法 | 问题现象 | 可能原因 | 解决方法 | |---------------------------|------------------------------|------------------------------------------| | 无声音 | 通路未正确激活 | 检查混音器开关和路由控件 | | 音量过小/过大 | 增益设置不当 | 调整音量控件值或参考引用[2]修改内核增益 | | 切换通路后爆音 | 控件切换顺序不当 | 在混音器路径中调整控件设置顺序 | | 特定设备无法识别 | 音频策略配置错误 | 检查`audio_policy_configuration.xml` | | 修改后系统无法启动 | 配置文件语法错误 | 恢复备份并重新检查XML | > **注意**:调试过程中建议一次只修改一个配置项,并记录更改内容,便于定位问题。 ### 调试工具总结 | 工具 | 用途 | 示例命令 | |---------------|-------------------------------------|---------------------------------------| | `logcat` | 查看Android系统日志 | `adb logcat -b all -v threadtime` | | `dmesg` | 查看内核启动日志 | `adb shell dmesg > dmesg.log` | | `tinymix` | 查看/修改混音器控件 | `adb shell tinymix "Control Name"` | | `audiorouter` | 强制切换音频路由(需系统支持) | `adb shell audiorouter -d 0 -p speaker` | | `strace` | 跟踪系统调用 | `adb shell strace -p <PID>` | 通过以上步骤,通常可以定位并解决大部分音频通路配置失败的问题。如果问题仍然存在,可能需要深入分析特定平台的HAL实现或检查硬件连接。 [^1]: Qualcomm Hexagon、AI Engine、HTA、HVX、HMX、Snapdragon、DSP 推理加速、AIC、QNN SDK、Tensor 编排、Android NNAPI、异构调度 摘要 [^2]: 8、FM音量调试 可以修改 DSP 里的 CodecRxGain 来改变 FM 的音量,但因为 Audio RecordPath 也用到CodecRxGain,因此修改 CodecRxGain 的会同时改变录音的音量。 代码上修改: a、kernel/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h 宏定义: - #define INT_RX_VOL_MAX_STEPS 0x2000 + #define INT_RX_VOL_MAX_STEPS 0x4000 - #define INT_RX_VOL_GAIN 0x2000 + #define INT_RX_VOL_GAIN 0x4000 §§相关问题§§ 1. 如何解决音频通路切换时的爆音问题? 2. 如何检查并修复ACDB文件加载失败? 3. 在Android 13上调试Qualcomm音频HAL需要注意哪些兼容性问题?</think>### Qualcomm 音频通路配置失败调试指南 调试音频通路配置失败需系统性地检查软硬件各环节,以下是关键调试步骤: --- #### 1. **日志分析(首要步骤)** - **Android系统日志**: ```bash adb logcat | grep -E "AudioFlinger|APM|AudioPolicy|HAL|ACDB|sound" ``` 重点关注`AudioPolicyManager`的路由决策和`HAL`层错误: ``` E AudioPolicyManager: getDeviceForStrategy() failed for strategy X E HAL : set_parameters: error -22 for path "speaker" ``` - **内核日志**: ```bash adb shell dmesg | grep -i "snd|audio|dsp|codec" ``` 检查DSP驱动错误(如引用[2]提到的DSP增益问题): ``` [ERR] q6afe_set_lpass_codec_digital_volume: gain 0x4000 out of range ``` --- #### 2. **混音器状态验证** 使用`tinymix`工具实时检查控件状态: ```bash adb shell tinymix ``` 重点关注: 1. **路由开关状态**:如`SPK DAC Switch`值应为`1` 2. **音量范围**:确认值在控件范围内(如引用[2]的`INT_RX_VOL_GAIN`) 3. **MUX选择**:如`SLIM RX0 MUX`是否指向正确音频--- #### 3. **配置文件语法检查** - **XML语法验证**: ```bash xmllint --noout mixer_paths.xml ``` - **控件存在性验证**: 对比`tinymix`输出与配置文件的控件名称,特别注意: - Qualcomm平台差异(如SM8450用`WSA_RX0`,旧平台用`SLIM RX0`) - Android版本差异(Android 13+需VNDK兼容控件) --- #### 4. **音频路由强制测试** ```bash # 强制切换到扬声器通路 adb shell audiorouter -d 0 -p speaker # 强制切换到耳机通路 adb shell audiorouter -d 1 -p headphones ``` 测试后立即用`tinymix`检查控件状态变化 --- #### 5. **内核级调试(DSP/Codec)** 针对引用[2]提到的DSP增益问题: 1. **检查驱动范围限制**: ```c // 内核驱动代码示例 (sound/soc/qcom/qdsp6v2/msm-pcm-routing-v2.c) if (gain > INT_RX_VOL_MAX_STEPS) { // 检查此处阈值 pr_err("gain %x out of range\n", gain); return -EINVAL; } ``` 2. **寄存器级调试**: ```bash adb shell cat /sys/kernel/debug/asoc/*/codec_reg ``` --- #### 6. **常见问题分类解决** | 故障现象 | 可能原因 | 解决方案 | |------------------------|--------------------------|--------------------------------------------------------------------------| | **完全无声音** | 1. 通路未激活<br>2. 时钟配置错误 | 1. 检查`Switch`控件状态<br>2. 验证`MCLK`/`BCLK`配置 | | **音量异常** | 1. 增益超范围<br>2. DSP校准错误 | 1. 调整`Volume`控件值<br>2. 检查ACDB校准文件[^1] | | **通路切换爆音** | 1. 控件顺序错误<br>2. 无淡入淡出 | 1. 调整XML中控件顺序<br>2. 添加`Volume Ramp`控件 | | **特定设备不识别** | 1. 音频策略配置错误<br>2. HAL未上报 | 1. 检查`audio_policy_configuration.xml`<br>2. 验证HAL的`get_devices`实现 | --- #### 7. **高级调试工具** - **QACT(Qualcomm音频校准工具)**: 连接设备直接读取DSP寄存器状态 - **HAL层调试**: 修改`audio_hw.c`添加调试日志: ```c ALOGD("set_parameters: path=%s, value=%d", path, value); // 添加在关键函数 ``` - **信号跟踪**: ```bash adb shell tinypcminfo -D /dev/snd/pcmC0D0p -P # 捕获PCM数据流 ``` > **关键提示**: > 1. 优先对比**原厂参考配置**(如`mixer_paths_<平台>_ref.xml`) > 2. Android 13+需确保HAL实现符合AIDL规范[^1] > 3. 修改内核参数(如引用[2])后必须重编译并验证驱动兼容性 [^1]: Qualcomm Hexagon、AI Engine、HTA、HVX、HMX、Snapdragon、DSP 推理加速、AIC、QNN SDK、Tensor 编排、Android NNAPI、异构调度 摘要 [^2]: 8、FM音量调试 可以修改 DSP 里的 CodecRxGain 来改变 FM 的音量,但因为 Audio RecordPath 也用到CodecRxGain,因此修改 CodecRxGain 的会同时改变录音的音量。 代码上修改: a、kernel/sound/soc/msm/qdsp6v2/msm-pcm-routing-v2.h 宏定义: - #define INT_RX_VOL_MAX_STEPS 0x2000 + #define INT_RX_VOL_MAX_STEPS 0x4000 - #define INT_RX_VOL_GAIN 0x2000 + #define INT_RX_VOL_GAIN 0x4000
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值