ao_pipewire: fix delay calculation

A figure from pipewire documentation:

```
           stream time domain           graph time domain
         /-----------------------\/-----------------------------\

 queue     +-+ +-+  +-----------+                 +--------+
 ---->     | | | |->| converter | ->   graph  ->  | kernel | -> speaker
 <----     +-+ +-+  +-----------+                 +--------+
 dequeue   buffers                \-------------------/\--------/
                                     graph              internal
                                    latency             latency
         \--------/\-------------/\-----------------------------/
           queued      buffered            delay
```

We calculate `end_time` in the following steps:

1. get current timestamp in mpv
```
int64_t end_time = mp_time_ns();
```

2. add duration of samples to enqueue
```
end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate;
```

3. add delay of the pipewire graph
```
end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom;
```

4. add duration of queued and buffered samples.
```
end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate;
```
New in this commit. `time.queued` is usually zero as `SPA_PARAM_BUFFERS_buffers`
is default to 1; however it is not always.
`time.buffered` is non-zero if there is a resampler involved.

5. add elapsed duration from when `time` is captured
```
end_time -= pw_stream_get_nsec(p->stream) - time.now;
```
New in this commit. `time` is captured at `time.now`.
From then, time has passed so we need to exclude the elapsed time,
by calculating the diff of `pw_stream_get_nsec()` and `time.now`.
This commit is contained in:
Misaki Kasumi 2024-03-26 21:57:12 +08:00 committed by sfan5
parent 2cbb13db9e
commit f974382ca0
1 changed files with 16 additions and 3 deletions

View File

@ -47,6 +47,15 @@ static inline int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time
#define spa_hook_remove(hook) if ((hook)->link.prev) spa_hook_remove(hook)
#endif
#if !PW_CHECK_VERSION(1, 0, 4)
static uint64_t pw_stream_get_nsec(struct pw_stream *stream)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return SPA_TIMESPEC_TO_NSEC(&ts);
}
#endif
enum init_state {
INIT_STATE_NONE,
INIT_STATE_SUCCESS,
@ -189,9 +198,13 @@ static void on_process(void *userdata)
time.rate.num = 1;
int64_t end_time = mp_time_ns();
/* time.queued is always going to be 0, so we don't need to care */
end_time += (nframes * 1e9 / ao->samplerate) +
((double) time.delay * SPA_NSEC_PER_SEC * time.rate.num / time.rate.denom);
end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom;
end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate;
#if PW_CHECK_VERSION(0, 3, 50)
end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate;
#endif
end_time -= pw_stream_get_nsec(p->stream) - time.now;
int samples = ao_read_data_nonblocking(ao, data, nframes, end_time);
b->size = samples;