mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-04-05 07:15:34 -04:00
doc: spa: Add more docs about SPA_IO_Clock and driver operations
This commit is contained in:
parent
67711e899c
commit
eb3d14053d
4 changed files with 124 additions and 7 deletions
111
doc/dox/internals/driver.dox
Normal file
111
doc/dox/internals/driver.dox
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/** \page page_driver Driver architecture and workflow
|
||||||
|
|
||||||
|
This document explains how drivers are structured and how they operate.
|
||||||
|
This is useful to know both for debugging and for writing new drivers.
|
||||||
|
|
||||||
|
(For details about how the graph does scheduling, which is tied to the driver,
|
||||||
|
see \ref page_scheduling ).
|
||||||
|
|
||||||
|
# Clocks
|
||||||
|
|
||||||
|
A driver is a node that starts graph cycles. Typically, this is accomplished
|
||||||
|
by using a timer that periodically invokes a callback, or by an interrupt.
|
||||||
|
|
||||||
|
Drivers use the monotonic system clock as the reference for timestamping. Note
|
||||||
|
that "monotonic system clock" does not refer to the \c MONOTONIC_RAW clock in
|
||||||
|
Linux, but rather, to the regular monotonic clock.
|
||||||
|
|
||||||
|
Drivers may actually be run by a custom internal clock instead of the monotonic
|
||||||
|
system clock. One example would be a sound card DAC's clock. Another would be
|
||||||
|
a network adapter with a built in PHC. Or, the driver may be using a system
|
||||||
|
clock other than the monotonic system clock. The driver then needs to perform
|
||||||
|
some sort of timestamp translation and drift compensation from that internal
|
||||||
|
clock to the monotonic clock, since it still needs to generate monotonic clock
|
||||||
|
timestamps for the beginning cycle. (More on that below.)
|
||||||
|
|
||||||
|
# Updates and graph cycle start
|
||||||
|
|
||||||
|
Every time a driver starts a graph cycle, it must update the contents of the
|
||||||
|
\ref spa_io_clock instance that is assigned to them through the
|
||||||
|
\ref spa_node_methods::set_io callback. The fields of the struct must be
|
||||||
|
updated as follows:
|
||||||
|
|
||||||
|
- \ref spa_io_clock::nsec : Must be set to the time (according to the monotonic
|
||||||
|
system clock) when the cycle that the driver is about to trigger started. To
|
||||||
|
minimize jitter, it is usually a good idea to increment this by a fixed amount
|
||||||
|
except for when the driver starts and when discontinuities occur in its clock.
|
||||||
|
- \ref spa_io_clock::rate : Set to a value that can translate samples to nanoseconds.
|
||||||
|
- \ref spa_io_clock::position : Current cycle position, in samples. This is the
|
||||||
|
ideal position of the graph cycle (this is explained in greater detail further below).
|
||||||
|
It is incremented by the dduration (in samples) at the beginning of each cycle. If
|
||||||
|
a discontinuity is experienced by the driver that results in a discontinuity in the
|
||||||
|
position of the old and the current cycle, consider setting the
|
||||||
|
\ref SPA_IO_CLOCK_FLAG_DISCONT flag to inform other nodes about this.
|
||||||
|
- \ref spa_io_clock::duration : Duration of this new cycle, in samples.
|
||||||
|
- \ref spa_io_clock::rate_diff : A decimal value that is set to whatever correction
|
||||||
|
factor the driver applied to for a drift between an internal driver clock and the
|
||||||
|
monotonic system clock. A value above 1.0 means that the internal driver clock
|
||||||
|
is faster than the monotonic system clock, and vice versa. Always set this to
|
||||||
|
1.0 if the driver is directly using the monotonic clock.
|
||||||
|
- \ref spa_io_clock::next_nsec : Must be set to the time (according to the monotonic
|
||||||
|
system clock) when the cycle that comes after the current one is to be started. In
|
||||||
|
some cases, this may actually be in the past relative to nsec, for example, when
|
||||||
|
some internal driver clock experienced a discontinuity. Consider setting the
|
||||||
|
\ref SPA_IO_CLOCK_FLAG_DISCONT flag in such a case. Just like with nsec, to
|
||||||
|
minimize jitter, it is usually a good idea to increment this by a fixed amount
|
||||||
|
except for when the driver starts and when discontinuities occur in its clock.
|
||||||
|
|
||||||
|
The driver node signals the start of the graph cycle by calling \ref spa_node_call_ready
|
||||||
|
with the \ref SPA_STATUS_HAVE_DATA and \ref SPA_STATUS_NEED_DATA flags passed
|
||||||
|
to that function call. That call must happen inside the thread that runs the
|
||||||
|
data loop assigned to the driver node.
|
||||||
|
|
||||||
|
As mentioned above, the \ref spa_io_clock::position field is the _ideal_ position
|
||||||
|
of the graph cycle, in samples. This contrasts with \ref spa_io_clock::nsec, which
|
||||||
|
is the moment in monotonic clock time when the cycle _actually_ happens. This is
|
||||||
|
an important distinction when driver is run by a clock that is different to the monotonic
|
||||||
|
cloc. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the pace
|
||||||
|
of that different clock (explained in the section below). In such a case,
|
||||||
|
\ref spa_io_clock::position still is incremented by the duration in samples.
|
||||||
|
|
||||||
|
# Using clocks other than the monotonic clock
|
||||||
|
|
||||||
|
As mentioned earlier, the driver may be run by an internal clock that is different
|
||||||
|
to the monotonic clock. If that other clock can be directly used for scheduling
|
||||||
|
graph cycle initiations, then it is sufficient to compute the offset between that
|
||||||
|
clock and the monotonic clock (that is, offset = other_clock_time - monotonic_clock_time)
|
||||||
|
at each cycle and use that offset to translate that other clock's time to the monotonic
|
||||||
|
clock time. This is accomplished by adding that offset to the \ref spa_io_clock::nsec
|
||||||
|
and \ref spa_io_clock::next_nsec fields. For example, when the driver uses the realtime
|
||||||
|
system clock instead of the monotonic system clock, then that realtime clock can still
|
||||||
|
be used with \c timerfd to schedule callback invocations within the data loop. Then, computing
|
||||||
|
the (realtime_clock_time - monotonic_clock_time) offset is sufficient, as mentioned,
|
||||||
|
to fulfill the requirements of the \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec
|
||||||
|
fields that their timestamps must be given in monotonic clock time.
|
||||||
|
|
||||||
|
If however that other clock cannot be used for scheduling graph cycle initiations directly
|
||||||
|
(for example, because the API of that clock has no functionality to trigger callbacks),
|
||||||
|
then, in addition to the aforementioned offset, the driver has to use the monotonic clock
|
||||||
|
for triggering callbacks (usually via \c timerfd) and adjust the time when callbacks are
|
||||||
|
invoked such that they match the pace of that other clock.
|
||||||
|
|
||||||
|
As an example (clock speed difference exaggerated for sake of clarity), suppose the other
|
||||||
|
clock is twice as fast as the monotonic clock. Then the monotonic clock timestamps have
|
||||||
|
to be calculated in a manner that halves the durations between said timestamps, and the
|
||||||
|
\ref spa_io_clock::rate_diff field is set to 2.0.
|
||||||
|
|
||||||
|
The dummy node driver uses a DLL for this purpose. It is fed the difference between the
|
||||||
|
expected position (in samples) and the actual position (derived from the current time
|
||||||
|
of the driver's internal clock), passes the delta between these two quantities into the
|
||||||
|
DLL, and the DLL computes a correction factor (2.0 in the above example) which is used
|
||||||
|
for scaling durations between \c timerfd timeouts. This forms a control loop, since the
|
||||||
|
correction factor causes the durations between the timeouts to be adjusted such that the
|
||||||
|
difference between the expected position and the actual position reaches zero. Keep in
|
||||||
|
mind the notes above about \ref spa_io_clock::position being the ideal position of the
|
||||||
|
graph cycle, meaning that even in this case, the duration it is incremented by is
|
||||||
|
_not_ scaled by the correction factor; the duration in samples remains unchanged.
|
||||||
|
|
||||||
|
(Other popular control loop mechanisms that are suitable alternatives to the DLL are
|
||||||
|
PID controllers and Kalman filters.)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
- \subpage page_library
|
- \subpage page_library
|
||||||
- \subpage page_dma_buf
|
- \subpage page_dma_buf
|
||||||
- \subpage page_scheduling
|
- \subpage page_scheduling
|
||||||
|
- \subpage page_driver
|
||||||
- \subpage page_latency
|
- \subpage page_latency
|
||||||
- \subpage page_tag
|
- \subpage page_tag
|
||||||
- \subpage page_native_protocol
|
- \subpage page_native_protocol
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ extra_docs = [
|
||||||
'dox/internals/objects.dox',
|
'dox/internals/objects.dox',
|
||||||
'dox/internals/audio.dox',
|
'dox/internals/audio.dox',
|
||||||
'dox/internals/scheduling.dox',
|
'dox/internals/scheduling.dox',
|
||||||
|
'dox/internals/driver.dox',
|
||||||
'dox/internals/protocol.dox',
|
'dox/internals/protocol.dox',
|
||||||
'dox/internals/pulseaudio.dox',
|
'dox/internals/pulseaudio.dox',
|
||||||
'dox/internals/dma-buf.dox',
|
'dox/internals/dma-buf.dox',
|
||||||
|
|
|
||||||
|
|
@ -114,15 +114,16 @@ struct spa_io_range {
|
||||||
* Driver nodes are supposed to update the contents of \ref SPA_IO_Clock before
|
* Driver nodes are supposed to update the contents of \ref SPA_IO_Clock before
|
||||||
* signaling the start of a graph cycle. These updated clock values become
|
* signaling the start of a graph cycle. These updated clock values become
|
||||||
* visible to other nodes in \ref SPA_IO_Position. Non-driver nodes do
|
* visible to other nodes in \ref SPA_IO_Position. Non-driver nodes do
|
||||||
* not need to update the contents of their \ref SPA_IO_Clock.
|
* not need to update the contents of their \ref SPA_IO_Clock. Also
|
||||||
|
* see \ref page_driver for further details.
|
||||||
*
|
*
|
||||||
* The host generally gives each node a separate \ref spa_io_clock in \ref
|
* The host generally gives each node a separate \ref spa_io_clock in \ref
|
||||||
* SPA_IO_Clock, so that updates made by the driver are not visible in the
|
* SPA_IO_Clock, so that updates made by the driver are not visible in the
|
||||||
* contents of \ref SPA_IO_Clock of other nodes. Instead, \ref SPA_IO_Position
|
* contents of \ref SPA_IO_Clock of other nodes. Instead, \ref SPA_IO_Position
|
||||||
* is used to look up the current graph time.
|
* is used to look up the current graph time.
|
||||||
*
|
*
|
||||||
* A node is a driver when \ref spa_io_clock.id in \ref SPA_IO_Clock and
|
* A node is a driver when \ref spa_io_clock::id and the ID in
|
||||||
* \ref spa_io_position.clock.id in \ref SPA_IO_Position are the same.
|
* \ref spa_io_position.clock in \ref SPA_IO_Position are the same.
|
||||||
*
|
*
|
||||||
* The flags are set by the graph driver at the start of each cycle.
|
* The flags are set by the graph driver at the start of each cycle.
|
||||||
*/
|
*/
|
||||||
|
|
@ -168,13 +169,16 @@ struct spa_io_clock {
|
||||||
* can be used to check if nodes share the same clock. */
|
* can be used to check if nodes share the same clock. */
|
||||||
uint64_t nsec; /**< Time in nanoseconds against monotonic clock
|
uint64_t nsec; /**< Time in nanoseconds against monotonic clock
|
||||||
* (CLOCK_MONOTONIC). This fields reflects a real time instant
|
* (CLOCK_MONOTONIC). This fields reflects a real time instant
|
||||||
* in the past. The value may have jitter. */
|
* in the past, when the current cycle started. The value may
|
||||||
|
* have jitter. */
|
||||||
struct spa_fraction rate; /**< Rate for position/duration/delay/xrun */
|
struct spa_fraction rate; /**< Rate for position/duration/delay/xrun */
|
||||||
uint64_t position; /**< Current position, in samples @ \ref rate */
|
uint64_t position; /**< Current position, in samples @ \ref rate */
|
||||||
uint64_t duration; /**< Duration of current cycle, in samples @ \ref rate */
|
uint64_t duration; /**< Duration of current cycle, in samples @ \ref rate */
|
||||||
int64_t delay; /**< Delay between position and hardware, in samples @ \ref rate */
|
int64_t delay; /**< Delay between position and hardware, in samples @ \ref rate */
|
||||||
double rate_diff; /**< Rate difference between clock and monotonic time, as a ratio of
|
double rate_diff; /**< Rate difference between clock and monotonic time, as a ratio of
|
||||||
* clock speeds. */
|
* clock speeds. A value higher than 1.0 means that the driver's
|
||||||
|
* internal clock is faster than the monotonic clock (by that
|
||||||
|
* factor), and vice versa. */
|
||||||
uint64_t next_nsec; /**< Estimated next wakeup time in nanoseconds.
|
uint64_t next_nsec; /**< Estimated next wakeup time in nanoseconds.
|
||||||
* This time is a logical start time of the next cycle, and
|
* This time is a logical start time of the next cycle, and
|
||||||
* is not necessarily in the future.
|
* is not necessarily in the future.
|
||||||
|
|
@ -308,8 +312,8 @@ enum spa_io_position_state {
|
||||||
*
|
*
|
||||||
* It is set on all nodes in \ref SPA_IO_Position, and the contents of \ref
|
* It is set on all nodes in \ref SPA_IO_Position, and the contents of \ref
|
||||||
* spa_io_position.clock contain the clock updates made by the driving node in
|
* spa_io_position.clock contain the clock updates made by the driving node in
|
||||||
* the graph in its \ref SPA_IO_Clock. Also, \ref spa_io_position.clock.id
|
* the graph in its \ref SPA_IO_Clock. Also, the ID in \ref spa_io_position.clock
|
||||||
* will contain the clock id of the driving node in the graph.
|
* will be the clock id of the driving node in the graph.
|
||||||
*
|
*
|
||||||
* The position clock indicates the logical start time of the current graph
|
* The position clock indicates the logical start time of the current graph
|
||||||
* cycle.
|
* cycle.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue