一、引言
本篇分为两部分,第一部分介绍来介绍audio设备的相关基础知识;第二部分为结合源码来介绍在linux内核中的ASOC框架
二、audio基础知识简单介绍
mic 和line in
我们的电脑声卡上,一般都会有Line in和Mic in两个接口,翻译成中文就是“线性输入”和“麦克风输入”,这两个都是输入端口,但是还是有区别的:
1、Line in端口:该端口主要用于连接电吉他、电子琴、合成器等外界设备的音频信号输出的录音,由于这些设备本身输出功率就比较大,因此需要连接到Line in端口录音,当然使用它们录音从某种程度上也可以被称为外部设备的“内录”。一般您使用的声卡越好,Line in里的噪音就会越低,录制效果也会比较好。
2、Mic in端口:这要是连接麦克风录音使用的。但是这个端口和Line in的区别在于它有前置放大器,换言之麦克风本身输出功率小,因此必须要有一个外部的放大设备来放大音频信号。这个端口就是起到这个作用。有兴趣的朋友可以尝试一下把你的麦克风直接连接到Line in端口录音……没有声音或者声音很小对吧?!道理很简单,麦克风的信号没有被放大,自然效果就不好了。
作用:
1、mic in接口:将自己的歌声录下来实现基本的“卡拉OK功能”;
2、line in接口:将品质较好的声音、音乐信号输入,通过计算机的控制将该信号录制成一个文件。
3、line Out接口:用来输出未经放大芯片放大的模拟音频信号,一般连接耳机
三、linux中的ASOC框架讲解
ASoC–ALSA System on Chip,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系
整体结构图
ASoC框架分为3部分:
1. platform(SOC 用来描述芯片的DAI接口,负责数据传输):
一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等(DAI: Digital Audio Interface),只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中.实际上,把Platform认为是某个SoC更好理解.
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口( 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进行交互。
数据传输接口:snd_soc_platform_driver
2. codec (用来描述音频编解码芯片,含有2部分:DAI接口,控制接口):
字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用.嵌入式Codec通常通过I2C对内部的寄存器进行控制.
在移动设备中,Codec的作用可以归结为4种,分别是:
1.对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
2.对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
3.对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
4.对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上。
驱动方面
ASoC架构中的Codec 是一个i2c_driver
它包含了一些音频的控件( Controls),音频接口, DAMP(动态音频电源管理)的定义和某些Codec IO功能。所有的Codec驱动都要提供以下特性:
Codec DAI 和 PCM的配置信息;
Codec的IO控制方式( I2C, SPI等);
Mixer和其他的音频控件;
Codec的ALSA音频操作接口;
描述Codec的最主要的几个数据结构分别是:
snd_soc_codec_driver,snd_soc_dai_driver
确定snd_soc_codec_driver和snd_soc_dai-driver的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用
snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到, Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。
Codec驱动的步骤:
定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。
snd_soc_register_codec 注册函数
在snd_soc_register_codec函数中,
传入参数snd_soc_codec_driver和snd_soc_dai_driver, 并创建snd_soc_codec和snd_soc_dai。
它申请了一个snd_soc_codec结构的实例:确定codec的名字,这个名字很重要, Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较(“alc5623-hifi”),从而找到该Codec的!然后初始化snd_soc_codec结构的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例。
通过snd_soc_register_dais函数对本Codec的dai进行注册
在snd_soc_register_dais函数中为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化。最后,它把codec实例链接到全局链表codec_list中,把dai链接到全局链表dai_list中,并且调snd_soc_instantiate_cards函数触发Machine驱动进行一次匹配绑定操作
3. machine (snd_soc_card,snd_soc_dai_link)
是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体.
跟单板相关,绑定Platform和Codec驱动,即表明使用的是哪个Platform,哪个CPU DAI、DMA、Codec和Codec DAI。
Machine驱动在一个重要的数据结构snd_soc_dai_link中,指定了Platform、 Codec、 codec_dai、 cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform, codec, dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来, Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。
ASOC和Codec的关系
ASOC的出现是为了让Codec独立于CPU,减少和CPU之间的耦合,这样同一个Codec驱动无需修改就可以适用任何一款平台。还是以下图做参考例子:
在Machine中已经知道,snd_soc_dai_link结构就指明了该Machine所使用的Platform和Codec。在Codec这边通过codec_dai和Platform侧的cpu_dai相互通信,既然相互通信,就需要遵守一定的规则,其中codec_dai和cpu_dai统一抽象为struct snd_soc_dai结构,而将dai的相关操作使用snd_soc_dai_driver抽象。同时也需要对所有的codec设备进行抽象封装,linux使用snd_soc_codec进行所有codec设备的抽象,而将codec的驱动抽象为snd_soc_codec_driver结构。
所有简单来说,Codec侧有四个重要的数据结构:
struct snd_soc_dai,struct snd_soc_dai_driver,struct snd_soc_codec,struct snd_soc_codec_driver。
四、Machine源码分析
在Machine中已经知道,snd_soc_dai_link结构就指明了该Machine所使用的Platform和Codec。在Codec这边通过codec_dai和Platform侧的cpu_dai相互通信,既然相互通信,就需要遵守一定的规则,其中codec_dai和cpu_dai统一抽象为struct snd_soc_dai结构,而将dai的相关操作使用snd_soc_dai_driver抽象。同时也需要对所有的codec设备进行抽象封装,linux使用snd_soc_codec进行所有codec设备的抽象,而将codec的驱动抽象为snd_soc_codec_driver结构。
1、查看所使用的DTS中,audio的硬件设备资源
xxxx_audio {
compatible = "xxxxx,xxxxxx";
dais {
dai0 {
audio-codec = <&xxxxx>;
audio-controller = <&i2s2>;
format = "i2s";
};
};
};
全局查找设备名,可以查到如下
static struct platform_driver uda134x_codec_driver = {
.driver = {
.name = "uda134x-codec",
.owner = THIS_MODULE,
},
.probe = uda134x_codec_probe,
.remove = uda134x_codec_remove,
};
查看此platform_driver的probe函数
static int aic3x_audio_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_card_aic3x;
int ret = 0;
card->dev = &pdev->dev;
ret = rockchip_of_get_sound_card_info(card);
if (ret < 0)
return ret;
return snd_soc_register_card(card); // 调用标准的alsa函数创建声卡实例 (定义 alc5623_card ,注册 snd_soc_register_card))
}
2、找到codec的代码
有两种方式:
一:直接通过设备树中的 ‘audio-codec’ 找到子节点后根据’compatible’属性查找
二:通过上述文件中snd_soc_dai_link结构:
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis",
};
在内核中搜索codec_name=“uda134x-codec”,会在kernel/sound/soc/codec/uda134x.c中发现。
3、Codec源码分析
1、snd_soc_dai_driver结构体构建
static const struct snd_soc_dai_ops aic3x_dai_ops = {
.hw_params = aic3x_hw_params,
.digital_mute = aic3x_mute,
.set_sysclk = aic3x_set_dai_sysclk,
.set_fmt = aic3x_set_dai_fmt,
};
static struct snd_soc_dai_driver aic3x_dai = {
.name = "tlv320aic3x-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
.ops = &aic3x_dai_ops,
.symmetric_rates = 1,
};
2、snd_soc_codec_driver结构体构建
static struct snd_soc_codec_driver soc_codec_dev_aic3x = {
.set_bias_level = aic3x_set_bias_level,
.idle_bias_off = true,
.reg_cache_size = ARRAY_SIZE(aic3x_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = aic3x_reg,
.probe = aic3x_probe,
.remove = aic3x_remove,
.suspend = aic3x_suspend,
.resume = aic3x_resume,
};
关于codec侧驱动总结:
1、分配名字为"codec_name"的平台驱动,注册。
2、定义struct snd_soc_codec_driver结构,设置,初始化。
3、定义struct snd_soc_dai_driver结构,设置,初始化。
4、调用snd_soc_register_codec函数注册codec。
五、PCM接口分析
PCM(Pulse Code Modulation),脉冲编码调制,PCM总线用于传输数字语音信号,包括4根信号线:FSYNC(同步)/PCLK(时钟)/DTX(发送)/DRX(接收)
PCM分为Master和Slave. Master提供PCLK及FSYNC。
PCM CLK是时钟信号,由master端提供,速率一般为512KHz到8.192MHz。每一个PCLK对应一位DTX/DRX的数据
PCM FSYNC为帧同步信号,其频率为PCM采样频率,一般为8KHz。一个帧同步信号的上升沿代表一帧(frame)数据的开始,一帧数据中分为若干个时隙(time slot),时隙对应channel。
DRX/DTX为数据收发,PCM数据格式有A-law/mu-law/linear三种,其中A-law/mu-law每个采样用8bit编码,即每个同步信号中需要传输8bit的数据,速率为8KHz8bit=64kbit/s。而linear每个采样16bit编码,每个同步信号中传输16bit数据,速率为8KHz16bit=128kbit/s
例如,PCLK为512KHz,FSYNC为8KHz,那么每个FSYNC能传输512/8=64位数据,假如使用16位线性PCM编码,那么一个同步信号能传输64/16=4个timeslot的数据,即4个channel。
线性PCM编码一个采样传输16bit数据,每个采样占0.125ms,20ms为一帧语音数据,包括160个采样,即160*16bit=320Byte数据
宽带线性PCM编码(FSYNC为8k)一个采样传输32bit数据,每20ms传输640Byte数据
I2S接口
先看一张原理图
可以看到,I2S有五根信号线
1、串行始终SCLK,也叫位始终(BCLK),对应数字音频的每一位数据,SCLK都有一个脉冲。 SCLK的频率=2 * 采样频率 * 采样位数 。
2、帧时钟LRCK(也称WS),用于切换左右声道的数据。LRCK为‘1’表示传输右声道数据,为“0”则是左声道。 LRCK的频率 = 采样频率
3、数据(SDATA),就是用二进制补码表示的音频数据,(MSB —> LSB:数据由高位到低位依次传输)
4、一般还有MCLK,主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍。
I2S多通道
I2S多通道跟2通道类似,就是在一个LRCK里面放n路数据
mclk = N * Sclk
I2S时序与PCM时序区别
I2S是SCK下降沿发送数据,上升沿接收数据(TRANS_NEGATIVE_POSITIVE),即SDI/SDO在下降沿时变化,对齐下降沿。
PCM是SCK上升沿发送数据,下降沿接收数据(TRANS _POSITIVE_NEGATIVE),即SDI/SDO在上升沿时变化,对齐上升沿。