文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

高通音频架构(三)

2023-08-18 15:15

关注

一、Kernel层

音频由于其特殊的工作,使得它的结构特别的复杂,而且在自己的结构基础上还引入了ALSA架构,不过在android系统上所引入的并非完整的ALSA架构而是精简版的tinyalsa,但是就算精简版也是内容相当丰厚。除此,音频还拥有自己的单独的处理器ADSP以及独立的电源管理系统DAPM(便携式动态音频电源管理),使得音频在任何时候都是以最低功耗运行,降低了便携设备的功耗。在某些播放场景甚至不需要CPU的介入,比如接打电话的通过音频,如果手机处于休眠可以不需要唤醒CPU直接传递语音数据。要想知道整个过程中音频数据的流转需要一步步去了解,音频架构中所涉及到的各个部分,缺一环则不可,先看看ALSA架构。

在这里插入图片描述

二、 ALSA

Advanced Linux Sound Architecture 高级Linux音频架构,对于android系统来说其实用的只是一个精简版的ALSA架构,有一部分ALSA的接口是放在用户空间,供上层调用来接通kernel

根据音频数据的流向再把音频内核分为以下三个层次:

2.1 Tinyalsa

ALSA lib的源码地址在:external/tinyalsa目录下,其中包含:tinyplay/tinycap/tinymix,这些是供用户空间之间调用的alsa接口,用来播放、录音及控制。并且它们的代码非常的简单,其主要功能是解耦,方便调试,这里不做过多赘述。

2.2 ALSA CORE

ASLA核心他的主要代码是在kernel/msm-x.xx/sound/core,alsa 核心层,向上提供逻辑设备(PCM / CTL / MIDI / TIMER /…)系统调用,向下驱动硬件设备( Machine / I2S / DMA / CODEC )

2.3 ASoc

ASoc(ALSA system on chip) 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系。主要代码存在于:vendor/qcom/opensource/audio-kernel,kernel/msm-x.xx/sound

ASoC被分为Machine、Platform和Codec三大部分。
其中的Machine驱动负责Platform和Codec之间的耦合和设备或板子特定的代码。

Platform驱动 的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。

在这里插入图片描述

ASoC对于Alsa来说,就是分别注册PCM/CONTROL类型的snd_device设备,并实现相应的操作方法集。
图中DAI是数字音频接口,用于配置音频数据格式等。

☁ Codec 驱动 向 ASoC 注册 snd_soc_codec 和 snd_soc_dai 设备。
☁ Platform 驱动 向 ASoC 注册 snd_soc_platform 和 snd_soc_dai 设备。
☁ Machine 驱动通过 snd_soc_dai_link 绑定 codec / dai / platform 。

Widget是各个组件内部的小单元。处在活动通路上电,不在活动通路下电。ASoC的DAPM正是通过控制这些Widget的上下电达到动态电源管理的效果。

☁ path描述与其它widget的连接关系。
☁ event用于通知该widget的上下电状态。
☁ power指示当前的上电状态。
☁ control实现空间用户接口用于控制widget的音量/通路切换等。

以上的内容可以对ALSA有个简单的了解,如果想要更深入的了解需要自行查找相关资料学习。那么我们知道了音频内核的组成,众所周知一台机器要想发出声音需要有声卡才行,声卡是我们音频中的核心,既然如此重要那么声卡和上面说的machine、platform和codec它们几者的关系如何呢?它们又是怎样开始工作的呢?接下来就来探究一下声卡的注册流程

三、声卡的注册流程

ASoC 的一切都从 Machine 驱动开始,包括声卡的注册,绑定 Platform 和 Codec 驱动等等。先看下machine的注册:

根据平台的不同machine代码存放的位置也有所差异,我们当前所看的machine所在位置在:vendor/qcom/opensource/audio-kernel/asoc/kona.c

static const struct of_device_id kona_asoc_machine_of_match[]  = {    { .compatible = "qcom,kona-asoc-snd",      .data = "codec"},    { .compatible = "qcom,kona-asoc-snd-stub",      .data = "stub_codec"},    {},}; static struct platform_driver kona_asoc_machine_driver = {    .driver = {        .name = DRV_NAME,        .owner = THIS_MODULE,        .pm = &snd_soc_pm_ops,        .of_match_table = kona_asoc_machine_of_match,        .suppress_bind_attrs = true,    },    .probe = msm_asoc_machine_probe,    .remove = msm_asoc_machine_remove,};module_platform_driver(kona_asoc_machine_driver);

machine 在开机时被注册为platform_driver,当匹配到"qcom,kona-asoc-snd"的device会执行probe,我们直接看

