Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上

简介: Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上


1、基本概念及逻辑关系

如上图,通过上一节声卡的学习我们已经知道PCM是声卡的一个子设备,或者表示一个PCM实例。

每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了。

一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。可以用如下图来表示他们直接的逻辑关系:

当一个子流已经存在,并且已经被打开,当再次被打开的时候,会被阻塞。        

在实际的应用中,通常不会如上图这么复杂,大多数情况下是一个声卡有一个PCM实例,PCM下面有一个playback和capture,而playback和capture各自有一个substream。

PCM层有几个很重要的结构体,我们通过如下的UML图来梳理他们直接的关系。

图片地址:http://hi.csdn.net/attachment/201104/2/0_1301728746sAUd.gif

       1、snd_pcm:挂在snd_card下面的一个snd_device。

       2、snd_pcm中的字段:streams[2]:该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream。

       3、snd_pcm_str中的substream字段:指向snd_pcm_substream结构。

       4、snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。

2、PCM创建流程

       PCM的整个创建流程请参考如下时序图进行理解:

alsa-driver的中间层已经提供新建PCM的API:

2.1、创建PCM实例

int snd_pcm_new(struct snd_card *card, const char *id, int device,
    int playback_count, int capture_count, struct snd_pcm **rpcm)

card:表示所属的声卡。

       ID:PCM实例的ID(名字)。

       device:表示目前创建的是该声卡下的第几个PCM,第一个PCM设备从0开始计数。

       playback_count:表示该PCM播放流中将会有几个substream。

       capture_count :表示该PCM录音流中将会有几个substream。

       rpcm:返回的PCM实例。

       该函数的主要作用是创建PCM逻辑设备,创建回放子流和录制子流实例,并初始化回放子流和录制子流的PCM操作函数(数据搬运时,需要调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备工作)。

2.2、设置PCM设备的操作函数

void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
         const struct snd_pcm_ops *ops)

pcm:上述snd_pcm_new 创建的PCM实例。

       direction:是指SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE,即设置为播放或者录音功能。

       snd_pcm_ops:结构中的函数通常就是我们驱动要实现的函数。

2.3、定义PCM的操作函数

       以AC97驱动(linux/sound/arm/pxa2xx-ac97.c)为例,在驱动中对于PCM进行了如下设置:

static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = {
  .open   = pxa2xx_ac97_pcm_open,
  .close    = pxa2xx_ac97_pcm_close,
  .hw_params  = pxa2xx_pcm_hw_params,
  .prepare  = pxa2xx_ac97_pcm_prepare,
  .trigger  = pxa2xx_pcm_trigger,
  .pointer  = pxa2xx_pcm_pointer,
};
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);

2.4、定义硬件参数

static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
  .info     = SNDRV_PCM_INFO_MMAP |
          SNDRV_PCM_INFO_MMAP_VALID |
          SNDRV_PCM_INFO_INTERLEAVED |
          SNDRV_PCM_INFO_PAUSE |
          SNDRV_PCM_INFO_RESUME,
  .formats    = SNDRV_PCM_FMTBIT_S16_LE |
          SNDRV_PCM_FMTBIT_S24_LE |
          SNDRV_PCM_FMTBIT_S32_LE,
  .period_bytes_min = 32,
  .period_bytes_max = 8192 - 32,
  .periods_min    = 1,
  .periods_max    = 256,
  .buffer_bytes_max = 128 * 1024,
  .fifo_size    = 32,
};
int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
{
  struct snd_soc_pcm_runtime *rtd = substream->private_data;
  struct snd_pcm_runtime *runtime = substream->runtime;
  struct snd_dmaengine_dai_dma_data *dma_params;
  int ret;
  runtime->hw = pxa2xx_pcm_hardware;
  dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
  if (!dma_params)
    return 0;
  /*
   * For mysterious reasons (and despite what the manual says)
   * playback samples are lost if the DMA count is not a multiple
   * of the DMA burst size.  Let's add a rule to enforce that.
   */
  ret = snd_pcm_hw_constraint_step(runtime, 0,
    SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
  if (ret)
    return ret;
  ret = snd_pcm_hw_constraint_step(runtime, 0,
    SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
  if (ret)
    return ret;
  ret = snd_pcm_hw_constraint_integer(runtime,
              SNDRV_PCM_HW_PARAM_PERIODS);
  if (ret < 0)
    return ret;
  return snd_dmaengine_pcm_open(
    substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev,
                 dma_params->chan_name));
}


相关文章
|
11天前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
11天前
|
Linux 程序员 芯片
【Linux驱动】普通字符设备驱动程序框架
【Linux驱动】普通字符设备驱动程序框架
|
23天前
|
Linux 数据安全/隐私保护 Windows
linux 搭建cloudreve win映射网络驱动器WebDav
linux 搭建cloudreve win映射网络驱动器WebDav
26 1
|
10天前
|
Linux
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
【Linux驱动学习(1)】USB与input子系统,linux统一设备模型,枚举,USB描述符深入剖析
|
1天前
|
安全 Linux Shell
Linux中SSH命令介绍
Linux中SSH命令介绍
10 2
|
1天前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
6 1
|
1天前
|
存储 Linux 程序员
tar命令详解:linux文件打包神器
tar命令详解:linux文件打包神器
|
2天前
|
Linux 应用服务中间件 数据安全/隐私保护