用户空间驱动的定时器

作者:

Ivan Orlov <ivan.orlov0322@gmail.com>

前言

本文档描述了用户空间驱动的定时器:虚拟 ALSA 定时器,可以使用 IOCTL 调用由用户空间应用程序创建和控制。当将音频流与我们没有导出 ALSA 定时器的定时源(例如,PTP 时钟)同步时,以及当使用 snd-aloop 同步通过两个虚拟声音设备的音频流时(例如,当我们的网络应用程序向一个 snd-aloop 设备发送帧时,而另一个声音应用程序在 snd-aloop 的另一端侦听时),这种定时器可能很有用。

启用用户空间驱动的定时器

可以使用 CONFIG_SND_UTIMER 配置选项在内核中启用用户空间驱动的定时器。它依赖于 CONFIG_SND_TIMER 选项,因此也应该启用它。

用户空间驱动的定时器 API

用户空间应用程序可以通过在 /dev/snd/timer 设备文件描述符上执行 SNDRV_TIMER_IOCTL_CREATE ioctl 调用来创建用户空间驱动的 ALSA 定时器。 应将 snd_timer_uinfo 结构作为 ioctl 参数传递。

struct snd_timer_uinfo {
    __u64 resolution;
    int fd;
    unsigned int id;
    unsigned char reserved[16];
}

resolution 字段设置虚拟定时器的期望分辨率,以纳秒为单位。resolution 字段只是提供有关虚拟定时器的信息,但不会影响计时本身。id 字段会被 ioctl 覆盖,并且在调用后在此字段中获得的标识符可以用作将定时器传递给 snd-aloop 内核模块或其他用户空间应用程序时的定时器子设备编号。 系统中一次最多可以有 128 个用户空间驱动的定时器,因此 id 值范围从 0 到 127。

除了覆盖 snd_timer_uinfo 结构外,ioctl 还会存储一个定时器文件描述符,该描述符可用于触发定时器,存储在 snd_timer_uinfo 结构的 fd 字段中。 为定时器分配文件描述符可确保该定时器只能由创建它的进程触发。 然后,可以使用定时器文件描述符上的 SNDRV_TIMER_IOCTL_TRIGGER ioctl 调用来触发定时器。

因此,创建和触发定时器的示例代码如下:

static struct snd_timer_uinfo utimer_info = {
    /* Timer is going to tick (presumably) every 1000000 ns */
    .resolution = 1000000ULL,
    .id = -1,
};

int timer_device_fd = open("/dev/snd/timer",  O_RDWR | O_CLOEXEC);

if (ioctl(timer_device_fd, SNDRV_TIMER_IOCTL_CREATE, &utimer_info)) {
    perror("Failed to create the timer");
    return -1;
}

...

/*
 * Now we want to trigger the timer. Callbacks of all of the
 * timer instances binded to this timer will be executed after
 * this call.
 */
ioctl(utimer_info.fd, SNDRV_TIMER_IOCTL_TRIGGER, NULL);

...

/* Now, destroy the timer */
close(timer_info.fd);

在 utimer ALSA 自检中可以找到创建和定时定时器的更详细示例。

用户空间驱动的定时器和 snd-aloop

当在虚拟声音回环的两端同步两个声音应用程序时,可以轻松地将用户空间驱动的定时器与 snd-aloop 模块一起使用。 例如,如果其中一个应用程序从网络接收声音帧并将它们发送到 snd-aloop pcm 设备,而另一个应用程序在另一个 snd-aloop pcm 设备上侦听帧,那么当通过网络接收到新的数据周期时,而不是在经过一定数量的 jiffies 时,ALSA 中间层应该启动数据传输,这是有意义的。 用户空间驱动的 ALSA 定时器可用于实现此目的。

要使用用户空间驱动的 ALSA 定时器作为 snd-aloop 的定时器源,请传递以下字符串作为 snd-aloop timer_source 参数

# modprobe snd-aloop timer_source="-1.4.<utimer_id>"

其中 utimer_id 是你使用 SNDRV_TIMER_IOCTL_CREATE 创建的定时器的 ID,4 是用户空间驱动的定时器设备的编号 (SNDRV_TIMER_GLOBAL_UDRIVEN)。

与 snd-aloop 一起使用的用户空间驱动的 ALSA 定时器的 resolution 应计算为 1000000000ULL / frame_rate * period_size,因为定时器将在每次准备好新的帧周期时进行计时。

之后,每次你使用 SNDRV_TIMER_IOCTL_TRIGGER 触发定时器时,新的数据周期将从一个 snd-aloop 设备传输到另一个设备。