一、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 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的步骤大概如下:
- pcm_open
- pcm_prepare
- pcm_start
- 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); /* 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 %s\n", 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\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); /* 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\n", rtd->dai_link->name); return ret; } dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",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\n", 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\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{/* 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_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; /* 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\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)) { /* 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\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; } /* 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\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; /* 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 = %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,音频内核的过程基本就结束了,更多的过程还在持续探索中。