GCC Code Coverage Report


Directory: ./
File: mediaoutput/FeedSyncWindow.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 18 19 94.7%
Functions: 4 5 80.0%
Branches: 14 16 87.5%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2013-2026 by the Falcon Player Developers.
5 *
6 * The Falcon Player (FPP) is free software, and is covered under
7 * multiple Open Source licenses. Please see the included 'LICENSES'
8 * file for descriptions of what files are covered by each license.
9 *
10 * This source file is covered under the LGPL v2.1 as described in the
11 * included LICENSE.LGPL file.
12 */
13
14 #include <cstddef>
15 #include <cstdint>
16 #include <deque>
17 #include <utility>
18
19 // A remote's audio error readings over the last two seconds, for
20 // GStreamerOutput::AdjustFeedSpeed().
21 //
22 // Each media sync packet gives one reading: where this remote's sound is
23 // against the position the master put in the packet (> 0: this remote is
24 // ahead). A packet can arrive late -- the master busy, the network, Wi-Fi
25 // holding it -- but never early, and a late one carries a position the
26 // master has already moved past, so it can only make this remote look
27 // further ahead than it is. The lowest reading of the last two seconds is
28 // the least late packet's, and the honest one: the same rule
29 // MasterFrame0() steers a remote's lights by (channeloutputthread.cpp).
30 // The master's own position steps (a whole audio period at a time) inflate
31 // readings the same way, so the lowest also sees through those.
32 //
33 // Steered by each packet alone, one packet more than the seek threshold
34 // late reads as this remote that far ahead: the seek pulls the sound behind
35 // by the delay, the landing teaches the next seek to aim further ahead, and
36 // one late packet costs two or three audible seeks.
37 //
38 // Readings are ms of monotonic time and seconds of error.
39 class FeedSyncWindow {
40 public:
41 static constexpr uint64_t kWindowMs = 2000; // as MasterFrame0()'s window
42 static constexpr std::size_t kAgreeing = 2; // readings before an error is acted on
43
44 // A seek (or a new song) starts a new timeline: earlier readings are of
45 // the old one.
46 16 void Clear() {
47 16 m_errs.clear();
48 16 }
49
50 // Add this packet's reading, returning the lowest of the window. The
51 // master going back -- a new song, a seek back -- starts over, as
52 // MasterFrame0() does when the master's frame goes back.
53 968 double Add(uint64_t nowMs, double err, double masterPos) {
54
3/4
✓ Branch 0 taken 953 times.
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 953 times.
968 if (m_lastMasterPos >= 0.0 && masterPos + 0.5 < m_lastMasterPos) {
55 ✗ m_errs.clear();
56 }
57 968 m_lastMasterPos = masterPos;
58
6/6
✓ Branch 0 taken 1864 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 917 times.
✓ Branch 3 taken 947 times.
✓ Branch 4 taken 917 times.
✓ Branch 5 taken 968 times.
1885 while (!m_errs.empty() && nowMs - m_errs.front().first > kWindowMs) {
59 917 m_errs.pop_front();
60 }
61 968 m_errs.emplace_back(nowMs, err);
62 968 return Lowest();
63 }
64
65 968 double Lowest() const {
66
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 968 times.
968 double best = m_errs.empty() ? 0.0 : m_errs.front().second;
67
2/2
✓ Branch 0 taken 4208 times.
✓ Branch 1 taken 968 times.
5176 for (const auto& e : m_errs) {
68
2/2
✓ Branch 0 taken 1163 times.
✓ Branch 1 taken 3045 times.
4208 if (e.second < best) {
69 1163 best = e.second;
70 }
71 }
72 968 return best;
73 }
74
75 // Enough readings that one late packet can't be the whole story.
76 968 bool Agreed() const {
77 968 return m_errs.size() >= kAgreeing;
78 }
79
80 std::size_t Size() const {
81 return m_errs.size();
82 }
83
84 private:
85 std::deque<std::pair<uint64_t, double>> m_errs;
86 double m_lastMasterPos = -1.0;
87 };
88