概述
Audio System 三 之 Linux ALSA音频系统分析
- User space audio code 代码路径
- Kernel space audio code 代码路径
- 三、Linux ALSA 音频系统架构
- 3.1 Platform
- 3.2 Codec
- 3.3 Machine
- 3.3.1 dai_link
- 3.3.2 hw constraints
- 四、ASoC Core
- 4.1ASoC Codec Driver
- 4.2 ASoC Platform Driver
- 4.3 ASoC Machine Driver
- 五、Codec Driver
- 5.1 Codec DAI and PCM configuration
- 5.2 Codec control IO
- 5.3 Mixers and audio controls
- 六、Codec audio operations
- 6.1 Codec register
- 七、Platform Driver
- 7.1 pcm operations
- 7.2 platform_driver 注册
- 八、Machine Driver
https://blog.csdn.net/zyuanyun
Google Pixel、Pixel XL 内核代码(Kernel-3.18):Kernel source for Pixel and Pixel XL - GitHub
AOSP 源码(Android 7.1.2): Android 系统全套源代码分享 (更新到 8.1.0_r1)
User space audio code 代码路径
User space audio code 代码路径
/hardware/qcom/audio/hal/ – (Audio HAL 源码)
/external/tinyalsa/ – (tinymix, tinyplay, tinycap 源码)
/vendor/qcom/proprietary/mm-audio/ – (QTI OMX audio encoder and decoders 源码,未公开)
/frameworks/av/media/audioserver/ – (Audioserver 源码)
/frameworks/av/media/libstagefright/ – (Google Stagefright 多媒体框架源码)
/frameworks/av/services/audioflinger/ – (Audioflinger 相关源码)
/external/bluetooth/bluedroid/ – (A2DP audio HAL 相关源码)/
/hardware/libhardware/modules/usbaudio/ – (USB HAL 源码)/
/vendor/qcom/proprietary/wfd/mm/source/framework/src/
– (Wi-Fi Display (WFD)、 WFDMMSourceAudioSource.cpp,未公开)
/system/core/include/system/ – (audio.h)/
Kernel space audio code 代码路径
• /kernel/sound/soc/msm/ – msm8996.c machine driver 源码
• /kernel/sound/soc/msm/qdsp6v2
– platform drivers, front end (FE), and back-end (BE) DAI driver,
– Hexagon DSP drivers for AFE, ADM, and ASM, voice driver 相关源码
• kernel/sound/soc/soc-.c
– All the SoC-.c ALSA SoCs framework 源码
• kernel/drivers/slimbus/
– SLIMbus driver 源码
• kernel/arch/arm/mach-msm/
– 包含比如 acpuclock-8996.c, board-8996-gpiomux.c, board-8996.c, and clock-8996.c
– related to the GPIO, clock, and board-specific information on the MSM8996 相关源码
• /kernel/arch/arm/mach-msm/qdsp6v2/
– Contains the drivers for DSP-based encoders and decoders,
– code for the aDSP loader, APR driver, Io memory driver, and other utility files
• /kernel/arch/arm/boot/dts
– Contains MSM8996-.dts and MSM8996-.Dtsi files that contain MSM8996-specific information;
– audio-related customization is available in files such as MSM8996.dtsi, msm8996-mtp.dtsi, and msm8996-cdp.dtsi
• /kernel/sound/soc/codecs/
– Contains the source code for the codec driver for WCD9335;
– codec driver-related source files are wcd9335.c, wcd9xxx-mbhc.c, wcd9xxx-resmgr.c,
– wcd9xxx-common.c, and so on.
• /kernel/drivers/mfd/
– Contains the source code for the codec driver; wcd9xxx-core.c,
– wcd9xxx-slimslave.c, and wcd9xxx-irq.c are the codec driverrelated files
三、Linux ALSA 音频系统架构
硬件平台及软件版本:
☁ Kernel - 3.18
☁ SoC - Qualcomm snapdragon
☁ CODEC - WCD9335
☁ Machine - msm8996
☁ Userspace - tinyalsa
Linux ALSA 音频系统架构大致如下:
-
Native ALSA Application
tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制 -
ALSA Library API
alsa 用户库接口,常见有 tinyalsa、alsa-lib -
ALSA CORE
alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC) -
ASoC CORE
asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系 -
Hardware Driver
音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec。
3.1 Platform
指某款 SoC 平台的音频模块,如 exynos、omap、qcom 等等。
Platform 又可细分两部分:
-
cpu dai
在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,
负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。
cpu_dai 通过 snd_soc_register_dai() 来注册。
注:DAI 是 Digital Audio Interface 的简称,分为 cpu_dai 和 codec_dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。 -
pcm dma
负责把 dma buffer 中的音频数据搬运到 I2S tx FIFO。
值得留意的是:
某些情形下是不需要 dma 操作的,比如 Modem 和 CODEC 直连,
因为 Modem 本身已经把数据送到 FIFO 了,这时只需启动 codec_dai 接收数据即可;
该情形下,Machine 驱动 dai_link 中需要设定 .platform_name = “msm-pcm-xxx”。
3.2 Codec
对于回放来说,userspace 送过来的音频数据是经过采样量化的数字信号,
在 codec 经过 DAC 转换成模拟信号然后输出到外放或耳机,这样我们就可以听到声音了。
Codec 字面意思是编解码器,但芯片里面的功能部件很多,常见的有 AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的 codec 芯片还有 EQ、DSP、SRC、DRC、AGC、Echo-Canceller、Noise-Suppression 等部件。
3.3 Machine
指某款机器,通过配置 dai_link
把 cpu_dai、codec_dai、modem_dai 各个音频接口给链结成一条条音频链路,然后注册 snd_soc_card。
和上面两个不一样,Platform 和 CODEC 驱动一般是可以重用的,而 Machine 有它特定的硬件特性,几乎是不可重用的。
所谓的硬件特性指:
- SoC Platform 与 Codec 的差异;
- DAIs 之间的链结方式;
- 通过某个 GPIO 打开 Amplifier;
- 通过某个 GPIO 检测耳机插拔;
- 使用某个时钟如 MCLK/External-OSC 作为 I2S、CODEC 的时钟源等等。
从上面的描述来看,对于回放的情形,PCM 数据流向大致是:
copy_from_user DMA I2S DAC
^ ^ ^ ^
+---------+ | +----------+ | +-----------+ | +-----+ | +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+ +----------+ +-----------+ +-----+ +------+
几个音频物理链路的概念:
3.3.1 dai_link
machine 驱动中定义的音频数据链路,它指定链路用到的 codec、codec_dai、cpu_dai、platform。
比如对于 WCD9335 平台的 media 链路:
.codec_dai_name = “snd-soc-dummy-dai”,
.codec_name = “snd-soc-dummy”,
.cpu_dai_name = “MultiMediaX”,
.platform_name = “msm-pcm-dsp.0”,
这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。
一个系统可能有多个音频数据链路,比如 media 和 voice,因此可以定义多个 dai_link 。
代码如下:
[->/sound/soc/msm/msm8996.c]
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm8996_common_dai_links[] = {
/* FrontEnd DAI Links */
{
.name = "MSM8996 Media1",
.stream_name = "MultiMedia1",
.cpu_dai_name = "MultiMedia1",
.platform_name = "msm-pcm-dsp.0",
.dynamic = 1,
.async_ops = ASYNC_DPCM_SND_SOC_PREPARE,
.dpcm_playback = 1,
.dpcm_capture = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.ignore_suspend = 1,
/* this dainlink has playback support */
.ignore_pmdown_time = 1,
.be_id = MSM_FRONTEND_DAI_MULTIMEDIA1
},
......
}
高通平台因DSP而存在特殊性,如上图,
Frontend 链接 “Platform”,经由 “Platform”->Backend链接到Codec。
Front-end DAI:
[->/sound/soc/msm/msm-dai-fe.c]
static struct snd_soc_dai_driver msm_fe_dais[] = {
{
.playback = {
.stream_name = "MultiMedia1 Playback",
.aif_name = "MM_DL1",
.rates = (SNDRV_PCM_RATE_8000_192000|
SNDRV_PCM_RATE_KNOT),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S24_3LE),
.channels_min = 1,
.channels_max = 8,
.rate_min = 8000,
.rate_max = 192000,
},
.capture = {
.stream_name = "MultiMedia1 Capture",
.aif_name = "MM_UL1",
.rates = (SNDRV_PCM_RATE_8000_192000|
SNDRV_PCM_RATE_KNOT),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE|
SNDRV_PCM_FMTBIT_S24_3LE),
.channels_min = 1,
.channels_max = 4,
.rate_min = 8000,
.rate_max = 48000,
},
.ops = &msm_fe_Multimedia_dai_ops,
.name = "MultiMedia1",
.probe = fe_dai_probe,
},
......
}
Back-end DAI:
[->sound/soc/msm/msm8996.c]
static struct snd_soc_dai_link msm8996_tasha_be_dai_links[] = {
/* Backend DAI Links */
{
.name = LPASS_BE_SLIMBUS_0_RX,
.stream_name = "Slimbus Playback",
.cpu_dai_name = "msm-dai-q6-dev.16384",
.platform_name = "msm-pcm-routing",
.codec_name = "tasha_codec",
.codec_dai_name = "tasha_mix_rx1",
.no_pcm = 1,
.dpcm_playback = 1,
.be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX,
.init = &msm_audrx_init,
.be_hw_params_fixup = msm_slim_0_rx_be_hw_params_fixup,
/* this dainlink has playback support */
.ignore_pmdown_time = 1,
.ignore_suspend = 1,
.ops = &msm8996_be_ops,
},
......
}
static struct snd_soc_dai_link msm8996_tasha_fe_dai_links[] = {
{
.name = LPASS_BE_SLIMBUS_4_TX,
.stream_name = "Slimbus4 Capture",
.cpu_dai_name = "msm-dai-q6-dev.16393",
.platform_name = "msm-pcm-hostless",
.codec_name = "tasha_codec",
.codec_dai_name = "tasha_vifeedback",
.be_id = MSM_BACKEND_DAI_SLIMBUS_4_TX,
.be_hw_params_fixup = msm_slim_4_tx_be_hw_params_fixup,
.ops = &msm8996_be_ops,
.no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
.ignore_suspend = 1,
},
......
}
3.3.2 hw constraints
指平台本身的硬件限制,如所能支持的通道数 / 采样率 / 数据格式、DMA 支持的数据周期大小(period size)、
周期次数(period count)等,通过 snd_pcm_hardware 结构体描述:
[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static struct snd_pcm_hardware msm_pcm_hardware_capture = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S24_3LE),
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 4,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
.fifo_size = 0,
};
hw params:用户层设置的硬件参数,如 channels、sample rate、pcm format、period size、period count;这些参数受 hw constraints 约束。
sw params:用户层设置的软件参数,如 start threshold、stop threshold、silence threshold。
四、ASoC Core
ASoC:ALSA System on Chip,是建立在标准 ALSA 驱动上,为了更好支持嵌入系系统 和 应用于移动设备的音频 codec 的一套软件体系,它依赖于标准 ALSA 驱动框架。
内核文档 /Documentation/alsa/soc/overview.txt 中详细介绍了 ASoC 的设计初衷。
- 独立的 codec 驱动: 标准的 ALSA 驱动框架里面 codec 驱动往往与 SoC / CPU 耦合过于紧密,不利于在多样化的平台/机器上移植复用
- 方便 codec 与 SoC 通过 PCM/I2S 总线建立链接
- 动态音频电源管理 DAPM,使得 codec 任何时候都工作在最低功耗状态,同时负责音频路由的创建
- POPs 和 click 音抑制弱化处理,在 ASoC 中通过正确的音频部件上下电次序来实现
- Machine 驱动的特定控制,比如耳机、麦克风的插拔检测,外放功放的开关
在概述中已经介绍了 ASoC 硬件设备驱动的三大构成:Codec、Platform 和 Machine,
下面列举各驱动的功能构成:
4.1ASoC Codec Driver
- Codec DAI 和 PCM 的配置信息
- Codec 的控制接口,如 I2C/SPI
- Mixer 和其他音频控件
- Codec 的音频接口函数,见 snd_soc_dai_ops 结构体定义
- DAPM 描述信息
- DAPM 事件处理句柄
- DAC 数字静音控制
4.2 ASoC Platform Driver
包括 dma 和 cpu_dai 两部分:
- dma 驱动实现音频 dma 操作,具体见 snd_pcm_ops 结构体定义
- cpu_dai 驱动实现音频数字接口控制器的描述和配置
4.3 ASoC Machine Driver
作为链结 Platform 和 Codec 的载体,它必须配置 dai_link 为音频数据链路指定 Platform 和 Codec。
处理 机器特有的音频控件 和音频事件,例如,回放时打开外放功放。
硬件设备驱动相关结构体:
-
snd_soc_codec_driver:
音频解码芯片,描述函数,如控件 / 微件 /音频路由 的描述信息、时钟配置、IO 控制等。 -
snd_soc_dai_driver
音频数据接口描述 及 操作函数,根据 codec 端和 soc 端,分为 codec_dai 和 cpu_dai。 -
snd_soc_platform_driver
音频 dma 设备描述及操作函数 -
snd_soc_dai_link
音频链路描述 及 板级操作函数
五、Codec Driver
基本是以内核文档 Documentation/sound/alsa/soc/codec.txt 中的内容为脉络来分析的。
Codec 的作用,之前已有描述,本章主要罗列下 Codec driver 中重要的数据结构及注册流程。
其中有着各种功能部件,包括但不限于 :
- ADC 把麦克风拾取的模拟信号转换成数字信号
- DAC 把音频接口过来的数字信号转换成模拟信号
- MIXER 混音器,把多路输入信号混合成单路输出
5.1 Codec DAI and PCM configuration
codec_dai 和 pcm 配置信息通过结构体 snd_soc_dai_driver 描述,包括 dai 的能力描述和操作接口,snd_soc_dai_driver 最终会被注册到 soc-core 中。
[->include/sound/soc-dai.h]
/*
* Digital Audio Interface Driver.
*
* Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
* operations and capabilities. Codec and platform drivers will register this
* structure for every DAI they have.
* This structure covers the clocking, formating and ALSA operations for each
* interface.
*/
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
unsigned int id;
int ac97_control;
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* ops */
const struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
name:codec_dai 的名称标识,dai_link 通过配置 codec_dai_name 来找到对应的 codec_dai;
probe:codec_dai 的初始化函数,注册声卡时回调;
playback:回放能力描述,如回放设备所支持的声道数、采样率、音频格式;
capture:录制能力描述,如录制设备所支持声道数、采样率、音频格式;
ops:codec_dai 的操作函数集,这些函数集非常重要,用于 dai 的时钟配置、格式配置、硬件参数配置。
codec_dai:
[->sound/soc/codecs/wcd9335.c]
static struct snd_soc_dai_driver tasha_i2s_dai[] = {
{
.name = "tasha_i2s_rx1",
.id = AIF1_PB,
.playback = {
.stream_name = "AIF1 Playback",
.rates = WCD9335_RATES_MASK,
.formats = TASHA_FORMATS_S16_S24_LE,
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 2,
},
.ops = &tasha_dai_ops,
},
{
.name = "tasha_i2s_tx1",
.id = AIF1_CAP,
.capture = {
.stream_name = "AIF1 Capture",
.rates = WCD9335_RATES_MASK,
.formats = TASHA_FORMATS,
.rate_max = 192000,
.rate_min = 8000,
.channels_min = 1,
.channels_max = 4,
},
.ops = &tasha_dai_ops,
},
......
}
5.2 Codec control IO
移动设备的音频 Codec,其控制接口一般是 I2C 或 SPI,控制接口用于读写 codec 的寄存器。
在 snd_soc_codec_driver 结构体中,有如下字段描述 Codec 的控制接口:
[->include/sound/soc.h]
/* codec driver */
struct snd_soc_codec_driver {
......
/* codec IO */
struct regmap *(*get_regmap)(struct device *);
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
int (*volatile_register)(struct snd_soc_codec *, unsigned int);
int (*readable_register)(struct snd_soc_codec *, unsigned int);
int (*writable_register)(struct snd_soc_codec *, unsigned int);
unsigned int reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
......
};
- read:读寄存器;
- write:写寄存器;
- volatile_register:
判断指定的寄存器是否是 volatile 属性;假如是,则读取寄存器时不是读 cache,而直接访问硬件; - readable_register:判断指定的寄存器是否可读;
- reg_cache_default:寄存器的缺省值;
- reg_cache_size:缺省的寄存器值数组大小;
- reg_word_size:寄存器宽度。
在 Linux-3.4.5 中,很多 codec 的控制接口都改用 regmap 了。
soc-core 中判断是否用的是 regmap,如果是,则调用 regmap 接口。
5.3 Mixers and audio controls
音频控件多用于部件开关和音量的设定,音频控件可通过 soc.h 中的宏来定义,
例如单一型控件:
[->include/sound/soc.h]
#define SOC_SINGLE(xname, reg, shift, max, invert)
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,
.put = snd_soc_put_volsw,
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
这种控件只有一个设置量,一般用于部件开关。
宏定义的参数说明:
- xname:控件的名称标识;
- reg:控件对应的寄存器地址;
- shift:控件控制位在寄存器中的偏移;
- max:控件设置值范围;
- invert:设定值是否取反。
其他类型控件类似,不一一介绍了。
上述只是宏定义,音频控件真正的结构是 snd_kcontrol_new:
[->/include/sound/control.h]
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
Codec 初始化时,通过 snd_soc_add_codec_controls() 把所有定义好的音频控件注册到 alsa-core ,
上层可以通过 tinymix、alsa_amixer 等工具查看修改这些控件的设定。
六、Codec audio operations
Codec 音频操作接口通过结构体 snd_soc_dai_ops 描述:
[->include/sound/soc-dai.h]
struct snd_soc_dai_ops {
/*
* DAI clocking configuration, all optional.
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_sysclk)(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*xlate_tdm_slot_mask)(unsigned int slots,
unsigned int *tx_mask, unsigned int *rx_mask);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
int (*get_channel_map)(struct snd_soc_dai *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot);
......
};
注释比较详细的了,Codec 音频操作接口分为 5 大部分:
时钟配置、格式配置、数字静音、PCM 音频接口、FIFO 延迟。
着重说下时钟配置及格式配置接口:
-
set_sysclk
codec_dai 系统时钟设置,当上层打开 pcm 设备时,
需要回调该接口设置 Codec 的系统时钟,Codec 才能正常工作; -
set_pll
Codec FLL 设置,Codec 一般接了一个 MCLK 输入时钟,
回调该接口基于 MCLK 来产生 Codec FLL 时钟,
接着 codec_dai 的 sysclk、bclk、lrclk 均可从 FLL 分频出来(假设 Codec 作为 master); -
set_fmt:codec_dai 格式设置,具体见 soc-dai.h;
-
SND_SOC_DAIFMT_I2S:音频数据是 I2S 格式,常用于多媒体音频;
-
SND_SOC_DAIFMT_DSP_A:音频数据是 PCM 格式,常用于通话语音;
-
SND_SOC_DAIFMT_CBM_CFM:Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;
-
SND_SOC_DAIFMT_CBS_CFS:Codec 作为 slave,BCLK 和 LRCLK 由 SoC/CPU 提供;
-
hw_params
codec_dai 硬件参数设置,根据上层设定的声道数、采样率、数据格式,来配置 codec_dai 相关寄存器。
WCD9335的snd_soc_dai_ops :
[->/sound/soc/codecs/wcd9335.c]
static struct snd_soc_dai_ops tasha_dai_ops = {
.startup = tasha_startup,
.shutdown = tasha_shutdown,
.hw_params = tasha_hw_params,
.prepare = tasha_prepare,
.set_sysclk = tasha_set_dai_sysclk,
.set_fmt = tasha_set_dai_fmt,
.set_channel_map = tasha_set_channel_map,
.get_channel_map = tasha_get_channel_map,
};
6.1 Codec register
当 platform_driver:
[->/sound/soc/codecs/wcd9335.c]
static struct platform_driver tasha_codec_driver = {
.probe = tasha_probe,
.remove = tasha_remove,
.driver = {
.name = "tasha_codec",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &tasha_pm_ops,
#endif
},
};
与.name = “tasha_codec” 的 platform_device
(该 platform_device 在 drivers/mfd/wcd9xxx-core.c 中注册wcd9xxx_device_init->wcd9xxx_check_codec_type->tasha_devs)匹配后,
[->drivers/mfd/wcd9xxx-core.c]
static struct mfd_cell tasha_devs[] = {
{
.name = "tasha_codec",
},
};
立即回调 tasha_probe() 注册 Codec:
[->/sound/soc/codecs/wcd9335.c]
static int tasha_probe(struct platform_device *pdev)
{
int ret = 0;
struct tasha_priv *tasha;
struct clk *wcd_ext_clk, *wcd_native_clk;
struct wcd9xxx_resmgr_v2 *resmgr;
struct wcd9xxx_power_region *cdc_pwr;
......
tasha = devm_kzalloc(&pdev->dev, sizeof(struct tasha_priv), GFP_KERNEL);
......
tasha->resmgr = resmgr;
tasha->swr_plat_data.handle = (void *) tasha;
tasha->swr_plat_data.read = tasha_swrm_read;
tasha->swr_plat_data.write = tasha_swrm_write;
tasha->swr_plat_data.bulk_write = tasha_swrm_bulk_write;
tasha->swr_plat_data.clk = tasha_swrm_clock;
tasha->swr_plat_data.handle_irq = tasha_swrm_handle_irq;
/* Register for Clock */
wcd_ext_clk = clk_get(tasha->wcd9xxx->dev, "wcd_clk");
tasha->wcd_ext_clk = wcd_ext_clk;
tasha->sido_voltage = SIDO_VOLTAGE_NOMINAL_MV;
set_bit(AUDIO_NOMINAL, &tasha->status_mask);
tasha->sido_ccl_cnt = 0;
......
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_SLIMBUS)
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tasha,
tasha_dai, ARRAY_SIZE(tasha_dai));
else if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tasha,
tasha_i2s_dai,
ARRAY_SIZE(tasha_i2s_dai));
else
ret = -EINVAL;
......
}
snd_soc_register_codec:将 codec_driver 和 codec_dai_driver 注册到 soc-core。
[->]
/**
* snd_soc_register_codec - Register a codec with the ASoC core
*
* @codec: codec to register
*/
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
创建一个 snd_soc_codec 实例,包含 codec_drv(snd_soc_dai_driver)相关信息,
封装给 soc-core 使用,相关代码段如下
[sound/soc/soc-core.c: snd_soc_register_codec]
struct snd_soc_codec *codec;
dev_dbg(dev, "codec register %sn", dev_name(dev));
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id);
if (codec->name == NULL) {
kfree(codec);
return -ENOMEM;
}
// 初始化 Codec 的寄存器缓存配置及读写接口
codec->write = codec_drv->write;
codec->read = codec_drv->read;
codec->volatile_register = codec_drv->volatile_register;
codec->readable_register = codec_drv->readable_register;
codec->writable_register = codec_drv->writable_register;
codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
codec->dapm.dev = dev;
codec->dapm.codec = codec;
codec->dapm.seq_notifier = codec_drv->seq_notifier;
codec->dapm.stream_event = codec_drv->stream_event;
codec->dev = dev;
codec->driver = codec_drv;
codec->num_dai = num_dai;
mutex_init(&codec->mutex);
把以上 codec 实例插入到 codec_list链表中(声卡注册时会遍历该链表,找到 dai_link 声明的 codec 并绑定):
[sound/soc/soc-core.c: snd_soc_register_codec]
list_add(&codec->list, &codec_list);
把 codec_drv 中的 snd_soc_dai_driver(tasha_dai 或者tasha_i2s_dai )注册到 soc-core:
[sound/soc/soc-core.c: snd_soc_register_codec]
snd_soc_register_dais(&codec->component, dai_drv, num_dai, false);
snd_soc_register_dais() 会把 dai 插入到 dai_list 链表中
(声卡注册时会遍历该链表,找到 dai_link 声明的 codec_dai 并绑定):
[sound/soc/soc-core.c: snd_soc_register_codec]
list_add(&dai->list, &dai_list);
最后顺便提下 codec 和 codec_dai 的区别:
codec 指音频芯片共有的部分,包括 codec 初始化函数、控制接口、寄存器缓存、控件、dapm 部件、音频路由、偏置电压设置函数等描述信息;
而 codec_dai 指 codec 上的音频接口驱动描述,包括时钟配置、格式配置、能力描述等等,各个接口的描述信息不一定都是一致的,所以每个音频接口都有着各自的驱动描述。
七、Platform Driver
概述中提到音频 Platform 驱动主要用于音频数据传输,这里又细分为两步
启动 dma 设备,把音频数据从 dma buffer 搬运到 cpu_dai FIFO,这部分驱动用 snd_soc_platform_driver 描述,后面分析用 pcm_dma 指代它。
启动数字音频接口控制器(I2S/PCM/AC97),把音频数据从 cpu_dai FIFO 传送到 codec_dai(高通平台会将数据传送到ADSP)这部分驱动用 snd_soc_dai_driver 描述,后面分析用 cpu_dai 指代它。
MSM8996 包含三个 Hexagon DSP :application, modem, and sensor。
Application DSP:不仅可以处理语音和音频,还可以处理计算机 视觉、视频、图像和Camera。
Sensor DSP:也叫做SLPI,所有的sensor都链接到SLPI上面,它管理所有的Sensor及相关算法。
对于 cpu_dai 驱动,从上面的类图我们可知,主要工作有:
-
实现 dai 操作函数,见 snd_soc_dai_ops 定义,用于配置和操作音频数字接口控制器,
如时钟配置 set_sysclk()、格式配置 set_fmt()、硬件参数配置 hw_params()、启动/停止数据传输 trigger() 等; -
实现 probe 函数(初始化)、remove 函数(卸载)、suspend/resume 函数(电源管理);
-
初始化 snd_soc_dai_driver 实例,包括回放和录制的能力描述、dai 操作函数集、probe/remove 回调、电源管理相关的 suspend/resume 回调;
-
通过 snd_soc_register_dai() 把初始化完成的 snd_soc_dai_driver 注册到 soc-core
首先创建一个 snd_soc_dai 实例,然后把该 snd_soc_dai 实例插入到 dai_list 链表
(声卡注册时会遍历该链表,找到 dai_link 声明的 cpu_dai 并绑定)。
[sound/soc/soc-core.c]
static int snd_soc_register_dais(struct snd_soc_component *component,
struct snd_soc_dai_driver *dai_drv, size_t count, bool legacy_dai_naming)
{
struct device *dev = component->dev;
struct snd_soc_dai *dai;
dev_dbg(dev, "ASoC: dai register %s #%Zun", dev_name(dev), count);
component->dai_drv = dai_drv;
component->num_dai = count;
for (i = 0; i < count; i++) {
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
......
if (count == 1 && legacy_dai_naming) {
dai->name = fmt_single_name(dev, &dai->id);
} else {
dai->name = fmt_multiple_name(dev, &dai_drv[i]);
if (dai_drv[i].id)
dai->id = dai_drv[i].id;
else
dai->id = i;
}
......
dai->component = component;
dai->dev = dev;
dai->driver = &dai_drv[i];
if (!dai->driver->ops)
dai->driver->ops = &null_dai_ops;
list_add(&dai->list, &component->dai_list);
}
return 0;
}
dai 操作函数的实现是 cpu_dai 驱动的主体,需要配置好相关寄存器让 I2S/PCM 总线控制器正常运转,snd_soc_dai_ops 字段的详细说明见 Codec audio operations 章节。
cpu_dai 驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma 设备信息(总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些 dma 设备信息在 pcm_dma 驱动中用到。
7.1 pcm operations
操作函数的实现是本模块的主体,见 snd_pcm_ops 结构体描述:
[->include/sound/pcm.h]
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream, unsigned int cmd, void *arg);
int (*compat_ioctl)(struct snd_pcm_substream *substream, unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*delay_blk)(struct snd_pcm_substream *substream);
int (*wall_clock)(struct snd_pcm_substream *substream, struct timespec *audio_ts);
int (*copy)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream, unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
int (*restart)(struct snd_pcm_substream *substream);
};
7.2 platform_driver 注册
当 platform_driver
[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static struct platform_driver msm_pcm_driver = {
.driver = {
.name = "msm-pcm-dsp",
.owner = THIS_MODULE,
.of_match_table = msm_pcm_dt_match,
},
.probe = msm_pcm_probe,
.remove = msm_pcm_remove,
};
与 .name = “msm-pcm-dsp” 的 platform_device 注册 匹配后,系统会回调 msm_pcm_probe() 注册 platform:
[->sound/soc/msm/qdsp6v2/msm-pcm-q6-v2.c]
static int msm_pcm_probe(struct platform_device *pdev)
{
struct msm_plat_data *pdata;
const char *latency_level;
rc = of_property_read_u32(pdev->dev.of_node, "qcom,msm-pcm-dsp-id", &id);
......
pdata = kzalloc(sizeof(struct msm_plat_data), GFP_KERNEL);
......
if (of_property_read_bool(pdev->dev.of_node, "qcom,msm-pcm-low-latency")) {
pdata->perf_mode = LOW_LATENCY_PCM_MODE;
rc = of_property_read_string(pdev->dev.of_node, "qcom,latency-level", &latency_level);
if (!rc) {
if (!strcmp(latency_level, "ultra"))
pdata->perf_mode = ULTRA_LOW_LATENCY_PCM_MODE;
else if (!strcmp(latency_level, "ull-pp"))
pdata->perf_mode = ULL_POST_PROCESSING_PCM_MODE;
}
}
else
pdata->perf_mode = LEGACY_PCM_MODE;
dev_set_drvdata(&pdev->dev, pdata);
return snd_soc_register_platform(&pdev->dev, &msm_soc_platform);
}
snd_soc_register_platform:将 platform_drv 注册到 soc-core。
创建一个 snd_soc_platform 实例,包含 platform_drv(snd_soc_platform_driver)的相关信息,封装给 soc-core 使用;
把以上创建的 platform 实例插入到 platform_list 链表上
(声卡注册时会遍历该链表,找到 dai_link 声明的 platform 并绑定)。
代码实现:
int snd_soc_register_platform(struct device *dev, const struct snd_soc_platform_driver *platform_drv)
{
struct snd_soc_platform *platform;
platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
ret = snd_soc_add_platform(dev, platform, platform_drv);
return ret;
}
至此,完成了 Platform 驱动的实现。
回放情形下,pcm_dma 设备负责把 dma buffer 中的数据搬运到 I2S tx FIFO,
I2S 总线控制器负责把 I2S tx FIFO 中的数据传送DSP,DSP经处理后传送到到 Codec。
八、Machine Driver
在前在的 Codec 和 Platform 介绍了 Codec、Platform 驱动,
但仅有 Codec、Platform 驱动是不能工作的,
需要一个角色把 codec、codec_dai、cpu_dai、platform 给链结起来才能构成一个完整的音频链路,
这个角色就由 machine_drv 承担了。
snd_soc_dai_link 结构体:
[->/include/sound/soc.h]
struct snd_soc_dai_link {
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
const char *cpu_name;
struct device_node *cpu_of_node;
const char *cpu_dai_name;
const char *codec_name;
struct device_node *codec_of_node;
const char *codec_dai_name;
struct snd_soc_dai_link_component *codecs;
unsigned int num_codecs;
const char *platform_name;
struct device_node *platform_of_node;
int be_id; /* optional ID for machine driver BE identification */
const struct snd_soc_pcm_stream *params;
unsigned int dai_fmt; /* format to set on init */
enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
unsigned int ignore_suspend:1;
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
unsigned int no_pcm:1;
unsigned int dynamic:1;
unsigned int no_host_mode:2;
unsigned int dpcm_capture:1;
unsigned int dpcm_playback:1;
unsigned int ignore_pmdown_time:1;
int (*init)(struct snd_soc_pcm_runtime *rtd);
int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
bool playback_only;
bool capture_only;
enum snd_soc_async_ops async_ops;
}
重点介绍如下几个字段:
-
codec_name
音频链路需要绑定的 codec 名称,声卡注册时会遍历 codec_list,找到同名的 codec 并绑定; -
platform_name
音频链路需要绑定的 platform 名称,声卡注册时会遍历 platform_list,找到同名的 platform 并绑定; -
cpu_dai_name
音频链路需要绑定的 cpu_dai 名称,声卡注册时会遍历 dai_list,找到同名的 dai 并绑定; -
codec_dai_name
音频链路需要绑定的 codec_dai 名称,声卡注册时会遍历 dai_list,找到同名的 dai 并绑定;
ops:重点留意 hw_params() 回调,一般来说这个回调是要实现的,用于配置 codec、codec_dai、cpu_dai 的数据格式和系统时钟。在 Codec audio operations 小节中有描述。
/sound/soc/msm/msm8996.c 中的 dai_link 定义,两个音频链路分别用于 Media 和 Voice:
[->/sound/soc/msm/msm8996.c]
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm8996_common_dai_links[] = {
/* FrontEnd DAI Links */
{
.name = "MSM8996 Media1",
.stream_name = "MultiMedia1",
.cpu_dai_name = "MultiMedia1",
.platform_name = "msm-pcm-dsp.0",
.dynamic = 1,
.async_ops = ASYNC_DPCM_SND_SOC_PREPARE,
.dpcm_playback = 1,
.dpcm_capture = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.ignore_suspend = 1,
/* this dainlink has playback support */
.ignore_pmdown_time = 1,
.be_id = MSM_FRONTEND_DAI_MULTIMEDIA1
},
......
{
.name = "VoiceMMode1",
.stream_name = "VoiceMMode1",
.cpu_dai_name = "VoiceMMode1",
.platform_name = "msm-pcm-voice",
.dynamic = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.no_host_mode = SND_SOC_DAI_LINK_NO_HOST,
.ignore_suspend = 1,
.ignore_pmdown_time = 1,
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
.be_id = MSM_FRONTEND_DAI_VOICEMMODE1,
},
}
除了 dai_link,机器中一些特定的音频控件和音频事件也可以在 machine_drv 定义,如耳机插拔检测、外部功放打开关闭等。
我们再分析 machine_drv 初始化过程:
[->/sound/soc/msm/msm8996.c]
static struct platform_driver msm8996_asoc_machine_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = msm8996_asoc_machine_of_match,
},
.probe = msm8996_asoc_machine_probe,
.remove = msm8996_asoc_machine_remove,
};
[->/sound/soc/msm/msm8996.c]
static int msm8996_asoc_machine_probe(struct platform_device *pdev)
{
struct snd_soc_card *card;
struct msm8996_asoc_mach_data *pdata;
const char *mbhc_audio_jack_type = NULL;
char *mclk_freq_prop_name;
const struct of_device_id *match;
......
pdata = devm_kzalloc(&pdev->dev, sizeof(struct msm8996_asoc_mach_data), GFP_KERNEL);
card = populate_snd_card_dailinks(&pdev->dev);
......
match = of_match_node(msm8996_asoc_machine_of_match, pdev->dev.of_node);
ret = msm8996_populate_dai_link_component_of_node(card);
......
ret = snd_soc_register_card(card);
}
设置dailinks后,继而调用 snd_soc_register_card() 注册声卡。
由于该过程很冗长,这里不一一贴代码分析了,但整个流程是比较简单的,
流程图如下:
-
取出 platform_device 的私有数据,该私有数据就是 snd_soc_card ;
-
snd_soc_register_card() 为每个 dai_link 分配一个 snd_soc_pcm_runtime 实例,别忘了之前提过 snd_soc_pcm_runtime 是 ASoC 的桥梁,保存着 codec、codec_dai、cpu_dai、platform 等硬件设备实例。
-
随后的工作都在 snd_soc_instantiate_card() 进行:
-
遍历 dai_list、codec_list、platform_list 链表,为每个音频链路找到对应的 cpu_dai、codec_dai、codec、platform;找到的 cpu_dai、codec_dai、codec、platform 保存到 snd_soc_pcm_runtime ,完成音频链路的设备绑定;
-
调用 snd_card_create() 创建声卡;
-
soc_probe_dai_link() 依次回调 cpu_dai、codec、platform、codec_dai 的 probe() 函数,完成各音频设备的初始化,随后调用
-
soc_new_pcm() 创建 pcm 逻辑设备(因为涉及到本系列的重点内容,后面具体分析这个函数);
-
最后调用 snd_card_register() 注册声卡。
[->sound/soc/soc-core.c]
soc_new_pcm 源码分析:
[->/sound/soc/soc-pcm.c]
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm *pcm;
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);
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);
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
capture, &pcm);
}
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: can't create pcm for %sn",
rtd->dai_link->name);
return ret;
}
dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %sn",num, new_name);
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
rtd->pcm = pcm;
pcm->private_data = rtd;
if (rtd->dai_link->no_pcm) {
if (playback)
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
if (platform->driver->pcm_new)
rtd->platform->driver->pcm_new(rtd);
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) {
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->hw_no_buffer = 1;
snd_soc_set_runtime_hwparams(
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
&no_host_hardware);
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->hw_no_buffer = 1;
snd_soc_set_runtime_hwparams(
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
&no_host_hardware);
}
}
/* 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;
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;
}
if (platform->driver->ops) {
rtd->ops.ack = platform->driver->ops->ack;
rtd->ops.copy = platform->driver->ops->copy;
rtd->ops.silence = platform->driver->ops->silence;
rtd->ops.page = platform->driver->ops->page;
rtd->ops.mmap = platform->driver->ops->mmap;
rtd->ops.restart = platform->driver->ops->restart;
}
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);
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(platform->dev, "ASoC: pcm constructor failed: %dn", ret);
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
return ret;
}
可见 soc_new_pcm() 最主要的工作是创建 pcm 逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的 pcm 操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。
最后
以上就是无情硬币为你收集整理的Audio System 三 之 Linux ALSA音频系统分析的全部内容,希望文章能够帮你解决Audio System 三 之 Linux ALSA音频系统分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复