动态 PCM¶
描述¶
动态 PCM 允许 ALSA PCM 设备在 PCM 流运行时将其 PCM 音频数字路由到各种数字端点。例如,PCM0 可以将数字音频路由到 I2S DAI0、I2S DAI1 或 PDM DAI2。这对于公开多个 ALSA PCM 并能路由到多个 DAI 的片上系统 (SoC) DSP 驱动程序非常有用。
DPCM 运行时路由由 ALSA 混音器设置决定,其方式与 ASoC 编解码器驱动程序中模拟信号的路由方式相同。DPCM 使用表示 DSP 内部音频路径的 DAPM 图,并使用混音器设置来确定每个 ALSA PCM 使用的路径。
DPCM 无需任何修改即可重用所有现有组件的编解码器、平台和 DAI 驱动程序。
基于 SoC DSP 的电话音频系统¶
考虑以下电话音频子系统。本文档中的所有示例都将以此为例 :-
| Front End PCMs | SoC DSP | Back End DAIs | Audio devices |
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
此图显示了一个简单的智能手机音频子系统。它支持蓝牙、FM 数字广播、扬声器、耳机插孔、数字麦克风和蜂窝调制解调器。此声卡公开 4 个 DSP 前端 (FE) ALSA PCM 设备并支持 6 个后端 (BE) DAI。每个 FE PCM 都可以将音频数据数字路由到任何 BE DAI。FE PCM 设备也可以将音频路由到多个 BE DAI。
示例 - DPCM 将播放从 DAI0 切换到 DAI1¶
音频正在耳机播放。过了一会儿,用户取下耳机,音频继续在扬声器上播放。
PCM0 到耳机的播放流程如下所示 :-
*************
PCM0 <============> * * <====DAI0=====> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
用户从插孔中移除耳机,因此现在必须使用扬声器 :-
*************
PCM0 <============> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <====DAI1=====> Codec Speakers
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
音频驱动程序处理过程如下 :-
机器驱动程序接收到插孔拔出事件。
机器驱动程序或音频 HAL 禁用耳机路径。
由于耳机路径已禁用,DPCM 对 DAI0 运行 PCM trigger(stop)、hw_free()、shutdown() 操作。
机器驱动程序或音频 HAL 启用扬声器路径。
由于路径已启用,DPCM 对 DAI1 扬声器运行 PCM ops 的 startup()、hw_params()、prepare() 和 trigger(start) 操作。
在此示例中,机器驱动程序或用户空间音频 HAL 可以更改路由,然后 DPCM 将负责管理 DAI PCM 操作,以启用或禁用链路。在此转换过程中音频播放不会停止。
DPCM 机器驱动程序¶
启用 DPCM 的 ASoC 机器驱动程序与普通机器驱动程序类似,但我们还需要 :-
定义 FE 和 BE DAI 链路。
定义任何 FE/BE PCM 操作。
定义部件图连接。
FE 和 BE DAI 链路¶
| Front End PCMs | SoC DSP | Back End DAIs | Audio devices |
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <----DAI2-----> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
对于上面的示例,我们必须定义 4 个 FE DAI 链路和 6 个 BE DAI 链路。FE DAI 链路定义如下 :-
SND_SOC_DAILINK_DEFS(pcm0,
DAILINK_COMP_ARRAY(COMP_CPU("System Pin")),
DAILINK_COMP_ARRAY(COMP_DUMMY()),
DAILINK_COMP_ARRAY(COMP_PLATFORM("dsp-audio")));
static struct snd_soc_dai_link machine_dais[] = {
{
.name = "PCM0 System",
.stream_name = "System Playback",
SND_SOC_DAILINK_REG(pcm0),
.dynamic = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
},
.....< other FE and BE DAI links here >
};
此 FE DAI 链路与常规 DAI 链路非常相似,不同之处在于我们还将 DAI 链路设置为 DPCM FE,并带有 dynamic = 1
。还有一个选项可以指定每个 FE 的触发调用顺序。这允许 ASoC 核心在其他组件之前或之后触发 DSP(因为某些 DSP 对 DAI/DSP 启动和停止序列的顺序有严格要求)。
上面的 FE DAI 将编解码器和代码 DAI 设置为虚拟设备,因为 BE 是动态的,并且会根据运行时配置而改变。
BE DAI 配置如下 :-
SND_SOC_DAILINK_DEFS(headset,
DAILINK_COMP_ARRAY(COMP_CPU("ssp-dai.0")),
DAILINK_COMP_ARRAY(COMP_CODEC("rt5640.0-001c", "rt5640-aif1")));
static struct snd_soc_dai_link machine_dais[] = {
.....< FE DAI links here >
{
.name = "Codec Headset",
SND_SOC_DAILINK_REG(headset),
.no_pcm = 1,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.be_hw_params_fixup = hswult_ssp0_fixup,
.ops = &haswell_ops,
},
.....< other BE DAI links here >
};
此 BE DAI 链路将 DAI0 连接到编解码器(在本例中为 RT5460 AIF1)。它设置了 no_pcm
标志以将其标记为 BE。
BE 还设置了忽略挂起和 PM 停机时间的标志。这允许 BE 在无主机模式下工作,其中主机 CPU 不传输数据,例如蓝牙电话呼叫 :-
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <----DAI1-----> Codec Speakers
* DSP *
PCM2 <------------> * * <====DAI2=====> MODEM
* *
PCM3 <------------> * * <====DAI3=====> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
这允许主机 CPU 在 DSP、MODEM DAI 和 BT DAI 仍在运行时进入休眠状态。
如果编解码器是由外部管理的设备,BE DAI 链路也可以将编解码器设置为虚拟设备。
同样,如果 CPU DAI 由 DSP 固件管理,BE DAI 也可以设置一个虚拟 CPU DAI。
FE/BE PCM 操作¶
上面的 BE 还公开了一些 PCM 操作和一个 fixup
回调。fixup 回调由机器驱动程序用于根据 FE hw 参数(重新)配置 DAI。即 DSP 可以对 FE 到 BE 进行 SRC 或 ASRC。
例如,DSP 将所有 FE hw 参数转换为以 48k、16bit、立体声的固定速率运行,用于 DAI0。这意味着 DAI0 的所有 FE hw_params 都必须在机器驱动程序中固定,以便 DAI 无论 FE 配置如何都能以所需配置运行。
static int dai0_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
/* The DSP will convert the FE rate to 48k, stereo */
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
/* set DAI0 to 16 bit */
params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
return 0;
}
其他 PCM 操作与常规 DAI 链路相同。根据需要使用。
部件图连接¶
BE DAI 链路通常在初始化时由 ASoC DAPM 核心连接到图中。但是,如果 BE 编解码器或 BE DAI 是虚拟的,则必须在驱动程序中明确设置 :-
/* BE for codec Headset - DAI0 is dummy and managed by DSP FW */
{"DAI0 CODEC IN", NULL, "AIF1 Capture"},
{"AIF1 Playback", NULL, "DAI0 CODEC OUT"},
编写 DPCM DSP 驱动程序¶
DPCM DSP 驱动程序看起来很像一个标准的平台类 ASoC 驱动程序,并结合了编解码器类驱动程序的元素。DSP 平台驱动程序必须实现 :-
前端 PCM DAI - 即 struct snd_soc_dai_driver。
显示从 FE DAI 到 BE 的 DSP 音频路由的 DAPM 图。
来自 DSP 图的 DAPM 部件。
用于增益、路由等的混音器。
DMA 配置。
BE AIF 部件。
第 6 项对于将音频路由到 DSP 外部很重要。需要为每个 BE 和每个流方向定义 AIF。例如,对于上面的 BE DAI0,我们将有 :-
SND_SOC_DAPM_AIF_IN("DAI0 RX", NULL, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_OUT("DAI0 TX", NULL, 0, SND_SOC_NOPM, 0, 0),
BE AIF 用于将 DSP 图连接到其他组件驱动程序(例如编解码器图)的图。
无主机 PCM 流¶
无主机 PCM 流是指不通过主机 CPU 路由的流。一个例子是从听筒到调制解调器的电话呼叫。
*************
PCM0 <------------> * * <----DAI0-----> Codec Headset
* *
PCM1 <------------> * * <====DAI1=====> Codec Speakers/Mic
* DSP *
PCM2 <------------> * * <====DAI2=====> MODEM
* *
PCM3 <------------> * * <----DAI3-----> BT
* *
* * <----DAI4-----> DMIC
* *
* * <----DAI5-----> FM
*************
在这种情况下,PCM 数据通过 DSP 路由。在此用例中,主机 CPU 仅用于控制,并且可以在流运行时进入休眠状态。
主机可以通过以下方式控制无主机链路 :-
将链路配置为 CODEC <-> CODEC 样式链路。在这种情况下,链路通过 DAPM 图的状态启用或禁用。这通常意味着有一个混音器控制,可用于连接或断开两个 DAI 之间的路径。
无主机 FE。此 FE 与 DAPM 图上的 BE DAI 链路有虚拟连接。然后由 FE 执行控制作为常规 PCM 操作。此方法对 DAI 链路提供更多控制,但需要更多的用户空间代码来控制链路。除非您的硬件需要更精细的 PCM 操作序列,否则建议使用 CODEC<->CODEC。
CODEC <-> CODEC 链路¶
当 DAPM 在 DAPM 图中检测到有效路径时,此 DAI 链路会启用。机器驱动程序为 DAI 链路设置了一些附加参数,即
static const struct snd_soc_pcm_stream dai_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link dais[] = {
< ... more DAI links above ... >
{
.name = "MODEM",
.stream_name = "MODEM",
.cpu_dai_name = "dai2",
.codec_dai_name = "modem-aif1",
.codec_name = "modem",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBP_CFP,
.c2c_params = &dai_params,
.num_c2c_params = 1,
}
< ... more DAI links here ... >
当 DAPM 检测到有效路径时,这些参数用于配置 DAI hw_params(),然后调用 PCM 操作来启动链路。当路径不再有效时,DAPM 还会调用相应的 PCM 操作来禁用 DAI。
无主机 FE¶
DAI 链路由不读取或写入任何 PCM 数据的 FE 启用。这意味着创建一个新的 FE,该 FE 通过虚拟路径连接到两个 DAI 链路。当 FE PCM 启动时,DAI 链路将启动;当 FE PCM 停止时,DAI 链路将停止。请注意,在此配置中,FE PCM 无法读取或写入数据。