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));
}


相关文章
|
2月前
|
存储 Linux
Linux环境下删除大文件后磁盘空间未释放问题诊断流程。
以上诊断流程涉及Linux底层机制与高级管理技能结合之处,并需要管理员根据实际环境灵活调整诊断策略与解决方案。
120 8
|
3月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
173 0
|
6月前
|
Linux C语言
Linux读写锁源码分析
本文分析了读写锁的实现原理与应用场景,基于glibc 2.17源码。读写锁通过读引用计数、写线程ID、条件变量等实现,支持读优先(默认)和写优先模式。读优先时,写锁可能饥饿;写优先时,读线程需等待写锁释放。详细解析了`pthread_rwlock_t`数据结构及加解锁流程,并通过实验验证:2000个读线程与1个写线程测试下,读优先导致写锁饥饿,写优先则正常抢占锁。
159 19
|
7月前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
277 16
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
10月前
|
存储 Oracle 安全
服务器数据恢复—LINUX系统删除/格式化的数据恢复流程
Linux操作系统是世界上流行的操作系统之一,被广泛用于服务器、个人电脑、移动设备和嵌入式系统。Linux系统下数据被误删除或者误格式化的问题非常普遍。下面北亚企安数据恢复工程师简单聊一下基于linux的文件系统(EXT2/EXT3/EXT4/Reiserfs/Xfs) 下删除或者格式化的数据恢复流程和可行性。
|
12月前
|
监控 安全 Java
linux服务器上启动framework应用程序流程
【10月更文挑战第17天】在Linux服务器上启动Framework应用程序需经过准备工作、部署、启动、监控及访问五个步骤。首先确保服务器满足系统要求并安装依赖项;接着上传应用文件,编译构建,配置参数;然后通过脚本、命令行或系统服务启动应用;启动后检查日志,监控性能;最后确认访问地址,验证应用运行状态。具体操作应参照应用文档。
157 2
|
12月前
|
监控 Java Linux
linux服务器上启动framework应用程序流程
【10月更文挑战第18天】在 Linux 服务器上启动框架应用程序的流程包括:准备工作(确保访问权限、上传部署文件、了解启动要求)、检查依赖项、配置环境变量、切换到应用程序目录、启动应用程序、监控启动过程以及验证应用程序是否正常运行。具体步骤可能因应用程序类型和框架而异。
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
29天前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
105 16