alsa-lib/src/mixer/simple_none.c: selem_write
alsa-lib/src/mixer/simple_none.c: selem_write_main
alsa-lib/src/mixer/simple_none.c: elem_write_volume
alsa-lib/src/control/hcontrol.c: snd_hctl_elem_write
alsa-lib/src/control/control.c: snd_ctl_elem_write
alsa-lib/src/control/control_hw.c: snd_ctl_hw_elem_write
这个函数调用ioctl系统调用,参数为SNDRV_CTL_IOCTL_ELEM_WRITE,进入内核空间。
linux-kernel/sound/core/control.c: snd_ctl_elem_write_user
linux-kernel/sound/core/control.c: snd_ctl_elem_write
驱动上层的数据放在snd_card.controls链表中。这个链表中的元素是snd_kcontrol类型的。而驱动底层使用snd_kcontrol_new类型的数据。
在设备初始化时,使用snd_soc_cnew或snd_ctl_new1函数从snd_kcontrol_new类型的模板中,生成相应的snd_kcontrol类型的数据。snd_kcontrol_new类型的数据可以用SOC_SINGLE、SOC_DOUBLE_VALUE之类的宏生成,其默认的put函数是snd_soc_put_volsw。
linux-kernel/sound/soc/soc-ops.c: snd_soc_put_volsw
linux-kernel/sound/soc/soc-io.c: snd_soc_component_update_bits
linux-kernel/drivers/base/regmap/regmap.c: regmap_update_bits_check
使用regmap技术映射I2C、SPI接口地址之后,即可调用相关函数修改相关音频外设的寄存器值了。
这里对SOC_SINGLE类的宏,详细说明一下,因为只有这些宏才是真正需要驱动开发者添加或修改的。
例如如下的代码:
SOC_DOUBLE_R_TLV("Digital Playback Volume", JZCODEC_DACLVOL, JZCODEC_DACRVOL,
0, 255, 0, jzcodec_tlv),
使用amixer命令观察该代码的效果:
Simple mixer control 'Digital',0
Capabilities: pvolume
Playback channels: Front Left - Front Right
Limits: Playback 0 - 255
Mono:
Front Left: Playback 255 [100%] [0.00dB]
Front Right: Playback 255 [100%] [0.00dB]
从中可以看出snd_kcontrol_new的name成员的名字是有特定含义的,不能随便定。
有的音频芯片,如TAS5086,它的音量寄存器用0xFF表示静音,而用0x0表示最大音量,这种情况下需要将SOC_SINGLE类宏的invert参数设为1。
除了SOC_SINGLE类宏之外,还有SOC_SINGLE_TLV类宏。后者和前者的主要区别在于:用TLV数组完成音量到寄存器值之间的转换。
例如如下代码:
static const DECLARE_TLV_DB_SCALE(tas5086_dac_tlv, -10350, 50, 1);
-10350是音量的最小值,表示-103.5dB。50是相邻的两个寄存器值之间的音量差(即步长),这里表示0.5dB。也就是说这个宏设定的音量值的单位都是0.01dB。
ASOC在Linux 3.X时代最大的改进就是添加了对多codec的支持。这里我们首先描述一下多Codec在音频设备设计中的意义,以及它的一些实现方案。
上图是实现方案A,它的特点是:
1.1个MCU通过2个Codec,间接连接了4个Speaker。在当前的音频设计中,音箱的扬声器个数越来越多,但是单个Codec所能提供的声道数量有限。当扬声器个数超过这一数量限制时,就需要采用多Codec方案了。
2.两个Codec有独立的I2C控制通道。这里的“独立”是逻辑意义上的。在物理上,两个Codec可挂在同一条I2C总线上,以不同的从机地址加以区分。
3.两个Codec共享I2S输入。
上图是实现方案B,它和方案A的区别为:两个Codec有独立的I2S输入。方案B根据用途,又可分为两种子方案:
1.方案B1:两个I2S的输入相同。
2.方案B2:两个I2S的输入不同。
1.首先讨论I2C控制的实现,这里以TAS5731M芯片为例。
在board初始化阶段,为两个codec各自生成一个I2C设备。
static struct i2c_board_info __initdata rtl819xd_i2c_devices[] = {
{
I2C_BOARD_INFO("tas5731", 0x1a), //TAS5731M Master
},
{
I2C_BOARD_INFO("tas5731", 0x1b), //TAS5731M Sub
},
};
2.方案A由于共享I2S,因此在驱动角度,必须将两个codec驱动整合到一个card之中。
相应的machine代码为:
static struct snd_soc_dai_link rtl819xd_jzcodec_dai[] = {
{
.name = "JZCODEC_MASTER",
.stream_name = "JZCODEC_MASTER",
.cpu_dai_name = "rtl8197d-i2s",
.codec_name = "tas5731.0-001a",
.codec_dai_name = "tas5731_master",
.init = rtl819xd_jzcodec_jzcodec_init,
.ops = &rtl819xd_jzcodec_ops,
.platform_name = "rtl819x-pcm-audio",
},
{
.name = "JZCODEC_SUB",
.stream_name = "JZCODEC_SUB",
.cpu_dai_name = "rtl8197d-i2s",
.codec_name = "tas5731.0-001b",
.codec_dai_name = "tas5731_sub",
.init = rtl819xd_jzcodec_jzcodec_init,
.ops = &rtl819xd_jzcodec_ops,
}
};
相应的codec代码为:
static struct snd_soc_dai_driver tas5731_dai[] = {
{
.name = "tas5731_master",
.id = 0,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 6,
.rates = TAS5731_PCM_RATES,
.formats = TAS5731_PCM_FORMATS,
},
.ops = &tas5731_dai_ops,
},
{
.name = "tas5731_sub",
.id = 1,
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 6,
.rates = TAS5731_PCM_RATES,
.formats = TAS5731_PCM_FORMATS,
},
.ops = &tas5731_dai_ops,
}
};
这里对代码的要点做一个解释:
1)codec_name的命名规则。codec_name一般以X.Y-Z的形式命名。
其中X为codec的I2C driver名称。注意这里的名称和I2C_BOARD_INFO中定义的名称不同,后者是根据i2c_device_id中定义的名称来命名的。只是在这个例子中,两者恰好是一致的。
Y表示I2C总线号。如果MCU有超过一条I2C总线的话,需要使用总线号加以区分。
Z表示Codec设备的从机地址。
2)由于两个Codec共享I2S输入,因此只需要其中一个Codec定义platform_name即可。
和方案A的差别在于:两个Codec都需要定义自己的platform_name。
这种情况用两个独立的card来解决就可以了。
https://blog.csdn.net/huyugv_830913/article/details/6159054
ALSA driver(Control)
以下是教程的一些细节的学习心得。
/* Build the pipeline */
pipeline = gst_parse_launch ("playbin2 uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
从这个教程可以看出,我们可以直接使用gst_parse_launch创建pipeline。
tee_src_pad_template = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (tee), "src%d");
这是代码的其中一段,这里只谈谈src%d
是怎么来的。使用gst-inspect工具查询tee插件的信息,得到如下内容:
Pad Templates:
SRC template: 'src%d'
Availability: On request
Has request_new_pad() function: gst_tee_request_new_pad
Capabilities:
ANY
SINK template: 'sink'
Availability: Always
Capabilities:
ANY
从中可知,tee插件SRC Pad的模板名就是src%d
。
教程的起点——helloworld。这是一个最基本的GStreamer播放器的例子,使用GTK作为GUI工具。
代码参见:
https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/python/python-gst-player-example.py
这个例子不能直接运行,需要根据具体情况,略作修改,修改的地方如下:
1)self.uri存放用于播放的媒体文件的URI,注意这里是URI,而不是普通的路径,如果要指定本地文件的话,需要使用file://
。
2)出错的时候,先用gst-inspect
检查一下,相应的插件是否安装好了。
在这一步中,我们给播放器添加了暂停和进度条控制的功能。
代码参见:
https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/gstreamer/step1/my-gst-player.py
在这一步中,我们的修改如下:
1.添加了快进和慢进的功能。
2.使用gst_parse_launch创建pipeline。该pipeline可以播放视频文件。
代码参见:
https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/gstreamer/step2/my-gst-player.py
在这一步中,我们使用一般的GStreamer函数构建和Step 2相同的pipeline。
代码参见:
https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/gstreamer/step3/my-gst-player.py
这里需要注意以下几点:
1.随机Pad只能用pad-add消息回调的方式添加。
2.以下代码片段在这里都可用,尽管不完全等效,请注意用法和差别:
new_pad_type = new_pad.get_current_caps().get_structure(0).get_name()
new_pad_type = new_pad.query_caps(None).to_string()
从这里也可以看出,gst_parse_launch会自动处理媒体流的格式匹配问题,而使用普通函数的时候,必须自己编程处理格式匹配的问题。
https://blog.csdn.net/sakulafly/article/details/22216775
GStreamer播放教程01——playbin2的使用
https://ubuntuforums.org/showthread.php?t=822338
GStreamer Volume Control
您的打赏,是对我的鼓励