之前写过一个音频驱动CODEC分析,当时忽略了阐述最基本的概念。要了解一个东西,首先要明白它是什么它起到什么作用,然后才会更好对它的工作流程更好的分析。所以这里提一下:

CODEC :音频芯片的控制,比如静音、打开(关闭)ADC(DAC)、设置ADC(DAC)的增益、耳机模式的检测等操作。

I2S   :数字音频接口,用于CPU和Codec之间的数字音频流raw data的传输。每当有playback或record操作时,snd_soc_dai_ops.prepare()会被调用,启动I2S总线。

PCM   :我不知道为什么会取这个模块名,它其实是定义DMA操作的,用于将音频数据通过DMA传到I2S控制器的FIFO中。

音频数据流向:

| copy_from_user |          | DMA |               | I2S/PCM/AC97 |
RAM--------------->dma buffer--------> I2S tx FIFO -----------------> CODEC -> SPK/Headset

PCM模块初始化

struct snd_soc_platform s3c_soc_platform = {
       .name         = "s3c-pcm-audio",
       .pcm_ops      = &s3c_pcm_ops,
       .pcm_new      = s3c_pcm_new,
       .pcm_free     = s3c_pcm_free_dma_buffers,
       .suspend      = s3c_pcm_suspend,
       .resume       = s3c_pcm_resume,
};
调用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 s3c_pcm_open(struct snd_pcm_substream *substream)
{
       struct snd_soc_pcm_runtime *rtd = substream->private_data;
       struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
       struct snd_pcm_runtime *runtime = substream->runtime;
       struct audio_stream_a *s = runtime->private_data;
       int ret;
 
       if (!cpu_dai->active) {
              audio_dma_request(&s[0], audio_dma_callback); //为playback stream分配DMA
              audio_dma_request(&s[1], audio_dma_callback); //为capture stream分配DMA
       }
       
       //设定runtime硬件参数
       snd_soc_set_runtime_hwparams(substream, &s3c_pcm_hardware);
 
       /* Ensure that buffer size is a multiple of period size */
       ret = snd_pcm_hw_constraint_integer(runtime,
                             SNDRV_PCM_HW_PARAM_PERIODS);
 
       return ret;
}
其中硬件参数要根据芯片的数据手册来定义,如:

static const struct snd_pcm_hardware s3c_pcm_hardware = {
       .info            = SNDRV_PCM_INFO_INTERLEAVED |
                                SNDRV_PCM_INFO_BLOCK_TRANSFER |
                                SNDRV_PCM_INFO_MMAP |
                                SNDRV_PCM_INFO_MMAP_VALID |
                                SNDRV_PCM_INFO_PAUSE |
                                SNDRV_PCM_INFO_RESUME,
       .formats         = SNDRV_PCM_FMTBIT_S16_LE |
                                SNDRV_PCM_FMTBIT_U16_LE |
                                SNDRV_PCM_FMTBIT_U8 |
                                SNDRV_PCM_FMTBIT_S8,
       .channels_min     = 2,
       .channels_max     = 2,
       .buffer_bytes_max = 128*1024,
       .period_bytes_min = PAGE_SIZE,
       .period_bytes_max = PAGE_SIZE*2,
       .periods_min      = 2,
       .periods_max      = 128,
       .fifo_size        = 32,
};
关于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。

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

/* 
 *  This is called when dma IRQ occurs at the end of each transmited block
 */
static void audio_dma_callback(void *data)
{
       struct audio_stream_a *s = data;    
 
       /* 
        * If we are getting a callback for an active stream then we inform
        * the PCM middle layer we've finished a period
        */
       if (s->active)
              snd_pcm_period_elapsed(s->stream);
 
       spin_lock(&s->dma_lock);
       if (s->periods > 0) 
              s->periods--;    
 
       audio_process_dma(s); //dma启动
       spin_unlock(&s->dma_lock);
}

hw_params
hw_params函数为substream(每打开一个playback或capture,ALSA core均产生相应的一个substream)设定DMA的源(目的)地址,以及DMA缓冲区的大小。

static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params)
{
       struct snd_pcm_runtime *runtime = substream->runtime;
       int err = 0;
 
       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
       runtime->dma_bytes = params_buffer_bytes(params);
       return err;
}
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)来分配。

prepare

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

trigger

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

static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
       struct runtime_data *prtd = substream->runtime->private_data;
       int ret = 0;
 
       spin_lock(&prtd->lock);
 
       switch (cmd) {
       case SNDRV_PCM_TRIGGER_START:
       case SNDRV_PCM_TRIGGER_RESUME:
       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
              prtd->state |= ST_RUNNING;
              dma_ctrl(prtd->params->channel, DMAOP_START); //DMA开启
              break;
 
       case SNDRV_PCM_TRIGGER_STOP:
       case SNDRV_PCM_TRIGGER_SUSPEND:
       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
              prtd->state &= ~ST_RUNNING;
              dma_ctrl(prtd->params->channel, DMAOP_STOP); //DMA停止
              break;
 
       default:
              ret = -EINVAL;
              break;
       }
 
       spin_unlock(&prtd->lock);
 
       return ret;
}
Trigger函数里面的操作应该是原子的,不要在调用这些操作时进入睡眠,trigger函数应尽量小,甚至仅仅是触发DMA。

