aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends/pipewire.cpp
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2021-08-18 13:01:42 -0700
committerChris Robinson <[email protected]>2021-08-18 13:01:42 -0700
commit04372b3f48adc57314ada7869714f0f00e971538 (patch)
tree881cabe53f4ce6ce7ea91a3b9c99c13504d98eec /alc/backends/pipewire.cpp
parente197b5256431ebae0059597a7f322ec8f7184e84 (diff)
Support getting device latency with PipeWire
Diffstat (limited to 'alc/backends/pipewire.cpp')
-rw-r--r--alc/backends/pipewire.cpp94
1 files changed, 93 insertions, 1 deletions
diff --git a/alc/backends/pipewire.cpp b/alc/backends/pipewire.cpp
index c884d80b..d7d0f712 100644
--- a/alc/backends/pipewire.cpp
+++ b/alc/backends/pipewire.cpp
@@ -26,6 +26,8 @@
#include <atomic>
#include <cstring>
#include <cerrno>
+#include <chrono>
+#include <ctime>
#include <list>
#include <memory>
#include <mutex>
@@ -88,6 +90,8 @@ _Pragma("GCC diagnostic pop")
namespace {
+using std::chrono::seconds;
+using std::chrono::nanoseconds;
using uint = unsigned int;
constexpr char pwireDevice[] = "PipeWire Output";
@@ -111,6 +115,7 @@ constexpr char pwireDevice[] = "PipeWire Output";
MAGIC(pw_stream_dequeue_buffer) \
MAGIC(pw_stream_destroy) \
MAGIC(pw_stream_get_state) \
+ MAGIC(pw_stream_get_time) \
MAGIC(pw_stream_new_simple) \
MAGIC(pw_stream_queue_buffer) \
MAGIC(pw_stream_set_active) \
@@ -146,6 +151,7 @@ PWIRE_FUNCS(MAKE_FUNC)
#define pw_stream_dequeue_buffer ppw_stream_dequeue_buffer
#define pw_stream_destroy ppw_stream_destroy
#define pw_stream_get_state ppw_stream_get_state
+#define pw_stream_get_time ppw_stream_get_time
#define pw_stream_new_simple ppw_stream_new_simple
#define pw_stream_queue_buffer ppw_stream_queue_buffer
#define pw_stream_set_active ppw_stream_set_active
@@ -849,9 +855,11 @@ struct PipeWirePlayback final : public BackendBase {
bool reset() override;
void start() override;
void stop() override;
+ ClockLatency getClockLatency() override;
- ThreadMainloop mLoop;
uint32_t mTargetId{PwIdAny};
+ nanoseconds mTimeBase{0};
+ ThreadMainloop mLoop;
PwStreamPtr mStream;
spa_io_rate_match *mRateMatch{};
std::unique_ptr<float*[]> mChannelPtrs;
@@ -1003,6 +1011,7 @@ bool PipeWirePlayback::reset()
mStream = nullptr;
}
mRateMatch = nullptr;
+ mTimeBase = GetDeviceClockTime(mDevice);
/* If connecting to a specific device, update various device parameters to
* match its format.
@@ -1143,6 +1152,89 @@ void PipeWirePlayback::stop()
mLoop.wait();
}
+ClockLatency PipeWirePlayback::getClockLatency()
+{
+ /* Given a real-time low-latency output, this is rather complicated to get
+ * accurate timing. So, here we go.
+ */
+
+ /* First, get the stream time info (tick delay, ticks played, and the
+ * CLOCK_MONOTONIC time closest to when that last tick was played).
+ */
+ pw_time ptime{};
+ {
+ MainloopLockGuard _{mLoop};
+ if(int res{pw_stream_get_time(mStream.get(), &ptime)})
+ ERR("Failed to get PipeWire stream time (res: %d)\n", res);
+ }
+
+ /* Now get the mixer time and the CLOCK_MONOTONIC time atomically (i.e. the
+ * monotonic clock closest to 'now', and the last mixer time at 'now').
+ */
+ nanoseconds mixtime{};
+ timespec tspec{};
+ uint refcount;
+ do {
+ refcount = mDevice->waitForMix();
+ mixtime = GetDeviceClockTime(mDevice);
+ clock_gettime(CLOCK_MONOTONIC, &tspec);
+ std::atomic_thread_fence(std::memory_order_acquire);
+ } while(refcount != ReadRef(mDevice->MixCount));
+
+ /* Convert the monotonic clock, stream ticks, and stream delay to
+ * nanoseconds.
+ */
+ nanoseconds monoclock{seconds{tspec.tv_sec} + nanoseconds{tspec.tv_nsec}};
+ nanoseconds curtic{}, delay{};
+ if UNLIKELY(ptime.rate.denom < 1)
+ {
+ /* If there's no stream rate, the stream hasn't had a chance to get
+ * going and return time info yet. Just use dummy values.
+ */
+ ptime.now = monoclock.count();
+ curtic = mixtime;
+ delay = nanoseconds{seconds{mDevice->BufferSize}} / mDevice->Frequency;
+ }
+ else
+ {
+ /* The stream gets recreated with each reset, so include the time that
+ * had already passed with previous streams.
+ */
+ curtic = mTimeBase;
+ /* More safely scale the ticks to avoid overflowing the pre-division
+ * temporary as it gets larger.
+ */
+ curtic += seconds{ptime.ticks / ptime.rate.denom} * ptime.rate.num;
+ curtic += nanoseconds{seconds{ptime.ticks%ptime.rate.denom} * ptime.rate.num} /
+ ptime.rate.denom;
+
+ /* The delay should be small enough to not worry about overflow. */
+ delay = nanoseconds{seconds{ptime.delay} * ptime.rate.num} / ptime.rate.denom;
+ }
+
+ /* If the mixer time is ahead of the stream time, there's that much more
+ * delay relative to the stream delay.
+ */
+ if(mixtime > curtic)
+ delay += mixtime - curtic;
+ /* Reduce the delay according to how much time has passed since the known
+ * stream time. This isn't 100% accurate since the system monotonic clock
+ * doesn't tick at the exact same rate as the audio device, but it should
+ * be good enough with ptime.now being constantly updated every few
+ * milliseconds with ptime.ticks.
+ */
+ delay -= monoclock - nanoseconds{ptime.now};
+
+ /* Return the mixer time and delay. Clamp the delay to no less than 0,
+ * incase timer drift got that severe.
+ */
+ ClockLatency ret{};
+ ret.ClockTime = mixtime;
+ ret.Latency = std::max(delay, nanoseconds{});
+
+ return ret;
+}
+
} // namespace