【TinyALSA全解析(三)】tinyplay、tincap、pcm_open源码解析

tinyplay、tincap、pcm_open源码解析

  • 一、本文的目的
  • 二、tinyplay.c源码分析
  • 三、tinycap.c源码分析
  • 四、pcm.c如何调度到Linux Kernel
    • 4.1 pcm_open解析
      • 4.1.1 pcm_open的主要流程
      • 4.1.2 流程说明
      • 4.1.3 调用方法
    • 4.2 pcm_write解析

/*****************************************************************************************************************/

声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!

创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢

/*****************************************************************************************************************/

一、本文的目的

本文分析tinyalsa中的的tinyplay和tinycap源码,tinyplay和tinycap是两个小工具。会分析这两个工具主要是调用pcm.c和pcm.h实现访问Linux kernel的硬件接口

本文先解析一下这两个工具的源代码,再分析一下pcm.c这个文件是怎么访问到底层的。

学习这两个工具的意义不仅仅是对这两个工具有所了解,在安卓的音频架构中,Android Audio HAL层的代码对硬件的访问也会通过pcm.c文件进行访问

tinyalsa的具体源码完整可以见Android 12_r8官方源码网站。本文以安卓12的r8分支进行讲解,安卓9-安卓14的tinyalsa的实现是大同小异的,掌握安卓12的TinyALSA源码再去看其它版本的安卓源码也是可以很快入手的。

二、tinyplay.c源码分析

tinyplay的主要流程如下:
tinyplay主要流程
概述一下上面的流程图,tinyplay的主要行为是:

  1. 读取播放的文件

  2. 解析wav文件,遍历了WAV文件中的每个数据块(chunk),直到找到音频数据块或无其他块可读。每个WAV文件都由RIFF数据块组成,每个块都有一个类型标识符(id)和一个大小字段(sz)。

    根据块的类型标识符(id),有三种可能的情况:

    • 如果ID是ID_FMT,这表示块是“fmt ”块,它会存储音频流的格式信息,包括样本率、比特率等。代码读取这个块的内容到chunk_fmt结构体。如果实际上块的大小大于结构体的大小,代码会跳过剩下的字节。

    • 如果ID是ID_DATA,这表示块包含了实际的音频样本数据。在这个情况下,暂停块的读取(通过设置more_chunks 为0)并保持块的数据大小。

    • 对于其他未知的块类型,代码简单地跳过它。

  3. 最后调用pcm.c中的ops进一步的去对硬件进行访问,涉及硬件有关操作的函数主要是pcm_open、pcm_write函数。pcm_frames_to_bytes函数用于将音频帧数转换为字节数(此时有音频的位深、声道数信息,就可以计算所需帧数据占用多少字节空间)。

三、tinycap.c源码分析

tinycap的主要流程如下:
tinycap主要流程

概述一下上面的流程图,tinycap的主要行为是:

  1. 以wb模式打开文件,wb模式表示以二进制模式打开一个文件进行写入操作。如果文件存在,则文件被截断为零长度(即文件的内容会被全部删除)。如果文件不存在,则创建一个新文件。
  2. 读取命令行的参数并存储在结构体struct wav_header中(对应tinycap,定义的变量名字为header,所有的内容将会存储在变量header中)
  3. wav文件格式有文件的头部信息,在wav文件的头部信息中有data_sz 字段用于记录整个音频数据占用字节,故此头部信息可以在录音完成后再计算写入,此处代码先跳过写wav的头部信息,先读取录音数据后再写入头部信息
  4. 调用pcm.c中的ops进一步的去对硬件进行访问,主要使用pcm_read函数去读录音数据,pcm_open和pcm_close对设备进行开关。音频的处理一般是以帧为单位处理的,但是在malloc内存 还有 读取数据的时候(pcm_read)的时候,是以字节为单位。因此用pcm_frames_to_bytes函数转换帧数为字节数去申请内存,然后会用pcm_bytes_to_frames将读取的字节转换为帧以保存在wav头部信息中。
  5. 写入wav文件的头部信息

