Userspace-driven timers¶
- Author:
Ivan Orlov <ivan.orlov0322@gmail.com>
Preface¶
This document describes the userspace-driven timers: virtual ALSA timers
which could be created and controlled by userspace applications using
IOCTL calls. Such timers could be useful when synchronizing audio
stream with timer sources which we don’t have ALSA timers exported for
(e.g. PTP clocks), and when synchronizing the audio stream going through
two virtual sound devices using snd-aloop
(for instance, when
we have a network application sending frames to one snd-aloop device,
and another sound application listening on the other end of snd-aloop).
Enabling userspace-driven timers¶
The userspace-driven timers could be enabled in the kernel using the
CONFIG_SND_UTIMER
configuration option. It depends on the
CONFIG_SND_TIMER
option, so it also should be enabled.
Userspace-driven timers API¶
Userspace application can create a userspace-driven ALSA timer by
executing the SNDRV_TIMER_IOCTL_CREATE
ioctl call on the
/dev/snd/timer
device file descriptor. The snd_timer_uinfo
structure should be passed as an ioctl argument:
struct snd_timer_uinfo {
__u64 resolution;
int fd;
unsigned int id;
unsigned char reserved[16];
}
The resolution
field sets the desired resolution in nanoseconds for
the virtual timer. resolution
field simply provides an information
about the virtual timer, but does not affect the timing itself. id
field gets overwritten by the ioctl, and the identifier you get in this
field after the call can be used as a timer subdevice number when
passing the timer to snd-aloop
kernel module or other userspace
applications. There could be up to 128 userspace-driven timers in the
system at one moment of time, thus the id value ranges from 0 to 127.
Besides from overwriting the snd_timer_uinfo
struct, ioctl stores
a timer file descriptor, which can be used to trigger the timer, in the
fd
field of the snd_timer_uinfo
struct. Allocation of a file
descriptor for the timer guarantees that the timer can only be triggered
by the process which created it. The timer then can be triggered with
SNDRV_TIMER_IOCTL_TRIGGER
ioctl call on the timer file descriptor.
So, the example code for creating and triggering the timer would be:
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);
More detailed example of creating and ticking the timer could be found in the utimer ALSA selftest.
Userspace-driven timers and snd-aloop¶
Userspace-driven timers could be easily used with snd-aloop
module
when synchronizing two sound applications on both ends of the virtual
sound loopback. For instance, if one of the applications receives sound
frames from network and sends them to snd-aloop pcm device, and another
application listens for frames on the other snd-aloop pcm device, it
makes sense that the ALSA middle layer should initiate a data
transaction when the new period of data is received through network, but
not when the certain amount of jiffies elapses. Userspace-driven ALSA
timers could be used to achieve this.
To use userspace-driven ALSA timer as a timer source of snd-aloop, pass
the following string as the snd-aloop timer_source
parameter:
# modprobe snd-aloop timer_source="-1.4.<utimer_id>"
Where utimer_id
is the id of the timer you created with
SNDRV_TIMER_IOCTL_CREATE
, and 4
is the number of
userspace-driven timers device (SNDRV_TIMER_GLOBAL_UDRIVEN
).
resolution
for the userspace-driven ALSA timer used with snd-aloop
should be calculated as 1000000000ULL / frame_rate * period_size
as
the timer is going to tick every time a new period of frames is ready.
After that, each time you trigger the timer with
SNDRV_TIMER_IOCTL_TRIGGER
the new period of data will be transferred
from one snd-aloop device to another.