音频由于其特殊的工作,使得它的结构特别的复杂,而且在自己的结构基础上还引入了ALSA架构,不过在android系统上所引入的并非完整的ALSA架构而是精简版的tinyalsa,但是就算精简版也是内容相当丰厚。除此,音频还拥有自己的单独的处理器ADSP以及独立的电源管理系统DAPM(便携式动态音频电源管理),使得音频在任何时候都是以最低功耗运行,降低了便携设备的功耗。在某些播放场景甚至不需要CPU的介入,比如接打电话的通过音频,如果手机处于休眠可以不需要唤醒CPU直接传递语音数据。要想知道整个过程中音频数据的流转需要一步步去了解,音频架构中所涉及到的各个部分,缺一环则不可,先看看ALSA架构。
Advanced Linux Sound Architecture 高级Linux音频架构,对于android系统来说其实用的只是一个精简版的ALSA架构,有一部分ALSA的接口是放在用户空间,供上层调用来接通kernel
根据音频数据的流向再把音频内核分为以下三个层次:
ALSA lib的源码地址在:external/tinyalsa目录下,其中包含:tinyplay/tinycap/tinymix,这些是供用户空间之间调用的alsa接口,用来播放、录音及控制。并且它们的代码非常的简单,其主要功能是解耦,方便调试,这里不做过多赘述。
ASLA核心他的主要代码是在kernel/msm-x.xx/sound/core,alsa 核心层,向上提供逻辑设备(PCM / CTL / MIDI / TIMER /…)系统调用,向下驱动硬件设备( Machine / I2S / DMA / CODEC )
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。声卡注册完之后,那么音频需要使用到的软件部分都基本就绪,就可以开始播放声音了
以我们手机系统播放手机铃声为例,在播放手机铃声的过程体现在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_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 %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_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 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的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 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_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 = %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,音频内核的过程基本就结束了,更多的过程还在持续探索中。