版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pengfei240/article/details/79152013
0. 前言
本文主要介绍alsa-timer相关代码的分析内容。
1. 介绍
官方文档可以参见下面的链接:
官网上对Timer的介绍很简单,我根据自己的使用情况总结如下:
- Timer被设计为使用声卡内部的定时器,但是也可以被其它定时器驱动,比如内核中的 snd_timer.ko 就是一个基于系统定时器的Timer。
- Timer使用循环Buffer来存储信息。
- 定时器最高精度是ns级,但实际的精度依赖于时钟源。
- 可以使用 NONBLOCK 或 BLOCK 的模式打开定时器。
- 如果需要查询Timer的状态,需要使用 timer_query 的接口。
我们后面主要是基于NONBLOCK模式进行分析。
2. Timer ID
Timer ID主要由以下5个字段组成:
名称 | 值 | 说明 |
---|---|---|
class 区分声卡类别 | SNDRV_TIMER_CLASS_GLOBAL | 全局声卡,也就是snd_timer.ko提供的默认Timer,只能有一个 |
SNDRV_TIMER_CLASS_CARD | Card提供的声卡 | |
SNDRV_TIMER_CLASS_PCM | PCM提供的声卡 | |
sclass 区分声卡子类 | SNDRV_TIMER_SCLASS_APPLICATION | 表明是应用程序使用 |
card 声卡ID | - | - |
device 区分时钟源 | SNDRV_TIMER_GLOBAL_SYSTEM | 全局定时器 |
SNDRV_TIMER_GLOBAL_HPET | 高精度定时器 | |
SNDRV_TIMER_GLOBAL_HRTIMER | 高精度定时器 | |
subdevice | - | 如果有必要可以用于区分 Capture / Playback |
3. 用户使用
用户使用定时器比较简单,试例代码可以参见alsa-lib源码包,路径为:alsa-lib-1.1.4.1/test/timer.c
其主要的流程图如下(NONBLOCK模式):
4. 内核部分
初始化
ALSA的默认Timer是使用 snd_timer.ko 提供的SNDRV_TIMER_CLASS_GLOBAL定时器,该定时器使用系统定时器作为时钟源,且不属于任何声卡。
初始化流程如下图:
内核主要数据联系图
主要数据结构图如下:
5. 代码的一些分析
- ALSA初始化时会使用结构体 snd_timer 来管理定时器本身,所有定时器由 snd_timer_list 串联在一起。具体的硬件操作由结构体 snd_timer_hardware 管理。
- 用户空间使用 snd_timer_t 记录定时器 handle。它使用 async_handlers 链表挂载回调函数,因此,理论上一个定时器可以挂载多个回调函数(多线程中处理多条音轨可能会用到)。
- /dev/timer 节点打开时会创建结构体 snd_timer_user ,并与文件节点绑定。在该结构体中会记录该用户的 Timer 的运行情况,如overrun计数;也会维护一个环形buffer作为消息队列,其中消息队列有 snd_timer_read 或 snd_timer_tread 两种格式,任选其一。
- 在 SNDRV_TIMER_IOCTL_SELECT 命令中会通过 snd_timer_instance_new 函数创建结构体 snd_timer_instance 。该结构体作为联系定时器和用户的桥梁,会绑定具体使用的 snd_timer 。主要负责记录用户和定时器的回调函数,并使用链表跟踪定时器需要做的进一步动作。比如,当需要发送消息给用户时,snd_timer_instance 会将 ack_list 挂载到 snd_timer 的 ack_list_head 或 ack_list_head 链表下进行消息发送,前者表明消息需要在定时器中断上下文中发送,后者表示消息需要在tasklet上下文中发送。