Antkillerfarm Hacking V7.0

ALSA(二), GStreamer(三), WebSocket, CoAP & MQTT

2016-03-06

从Gstreamer到ALSA

3.音量操作(续)

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接口地址之后,即可调用相关函数修改相关音频外设的寄存器值了。

4.SOC_SINGLE类宏

这里对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。

5.SOC_SINGLE_TLV类宏

除了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对多Codec的支持

音频设计方案

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的输入不同。

方案A的代码实现

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即可。

方案B1的代码实现

和方案A的差别在于:两个Codec都需要定义自己的platform_name。

方案B2的代码实现

这种情况用两个独立的card来解决就可以了。

ALSA参考资源

https://blog.csdn.net/huyugv_830913/article/details/6159054

ALSA driver(Control)

GStreamer编程(续)

以下是教程的一些细节的学习心得。

basic-tutorial-1.c

/* Build the pipeline */
pipeline = gst_parse_launch ("playbin2 uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);

从这个教程可以看出,我们可以直接使用gst_parse_launch创建pipeline。

basic-tutorial-7.c

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

GStreamer的Python开发教程

Step 0

教程的起点——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检查一下,相应的插件是否安装好了。

Step 1

在这一步中,我们给播放器添加了暂停和进度条控制的功能。

代码参见:

https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/gstreamer/step1/my-gst-player.py

Step 2

在这一步中,我们的修改如下:

1.添加了快进和慢进的功能。

2.使用gst_parse_launch创建pipeline。该pipeline可以播放视频文件。

代码参见:

https://github.com/antkillerfarm/antkillerfarm_crazy/blob/master/gstreamer/step2/my-gst-player.py

Step 3

在这一步中,我们使用一般的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

WebSocket

在浏览器中通过http仅能实现单向的通信。AJAX通过轮询方式,达到全双工通信,但效率不高。

面对这种状况,HTML5定义了WebSocket协议(基于TCP),能更好的节省服务器资源和带宽并达到实时通讯。

浏览器请求

GET /webfin/websocket/ HTTP/1.1
  Host: localhost
  Upgrade: websocket
 Connection: Upgrade
  Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
  Origin: http://www.sohu.com
  Sec-WebSocket-Version: 13

服务器回应

HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

CoAP & MQTT

CoAP(Constrained Application Protocol)协议,是IETF针对物联网提出的应用层协议。

参考:

http://blog.csdn.net/xukai871105/article/details/17734163

CoAP协议学习——CoAP基础

MQTT(MQ Telemetry Transport)是一个轻量级的machine-to-machine通信协议。适合于低带宽、不可靠连接、CPU内存资源紧张的嵌入式设备,它有可能成为物联网的重要协议。

官网:

http://mqtt.org/

参考:

https://mp.weixin.qq.com/s/i1WziUgG9TcLvvX1-2Nizw

初识MQTT协议

https://mp.weixin.qq.com/s/ZaBkqlmWboOT7SEpQOln_Q

MQTT协议之连接

https://mp.weixin.qq.com/s/1sq630gHR_z1NeSRILMMpw

MQTT协议之发布订阅

https://www.zhihu.com/question/21816631

MQTT和Websocket的区别是什么?

Fork me on GitHub