| 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 |