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送入dmabuffer中,而应用程序则不断地从该buffer中读走音频数据。
以上只是参考别人博客,概括性的总结了一下Linux音频子系统,如果想深入了解,可以查看博客:
http://blog.csdn.net/droidphone/article/details/6289712

嵌入式Linux音频驱动开发相关推荐

  1. linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第四十一章嵌入式Linux LED驱动开发实验 上一章我 ...

  2. 【正点原子Linux连载】第四十一章 嵌入式Linux LED驱动开发实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. 嵌入式linux设备驱动开发,嵌入式Linux设备驱动开发简介.pdf

    清远见--嵌入式培训专家 http :// "黑色经典"系列之<嵌入式Linux 应用程序开发详解> 11 章 嵌入式Linux 设备驱动开发 本章目标 本书从 6 章 ...

  4. 【正点原子MP157连载】第二十一章 嵌入式Linux LED驱动开发实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  5. 嵌入式 Linux 内核驱动开发【The first day: 36093万字】

    嵌入式 Linux 内核驱动开发[1] 嵌入式 Linux 内核驱动开发前言 第1章 Linux 内核裁剪和定制 [1]Linux 内核开发简介 [2] Linux 源码阅读工具 [1.2.1]Sou ...

  6. Linux音频驱动开发概括

    原址 1.嵌入式音频系统硬件连接 下图所示的嵌入式设备使用IIS将音频数据发送给编解码器.对编解码器的I/O寄存器的编程通过IIC总线进行. 2.音频体系结构-ALSA ALSA是Advanced L ...

  7. 嵌入式 Linux LED 驱动开发实验

    41.1 Linux 下 LED 灯驱动原理 Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器.所以本章的 LED 灯驱动最 终也是对 I.MX6ULL 的 IO 口进行配置,与裸机实验不 ...

  8. linux课程_【课程完结】嵌入式Linux应用/驱动开发基础知识两大篇章已全部录制完毕 共72集...

    完结撒花 <第四篇嵌入式Linux应用开发基础知识> <第五篇嵌入式Linux驱动开发基础知识> 两大篇章已全部录制完毕 共计 72 集 01 嵌入式Linux应用开发基础知识 ...

  9. 嵌入式linux应用程序开发详解_【精品套餐】嵌入式linux应用驱动开发完全学习路线...

    学习本课程,你将收获 本课程由<朱老师物联网大讲堂>推出,朱有鹏老师精心录制,提供从零开始.全面系统的学习体系,目的是让大家真正彻底的掌握嵌入式Linux应用和驱动程序开发知识和技能,真正 ...

最新文章

  1. ug编程内公差和外公差是什么_数控加工编程的主要内容有哪些?
  2. edgeR基因表达差异分析
  3. SAP零售业解决方案
  4. Effective C++ 的52个条款列表
  5. vc6.0报delete and rebuild Error executing link.exe.错误
  6. GATT之Device information Service
  7. 电脑cpu温度过高怎么办_网络资讯:电脑cpu占用过高处理办法
  8. doT js模板入门
  9. oracle processes 的大小,Oracle中sessions和processes的大小关系(10g和11g不同)
  10. 三星android10电话拦截功能,三星手机阻止来电怎么设置 拒接骚扰电话教程 - Iefans...
  11. 苹果App签名工具有哪些?
  12. Excel文本取首字母(包括繁体)VBA开发工具
  13. 基于CUDA的并行lammps编译及测试
  14. java设置cpu亲和性_cpu亲和性绑定
  15. 简单的微服务feign之间调用授权/安全验证
  16. 《中国历代著名文学家评传》目录
  17. 补缴2个月社保折腾1年多 刁钻政策玩死人
  18. Hbase数据库完全分布式搭建以及java中操作Hbase
  19. 15 THINGS ALL GIRLS SHOULD KNOW ABOUT THEIR VAGINA
  20. 锁屏解锁对象-OPPO事例

热门文章

  1. LICEcap – 灵活好用,GIF 屏幕录制工具
  2. WINCC历史报表(归档查询)实例
  3. 超详细——Java面试高频
  4. Apache Oozie(2):Apache Oozie安装
  5. arm linux 自动开机执行sh脚本
  6. vue打包压缩 使用gzip
  7. PVC塑料加速老化测试介绍
  8. 安全路上慢慢走,持续更新
  9. ​Cloneable接口
  10. Python之Pillow(PIL)库