高通音频架构(三)

一、Kernel层

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

在这里插入图片描述

二、 ALSA

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

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

  • tinyAlsa
  • ALSA Core
  • ASoC

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输出驱动耳机或者是喇叭的音信信号。

  • Machine

    • 用于描述设备组件信息和特定的控制如耳机/外放等。

    • Machine是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。

    • Machine 这一部分将平台驱动和 Codec 驱动绑定在一起,描述了板级的硬件特征。
      主要负责 Platform 和 Codec 之间的耦合以及部分和设备或板子特定的代码。
      Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);

    • 单独的 Platform 和 Codec 驱动是不能工作的,它必须由 Machine 驱动把它们结合在一起才能完成整个设备的音频处理工作。

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

  • Platform

    • 用于实现平台相关的DMA驱动和音频接口等。

    • Platform 一般是指某一个SoC平台,比如 pxaxxx,s3cxxxx,omapxxx 等等,与音频相关的通常包含该 SoC 中的时钟、DMA、I2S、PCM等等,只要指定了 SoC,那么我们可以认为它会有一个对应的 Platform,它只与 SoC 相关,与 Machine 无关,这样我们就可以把 Platform 抽象出来,使得同一款 SoC 不用做任何的改动,就可以用在不同的 Machine 中。实际上,把 Platform 认为是某个 SoC 更好理解。

    • 这一部分只关心CPU本身,不关心Codec。
      主要处理两个问题:DMA引擎 和 SoC集成的PCM、I2S或AC ‘97数字接口控制。
      主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。

    • 在具体实现上,ASoC 有把 Platform 驱动分为两个部分:snd_soc_platform_driver 和 snd_soc_dai_driver。

    • 其中,platform_driver 负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

  • Codec

    • 用于实现平台无关的功能,如寄存器读写接口,音频接口,各widgets的控制接口和DAPM的实现等。

    • 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和 多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个 Codec 可以被不同的Machine使用。嵌入式 Codec 通常通过I2C对内部的寄存器进行控制。

    • 这一部分只关心 Codec 本身,与 CPU 平台相关的特性不由此部分操作。

    • 在移动设备中,Codec 的作用可以归结为4种,分别是:
      1. 对 PCM 等信号进行 D/A 转换,把数字的音频信号转换为模拟信号。
      2. 对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号。
      3. 对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的。
      4. 对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等。

    • ASoC 对 Codec 的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。
      ASoC 对 Codec 驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个 Codec 的代码不经修改即可用在不同的平台上。

  • DAPM

    • DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,

    • DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。
      DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。
      用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。

  • DPCM :Dynamic PCM

在这里插入图片描述

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 treen", __func__);
        return -EINVAL;
    }
    dev_dbg(&pdev->dev, "msm_asoc_machine_proben");
    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 uninitializedn", __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:%dn",
            __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:%dn",
            __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 registeredn",
         __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_preppare
static 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);
        /* fallthru */
    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) {
        /*create compress_device"*/
        ret = cpu_dai->driver->compress_new(rtd, num);
        if (ret < 0) {
            dev_err(card->dev, "ASoC: can't create compress %sn",
                     dai_link->stream_name);
            return ret;
        }
    } else {
 
        if (!dai_link->params) {
            /* create the pcm */
            // 判断dailink的params参数为空时,代表该dailink没建立pcm则新建pcm
            ret = soc_new_pcm(rtd, num);
            if (ret < 0) {
                dev_err(card->dev, "ASoC: can't create pcm %s :%dn",
                       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);
 
            /* link the DAI widgets */
            ret = soc_link_dai_widgets(card, dai_link, rtd);
            if (ret)
                return ret;
        }
    }
 
    return 0;
}
 
/* create a new pcm */
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;
    }
 
    /* create the PCM */
    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 %sn",
            rtd->dai_link->name);
        return ret;
    }
    dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %sn",num, new_name);
 
    /* DAPM dai link stream work */
    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: %dn",
                    ret);
                return ret;
            }
        }
        goto out;
    }
    // 设置默认的硬件参数,一般不同的硬件会有不同的硬件参数在其驱动初始化的时候会设置,这里暂时给默认值
    /* setup any hostless PCMs - i.e. no host IO is performed */
    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
    /* ASoC PCM operations */
    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: %dn",
                ret);
            return ret;
        }
    }
 
    pcm->private_free = soc_pcm_private_free;
out:
    dev_dbg(rtd->card->dev, "%s <-> %s mapping okn",
         (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
{/* hw:x,9 */
    .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,
    /* this dainlink has playback support */
    .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_prepare
static 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 NULLn", __func__);
        return -EINVAL;
    }
 
    pdata = (struct msm_plat_data *)
        dev_get_drvdata(component->dev);
    if (!pdata) {
        pr_err("%s: platform data not populatedn", __func__);
        return -EINVAL;
    }
    if (!prtd || !prtd->audio_client) {
        pr_err("%s: private data null or audio client freedn",
            __func__);
        return -EINVAL;
    }
    params = &soc_prtd->dpcm[substream->stream].hw_params;
 
    pr_debug("%sn", __func__);
    prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream);
    prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
    prtd->pcm_irq_pos = 0;
    /* rate and channels are sent to audio driver */
    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: %xn", __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 %dn", __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:%dn", __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 failedn", __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%xn",
                __func__, port_id);
        return -EINVAL;
    }
 
    dev_dbg(dai->dev, "%s: dai id %d, afe port id = 0x%xn"
        "dai_data->channels = %u sample_rate = %un", __func__,
        dai->id, port_id, dai_data->channels, dai_data->rate);
 
    if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) {
        /* PORT START should be set if prepare called
         * in active state.
         */
        // 使用指定的端口配置配置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%xn",
                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 startedn",
                __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的start
static 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有trigger
static 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 startn", __func__);
        // 命令将ASM设置为不等待ack的运行状态
        ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
        break;
    case SNDRV_PCM_TRIGGER_STOP:
        pr_debug("SNDRV_PCM_TRIGGER_STOPn");
        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;
        }
        /* pending CMD_EOS isn't expected */
        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_PAUSEn");
        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_xfer
snd_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; /* draining */
       }
       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 = %dn",
                __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 waitn",
                __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 failedn", __func__);
            ret = -ETIMEDOUT;
            goto fail;
        }
        ret = 0;
 
        if (prtd->reset_event) {
            pr_err("%s: In SSR return ENETRESET after waitn",
                __func__);
            return -ENETRESET;
        }
 
        if (!atomic_read(&prtd->out_count)) {
            pr_err("%s: pcm stopped out_count 0n", __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=%dn",
                 __func__, fbytes, xfer, size);
            if (copy_from_user(bufptr, buf, xfer)) {
                ret = -EFAULT;
                pr_err("%s: copy_from_user failedn",
                    __func__);
                q6asm_cpu_buf_release(IN, prtd->audio_client);
                goto fail;
            }
            buf += xfer;
            fbytes -= xfer;
            pr_debug("%s:fbytes = %lu: xfer=%dn", __func__,
                 fbytes, xfer);
            if (atomic_read(&prtd->start)) {
                pr_debug("%s:writing %d bytes of buffer to dspn",
                        __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,音频内核的过程基本就结束了,更多的过程还在持续探索中。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>