static int msm_asoc_machine_probe(struct platform_device *pdev){    struct snd_soc_card *card = NULL;    struct msm_asoc_mach_data *pdata = NULL;    const char *mbhc_audio_jack_type = NULL;    int ret = 0;    uint index = 0;    struct clk *lpass_audio_hw_vote = NULL;     dev_info(&pdev->dev, "%s : enter!\n", __func__);    if (!pdev->dev.of_node) {        dev_err(&pdev->dev, "%s: No platform supplied from device tree\n", __func__);        return -EINVAL;    }    dev_dbg(&pdev->dev, "msm_asoc_machine_probe\n");    pdata = devm_kzalloc(&pdev->dev,            sizeof(struct msm_asoc_mach_data), GFP_KERNEL);    if (!pdata)        return -ENOMEM;     of_property_read_u32(pdev->dev.of_node,                "qcom,lito-is-v2-enabled",                &pdata->lito_v2_enabled);     // 找到所有的dailink,并把他们都保存到card中,这些dailink大部分是写死在当前文件中    card = populate_snd_card_dailinks(&pdev->dev);         if (!card) {        dev_err(&pdev->dev, "%s: Card uninitialized\n", __func__);        ret = -EINVAL;        goto err;    }     if (get_aw882xx_i2c_probe_status() == 0) {        dev_info(&pdev->dev, "%s: aw pa never probe", __func__);        return -EPROBE_DEFER;    }     card->dev = &pdev->dev;    platform_set_drvdata(pdev, card);    snd_soc_card_set_drvdata(card, pdata);     ret = snd_soc_of_parse_card_name(card, "qcom,model");    if (ret) {        dev_err(&pdev->dev, "%s: parse card name failed, err:%d\n",            __func__, ret);        goto err;    }    // 解析设备树中的路由    ret = snd_soc_of_parse_audio_routing(card, "qcom,audio-routing");    if (ret) {        dev_err(&pdev->dev, "%s: parse audio routing failed, err:%d\n",            __func__, ret);        goto err;    }    // 解析每个dailink中,platform、cpu、codec相应的phandle    ret = msm_populate_dai_link_component_of_node(card);    if (ret) {        ret = -EPROBE_DEFER;        goto err;    }     ret = msm_init_aux_dev(pdev, card);    if (ret)        goto err;    // 注册声卡,这里调用到了soc-core.c中    ret = devm_snd_soc_register_card(&pdev->dev, card);    if (ret == -EPROBE_DEFER) {        if (codec_reg_done)            ret = -EINVAL;        goto err;    } else if (ret) {        dev_err(&pdev->dev, "%s: snd_soc_register_card failed (%d)\n",            __func__, ret);        goto err;    }    dev_info(&pdev->dev, "%s: Sound card %s registered\n",         __func__, card->name);    ...    return 0;err:    devm_kfree(&pdev->dev, pdata);    return ret;}

在machine的probe中主要是在解析设备树,然后还有整合所有的dailink,这些dailink分别保存在多个dailink数组中,创建了snd_soc_card,还有将各个platform、cpu、codec的phandle解析出来,注册声卡的过程却没有体现,那么再到soc-core.c中去看看注册声卡的过程,在machine调用devm_snd_soc_register_card之后到了soc-devres.c中

int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card){    struct snd_soc_card **ptr;    int ret;     ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);    if (!ptr)        return -ENOMEM;     ret = snd_soc_register_card(card);    if (ret == 0) {        *ptr = card;        devres_add(dev, ptr);    } else {        devres_free(ptr);    }     return ret;}