四、pcm.c如何调度到Linux Kernel

在 pcm.c 中,TinyALSA 使用了一些系统调用(如 open(), ioctl(), mmap(), close() 等)来访问和控制 ALSA 音频设备。这些系统调用是 Linux 内核提供的接口,应用程序可以通过这些接口与内核进行交互。

其中open() 系统调用用于打开 ALSA 设备,ioctl() 系统调用用于控制 ALSA 设备(如设置音频参数),mmap() 系统调用用于映射 ALSA 设备的内存,以便应用程序可以直接访问这些内存,close() 系统调用用于关闭 ALSA 设备

Tip:
ALSA是位于Linux Kernel层面的音频系统。
TinyALSA是AOSP(Android Open Source Project)的一部分。
TinyALSA位于ALSA的上层,他们之间的关系是使用关系。
TinyALSA使用 ALSA 提供的接口与 Linux 内核进行交互。

本文就以pcm_open和pcm_write函数为例进行讲解,pcm_close和pcm_read函数的源码其实是类似的操作。熟悉pcm_open和pcm_write函数后,再去看pcm.c文件中的其它函数源码会有举一反三的效果。

参考源码:Android 12_r8官方pcm.c文件源码

4.1 pcm_open解析

4.1.1 pcm_open的主要流程

pcm_open主要流程

概述一下上面的流程图,tinycap的主要行为是:

  1. 创建一个新的PCM对象,并将传入的配置和标志复制到该对象中。
  2. 接下来,函数通过snd_utils_get_dev_node函数获取PCM设备的节点,并通过snd_utils_get_node_type函数获取设备的类型。根据设备类型,函数选择相应的操作集(ops)。
  3. 函数尝试打开PCM设备。如果打开失败,函数将清理已分配的资源并返回错误。如果设备成功打开,函数将获取设备的信息,并设置硬件参数(hw_params)。这些参数包括音频格式、子格式、周期大小、样本位数、帧位数、通道数、周期数和采样率
  4. 函数设置软件参数(sw_params)。这些参数包括时间戳模式、周期步长、启动阈值、停止阈值、可用最小值、传输对齐、静音阈值、静音大小和边界
  5. 函数尝试映射硬件状态。如果映射失败,函数将清理已分配的资源并返回错误。如果映射成功,函数将返回打开的PCM对象。

4.1.2 流程说明

  1. 函数选择相应的操作集(ops)主要有两种:
//plug_ops
struct pcm_ops plug_ops = {.open = pcm_plug_open,.close = pcm_plug_close,.ioctl = pcm_plug_ioctl,.mmap = pcm_plug_mmap,.munmap = pcm_plug_munmap,.poll = pcm_plug_poll,
};
//hw_ops
struct pcm_ops hw_ops = {.open = pcm_hw_open,.close = pcm_hw_close,.ioctl = pcm_hw_ioctl,.mmap = pcm_hw_mmap,.munmap = pcm_hw_munmap,.poll = pcm_hw_poll,
};

hw_ops指的就是操作硬件设备的一组函数。plug_ops是处理plug插件的一组函数。

Tip: plug 是 ALSA 中的一种插件,它可以自动进行音频格式转换,例如从一个采样率转换到另一个采样率,或者从一个音频格式转换到另一个音频格式。参考ALSA官方文档中的:ALSA官方文档

  1. pcm_hw_open访问硬件的方式

主要就是通过节点访问到Linux kernel层面

static int pcm_hw_open(unsigned int card, unsigned int device,unsigned int flags, void **data,__attribute__((unused)) void *node)
{
//...snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,flags & PCM_IN ? 'c' : 'p');fd = open(fn, O_RDWR|O_NONBLOCK);
//...
}
  1. 设置的硬件和软件参数的含义

总结表格:

