丹江口网站开发,永久的免费网站地址,群晖非插件搭建wordpress,现在手机网站用什么做的tinyplay、tincap、pcm_open源码解析 一、本文的目的二、tinyplay.c源码分析三、tinycap.c源码分析四、pcm.c如何调度到Linux Kernel4.1 pcm_open解析4.1.1 pcm_open的主要流程4.1.2 流程说明4.1.3 调用方法 4.2 pcm_write解析 /*********************************************… tinyplay、tincap、pcm_open源码解析 一、本文的目的二、tinyplay.c源码分析三、tinycap.c源码分析四、pcm.c如何调度到Linux Kernel4.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的主要行为是 读取播放的文件 解析wav文件遍历了WAV文件中的每个数据块chunk直到找到音频数据块或无其他块可读。每个WAV文件都由RIFF数据块组成每个块都有一个类型标识符id和一个大小字段sz。 根据块的类型标识符id有三种可能的情况 如果ID是ID_FMT这表示块是“fmt ”块它会存储音频流的格式信息包括样本率、比特率等。代码读取这个块的内容到chunk_fmt结构体。如果实际上块的大小大于结构体的大小代码会跳过剩下的字节。 如果ID是ID_DATA这表示块包含了实际的音频样本数据。在这个情况下暂停块的读取通过设置more_chunks 为0并保持块的数据大小。 对于其他未知的块类型代码简单地跳过它。 最后调用pcm.c中的ops进一步的去对硬件进行访问涉及硬件有关操作的函数主要是pcm_open、pcm_write函数。pcm_frames_to_bytes函数用于将音频帧数转换为字节数此时有音频的位深、声道数信息就可以计算所需帧数据占用多少字节空间。
三、tinycap.c源码分析
tinycap的主要流程如下
概述一下上面的流程图tinycap的主要行为是
以wb模式打开文件wb模式表示以二进制模式打开一个文件进行写入操作。如果文件存在则文件被截断为零长度即文件的内容会被全部删除。如果文件不存在则创建一个新文件。读取命令行的参数并存储在结构体struct wav_header中对应tinycap定义的变量名字为header所有的内容将会存储在变量header中wav文件格式有文件的头部信息在wav文件的头部信息中有data_sz 字段用于记录整个音频数据占用字节故此头部信息可以在录音完成后再计算写入此处代码先跳过写wav的头部信息先读取录音数据后再写入头部信息。调用pcm.c中的ops进一步的去对硬件进行访问主要使用pcm_read函数去读录音数据pcm_open和pcm_close对设备进行开关。音频的处理一般是以帧为单位处理的但是在malloc内存 还有 读取数据的时候pcm_read的时候是以字节为单位。因此用pcm_frames_to_bytes函数转换帧数为字节数去申请内存然后会用pcm_bytes_to_frames将读取的字节转换为帧以保存在wav头部信息中。写入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的主要流程 概述一下上面的流程图tinycap的主要行为是
创建一个新的PCM对象并将传入的配置和标志复制到该对象中。接下来函数通过snd_utils_get_dev_node函数获取PCM设备的节点并通过snd_utils_get_node_type函数获取设备的类型。根据设备类型函数选择相应的操作集ops。函数尝试打开PCM设备。如果打开失败函数将清理已分配的资源并返回错误。如果设备成功打开函数将获取设备的信息并设置硬件参数hw_params。这些参数包括音频格式、子格式、周期大小、样本位数、帧位数、通道数、周期数和采样率。函数设置软件参数sw_params。这些参数包括时间戳模式、周期步长、启动阈值、停止阈值、可用最小值、传输对齐、静音阈值、静音大小和边界。函数尝试映射硬件状态。如果映射失败函数将清理已分配的资源并返回错误。如果映射成功函数将返回打开的PCM对象。
4.1.2 流程说明
函数选择相应的操作集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官方文档
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);
//...
}设置的硬件和软件参数的含义
总结表格
参数类型描述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进行处理。