接着直接调用了soc-core.c中的snd_soc_register_card,这个过程很长简单列举一下流程

    声卡注册流程    devm_snd_soc_register_card()    + snd_soc_register_card()      + snd_soc_bind_card()        + snd_soc_instantiate_card()          + for_each_card_links(card, dai_link) {          |   soc_bind_dai_link() // 绑定dai link          |     + snd_soc_find_dai(dai_link->cpus); // cpus dai匹配,          |     |   + snd_soc_is_matching_component(dlc, component) // 先匹配of_node          |     |   | // 然后如果dai_name不为空,比较组件驱动名字和dai_link中cpu_dai_name          |     |   + strcmp(..., dlc->dai_name)          |     + for_each_link_codecs(dai_link, i, codec) // codec dai匹配          |     + for_each_link_platforms(dai_link, i, platform) // platform dai匹配          |     |          |     + soc_add_pcm_runtime() // 将rtd->list加入到card->rtd_list里,          |        + rtd->num = card->num_rtd; // 设备号,该num即为我们例子里的54          |        + card->num_rtd++; // 声卡的运行时例+1          + }          + snd_card_register()          | + snd_device_register_all()          |   + list_for_each_entry(dev, &card->devices, list) {          |   |   __snd_device_register()          |   |     + dev->ops->dev_register(dev); // 遍历注册设备          +   + }

在声卡注册过程中会根据machine给过来的dailink信息,把相应的cpu_dai、codec_dai和platform绑定在一起储存在一个snd_soc_pcm_runtime中,然后以rtd_list形式存在card 中。snd_card_new时创建/dev/snd/controlX节点,snd_card_register遍历注册为/dev/snd/(pcmCXDXp/pcmCXDXc)

声卡注册过程是从machine开始,然后建立各个节点结束,controlX主要是控制节点,而pcmCXDXp/pcmCXDXc这两个节点是数据节点,一般控制节点有且只有一个而数据节点会有多个,并且是p/c成对的,p代表是playback,c代表capture。声卡注册完之后,那么音频需要使用到的软件部分都基本就绪,就可以开始播放声音了

四、数据从HAL到kernel

以我们手机系统播放手机铃声为例,在播放手机铃声的过程体现在tinyalsa的步骤大概如下:

  1. pcm_open
  2. pcm_prepare
  3. pcm_start
  4. pcm_write

pcm_open:打开/dev/snd/pcmCxDxp 节点,然后获取pcm信息,对应节点的file_operations如下,后面简称fops,代码在kernel 下面pcm_native.c:
折叠源码

const struct file_operations snd_pcm_f_ops[2] = {    {        .owner =        THIS_MODULE,        .write =        snd_pcm_write,        .write_iter =       snd_pcm_writev,        .open =         snd_pcm_playback_open,        .release =      snd_pcm_release,        .llseek =       no_llseek,        .poll =         snd_pcm_poll,        .unlocked_ioctl =   snd_pcm_ioctl,        .compat_ioctl =     snd_pcm_ioctl_compat,        .mmap =         snd_pcm_mmap,        .fasync =       snd_pcm_fasync,        .get_unmapped_area =    snd_pcm_get_unmapped_area,    },    {        .owner =        THIS_MODULE,        .read =         snd_pcm_read,        .read_iter =        snd_pcm_readv,        .open =         snd_pcm_capture_open,        .release =      snd_pcm_release,        .llseek =       no_llseek,        .poll =         snd_pcm_poll,        .unlocked_ioctl =   snd_pcm_ioctl,        .compat_ioctl =     snd_pcm_ioctl_compat,        .mmap =         snd_pcm_mmap,        .fasync =       snd_pcm_fasync,        .get_unmapped_area =    snd_pcm_get_unmapped_area,    }};

可以看到每个pcm节点对应了两套fops,一个是播放一个是录制,当节点被打开时触发open函数,整个过程比较长简单表示如下:

snd_pcm_playback_open+----snd_lookup_minor_data // 查找对应从设备号及类型的pcm+----snd_pcm_open // 打开pcm     +----  while (1) {                snd_pcm_open_file                +----snd_pcm_open_substream // 打开pcm的子流                    +----dpcm_fe_dai_open(substream->ops->open)                        +----dpcm_path_get                        +----dpcm_process_paths+----dpcm_add_paths+----for(i = 0; i < list->num_widgets; i++){    +----dpcm_get_be // 获取be    +----dpcm_be_connect // fe 和 be链接    }           }

在pcm节点被打开时,首先会在内存中搜寻之前建立pcm时保存的pcm实例,然后获取该pcm对应的be 和fe,这里涉及到的fe表示前端,be表示后端,这是在DPCM中提出的概念,获取了相应的fe 和be之后将其链接

pcm_prepare:是向kernel中发送了SNDRV_PCM_IOCTL_PREPARE指令对应触发kernel中pcm_compat.c 函数snd_pcm_ooctl_compat:

snd_pcm_ioctl_compat+---snd_pcm_common_ioctl+------snd_pcm_prepare // 三个函数调用直接到snd_pcm_prepparestatic int snd_pcm_prepare(struct snd_pcm_substream *substream,               struct file *file){    int f_flags;     if (file)        f_flags = file->f_flags;    else        f_flags = substream->f_flags;     snd_pcm_stream_lock_irq(substream);    // 这个地方会判断当时substream的状态如果是pause会执行pause动作,如果是suspended会执行stop动作    switch (substream->runtime->status->state) {    case SNDRV_PCM_STATE_PAUSED:        snd_pcm_pause(substream, 0);            case SNDRV_PCM_STATE_SUSPENDED:        snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);        break;    }    snd_pcm_stream_unlock_irq(substream);    // 这个地方执行的是prepare的一系列动作,执行的是struct action_ops snd_pcm_action_prepare的三个函数pre_ation、do_action、post_action,主要看下do_aciton    return snd_pcm_action_nonatomic(&snd_pcm_action_prepare,                    substream, f_flags);} static const struct action_ops snd_pcm_action_prepare = {    .pre_action = snd_pcm_pre_prepare,    .do_action = snd_pcm_do_prepare,    .post_action = snd_pcm_post_prepare}; static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state){    int err;    err = substream->ops->prepare(substream);    if (err < 0)        return err;    return snd_pcm_do_reset(substream, 0);}

pcm_prepare执行到内核层之后可以看出,最后是执行了substream->ops→prepare,那么这个substream 是什么呢,它又在哪里定义的呢?带着这些问题我们重新去查看代码会发现,原来substream是在soc-pcm.c 的snd_new_pcm中定义的,而snd_new_pcm是在soc_probe_link_dais调用:

static int soc_probe_link_dais(struct snd_soc_card *card,        struct snd_soc_pcm_runtime *rtd, int order){......     if (cpu_dai->driver->compress_new) {                ret = cpu_dai->driver->compress_new(rtd, num);        if (ret < 0) {            dev_err(card->dev, "ASoC: can't create compress %s\n",                     dai_link->stream_name);            return ret;        }    } else {         if (!dai_link->params) {                        // 判断dailink的params参数为空时,代表该dailink没建立pcm则新建pcm            ret = soc_new_pcm(rtd, num);            if (ret < 0) {                dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",                       dai_link->stream_name, ret);                return ret;            }            ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);            if (ret < 0)                return ret;            ret = soc_link_dai_pcm_new(rtd->codec_dais,                           rtd->num_codecs, rtd);            if (ret < 0)                return ret;        } else {            INIT_DELAYED_WORK(&rtd->delayed_work,                        codec2codec_close_delayed_work);                         ret = soc_link_dai_widgets(card, dai_link, rtd);            if (ret)                return ret;        }    }     return 0;} int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num){    struct snd_soc_dai *codec_dai;    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;    struct snd_soc_component *component;    struct snd_soc_rtdcom_list *rtdcom;    struct snd_pcm *pcm;    struct snd_pcm_str *stream;    char new_name[64];    int ret = 0, playback = 0, capture = 0;    int i;     if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {        playback = rtd->dai_link->dpcm_playback;        capture = rtd->dai_link->dpcm_capture;    } else {        for (i = 0; i < rtd->num_codecs; i++) {            codec_dai = rtd->codec_dais[i];            if (codec_dai->driver->playback.channels_min)                playback = 1;            if (codec_dai->driver->capture.channels_min)                capture = 1;        }         capture = capture && cpu_dai->driver->capture.channels_min;        playback = playback && cpu_dai->driver->playback.channels_min;    }     if (rtd->dai_link->playback_only) {        playback = 1;        capture = 0;    }     if (rtd->dai_link->capture_only) {        playback = 0;        capture = 1;    }         if (rtd->dai_link->no_pcm) {        snprintf(new_name, sizeof(new_name), "(%s)",            rtd->dai_link->stream_name);        // 创建pcm文件节点pcmCxDxp/c,和下面的snd_pcm_new 相比只有new_name 不同        ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,                playback, capture, &pcm);    } else {        if (rtd->dai_link->dynamic)            snprintf(new_name, sizeof(new_name), "%s (*)",                rtd->dai_link->stream_name);        else            snprintf(new_name, sizeof(new_name), "%s %s-%d",                rtd->dai_link->stream_name,                (rtd->num_codecs > 1) ?                "multicodec" : rtd->codec_dai->name, num);        // 创建pcm文件节点pcmCxDxp/c        ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,            capture, &pcm);    }    if (ret < 0) {        dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",            rtd->dai_link->name);        return ret;    }    dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);         INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);     pcm->nonatomic = rtd->dai_link->nonatomic;    rtd->pcm = pcm;    pcm->private_data = rtd;     if (rtd->dai_link->no_pcm) {        // 保存rtd        if (playback)            pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;        if (capture)            pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;        for_each_rtdcom(rtd, rtdcom) {            component = rtdcom->component;             if (!component->driver->pcm_new)                continue;            // 当前runtime所绑定的组件执行probe            ret = component->driver->pcm_new(rtd);            if (ret < 0) {                dev_err(component->dev,                    "ASoC: pcm constructor failed: %d\n",                    ret);                return ret;            }        }        goto out;    }    // 设置默认的硬件参数,一般不同的硬件会有不同的硬件参数在其驱动初始化的时候会设置,这里暂时给默认值        if (rtd->dai_link->no_host_mode) {        if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {            stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];            stream->substream->hw_no_buffer = 1;            snd_soc_set_runtime_hwparams(stream->substream, &no_host_hardware);        }        if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {            stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];            stream->substream->hw_no_buffer = 1;            snd_soc_set_runtime_hwparams(stream->substream, &no_host_hardware);        }    }     // 设置相应的ops 函数,并且后面会设置给pcm 的substream        if (rtd->dai_link->dynamic) {        rtd->ops.open       = dpcm_fe_dai_open;        rtd->ops.hw_params  = dpcm_fe_dai_hw_params;        rtd->ops.prepare    = dpcm_fe_dai_prepare;             // substream->ops→prepare        rtd->ops.trigger    = dpcm_fe_dai_trigger;        rtd->ops.hw_free    = dpcm_fe_dai_hw_free;        rtd->ops.close      = dpcm_fe_dai_close;        rtd->ops.pointer    = soc_pcm_pointer;        rtd->ops.delay_blk  = soc_pcm_delay_blk;        rtd->ops.ioctl      = soc_pcm_ioctl;        rtd->ops.compat_ioctl   = soc_pcm_compat_ioctl;    } else {        rtd->ops.open       = soc_pcm_open;        rtd->ops.hw_params  = soc_pcm_hw_params;        rtd->ops.prepare    = soc_pcm_prepare;        rtd->ops.trigger    = soc_pcm_trigger;        rtd->ops.hw_free    = soc_pcm_hw_free;        rtd->ops.close      = soc_pcm_close;        rtd->ops.pointer    = soc_pcm_pointer;        rtd->ops.delay_blk  = soc_pcm_delay_blk;        rtd->ops.ioctl      = soc_pcm_ioctl;        rtd->ops.compat_ioctl   = soc_pcm_compat_ioctl;    }     for_each_rtdcom(rtd, rtdcom) {        const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;         if (!ops)            continue;         if (ops->ack)            rtd->ops.ack        = soc_rtdcom_ack;        if (ops->copy_user)            rtd->ops.copy_user  = soc_rtdcom_copy_user;        if (ops->copy_kernel)            rtd->ops.copy_kernel    = soc_rtdcom_copy_kernel;        if (ops->fill_silence)            rtd->ops.fill_silence   = soc_rtdcom_fill_silence;        if (ops->page)            rtd->ops.page       = soc_rtdcom_page;        if (ops->mmap)            rtd->ops.mmap       = soc_rtdcom_mmap;    }     // 这里就把上面设置的ops同样赋值给pcm 的substream->ops    if (playback)        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);     if (capture)        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);     for_each_rtdcom(rtd, rtdcom) {        component = rtdcom->component;         if (!component->driver->pcm_new)            continue;         ret = component->driver->pcm_new(rtd);        if (ret < 0) {            dev_err(component->dev,                "ASoC: pcm constructor failed: %d\n",                ret);            return ret;        }    }     pcm->private_free = soc_pcm_private_free;out:    dev_dbg(rtd->card->dev, "%s <-> %s mapping ok\n",         (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,         cpu_dai->name);    return ret;}

看完了snd_new_pcm之后就找到了原来substream→ops→prepare对应的是函数dpcm_fe_dai_prepare,这里涉及到了一个新的概念DPCM,顾名思义Dynamic PCM动态的PCM,动态 PCM 允许 ALSA PCM 设备在 PCM 流运行时以数字方式将其 PCM 音频路由到各种数字端点。例如PCM0 可以将数字音频路由到 I2S DAI0、I2S DAI1 或 PDM DAI2。DPCM 运行时路由由 ALSA mixer配置确定,与模拟信号在 ASoC codec driver的路由方式相同。 DSP内部有DAPM的mixer配置图,由mixer来配置pcm路径。在DPCM中分为前端和后端,前端连接着音频数据后端连接着播放设备。substream→ops→prepare调用到了dpcm_fe_dai_prepare,这个函数最终是分别调用了该runtime中的dailink、component、codecdai、cpu_dai的prepare函数。

回到刚开始我们播放的是手机铃声,手机铃声是数据低延迟播放模式,前往混音器的配置文件查看,位置在vendor下面,默认的mixer_paths.xml ,一般会使用其他的配置,如果没有其他配置才会使用当前默认,具体解析过程在HAL层platform.c中,我们看mixer_paths_lagoonqrd.xml,这是当前正在使用的,

   <path name="low-latency-playback">              <ctl name="PRI_MI2S_RX Audio Mixer MultiMedia5" value="1" />   path>

path表示的是一条usecase,代表的是一条音频播放路径,从上面可以看出控制流的前端是MultiMedia5,连接的是pcm数据,后端是PRI_MI2S_RX,连接的是codec、外放,那么我们根据这两个名字分别找到对应的dailink
折叠源码

// fe dailink{    .name = MSM_DAILINK_NAME(LowLatency),    .stream_name = "MultiMedia5",    .cpu_dai_name = "MultiMedia5",    .platform_name = "msm-pcm-dsp.1",    .dynamic = 1,    .async_ops = ASYNC_DPCM_SND_SOC_PREPARE,    .dpcm_playback = 1,    .dpcm_capture = 1,    .codec_dai_name = "snd-soc-dummy-dai",    .codec_name = "snd-soc-dummy",    .trigger = {SND_SOC_DPCM_TRIGGER_POST,            SND_SOC_DPCM_TRIGGER_POST},    .ignore_suspend = 1,        .ignore_pmdown_time = 1,    .id = MSM_FRONTEND_DAI_MULTIMEDIA5,    .ops = &msm_fe_qos_ops,}, // be dailink{    .name = LPASS_BE_PRI_MI2S_RX,    .stream_name = "Primary MI2S Playback",    .cpu_dai_name = "msm-dai-q6-mi2s.0",    .platform_name = "msm-pcm-routing",    .num_codecs = ARRAY_SIZE(awinic_codecs),    .codecs = awinic_codecs,    .no_pcm = 1,    .dpcm_playback = 1,    .id = MSM_BACKEND_DAI_PRI_MI2S_RX,    .be_hw_params_fixup = msm_be_hw_params_fixup,    .ops = &msm_mi2s_be_ops,    .ignore_suspend = 1,    .ignore_pmdown_time = 1,},

找到了对应的dailink,然后再看下substream→ops→prepare分别调用了哪里,先看fe dailink,通过对应的名字分别能找到相应的dai,发现:cpu_dai,codec_dai没有prepare,dailink、platform 有prepare。platform prepare代码位于msm-pcm-q6-v2.c的msm_pcm_ops:

msm_pcm_prepare+----msm_pcm_playback_preparestatic int msm_pcm_playback_prepare(struct snd_pcm_substream *substream){    struct snd_pcm_runtime *runtime = substream->runtime;    struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;    struct snd_soc_component *component =            snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME);    struct msm_audio *prtd = runtime->private_data;    struct msm_plat_data *pdata;    struct snd_pcm_hw_params *params;    int ret;    uint32_t fmt_type = FORMAT_LINEAR_PCM;    uint16_t bits_per_sample;    uint16_t sample_word_size;     if (!component) {        pr_err("%s: component is NULL\n", __func__);        return -EINVAL;    }     pdata = (struct msm_plat_data *)        dev_get_drvdata(component->dev);    if (!pdata) {        pr_err("%s: platform data not populated\n", __func__);        return -EINVAL;    }    if (!prtd || !prtd->audio_client) {        pr_err("%s: private data null or audio client freed\n",            __func__);        return -EINVAL;    }    params = &soc_prtd->dpcm[substream->stream].hw_params;     pr_debug("%s\n", __func__);    prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream);    prtd->pcm_count = snd_pcm_lib_period_bytes(substream);    prtd->pcm_irq_pos = 0;        prtd->samp_rate = runtime->rate;    prtd->channel_mode = runtime->channels;    if (prtd->enabled)        return 0;     prtd->audio_client->perf_mode = pdata->perf_mode;    pr_debug("%s: perf: %x\n", __func__, pdata->perf_mode);     switch (params_format(params)) {    case SNDRV_PCM_FORMAT_S32_LE:        bits_per_sample = 32;        sample_word_size = 32;        break;    case SNDRV_PCM_FORMAT_S24_LE:        bits_per_sample = 24;        sample_word_size = 32;        break;    case SNDRV_PCM_FORMAT_S24_3LE:        bits_per_sample = 24;        sample_word_size = 24;        break;    case SNDRV_PCM_FORMAT_S16_LE:    default:        bits_per_sample = 16;        sample_word_size = 16;        break;    }    if (prtd->compress_enable) {        fmt_type = FORMAT_GEN_COMPR;        pr_debug("%s: Compressed enabled!\n", __func__);        ret = q6asm_open_write_compressed(prtd->audio_client, fmt_type,                COMPRESSED_PASSTHROUGH_GEN);        if (ret < 0) {            pr_err("%s: q6asm_open_write_compressed failed (%d)\n",            __func__, ret);            q6asm_audio_client_free(prtd->audio_client);            prtd->audio_client = NULL;            return -ENOMEM;        }    } else {        // 判断 adsp asm api的版本是否大于2        if ((q6core_get_avcs_api_version_per_service(                APRV2_IDS_SERVICE_ID_ADSP_ASM_V) >=                ADSP_ASM_API_VERSION_V2))            // 打开音频播放的session会与adsp通信            ret = q6asm_open_write_v5(prtd->audio_client,                fmt_type, bits_per_sample);        else            ret = q6asm_open_write_v4(prtd->audio_client,                fmt_type, bits_per_sample);         if (ret < 0) {            pr_err("%s: q6asm_open_write failed (%d)\n",            __func__, ret);            q6asm_audio_client_free(prtd->audio_client);            prtd->audio_client = NULL;            return -ENOMEM;        }        // 发送校准数据        ret = q6asm_send_cal(prtd->audio_client);        if (ret < 0)            pr_debug("%s : Send cal failed : %d", __func__, ret);    }    pr_debug("%s: session ID %d\n", __func__,            prtd->audio_client->session);    prtd->session_id = prtd->audio_client->session;     if (prtd->compress_enable) {        ret = msm_pcm_routing_reg_phy_compr_stream(                soc_prtd->dai_link->id,                prtd->audio_client->perf_mode,                prtd->session_id,                SNDRV_PCM_STREAM_PLAYBACK,                COMPRESSED_PASSTHROUGH_GEN);    } else {        // 打开注册adm        ret = msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->id,            prtd->audio_client->perf_mode,            prtd->session_id, substream->stream);    }    if (ret) {        pr_err("%s: stream reg failed ret:%d\n", __func__, ret);        return ret;    }    if (prtd->compress_enable) {        ret = q6asm_media_format_block_gen_compr(            prtd->audio_client, runtime->rate,            runtime->channels, !prtd->set_channel_map,            prtd->channel_map, bits_per_sample);    } else {                 if ((q6core_get_avcs_api_version_per_service(                APRV2_IDS_SERVICE_ID_ADSP_ASM_V) >=                ADSP_ASM_API_VERSION_V2)) {            // 设置格式参数发送至adsp            ret = q6asm_media_format_block_multi_ch_pcm_v5(                prtd->audio_client, runtime->rate,                runtime->channels, !prtd->set_channel_map,                prtd->channel_map, bits_per_sample,                sample_word_size, ASM_LITTLE_ENDIAN,                DEFAULT_QF);        } else {            ret = q6asm_media_format_block_multi_ch_pcm_v4(                prtd->audio_client, runtime->rate,                runtime->channels, !prtd->set_channel_map,                prtd->channel_map, bits_per_sample,                sample_word_size, ASM_LITTLE_ENDIAN,                DEFAULT_QF);        }    }    if (ret < 0)        pr_info("%s: CMD Format block failed\n", __func__);     atomic_set(&prtd->out_count, runtime->periods);     prtd->enabled = 1;    prtd->cmd_pending = 0;    prtd->cmd_interrupt = 0;     return 0;}

其次再看be 端的prepare,cpu_dai 在msm-dai-q6-v2.c,其prepare如下
折叠源码

static int msm_dai_q6_mi2s_prepare(struct snd_pcm_substream *substream,        struct snd_soc_dai *dai){    struct msm_dai_q6_mi2s_dai_data *mi2s_dai_data =        dev_get_drvdata(dai->dev);    struct msm_dai_q6_dai_data *dai_data =        (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?         &mi2s_dai_data->rx_dai.mi2s_dai_data :         &mi2s_dai_data->tx_dai.mi2s_dai_data);    u16 port_id = 0;    int rc = 0;     if (msm_mi2s_get_port_id(dai->id, substream->stream,                 &port_id) != 0) {        dev_err(dai->dev, "%s: Invalid Port ID 0x%x\n",                __func__, port_id);        return -EINVAL;    }     dev_dbg(dai->dev, "%s: dai id %d, afe port id = 0x%x\n"        "dai_data->channels = %u sample_rate = %u\n", __func__,        dai->id, port_id, dai_data->channels, dai_data->rate);     if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {                // 使用指定的端口配置配置AFE会        rc = afe_port_start(port_id, &dai_data->port_config,                    dai_data->rate);        if (rc < 0)            dev_err(dai->dev, "fail to open AFE port 0x%x\n",                dai->id);        else            set_bit(STATUS_PORT_STARTED,                dai_data->status_mask);    }    if (!test_bit(STATUS_PORT_STARTED, dai_data->hwfree_status)) {        set_bit(STATUS_PORT_STARTED, dai_data->hwfree_status);        dev_dbg(dai->dev, "%s: set hwfree_status to started\n",                __func__);    }    return rc;}

be dailink绑定的platform 在msm-pcm-routing-v2.c,其prepare比较长,也是类似打开一个adm,这里不贴代码,至此prepare的事情基本告一段落,然后是start。

pcm_start :alsa lib 会发送一个SNDRV_PCM_IOCTL_START的指令到kernel,对应于pcm_native.c中snd_pcm_ioctl
折叠源码

snd_pcm_ioctl_compat+----snd_pcm_common_ioctl    +----snd_pcm_start_lock_irq        +----snd_pcm_action_lock_irq            +----snd_pcm_action                +----action_ops->pre_action                -----action_ops->do_action                -----action_ops->post_action // 最终分别调用了snd_pcm_action_start的pre_action,do_action,post_action,同prepare类似后面会调各个dai的startstatic const struct action_ops snd_pcm_action_start = {    .pre_action = snd_pcm_pre_start,    .do_action = snd_pcm_do_start,    .undo_action = snd_pcm_undo_start,    .post_action = snd_pcm_post_start}; //fe platform trigger,而且只有fe 的platform有triggerstatic int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd){    int ret = 0;    struct snd_pcm_runtime *runtime = substream->runtime;    struct msm_audio *prtd = runtime->private_data;     switch (cmd) {    case SNDRV_PCM_TRIGGER_START:    case SNDRV_PCM_TRIGGER_RESUME:    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:        pr_debug("%s: Trigger start\n", __func__);        // 命令将ASM设置为不等待ack的运行状态        ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);        break;    case SNDRV_PCM_TRIGGER_STOP:        pr_debug("SNDRV_PCM_TRIGGER_STOP\n");        atomic_set(&prtd->start, 0);        if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {            prtd->enabled = STOPPED;            ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);            break;        }                WARN_ON_ONCE(test_bit(CMD_EOS, &prtd->cmd_pending));        set_bit(CMD_EOS, &prtd->cmd_pending);        ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);        if (ret)            clear_bit(CMD_EOS, &prtd->cmd_pending);        break;    case SNDRV_PCM_TRIGGER_SUSPEND:    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:        pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n");        ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);        atomic_set(&prtd->start, 0);        break;    default:        ret = -EINVAL;        break;    }     return ret;}

pcm_write:前面都是做准备,这里才是真正的把数据送下来了,alsa lib pcm_write会将SNDRV_PCM_IOCTL_WRITEI_FRAMES指令发送到kernel,触发pcm_native.c 中snd_pcm_ioctl,如下

snd_pcm_ioctl+----snd_pcm_common_ioctl   +----snd_pcm_xferi_frames_ioctl       +----snd_pcm_lib_write           +----__snd_pcm_lib_xfersnd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,                    void *data, bool interleaved,                    snd_pcm_uframes_t size, bool in_kernel){   struct snd_pcm_runtime *runtime = substream->runtime;   snd_pcm_uframes_t xfer = 0;   snd_pcm_uframes_t offset = 0;   snd_pcm_uframes_t avail;   pcm_copy_f writer;   pcm_transfer_f transfer;   bool nonblock;   bool is_playback;   int err;   err = pcm_sanity_check(substream);   if (err < 0)       return err;   is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;   if (interleaved) {// 传过来参数为1       if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&           runtime->channels > 1)           return -EINVAL;       writer = interleaved_copy;// 这里writer函数后面会调用   } else {       if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)           return -EINVAL;       writer = noninterleaved_copy;   }   if (!data) {       if (is_playback)           transfer = fill_silence;       else           return -EINVAL;   } else if (in_kernel) {       if (substream->ops->copy_kernel)           transfer = substream->ops->copy_kernel;       else           transfer = is_playback ?               default_write_copy_kernel : default_read_copy_kernel;   } else {//第三种情况符合       if (substream->ops->copy_user)           transfer = (pcm_transfer_f)substream->ops->copy_user;// 这里将soc_rtdcom_copy_user赋值给了transfer,后续调用       else           transfer = is_playback ?               default_write_copy : default_read_copy;   }   if (size == 0)       return 0;   nonblock = !!(substream->f_flags & O_NONBLOCK);   snd_pcm_stream_lock_irq(substream);   err = pcm_accessible_state(runtime);   if (err < 0)       goto _end_unlock;   if (!is_playback &&       runtime->status->state == SNDRV_PCM_STATE_PREPARED &&       size >= runtime->start_threshold) {       err = snd_pcm_start(substream);       if (err < 0)           goto _end_unlock;   }   runtime->twake = runtime->control->avail_min ? : 1;   if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)       snd_pcm_update_hw_ptr(substream);   avail = snd_pcm_avail(substream); // 获取播放时的可用空间   while (size > 0) {       snd_pcm_uframes_t frames, appl_ptr, appl_ofs;       snd_pcm_uframes_t cont;       if (!avail) {// 如果可写空间不够了就会触发停止           if (!is_playback &&               runtime->status->state == SNDRV_PCM_STATE_DRAINING) {               snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);               goto _end_unlock;           }           if (nonblock) {               err = -EAGAIN;               goto _end_unlock;           }           runtime->twake = min_t(snd_pcm_uframes_t, size,                   runtime->control->avail_min ? : 1);           err = wait_for_avail(substream, &avail);           if (err < 0)               goto _end_unlock;           if (!avail)               continue;        }       frames = size > avail ? avail : size;       appl_ptr = READ_ONCE(runtime->control->appl_ptr);       appl_ofs = appl_ptr % runtime->buffer_size;       cont = runtime->buffer_size - appl_ofs;       if (frames > cont)           frames = cont;       if (snd_BUG_ON(!frames)) {           runtime->twake = 0;           snd_pcm_stream_unlock_irq(substream);           return -EINVAL;       }       snd_pcm_stream_unlock_irq(substream);       // 这里最后是调用了上面赋值的copy_user,最后会调用到msm-pcm-q6-v2.c中的msm_pcm_playback_copy,将数据拷贝到dsp,直到没有数据可拷贝       err = writer(substream, appl_ofs, data, offset, frames,                transfer);       snd_pcm_stream_lock_irq(substream);       if (err < 0)           goto _end_unlock;       err = pcm_accessible_state(runtime);       if (err < 0)           goto _end_unlock;       appl_ptr += frames;       if (appl_ptr >= runtime->boundary)           appl_ptr -= runtime->boundary;       err = pcm_lib_apply_appl_ptr(substream, appl_ptr);       if (err < 0)           goto _end_unlock;       offset += frames;       size -= frames;       xfer += frames;       avail -= frames;       if (is_playback &&           runtime->status->state == SNDRV_PCM_STATE_PREPARED &&           snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {           err = snd_pcm_start(substream);// 再次触发start           if (err < 0)               goto _end_unlock;       }   }_end_unlock:   runtime->twake = 0;   if (xfer > 0 && err >= 0)       snd_pcm_update_state(substream, runtime);   snd_pcm_stream_unlock_irq(substream);   return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;}

上面函数之后会进入循环拷贝使用的是msm-pcm-q6-v2.c中的msm_pcm_playback_copy函数不停的向dsp拷贝数据

static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a,    unsigned long hwoff, void __user *buf, unsigned long fbytes){    int ret = 0;    int xfer = 0;    char *bufptr = NULL;    void *data = NULL;    uint32_t idx = 0;    uint32_t size = 0;    uint32_t retries = 0;     struct snd_pcm_runtime *runtime = substream->runtime;    struct msm_audio *prtd = runtime->private_data;     pr_debug("%s: prtd->out_count = %d\n",                __func__, atomic_read(&prtd->out_count));     while ((fbytes > 0) && (retries < MAX_PB_COPY_RETRIES)) {        if (prtd->reset_event) {            pr_err("%s: In SSR return ENETRESET before wait\n",                __func__);            return -ENETRESET;        }         ret = wait_event_timeout(the_locks.write_wait,                (atomic_read(&prtd->out_count)),                msecs_to_jiffies(TIMEOUT_MS));        if (!ret) {            pr_err("%s: wait_event_timeout failed\n", __func__);            ret = -ETIMEDOUT;            goto fail;        }        ret = 0;         if (prtd->reset_event) {            pr_err("%s: In SSR return ENETRESET after wait\n",                __func__);            return -ENETRESET;        }         if (!atomic_read(&prtd->out_count)) {            pr_err("%s: pcm stopped out_count 0\n", __func__);            return 0;        }             // 检索下一个可用的 cpu buf        data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size,            &idx);        if (data == NULL) {            retries++;            continue;        } else {            retries = 0;        }         if (fbytes > size)            xfer = size;        else            xfer = fbytes;         bufptr = data;        if (bufptr) {            pr_debug("%s:fbytes =%lu: xfer=%d size=%d\n",                 __func__, fbytes, xfer, size);            if (copy_from_user(bufptr, buf, xfer)) {                ret = -EFAULT;                pr_err("%s: copy_from_user failed\n",                    __func__);                q6asm_cpu_buf_release(IN, prtd->audio_client);                goto fail;            }            buf += xfer;            fbytes -= xfer;            pr_debug("%s:fbytes = %lu: xfer=%d\n", __func__,                 fbytes, xfer);            if (atomic_read(&prtd->start)) {                pr_debug("%s:writing %d bytes of buffer to dsp\n",                        __func__, xfer);                // 调用asm 将数据写入到dsp                ret = q6asm_write(prtd->audio_client, xfer,0, 0, NO_TIMESTAMP);                if (ret < 0) {                    ret = -EFAULT;                    q6asm_cpu_buf_release(IN,                        prtd->audio_client);                    goto fail;                }            } else                atomic_inc(&prtd->out_needed);            atomic_dec(&prtd->out_count);        }    }fail:    if (retries >= MAX_PB_COPY_RETRIES)        ret = -ENOMEM;     return  ret;}

上面函数就是将数据写入到dsp进行下一步处理的,期间还涉及了几个新的概念:asm,adm,afe。还有将数据传送至dsp的apr

ASM(Audio Stream Manager)

   用于与DSP ASM 模块通信的接口   提供将 PCM 数据路由至 DSP 的机制,支持按数据流进行后期处理/预处理

ADM(Audio Device Manager)

    允许在 DSP 中使用 ADM 服务     配置 COPP 和路由矩阵    与音频校准数据库 (ACDB) 进行通信,使用正确的校准数据配置 COPP    将 ASM 会话 ID 路由至 ADM 会话

AFE(Audio Front-End)

    允许在 DSP 中使用 AFE 服务     激活/禁用音频硬件端口     子系统管理器 – 发生 MDSP 复位事件时,通知音频和语音驱动程序关闭待处理会话、执行清理操作并等待一个指示 MDSP 已启动的事件

APR(Asynchronous Packet Router)

    为处理器间通信提供异步框架     用于与 Hexagon 和调制解调器处理器进行通信     Image loader PIL – 载入 MDSP 图像

整个内核过程,在音频流经过不同的usecase后输出给LPASS,在LPASS的DSP模块进行重采样、音效处理、混音的操作后经过SLIMbus/I2S给codec进行解码转换为模拟信号给喇叭进行信号放大。到这里你 数据就已经进入到了dsp,音频内核的过程基本就结束了,更多的过程还在持续探索中。

来源地址:https://blog.csdn.net/wh2526422/article/details/126247382

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