参数类型描述
SNDRV_PCM_HW_PARAM_FORMAT硬件PCM数据的格式,例如16位、24位、32位等
SNDRV_PCM_HW_PARAM_SUBFORMAT硬件PCM数据的子格式,通常设置为SNDRV_PCM_SUBFORMAT_STD,表示标准的线性PCM格式
SNDRV_PCM_HW_PARAM_PERIOD_SIZE硬件每个周期的帧数
SNDRV_PCM_HW_PARAM_SAMPLE_BITS硬件每个样本的位数
SNDRV_PCM_HW_PARAM_FRAME_BITS硬件每个帧的位数,等于样本位数乘以通道数
SNDRV_PCM_HW_PARAM_CHANNELS硬件音频数据的通道数
SNDRV_PCM_HW_PARAM_PERIODS硬件缓冲区中周期的数量
SNDRV_PCM_HW_PARAM_RATE硬件音频数据的采样率
sparams.tstamp_mode软件时间戳模式
sparams.period_step软件周期步长
sparams.start_threshold软件启动阈值
sparams.stop_threshold软件停止阈值
sparams.avail_min软件可用的最小帧数
sparams.xfer_align软件传输对齐
sparams.silence_threshold软件静音阈值
sparams.silence_size软件静音大小
sparams.boundary软件边界,定义了循环缓冲区的大小

硬件参数说明:

SNDRV_PCM_HW_PARAM_FORMAT:这是PCM数据的格式,例如16位、24位、32位等。

SNDRV_PCM_HW_PARAM_SUBFORMAT:这是PCM数据的子格式,通常设置为SNDRV_PCM_SUBFORMAT_STD,表示标准的线性PCM格式。

SNDRV_PCM_HW_PARAM_PERIOD_SIZE:这是每个周期的帧数。一个周期是音频数据的一个块,当一个周期的数据被播放或录制后,驱动程序将生成一个中断。

SNDRV_PCM_HW_PARAM_SAMPLE_BITS:这是每个样本的位数,它与SNDRV_PCM_HW_PARAM_FORMAT相关。

SNDRV_PCM_HW_PARAM_FRAME_BITS:这是每个帧的位数,它等于样本位数乘以通道数。

SNDRV_PCM_HW_PARAM_CHANNELS:这是音频数据的通道数,例如立体声有两个通道。

SNDRV_PCM_HW_PARAM_PERIODS:这是缓冲区中周期的数量。缓冲区是存储音频数据的内存区域,它由多个周期组成。

SNDRV_PCM_HW_PARAM_RATE:这是音频数据的采样率,例如44100Hz或48000Hz。

软件参数说明:

sparams.tstamp_mode:这是时间戳模式,设置为SNDRV_PCM_TSTAMP_ENABLE表示启用时间戳。

sparams.period_step:这是周期步长,通常设置为1。

sparams.start_threshold:这是启动阈值,当可用的帧数达到这个值时,播放或录制将开始。

sparams.stop_threshold:这是停止阈值,当可用的帧数达到这个值时,播放或录制将停止。

sparams.avail_min:这是可用的最小帧数,当可用的帧数达到这个值时,驱动程序将生成一个中断。

sparams.xfer_align:这是传输对齐,通常设置为周期大小的一半。

sparams.silence_threshold:这是静音阈值,当可用的帧数低于这个值时,驱动程序将生成静音数据。

sparams.silence_size:这是静音大小,它定义了静音数据的长度。

sparams.boundary:这是边界,它定义了循环缓冲区的大小。

4.1.3 调用方法

举个例子,如果想用音频节点0-0播放48K 2ch u16bit声音,可以这样设置参数:

unsigned int card = 0;
unsigned int device = 0;
unsigned int flags = PCM_OUT;
struct pcm_config config = {.channels = 2,.rate = 48000,.format = PCM_FORMAT_S16_LE,.period_size = 1024,.period_count = 4,
};
struct pcm *pcm = pcm_open(card, device, flags, &config);

4.2 pcm_write解析

源码的解析如下:

