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模块初始化:

[html] view plain copy

  1. struct snd_soc_platform rk29_soc_platform = {
  2. .name       = "rockchip-audio",
  3. .pcm_ops    = &rockchip_pcm_ops,
  4. .pcm_new    = rockchip_pcm_new,
  5. .pcm_free   = rockchip_pcm_free_dma_buffers,
  6. };
  7. EXPORT_SYMBOL_GPL(rk29_soc_platform);
  8. static int __init rockchip_soc_platform_init(void)
  9. {
  10. DBG("Enter::%s, %d\n", __FUNCTION__, __LINE__);
  11. return snd_soc_register_platform(&rk29_soc_platform);
  12. }
  13. module_init(rockchip_soc_platform_init);
  14. static void __exit rockchip_soc_platform_exit(void)
  15. {
  16. snd_soc_unregister_platform(&rk29_soc_platform);
  17. }

调用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结构体,该结构体的操作函数集的实现是本模块的主体。

[html] view plain copy

  1. struct snd_pcm_ops {
  2. int (*open)(struct snd_pcm_substream *substream);
  3. int (*close)(struct snd_pcm_substream *substream);
  4. int (*ioctl)(struct snd_pcm_substream * substream,
  5. unsigned int cmd, void *arg);
  6. int (*hw_params)(struct snd_pcm_substream *substream,
  7. struct snd_pcm_hw_params *params);
  8. int (*hw_free)(struct snd_pcm_substream *substream);
  9. int (*prepare)(struct snd_pcm_substream *substream);
  10. int (*trigger)(struct snd_pcm_substream *substream, int cmd);
  11. snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
  12. int (*copy)(struct snd_pcm_substream *substream, int channel,
  13. snd_pcm_uframes_t pos,
  14. void __user *buf, snd_pcm_uframes_t count);
  15. int (*silence)(struct snd_pcm_substream *substream, int channel,
  16. snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
  17. struct page *(*page)(struct snd_pcm_substream *substream,
  18. unsigned long offset);
  19. int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
  20. int (*ack)(struct snd_pcm_substream *substream);
  21. };

我们主要实现open、close、hw_params、hw_free、prepare和trigger接口。

open

open函数为PCM模块设定支持的传输模式、数据格式、通道数、period等参数,并为playback/capture stream分配相应的DMA通道。其一般实现如下:

[html] view plain copy

  1. static int rockchip_pcm_open(struct snd_pcm_substream *substream)
  2. {
  3. struct snd_pcm_runtime *runtime = substream->runtime;
  4. struct rockchip_runtime_data *prtd;
  5. DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
  6. snd_soc_set_runtime_hwparams(substream, &rockchip_pcm_hardware);
  7. prtd = kzalloc(sizeof(struct rockchip_runtime_data), GFP_KERNEL);
  8. if (prtd == NULL)
  9. return -ENOMEM;
  10. spin_lock_init(&prtd->lock);
  11. runtime->private_data = prtd;
  12. return 0;
  13. }

其中硬件参数要根据芯片的数据手册来定义,如:

[html] view plain copy

  1. int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
  2. const struct snd_pcm_hardware *hw)
  3. {
  4. struct snd_pcm_runtime *runtime = substream->runtime;
  5. runtime->hw.info = hw->info;
  6. runtime->hw.formats = hw->formats;
  7. runtime->hw.period_bytes_min = hw->period_bytes_min;
  8. runtime->hw.period_bytes_max = hw->period_bytes_max;
  9. runtime->hw.periods_min = hw->periods_min;
  10. runtime->hw.periods_max = hw->periods_max;
  11. runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
  12. runtime->hw.fifo_size = hw->fifo_size;
  13. return 0;
  14. }

关于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模块中实现这个中断。

[html] view plain copy

  1. void rockchip_pcm_dma_irq(s32 ch, void *data)
  2. {
  3. struct snd_pcm_substream *substream = data;
  4. struct rockchip_runtime_data *prtd;
  5. unsigned long flags;
  6. DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
  7. prtd = substream->runtime->private_data;
  8. if (substream)
  9. snd_pcm_period_elapsed(substream);
  10. spin_lock(&prtd->lock);
  11. prtd->dma_loaded--;
  12. if (prtd->state & ST_RUNNING) {
  13. rockchip_pcm_enqueue(substream);
  14. }
  15. spin_unlock(&prtd->lock);
  16. local_irq_save(flags);
  17. if (prtd->state & ST_RUNNING) {
  18. if (prtd->dma_loaded) {
  19. if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  20. audio_start_dma(substream, DMA_MODE_WRITE);
  21. else
  22. audio_start_dma(substream, DMA_MODE_READ);
  23. }
  24. }
  25. local_irq_restore(flags);
  26. }

prepare

当pcm“准备好了”调用该函数。在这里根据channels、buffer_bytes等来设定DMA传输参数,跟具体硬件平台相关。注:每次调用snd_pcm_prepare()的时候均会调用prepare函数。

trigger

当pcm开始、停止、暂停的时候都会调用trigger函数。

