定制app开发高通音频架构(三)

一、Kernel层

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

二、 ALSA

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

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

  • 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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!\", __func__);    if (!pdev->dev.of_node) {        dev_err(&pdev->dev, "%s: No platform supplied from device tree\", __func__);        return -EINVAL;    }    dev_dbg(&pdev->dev, "msm_asoc_machine_probe\");    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\", __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\",            __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\",            __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)\",            __func__, ret);        goto err;    }    dev_info(&pdev->dev, "%s: Sound card %s registered\",         __func__, card->name);    ...    return 0;err:    devm_kfree(&pdev->dev, pdata);    return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

在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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

接着直接调用了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); // 遍历注册设备          +   + }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在声卡注册过程中会根据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,    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

可以看到每个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链接                                }           }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在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);        /* 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);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

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 %s\",                     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 :%d\",                       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 %s\",            rtd->dai_link->name);        return ret;    }    dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\",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: %d\",                    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: %d\",                ret);            return ret;        }    }     pcm->private_free = soc_pcm_private_free;out:    dev_dbg(rtd->card->dev, "%s <-> %s mapping ok\",         (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,         cpu_dai->name);    return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232

看完了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、、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>
  • 1
  • 2
  • 3

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,},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

找到了对应的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\", __func__);        return -EINVAL;    }     pdata = (struct msm_plat_data *)        dev_get_drvdata(component->dev);    if (!pdata) {        pr_err("%s: platform data not populated\", __func__);        return -EINVAL;    }    if (!prtd || !prtd->audio_client) {        pr_err("%s: private data null or audio client freed\",            __func__);        return -EINVAL;    }    params = &soc_prtd->dpcm[substream->stream].hw_params;     pr_debug("%s\", __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: %x\", __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!\", __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)\",            __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)\",            __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\", __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\", __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\", __func__);     atomic_set(&prtd->out_count, runtime->periods);     prtd->enabled = 1;    prtd->cmd_pending = 0;    prtd->cmd_interrupt = 0;     return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160

其次再看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\",                __func__, port_id);        return -EINVAL;    }     dev_dbg(dai->dev, "%s: dai id %d, afe port id = 0x%x\"        "dai_data->channels = %u sample_rate = %u\", __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%x\",                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\",                __func__);    }    return rc;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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\", __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\");        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_PAUSE\");        ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);        atomic_set(&prtd->start, 0);        break;    default:        ret = -EINVAL;        break;    }     return ret;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

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; /* 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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144

上面函数之后会进入循环拷贝使用的是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\",                __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\",                __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\", __func__);            ret = -ETIMEDOUT;            goto fail;        }        ret = 0;         if (prtd->reset_event) {            pr_err("%s: In SSR return ENETRESET after wait\",                __func__);            return -ENETRESET;        }         if (!atomic_read(&prtd->out_count)) {            pr_err("%s: pcm stopped out_count 0\", __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\",                 __func__, fbytes, xfer, size);            if (copy_from_user(bufptr, buf, xfer)) {                ret = -EFAULT;                pr_err("%s: copy_from_user failed\",                    __func__);                q6asm_cpu_buf_release(IN, prtd->audio_client);                goto fail;            }            buf += xfer;            fbytes -= xfer;            pr_debug("%s:fbytes = %lu: xfer=%d\", __func__,                 fbytes, xfer);            if (atomic_read(&prtd->start)) {                pr_debug("%s:writing %d bytes of buffer to dsp\",                        __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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

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

ASM(Audio Stream Manager)

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

ADM(Audio Device Manager)

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

AFE(Audio Front-End)

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

APR(Asynchronous Packet Router)

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

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

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发