int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{// 定义一个snd_xferi结构体,用于存储音频数据和帧数struct snd_xferi x;// 检查PCM设备是否是输入设备,如果是,返回错误if (pcm->flags & PCM_IN)return -EINVAL;// 设置snd_xferi结构体的buf字段为传入的数据x.buf = (void*)data;// 设置snd_xferi结构体的frames字段为传入的数据量除以每帧的字节数x.frames = count / (pcm->config.channels *pcm_format_to_bits(pcm->config.format) / 8);// 进入一个无限循环for (;;) {// 检查PCM设备是否正在运行if (!pcm->running) {// 如果不是,尝试准备PCM设备int prepare_error = pcm_prepare(pcm);// 如果准备失败,返回错误if (prepare_error)return prepare_error;// 尝试向PCM设备写入初始数据if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))// 如果写入失败,返回错误return oops(pcm, errno, "cannot write initial data");// 如果写入成功,将PCM设备标记为正在运行,并返回0pcm->running = 1;return 0;}// 如果PCM设备正在运行,尝试向设备写入数据if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {// 如果写入失败,重置PCM设备的prepared和running字段pcm->prepared = 0;pcm->running = 0;// 检查错误是否是EPIPE(管道破裂)if (errno == EPIPE) {// 如果是,增加underruns计数,并根据PCM设备的flags字段决定是否重新开始pcm->underruns++;if (pcm->flags & PCM_NORESTART)return -EPIPE;continue;}// 如果不是EPIPE错误,返回错误return oops(pcm, errno, "cannot write stream data");}// 如果写入成功,返回0return 0;}
}

主要就是参数和环境检查,然后就调用ioctl进行处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/209402.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git:分布式版本控制系统的崛起与演变

简介 Git是一个开源的分布式版本控制系统,旨在有效、高速地处理从很小到非常大的项目版本管理。它是由Linus Torvalds于2005年创建的,最初是为了服务于Linux内核开发的版本控制需求。Git通过强大的分支功能、高效的缓存机制以及可扩展的架构设计&#xf…

会话 cookie 及隐私的那些事

什么是会话 Cookie? 会话 Cookie 的概念非常简单。 会话 Cookie,也称为临时 Cookie 或内存 Cookie,是网站在浏览会话期间存储在用户计算机或设备上的小数据片段。 它是由网站生成并由您的浏览器存储和使用的多种 Cookie 之一。 常规 Cookie 或“持久”Cookie 是通常在您的…

移动硬盘灯闪但读不出?3个方法轻松解决!

“我的移动硬盘用了很久了,最近总是会出现插入后明明灯闪了,但是却什么都读不出的情况。这是为什么呀?有什么简单的解决方法吗?” 当移动硬盘的指示灯闪烁却无法成功读取数据时,这可能引起许多用户的疑虑和困扰。怎么解…

数字孪生压缩空气储能管控平台

压缩空气储能在解决可再生能源不稳定性和提供可靠能源供应方面具有重要的优势。压缩空气储能,是指在电网负荷低谷期将电能用于压缩空气,在电网负荷高峰期释放压缩空气推动汽轮机发电的储能方式。通过提高能量转换效率、增加储能密度、快速启动和调节能力…

linux安装镜像cento7

点击创建新的虚拟机 点击典型,下一步 浏览,centos7下载文件的位置 找到位置后,效果如下图所示 下一步,填写用户名和密码,再点击下一步 给虚拟机起名字,默认就行;虚拟机安装路径,默认…

Python生产者消费者模型

额滴名片儿 🎈 博主:一只程序猿子 🎈 博客主页:一只程序猿子 博客主页 🎈 个人介绍:爱好(bushi)编程! 🎈 创作不易:如喜欢麻烦您点个👍或者点个⭐&#xff01…

为什么SSL证书要设有有效期?

在当今的数字化时代,网络安全已经成为了每个企业和个人都必须关注的重要问题。为了保护网站数据的安全传输,SSL证书应运而生。然而,你是否注意到,SSL证书并不是永久有效的,而是有一定的有效期。那么,为什么…

ZooKeeper 如何保证数据一致性?

在分布式场景中,ZooKeeper 的应用非常广泛,比如数据发布和订阅、命名服务、配置中心、注册中心、分布式锁等。 ZooKeeper 提供了一个类似于 Linux 文件系统的数据模型,和基于 Watcher 机制的分布式事件通知,这些特性都依赖 ZooKee…