[html] view plain copy

  1. static int rockchip_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
  2. {
  3. struct rockchip_runtime_data *prtd = substream->runtime->private_data;
  4. int ret = 0;
  5. /**************add by qiuen for volume*****/
  6. struct snd_soc_pcm_runtime *rtd = substream->private_data;
  7. struct snd_soc_dai *pCodec_dai = rtd->dai->codec_dai;
  8. int vol = 0;
  9. int streamType = 0;
  10. DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
  11. if(cmd==SNDRV_PCM_TRIGGER_VOLUME){
  12. vol = substream->number % 100;
  13. streamType = (substream->number / 100) % 100;
  14. DBG("enter:vol=%d,streamType=%d\n",vol,streamType);
  15. if(pCodec_dai->ops->set_volume)
  16. pCodec_dai->ops->set_volume(streamType, vol);
  17. }
  18. /****************************************************/
  19. spin_lock(&prtd->lock);
  20. switch (cmd) {
  21. case SNDRV_PCM_TRIGGER_START:
  22. DBG(" START \n");
  23. prtd->state |= ST_RUNNING;
  24. rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_START);
  25. /*
  26. if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  27. audio_start_dma(substream, DMA_MODE_WRITE);
  28. } else {
  29. audio_start_dma(substream, DMA_MODE_READ);
  30. }
  31. */
  32. #ifdef CONFIG_ANDROID_POWER
  33. android_lock_suspend(&audio_lock);
  34. DBG("%s::start audio , lock system suspend\n" , __func__ );
  35. #endif
  36. break;
  37. case SNDRV_PCM_TRIGGER_RESUME:
  38. DBG(" RESUME \n");
  39. break;
  40. case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  41. DBG(" RESTART \n");
  42. break;
  43. case SNDRV_PCM_TRIGGER_STOP:
  44. case SNDRV_PCM_TRIGGER_SUSPEND:
  45. case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  46. DBG(" STOPS \n");
  47. prtd->state &= ~ST_RUNNING;
  48. rk29_dma_ctrl(prtd->params->channel, RK29_DMAOP_STOP);
  49. //disable_dma(prtd->params->channel);
  50. #ifdef CONFIG_ANDROID_POWER
  51. android_unlock_suspend(&audio_lock );
  52. DBG("%s::stop audio , unlock system suspend\n" , __func__ );
  53. #endif
  54. break;
  55. default:
  56. ret = -EINVAL;
  57. break;
  58. }
  59. spin_unlock(&prtd->lock);
  60. return ret;
  61. }

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中间层可以改变或更新这些信息。

原文地址:https://www.cnblogs.com/newjiang/p/9026071.html

时间: 2024-11-13 08:55:25

linux alsa pcm(此pcm非硬件pcm接口)的相关文章

Linux ALSA声卡驱动之三:PCM设备的创建

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 1. PCM是什么 PCM是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制.我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精度进行量化,这些量化后的数值被连续地输出.传输.处

Linux ALSA 音频系统:物理链路篇

1. Overview 硬件平台及软件版本: Kernel - 3.4.5 SoC - Samsung exynos CODEC - WM8994 Machine - goni_wm8994 Userspace - tinyalsa Linux ALSA 音频系统架构大致如下: +--------+ +--------+ +--------+ |tinyplay| |tinycap | |tinymix | +--------+ +--------+ +--------+ | ^ ^ V | V

Linux ALSA 音频系统:逻辑设备篇

6. 声卡和 PCM 设备的建立过程 前面几章分析了 Codec.Platform.Machine 驱动的组成部分及其注册过程,这三者都是物理设备相关的,大家应该对音频物理链路有了一定的认知.接着分析音频驱动的中间层,由于这些并不是真正的物理设备,故我们称之为逻辑设备. PCM 逻辑设备,我们又习惯称之为 PCM 中间层或 pcm native,起着承上启下的作用:往上是与用户态接口的交互,实现音频数据在用户态和内核态之间的拷贝:往下是触发 codec.platform.machine 的操作函

基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)

作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第二篇,主要讲述发送端程序的原理和过程. 第一篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一) 第三篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三) 以下是正文: 发送端程序基于MFC的对话框类实现,开发环境Visual Studio 2012,主要实现了5个功能,下面逐个讲述:

基于Linux ALSA音频驱动的wav文件解析及播放程序 2012

本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重要参数,利用alsa音频驱动的API设置好参数,最后打开wav文件,定位到数据区,把音频数据依次写到音频驱动中去,开始播放,当写入完成后,退出写入的循环. 注意:本设计需要alsa的libasound-dev的库,编译链接时需要连接 —lasound. #include<stdio.h>#incl

基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)

作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程. 第一篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一) 第二篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二) 以下是正文: 在进行接收端程序开发前,首先要了解Orangpi Zero的声音设备. Orangpi可以通过ALSA(The Adv

Linux ALSA介绍

1. 介绍 ALSA(即Advanced Linux Sound Architecture), 是目前Linux的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示 TIP: 笔者的代码分析基于linux-4.14.19 2. 初始化 系统启动中ALSA初始化过程如下 alsa_sound_init() /* 注册alsa字符设备 */ register_chrdev(116, "alsa", &snd_fops) /* 创建/proc/asound目录及下属v

Kali Linux 安装 卡在 探测网络硬件 解决方法

在虚拟机上安装 Kali 的时候什么问题都没有,在实体机上安装就出现问题了. 首先选择的是 Install,就是文字安装,安装的时候经过选择国家语言,到探测网络硬件,屏幕上显示"探测网络硬件",然后就一直卡在那.然后又用了 Graphical Install,结果还是一样,等了半个小时还是卡着不动. 在网上找了有方法可以跳过探测网络硬件的,但是我试过了发现还是不行,因为他跳过探测网络硬件的话,后面的获取网络地址还是会卡住. 然后没办法了,就准备先从 Live 进系统看看,进这个倒是一点

Linux学习—centos7设置开机非图形化

centos7修改默认开机状态 在centos6中的开机默认开机状态保存在/etc/inittab文件中,那centos7中会不会发生变化呢,我们不妨同样地查看centos7中的/etc/inittab文件: 发现和centos6中的内容不一样,大致内容为centos7中不再使用runlevels的概念,而是改用targets来代表运行状态,multi-user.target对应centos6中的runlevel 3(多用户模式),graphical.target对应于centos6中的runl