linux alsa pcm(此pcm非硬件pcm接口)
转:https://blog.csdn.net/crycheng/article/details/7095899
CODEC :音频芯片的控制,比如静音、打开(关闭)ADC(DAC)、设置ADC(DAC)的增益、耳机模式的检测等操作。
I2S :数字音频接口,用于CPU和Codec之间的数字音频流raw data的传输。每当有playback或record操作时,snd_soc_dai_ops.prepare()会被调用,启动I2S总线。
PCM :我不知道为什么会取这个模块名,它其实是定义DMA操作的,用于将音频数据通过DMA传到I2S控制器的FIFO中。
这里的PCM实际是就是更新和管理音频数据流的地址,分配DMA等等,将RAM中存放的音频数据的地址传给I2S,不是PCM协议。
音频数据流向:
| DMA | | I2S/PCM/AC97 |
RAM --------> I2SControllerFIFO -----------------> CODEC ----> SPK/Headset
PCM模块初始化:
- struct snd_soc_platform rk29_soc_platform = {
- .name = "rockchip-audio",
- .pcm_ops = &rockchip_pcm_ops,
- .pcm_new = rockchip_pcm_new,
- .pcm_free = rockchip_pcm_free_dma_buffers,
- };
- EXPORT_SYMBOL_GPL(rk29_soc_platform);
- static int __init rockchip_soc_platform_init(void)
- {
- DBG("Enter::%s, %d\n", __FUNCTION__, __LINE__);
- return snd_soc_register_platform(&rk29_soc_platform);
- }
- module_init(rockchip_soc_platform_init);
- static void __exit rockchip_soc_platform_exit(void)
- {
- snd_soc_unregister_platform(&rk29_soc_platform);
- }
调用snd_soc_register_platform()向ALSA core注册一个snd_soc_platform结构体。
成员pcm_new需要调用dma_alloc_writecombine()给DMA分配一块write-combining的内存空间,并把这块缓冲区的相关信息保存到substream->dma_buffer中,相当于构造函数。pcm_free则相反。这些成员函数都还算简单,看看代码即可以理解其流程。
snd_pcm_ops
接着我们看一下snd_pcm_ops结构体,该结构体的操作函数集的实现是本模块的主体。
- struct snd_pcm_ops {
- int (*open)(struct snd_pcm_substream *substream);
- int (*close)(struct snd_pcm_substream *substream);
- int (*ioctl)(struct snd_pcm_substream * substream,
- unsigned int cmd, void *arg);
- int (*hw_params)(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params);
- int (*hw_free)(struct snd_pcm_substream *substream);
- int (*prepare)(struct snd_pcm_substream *substream);
- int (*trigger)(struct snd_pcm_substream *substream, int cmd);
- snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
- int (*copy)(struct snd_pcm_substream *substream, int channel,
- snd_pcm_uframes_t pos,
- void __user *buf, snd_pcm_uframes_t count);
- int (*silence)(struct snd_pcm_substream *substream, int channel,
- snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
- struct page *(*page)(struct snd_pcm_substream *substream,
- unsigned long offset);
- int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
- int (*ack)(struct snd_pcm_substream *substream);
- };
我们主要实现open、close、hw_params、hw_free、prepare和trigger接口。
open
open函数为PCM模块设定支持的传输模式、数据格式、通道数、period等参数,并为playback/capture stream分配相应的DMA通道。其一般实现如下:
- static int rockchip_pcm_open(struct snd_pcm_substream *substream)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct rockchip_runtime_data *prtd;
- DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
- snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware);
- prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL);
- if (prtd == NULL)
- return -ENOMEM;
- spin_lock_init(&prtd->lock);
- runtime->private_data = prtd;
- return 0;
- }
其中硬件参数要根据芯片的数据手册来定义,如:
- int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
- const struct snd_pcm_hardware *hw)
- {
- struct snd_pcm_runtime *runtime = substream->runtime;
- runtime->hw.info = hw->info;
- runtime->hw.formats = hw->formats;
- runtime->hw.period_bytes_min = hw->period_bytes_min;
- runtime->hw.period_bytes_max = hw->period_bytes_max;
- runtime->hw.periods_min = hw->periods_min;
- runtime->hw.periods_max = hw->periods_max;
- runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
- runtime->hw.fifo_size = hw->fifo_size;
- return 0;
- }
关于peroid的概念有这样的描述:The “period” is a term that corresponds to a fragment in the OSS world. The period defines the size at which a PCM interrupt is generated. peroid的概念很重要,建议去alsa官网找相关详细说明了解一下。
上层ALSA lib可以通过接口来获得这些参数的,如snd_pcm_hw_params_get_buffer_size_max()来取得buffer_bytes_max。
hw_free是hw_params的相反操作,调用snd_pcm_set_runtime_buffer(substream, NULL)即可。
注:代码中的dma_buffer是DMA缓冲区,它通过4个字段定义:dma_area、dma_addr、dma_bytes和dma_private。其中dma_area是缓冲区逻辑地址,dma_addr是缓冲区的物理地址,dma_bytes是缓冲区的大小,dma_private是ALSA的DMA管理用到的。dma_buffer是在pcm_new()中初始化的;当然也可以把分配dma缓冲区的工作放到这部分来实现,但考虑到减少碎片,故还是在pcm_new中以最大size(即buffer_bytes_max)来分配。
关于DMA的中断处理
另外留意open函数中的audio_dma_request(&s[0], audio_dma_callback);中的audio_dma_callback,这是dma的中断函数,这里以callback的形式存在,其实到dma的底层还是这样的形式:static irqreturn_t dma_irq_handler(int irq, void *dev_id),在DMA中断处理dma_irq_handler()中调用callback。这些跟具体硬件平台的DMA实现相关,如果没有类似的机制,那么还是要在pcm模块中实现这个中断。
- void rockchip_pcm_dma_irq(s32 ch, void *data)
- {
- struct snd_pcm_substream *substream = data;
- struct rockchip_runtime_data *prtd;
- unsigned long flags;
- DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
- prtd = substream->runtime->private_data;
- if (substream)
- snd_pcm_period_elapsed(substream);
- spin_lock(&prtd->lock);
- prtd->dma_loaded--;
- if (prtd->state & ST_RUNNING) {
- rockchip_pcm_enqueue(substream);
- }
- spin_unlock(&prtd->lock);
- local_irq_save(flags);
- if (prtd->state & ST_RUNNING) {
- if (prtd->dma_loaded) {
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- audio_start_dma(substream, DMA_MODE_WRITE);
- else
- audio_start_dma(substream, DMA_MODE_READ);
- }
- }
- local_irq_restore(flags);
- }
prepare
当pcm“准备好了”调用该函数。在这里根据channels、buffer_bytes等来设定DMA传输参数,跟具体硬件平台相关。注:每次调用snd_pcm_prepare()的时候均会调用prepare函数。
trigger
当pcm开始、停止、暂停的时候都会调用trigger函数。
- static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
- {
- struct rockchip_runtime_data *prtd = substream->runtime->private_data;
- int ret = 0;
- /**************add by qiuen for volume*****/
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai;
- int vol = 0;
- int streamType = 0;
- DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
- if(cmd==SNDRV_PCM_TRIGGER_VOLUME){
- vol = substream->number % 100;
- streamType = (substream->number / 100) % 100;
- DBG("enter:vol=%d,streamType=%d\n",vol,streamType);
- if(pCodec_dai->ops->set_volume)
- pCodec_dai->ops->set_volume(streamType, vol);
- }
- /****************************************************/
- spin_lock(&prtd->lock);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- DBG(" START \n");
- prtd->state |= ST_RUNNING;
- rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START);
- /*
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- audio_start_dma(substream, DMA_MODE_WRITE);
- } else {
- audio_start_dma(substream, DMA_MODE_READ);
- }
- */
- #ifdef CONFIG_ANDROID_POWER
- android_lock_suspend(&audio_lock);
- DBG("%s::start audio , lock system suspend\n" , __func__ );
- #endif
- break;
- case SNDRV_PCM_TRIGGER_RESUME:
- DBG(" RESUME \n");
- break;
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- DBG(" RESTART \n");
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- DBG(" STOPS \n");
- prtd->state &= ~ST_RUNNING;
- rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP);
- //disable_dma(prtd->params->channel);
- #ifdef CONFIG_ANDROID_POWER
- android_unlock_suspend(&audio_lock );
- DBG("%s::stop audio , unlock system suspend\n" , __func__ );
- #endif
- break;
- default:
- ret = -EINVAL;
- break;
- }
- spin_unlock(&prtd->lock);
- return ret;
- }
Trigger函数里面的操作应该是原子的,不要在调用这些操作时进入睡眠,trigger函数应尽量小,甚至仅仅是触发DMA。
pointer
static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream)
PCM中间层通过调用这个函数来获取缓冲区的位置。一般情况下,在中断函数中调用snd_pcm_period_elapsed()或在pcm中间层更新buffer的时候调用它。然后pcm中间层会更新指针位置和计算缓冲区可用空间,唤醒那些在等待的线程。这个函数也是原子的。
snd_pcm_runtime
我们会留意到ops各成员函数均需要取得一个snd_pcm_runtime结构体指针,这个指针可以通过substream->runtime来获得。snd_pcm_runtime是pcm运行时的信息。当打开一个pcm子流时,pcm运行时实例就会分配给这个子流。它拥有很多多种信息:hw_params和sw_params配置拷贝,缓冲区指针,mmap记录,自旋锁等。snd_pcm_runtime对于驱动程序操作集函数是只读的,仅pcm中间层可以改变或更新这些信息。
linux alsa pcm(此pcm非硬件pcm接口)的更多相关文章
- Linux ALSA声卡驱动之三:PCM设备的创建
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 1. PCM是什么 模数转换 模拟信号经过pcm(脉冲编码调制)后为pcm数据: PCM是 ...
- 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)
作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第二篇,主要讲述发送端程序的原理和过程. 第一 ...
- 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)
作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程. 第一 ...
- Linux ALSA介绍
1. 介绍 ALSA(即Advanced Linux Sound Architecture), 是目前Linux的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示 TIP: 笔者的代 ...
- Linux ALSA声卡驱动之八:ASoC架构中的Platform
1. Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...
- 基于Linux ALSA音频驱动的wav文件解析及播放程序 2012
本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重 ...
- 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一)
作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第一篇,作为前言和概述. 第二篇:基于Oran ...
- Linux企业集群用商用硬件和免费软件构建高可用集群PDF
Linux企业集群:用商用硬件和免费软件构建高可用集群 目录: 译者序致谢前言绪论第一部分 集群资源 第1章 启动服务 第2章 处理数据包 第3章 编译内容 第二部分 高可用性 第4章 使用rsync ...
- Widows与linux关于隐形文件和非隐形文件の对比
Widows与linux关于隐形文件和非隐形文件の对比 对于windows来说 ,它本身有一些隐藏文件,为了防止一些菜鸟不小心把电脑的主要文件删除,还有就是里面存放一些你不知道的后门. 对此我们一些同 ...
随机推荐
- UTF-8和GBK的区别
GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准).GBK编码专门用来解决中文编码的,是双字节的.不论中英文都是双字节的. UTF-8编码是用以解决国际上字符的一种 ...
- HTML 网页中以超链接的方式调用iphone 手机的app
2011-11-13 14:36:33| 分类: 随笔 | 标签:iphone 调用iphone手机app |举报|字号 订阅 <1>. 调用iphone 手机地图APP的 ...
- Hello JFinal World
JFinal 是基于 Java 语言的极速 WEB + ORM 开发框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展.Restful.在拥有Java 语言所有优势 ...
- JavaScript 框架(库)
JavaScript 高级程序设计(特别是对浏览器差异的复杂处理),通常很困难也很耗时. 为了应对这些调整,许多的 JavaScript (helper) 库应运而生. 这些 JavaScript 库 ...
- Mysql事务-隔离级别
MYSQL事务-隔离级别 事务是什么? 事务简言之就是一组SQL执行要么全部成功,要么全部失败.MYSQL的事务在存储引擎层实现. 事务都有ACID特性: 原子性(Atomicity):一个事务必须被 ...
- unity3d面试题与参考答案
1.C#程序题 1 2 3 4 5 6 7 8 9 10 11 private static void aaa(int x) { x = 10; } private static void bbb ...
- ExtJs学习-搭建开发环境
Extjs是一个非常棒的ajax框架,可以用来开发十分绚丽外观的客户端框架,能使B/S框架更加具有活力.它是一个用javascript编写的框架,与后台技术无关的ajax框架.因此,可以把ExtJs使 ...
- eclipse 4.3 汉化
打开浏览器,浏览“参考资料”内给出的“eclipse语言包下载”地址,在博客新页面找到地址链接,如图所示.“Babel Language...”开头的一栏下面就是各个eclise版本的语言包,此处以I ...
- Observable观察者模式的使用
今天我们公司封装的类中没有加上Observable观察者模式,但是很多地方需要用到Observable观察者模式 接下来就向大家介绍一下我的使用吧! 在介绍之前我们写了一个方法 public clas ...
- Android开发:《Gradle Recipes for Android》阅读笔记(翻译)3.5——在flavors间合并java代码
问题: 你想要在单独的product flavors里面增加Acitivity或者其它java类. 解决方案: 创建合适的代码目录,增加java类,将它们和main代码合并. 讨论: flavors和 ...