mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-10-29 05:40:27 -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_dma_buf | ||||
| - \subpage page_scheduling | ||||
| - \subpage page_driver | ||||
| - \subpage page_latency | ||||
| - \subpage page_tag | ||||
| - \subpage page_native_protocol | ||||
|  |  | |||
|  | @ -68,6 +68,7 @@ extra_docs = [ | |||
|   'dox/internals/objects.dox', | ||||
|   'dox/internals/audio.dox', | ||||
|   'dox/internals/scheduling.dox', | ||||
|   'dox/internals/driver.dox', | ||||
|   'dox/internals/protocol.dox', | ||||
|   'dox/internals/pulseaudio.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 | ||||
|  * 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 | ||||
|  * 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 | ||||
|  * 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 | ||||
|  * 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 | ||||
|  * \ref spa_io_position.clock.id in \ref SPA_IO_Position are the same. | ||||
|  * A node is a driver when \ref spa_io_clock::id and the ID in | ||||
|  * \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. | ||||
|  */ | ||||
|  | @ -168,13 +169,16 @@ struct spa_io_clock { | |||
| 					  *  can be used to check if nodes share the same clock. */ | ||||
| 	uint64_t nsec;			/**< Time in nanoseconds against monotonic clock
 | ||||
| 					  * (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 */ | ||||
| 	uint64_t position;		/**< Current position, 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 */ | ||||
| 	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.
 | ||||
| 					  *  This time is a logical start time of the next cycle, and | ||||
| 					  *  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 | ||||
|  * 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 | ||||
|  * will contain the clock id of the driving node in the graph. | ||||
|  * the graph in its \ref SPA_IO_Clock.  Also, the ID in \ref spa_io_position.clock | ||||
|  * will be the clock id of the driving node in the graph. | ||||
|  * | ||||
|  * The position clock indicates the logical start time of the current graph | ||||
|  * cycle. | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Carlos Rafael Giani
						Carlos Rafael Giani