用户空间驱动的定时器

作者:

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 设备上的帧,那么当通过网络接收到新的数据周期时,ALSA 中间层应该启动数据事务,而不是在经过一定数量的 jiffies 时,这是有意义的。 用户空间驱动的 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 设备传输到另一个设备。