嵌入式Linux驱动程序开发(四)-音频驱动开发

姓名:薛绍宏     学号:19020100016    学院:电子工程学院

转自:https://blog.csdn.net/lzhf1122/article/details/78925519?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162722233316780264011150%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162722233316780264011150&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-7-78925519.pc_search_result_control_group&utm_term=%E5%B5%8C%E5%85%A5%E5%BC%8Flinux%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91&spm=1018.2226.3001.4449

【嵌牛导读】本文介绍了嵌入式Linux音频驱动开发

【嵌牛鼻子】嵌入式Linux驱动程序开发

【嵌牛提问】嵌入式Linux音频驱动如何开发?

【嵌牛正文】

1.嵌入式音频系统硬件连接

下图所示的嵌入式设备使用IIS将音频数据发送给编解码器。对编解码器的I/O寄存器的编程通过IIC总线进行。

2.音频体系结构-ALSA

ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构

在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。

3.ALSA设备文件

ALSA驱动核心会创建和管理一些设备节点,比如:

/dev/snd/controlC0: 一个控制结点,(应用程序用它来控制声卡,例如通道选择,音量的控制等)

/dev/snd/pcmC0D0p:用于播放的pcm设备

/dev/snd/pcmC0D0c:用于录音的pcm设备

C0D0代表的是声卡0中的设备0,最后一个c代表capture,最后一个p代表playback。

4.声卡的建立流程

第一步,创建snd_card的一个实例

snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下.

第二步,创建声卡的功能部件(逻辑设备),例如PCM, Mixer等,并将逻辑设备与步骤一创建的声卡绑定

通常, alsa-driver的已经提供了一些常用的部件的创建函数,PCM ---- snd_pcm_new()、CONTROL -- snd_ctl_create()

第三步,将声卡注册进ALSA框架

经过以上的创建步骤之后,声卡的逻辑结构如下图所示:

5.PCM设备的创建

对于一个pcm设备,可以生成两个设备文件,一个用于playback,一个用于capture,代码中也确定了他们的命名规则:

playback -- pcmCxDxp,通常系统中只有一个声卡和一个pcm,它就是pcmC0D0p

capture -- pcmCxDxc,通常系统中只有一个声卡和一个pcm,它就是pcmC0D0c

新建一个pcm设备的过程:

snd_card_create ,pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡

snd_pcm_new, 调用该api创建一个pcm,在该api中会做以下事情:

建立playback stream,相应的substream也同时建立

建立capture stream,相应的substream也同时建立

调用snd_device_new()把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数 snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用

snd_pcm_set_ops, 设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数

snd_card_register 注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序( alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc

6.Control设备的创建

Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或设置control的状态,从而达到控制音频Codec进行各种Mixer等控制操作。

要自定义一个Control,我们首先要定义3各回调函数:info,get和put。然后,定义一个snd_kcontrol_new结构:

static struct snd_kcontrol_new my_control __devinitdata = { 

    .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 

    .name = "PCM Playback Switch", 

    .index = 0, 

    .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 

    .private_value = 0xffff, 

    .info = my_control_info, 

    .get = my_control_get, 

    .put = my_control_put 

};

iface字段指出了control的类型

name字段是该control的名字

index字段用于保存该control的在该卡中的编号。

access字段包含了该control的访问类型。

private_value字段包含了一个任意的长整数类型值。

info回调函数用于获取control的详细信息

get回调函数用于读取control的当前值,并返回给用户空间的应用程序。

put回调函数用于把应用程序的控制值设置到control中。

我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_card_create()内被创建,snd_card_create()通过调用snd_ctl_create()函数创建control设备节点。所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建。

7.移动设备中的ALSA(ASoC)

ASoC--ALSA System on Chip ,是为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。ASoC不能单独存在,它建立在标准ALSA驱动之上,必须和标准的ALSA驱动框架相结合才能工作。

在软件层面, ASoC也把嵌入式设备的音频系统同样分为3大部分, Machine, Platform和Codec

Machine驱动:跟单板相关,绑定Platform和Codec驱动,即表明使用的是哪个Platform,哪个CPU DAI、DMA、Codec和Codec DAI。

Platform驱动 :它包含了该SoC平台的音频DMA和音频接口DAI的配置和控制( I2S, PCM等等)

Codec驱动 :它包含了一些音频的控件( Controls),音频接口, DAMP(动态音频电源管理)的定义和某些Codec IO功能。所有的Codec驱动都要提供以下特性:

Codec DAI 和 PCM的配置信息;

Codec的IO控制方式( I2C, SPI等);

Mixer和其他的音频控件;

Codec的ALSA音频操作接口;

8.ASoC架构中的Machine

ASoC把声卡注册为Platform Device。

Machine驱动在一个重要的数据结构snd_soc_dai_link中,指定了Platform、 Codec、 codec_dai、 cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform, codec, dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来, Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。

platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

在soc_probe函数中会完成以下任务:

调用标准的alsa函数创建声卡实例

挨个调用了codec, dai和platform驱动的probe函数

调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备

最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册

9.ASoC架构中的Codec

在移动设备中, Codec的作用可以归结为4种,分别是:

对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号

对Mic、 Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号

对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的

对音频信号做出相应的处理,例如音量控制,功率放大, EQ控制等等

描述Codec的最主要的几个数据结构分别是:

snd_soc_codec, snd_soc_codec_driver, snd_soc_dai, snd_soc_dai_driver,其中的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_codec结构的实例:确定codec的名字,这个名字很重要, Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该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驱动进行一次匹配绑定操作

10.ASoC架构中的Platform

Platform驱动的主要作用是完成音频数据的管理,最终通过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的注册

实现该驱动大致可以分为以下几个步骤:

定义一个snd_soc_platform_driver结构的实例;

在platform_driver的probe回调中利用ASoC的API: snd_soc_register_platform()注册上面定义的实例;

实现snd_soc_platform_driver中的各个回调函数

snd_soc_platform_driver中的ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:

ops.open:当应用程序打开一个pcm设备时,该函数会被调用

ops.hw_params:驱动的hw_params阶段,该函数会被调用,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数

ops.prepare:正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。

ops.trigger:数据传送的开始,暂停,恢复和停止时,该函数会被调用。

ops.pointer:该函数返回传送数据的当前位置

cpu的snd_soc_dai driver驱动的注册

dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样, dai驱动也是实现为一个platform driver,实现一个dai驱动大致可以分为以下几个步骤:

定义一个snd_soc_dai_driver结构的实例;

在对应的platform_driver中的probe回调中通过API: snd_soc_register_dai或者snd_soc_register_dais,注

册snd_soc_dai实例;

实现snd_soc_dai_driver结构中的probe、 suspend等回调;

实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数

它的ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合, dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:

工作时钟配置函数 通常由machine驱动调用

标准的snd_soc_ops回调 通常由soc-core在进行PCM操作时调用

抗pop, pop声 由soc-core调用

11.音频数据的dma操作

soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的

申请dma buffer

在声卡的建立阶段,pcm_new回调函数会被调用,函数进一步为playback和capture分别调用preallocate_dma_buffer函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作

在声卡的hw_params阶段, snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api: snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中( .dma_area, .dma_addr, .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。

dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api: snd_soc_dai_set_dma_data进行设置的。紧随其后, snd_soc_platform_driver结构的ops->hw_params回调利用api: snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作

dma buffer管理

播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取 出数据,经dai送往codec中。录音时则正好相反, codec源源不断地把A/D转换好的音频数据经过dai送入dma buffer中,而应用程序则不断地从该buffer中读走音频数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容