【数据结构初阶(5)】链式队列的基本操作实现

文章目录 队列的定义初始化队列队尾入队列队头出队列取队头元素取队尾元素获取队列有效元素个数判断队空销毁队列 因为队列比较简单,关于队列的概念就不过多赘述了,本文只讲链队的基本操作实现 队列的定义 定义队列结点结构 链队中的每个结点都应该包…

hutool的bug之 DateUtil.endOfDay(DateUtil.date())

hutool 工具类DateUtil 使用时谨慎 DateUtil.endOfDay 得到的时间保存到数据时会增加一秒 首先比较下时间的long值: 这样就很明显的看出来,hutool工具类的date是毫秒位多了.999,保存到mysql 的时候,MySQL数据库对于毫秒大于500的数据进行…

算法通关村第十六关-白银挑战滑动窗口经典题目

大家好我是苏麟 , 今天带来滑动窗口经典的一些题目 . 我们继续来研究一些热门的、高频的滑动窗口问题 大纲 最长子串专题无重复字符的最长子串 长度最小的子数组盛最多水的容器 最长子串专题 无重复字符的最长子串 描述 : 给定一个字符串 s ,请你找出其中不含有重…

贸易公司ERP用什么软件好

不同行业的贸易公司有不同的业务结构和管理模式,日常经营管理过程中遇到的难点呈现多样化,而很多贸易公司在仓库、财务、销售、采购、订单、客户等业务一体化和部门协同效率等方面还有很多提升空间。 有些贸易公司涉及多仓库、多门店、多税制、多汇率、…

如何绕过某讯手游保护系统并从内存中获取Unity3D引擎的Dll文件

​ 某讯的手游保护系统用的都是一套,在其官宣的手游加固功能中有一项宣传是对比较热门的Unity3d引擎的手游保护方案,其中对Dll文件的保护介绍如下, “Dll加固混淆针对Unity游戏,对Dll模块的变量名、函数名、类名进行加密混淆处理&…

CSS实现小球边界碰撞回弹

如何通过CSS实现一个物体在屏幕中无限的边界碰撞回弹呢?我们可以使用动画效果实现 代码 我们只做一个小球,通过定位属性叠加动画的方式, 让小球在屏幕中进行运动,通过设置animation的alternate属性来设置回弹。最后,只…

笔记-基于CH579M模块通过网线直连电脑进行数据收发(无需网络)

刚学习,做个记录。 基于CH579M模块通过网线直连电脑进行数据收发(无需网络) 目录 一、工具1、CH579模块2、 网线3、电脑以及网络调试工具 二、操作步骤1、TCP/UDP等程序下载以及设置以太网IP2、网络断开3、检查以太网是否正常显示并稳定4、打开网络调试助手进行测试…

电气间隙和爬电距离的算法

电气间隙和爬电距离 一、定义 1、电气间隙:不同电位的两个导电部件间最短的空间直线距离。 2、爬电距离:不同电位的两个导电部件之间沿绝缘材料表面的最短距离。 3、隔离距离(机械式开关电器一个极的):满足对隔离器…

音频处理关键知识点

1 引言 现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。 目前我们在计算机上进行音频播放都需要依赖于音频文件。音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号…

echarts笔记-GeoJSON河北数据下并裁剪为冀北地图并使用echarts加载

首先找个网站把河北的GeoJSON数据下载下来,我用的是这个,理论上任意一个都可以 DataV.GeoAtlas地理小工具系列 将json数据下载后,进行裁剪,仅保留冀北数据。 如下,我裁剪的数据: {"type": &qu…

Python练习题(四)

本文主要是【Python】——Python练习题的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄每日一句:狠狠沉淀&a…

ssm医院门诊互联电子病历管理信息系统源码和论文

摘 要 网络的广泛应用给生活带来了十分的便利。所以把医院门诊互联电子病历管理与现在网络相结合,利用java技术建设医院门诊互联电子病历管理信息系统,实现医院门诊互联电子病历的信息化。则对于进一步提高医院门诊互联电子病历管理发展,对…