pointer

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

ALSA之PCM分析相关推荐

  1. Linux ALSA音频框架分析五:HDA Driver分析

    Linux ALSA音频框架分析五:HDA Driver分析 一 概述 HDA(High Definition Audio)是intel设计的用来取代AC97的音频标准,硬件架构上由hda dodec ...

  2. ALSA驱动框架分析

    一.ALSA驱动框架介绍 1.ALSA介绍 ALSA(Advanced Linux Sound Architecture-高级linux声音架构),目前已经成为了linux的主流音频体系结构,ALSA ...

  3. Linux ALSA源码分析(基于Linux 5.18)

    Linux ALSA音频驱动框架详细的描述的ALSA驱动框架的分层及各模块的主要作用,现在从源码的角度来分析ALSA驱动. 1.ALSA驱动代码文件结构 在Linux5.18代码树中,Alsa的代码文 ...

  4. Alsa中PCM参数设置

    分类: LINUX 1) PCM设备的句柄. 2) 指定同时可供回放或截获的PCM流的方向 3) 提供一些关于我们想要使用的设置选项的信息,比如缓冲区大小,采样率,PCM数据格式等 4) 检查硬件是否 ...

  5. ALSA中PCM的使用

    一.预备知识 1.声音分分类 (0)极低频: 20-40Hz (1)低频: 40-80Hz (2)中低频: 80-160Hz(3)中频: 160Hz-1280Hz这个频段之间横跨的幅度是最宽的,几乎把 ...

  6. alsa录制pcm音频及fdk-aac编码

    1. 利用alsa库录制PCM音频,并保存为音频文件. (该部分代码摘自网络,原链接不记得了,侵删.但是代码亲测可用,在此做个demo备用) /*** @file record_pcm.c* @bri ...

  7. linux 3.0.8 alsa数据流程分析

    ALSA打开数据流程      soc_pcm_open => cpu_dai->driver->ops->startup => platform->driver- ...

  8. ALSA编程细节分析

    [Loong]:之前写过基于ALSA的WAV播放录音程序,见http://blog.csdn.net/sepnic/archive/2011/01/14/6140824.aspx.现在本想好好整理一下 ...

  9. alsa buffer原理_【关于alsa buffer】ALSA编程细节分析

    二. 编程细节 按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的: 1.1 设备层次 在alsa驱动这一层,目前为止,抽象出了4层设备: 一是hw:0,0: 二是plug ...

最新文章

  1. 深度学习时间序列预测:LSTM算法构建时间序列单变量模型预测大气压( air pressure)+代码实战
  2. jackson 问题定位
  3. 【企业管理】2019年11 月 每日花语
  4. Exploring the Amazon Echo Dot, Part 2: Into MediaTek utility hell
  5. css html 抽屉,CSS快速入门-前端布局1(抽屉)(示例代码)
  6. gin源码解析(1) - gin 与 net/http 的关系
  7. 分块-洛谷P3203 [HNOI2010]BOUNCE 弹飞绵羊
  8. 使用 Request.QueryString 接受参数时,跟编码有关的一些问题
  9. 12 大 AI App 技术创意,教你如何在 2020 年赚到钱
  10. 解决 ORA-28001: the password has expired 问题
  11. wince 内存释放_【转载】让我生不如死的WINCE内存泄漏
  12. Java之品优购课程讲义_day12(6)
  13. Karabiner Elements使用技巧分享,帮您简单修改使用键位
  14. 微信群机器人管理系统源码
  15. 施一公等团队登Science封面:AI与冷冻电镜揭示「原子级」NPC结构,生命科学突破...
  16. Android 关于 ActionBarSherlock 的使用
  17. php夜间,php实现自动开启/关闭夜间模式
  18. 滚轮JAVA_java滚轮
  19. 137、易燃固体的火灾危险性
  20. MATLAB - 三维图绘制

热门文章

  1. Heartbeat集群配置实例
  2. 《Java 7程序设计入门经典》一1.10 另一种数据类型
  3. Oracle的DBV工具
  4. Android人脸检测类FaceDetector
  5. Erlang编程语言的一些痛点
  6. sqlmap的篡改绕过WAF
  7. InfoPath中repeationg section动态填充数据
  8. 工信部召开地方信息安全工作会议
  9. java程序打包exe
  10. MATLAB编写自己的BP神经网络程序