GCC Code Coverage Report


Directory: ./
File: Sequence.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 5 5 100.0%
Functions: 4 4 100.0%
Branches: 0 0 -%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2013-2022 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 // PLUGIN API: native plugins compile against this header and are loaded with
15 // dlopen().
16 // - Plugins use the global `sequence` and call its methods by symbol
17 // (SendBlankingData, ...), and rely on FPPD_MAX_CHANNELS.
18 // Data layout here is NOT plugin ABI, so members can change freely. Changing
19 // or removing a signature a plugin calls is: keep the old one as an overload
20 // (as Timers::addPeriodicTimer does) or bump FPP_PLUGIN_API_VERSION in
21 // Plugin.h. See .claude/PLUGIN-ABI.md for which plugins use what.
22
23 #include <stdio.h>
24 #include <string>
25
26 #include <atomic>
27 #include <condition_variable>
28 #include <list>
29 #include <map>
30 #include <memory>
31 #include <mutex>
32 #include <thread>
33 #include <vector>
34
35 #include "AtomicSharedPtr.h"
36 #include "fseq/FSEQFile.h"
37
38 #define FPPD_MAX_CHANNELS (8192 * 1024)
39 #define DATA_DUMP_SIZE 28
40
41 // reserve 4 channels of 0 and 4 channels of 0xFF for indexes
42 // that require one or the other
43 #define FPPD_OFF_CHANNEL FPPD_MAX_CHANNELS
44 #define FPPD_WHITE_CHANNEL (FPPD_MAX_CHANNELS + 4)
45 #define FPPD_MAX_CHANNEL_NUM (FPPD_WHITE_CHANNEL + 4)
46
47 class Sequence {
48 public:
49 Sequence();
50 ~Sequence();
51
52 int IsSequenceRunning(void);
53 int IsSequenceRunning(const std::string& filename);
54 int OpenSequenceFile(const std::string& filename, int startFrame = 0, int startSecond = -1);
55 void StartSequence(const std::string& filename, int startFrame);
56 void StartSequence();
57 void ProcessSequenceData(int ms);
58 void SeekSequenceFile(int frameNumber);
59 // Takes the channel output thread's pending frame skip and seeks that
60 // many frames on from the frame being output. Under m_sequenceLock, which
61 // every open holds while it cancels the pending skip, so a skip counted
62 // from one open's position is never applied to the next: a remote's stale
63 // sync packet from the last play-through of the same file used to jump the
64 // next one to its end.
65 void SkipFrames(std::atomic_int& skip);
66 void ReadSequenceData(bool forceFirstFrame = false);
67 void SendSequenceData(void);
68 void SendBlankingData(void);
69 void CloseIfOpen(const std::string& filename);
70 void CloseSequenceFile(void);
71 void ToggleSequencePause(void);
72 void SingleStepSequence(void);
73 void SingleStepSequenceBack(void);
74 int SequenceIsPaused(void);
75
76 bool hasBridgeData();
77 130913 bool isDataProcessed() const { return m_dataProcessed.load(std::memory_order_acquire); }
78 17715 void setDataNotProcessed() { m_dataProcessed = false; }
79
80 // Advanced by the output thread every frame and read by status, the
81 // playlist and the crash handler from other threads.
82 std::atomic_int m_seqMSDuration;
83 std::atomic_int m_seqMSElapsed;
84 std::atomic_int m_seqMSRemaining;
85 char* m_seqData;
86 std::string m_seqFilename;
87
88 // Every read of m_seqFilename from a thread that is not holding
89 // m_sequenceLock must go through this. The open/close paths reassign the
90 // string, and a reallocating assignment hands a concurrent raw reader a
91 // freed buffer; copying under m_seqFilenameLock is the only safe read.
92 std::string GetSeqFilenameCopy() const;
93
94 // Run every queued sequence preset now, on the calling thread. Normally
95 // driven by the main-loop timer queuePreset() arms; called directly once
96 // during shutdown, because a preset queued in the last loop iteration
97 // (a sequence that ended, or a stop command, right as the loop exits) has
98 // no later tick to fire on and would otherwise be dropped. MUST be called
99 // with no sequence lock held -- presets run arbitrary commands.
100 void drainPendingPresets();
101
102 117503 int GetSeqStepTime() const { return m_seqStepTime; }
103 void BlankSequenceData(bool clearBridge = false);
104
105 // expireMS is on the GetMonotonicTimeMS() clock.
106 void SetBridgeData(uint8_t* data, int startChannel, int len, uint64_t expireMS);
107
108 // Bridge data arrives in bursts (one recvmmsg drain can be hundreds of
109 // packets). WriteBridgeData copies one packet's channels into the bridge
110 // buffer without touching the range bookkeeping; CommitBridgeRanges then
111 // records all of a burst's ranges under a single lock acquisition.
112 // SetBridgeData above is the two combined, for single-packet callers.
113 bool WriteBridgeData(uint8_t* data, int startChannel, int len);
114 void CommitBridgeRanges(const std::vector<std::pair<uint32_t, uint32_t>>& ranges, uint64_t expireMS);
115
116 private:
117 void ProcessVariableHeaders();
118 void SetLastFrameData(FSEQFile::FrameData* data);
119
120 // A sequence file we are asked to play that isn't present here. The
121 // "does not exist" warning carries a timeout so it clears itself once the
122 // player moves on, but a remote gets no second open attempt after the
123 // first failure (m_seqFilename is left set, so StartSequence short-circuits),
124 // so the warning has to be re-armed from the sync path or it expires while
125 // the sequence is still playing on the player.
126 void SetMissingSequenceWarning(const std::string& filename, const std::string& warning);
127 void RefreshMissingSequenceWarning();
128 void ClearMissingSequenceWarning();
129
130 void setBridgePrioritySetting(const std::string& value);
131 bool m_prioritize_sequence_over_bridge;
132 bool m_warn_if_bridging = false;
133
134 class BridgeRangeData {
135 public:
136 3055 BridgeRangeData() :
137 3055 startChannel(0) {}
138
139 uint32_t startChannel;
140 // map of len -> ms when it expires, in MOST cases, this will be a single len (like 512 for e1.31)
141 std::map<uint32_t, uint64_t> expires;
142 };
143 std::map<uint64_t, BridgeRangeData> m_bridgeRanges;
144 std::mutex m_bridgeRangesLock;
145 uint8_t* m_bridgeData;
146 // Mirrors !m_bridgeRanges.empty(); updated wherever the map is modified
147 // (under m_bridgeRangesLock) so hasBridgeData() - called several times a
148 // frame from both the main loop and the output thread - needs no lock.
149 std::atomic<bool> m_hasBridgeRanges{ false };
150 // Per-frame scratch for the merge in ProcessSequenceData, kept as a
151 // member so its allocation is reused frame to frame.
152 std::vector<std::pair<uint32_t, uint32_t>> m_bridgeRangeScratch;
153
154 AtomicSharedPtr<FSEQFile> m_seqFile;
155
156 // 2 while a file opens, 1 once its header is read, 0 when it plays. Set
157 // by whichever thread opens or starts the sequence; read by the
158 // read-ahead thread, the output thread and status.
159 std::atomic_int m_seqStarting;
160 int m_seqPaused;
161 int m_seqStepTime;
162 int m_seqSingleStep;
163 int m_seqSingleStepBack;
164 float m_seqRefreshRate;
165 int m_remoteBlankCount;
166 // Set by whichever thread processed the frame (normally the output
167 // thread) and cleared by the bridge receive path and sequence loads.
168 std::atomic<bool> m_dataProcessed;
169 int m_numSeek;
170
171 int m_blankBetweenSequences;
172
173 // A sequence that played to its end, until the next open. Guarded by
174 // m_sequenceLock. A remote whose copy is shorter than the master's
175 // finishes first, and every later sync packet for it used to reopen the
176 // file at a frame past its end -- several reopens a second, each firing
177 // SEQUENCE_STARTED/STOPPED, until the master stopped.
178 std::string m_endedSeqFilename;
179 // Sync packets for the ended sequence are ignored only from this frame
180 // on: the last two seconds of our copy, or past its end. One asking for
181 // an earlier frame means we ended early (a stale sync packet from the
182 // previous play-through of the same file jumped us to the end), and it
183 // reopens the file there, as remotes did before m_endedSeqFilename.
184 int m_endedSeqIgnoreFrom = 0;
185
186 // The name the sequence was opened as -- the master's name for it -- even
187 // when a remote is playing fallback.fseq in its place. m_seqFilename names
188 // the file actually playing; sync packets and stop packets carry this one.
189 // Matching sync against m_seqFilename alone meant a fallback never took the
190 // master's position (so the output thread gave up after two seconds) and
191 // was never stopped by the master's stop. Guarded by m_sequenceLock.
192 std::string m_seqSyncName;
193
194 // Bumped by every OpenSequenceFile() under m_sequenceLock. The open drops
195 // that lock while it sends the MultiSync open packet; if another open ran
196 // in that window, the older one stands down instead of replacing the newer
197 // file with its own (leaving the newer one's caches and triggers behind).
198 uint64_t m_openGeneration = 0;
199
200 // Both guarded by m_sequenceLock.
201 std::string m_missingSeqFilename;
202 std::string m_missingSeqWarning;
203
204 std::recursive_mutex m_sequenceLock;
205 // Covers m_seqFilename only, and is always the innermost lock taken. The
206 // three assignments to m_seqFilename all happen under m_sequenceLock, so
207 // readers that hold m_sequenceLock need nothing more; this exists for the
208 // readers that don't (the status producers, the output thread, MultiSync).
209 mutable std::mutex m_seqFilenameLock;
210
211 std::atomic_int m_lastFrameRead;
212 // Read by the read-ahead thread outside frameCacheLock (under readFileLock
213 // only), written by CloseSequenceFile() under frameCacheLock.
214 std::atomic<bool> m_doneRead;
215 // Set by the destructor while the read-ahead thread is still checking it.
216 std::atomic<bool> m_shuttingDown;
217 std::thread* m_readThread;
218 std::list<FSEQFile::FrameData*> frameCache;
219 std::list<FSEQFile::FrameData*> pastFrameCache;
220 FSEQFile::FrameData* m_lastFrameData;
221 void clearCaches();
222 std::mutex frameCacheLock;
223 std::mutex readFileLock; // lock for just the stuff needed to read from the file (m_seqFile variable)
224 std::condition_variable frameLoadSignal;
225 std::condition_variable frameLoadedSignal;
226
227 // Frame-triggered presets and effects from the file's FC/FE variable
228 // headers. Built whole by ProcessVariableHeaders() and published with the
229 // file; the output thread reads a snapshot. They used to be plain maps
230 // that open/close cleared and refilled while the output thread was
231 // looking things up in them.
232 //
233 // Each command/effect name is stored once in `names` and the frame maps
234 // hold indexes into it. An FC/FE block gives a name followed by its frame
235 // ranges, and copying the name into every range let one long name with a
236 // few million ranges expand a small header into hundreds of MB.
237 struct Triggers {
238 std::vector<std::string> names;
239 std::map<uint32_t, std::vector<uint32_t>> commandPresets;
240 std::map<uint32_t, std::vector<uint32_t>> effectsOn;
241 std::map<uint32_t, std::vector<uint32_t>> effectsOff;
242 };
243 AtomicSharedPtr<Triggers> m_triggers;
244 // Frame number of m_lastFrameData (-1 for none), set with it under
245 // frameCacheLock, so the output thread can read it without the lock --
246 // and without touching a frame another thread may be freeing.
247 std::atomic<int64_t> m_lastFrameNumber{ -1 };
248 // Last frame whose triggers have fired; reset when a file opens.
249 std::atomic<int64_t> m_lastTriggeredFrame{ -1 };
250 // FE-started effects still running. They loop, so they are stopped when
251 // the sequence closes rather than left running after it.
252 std::mutex m_triggerEffectsLock;
253 std::vector<std::string> m_triggerEffects;
254 // The trigger table m_triggerEffects belongs to, or null once it has been
255 // forgotten (closed). Guarded by m_triggerEffectsLock. The output thread
256 // can be inside FireFrameTriggers() with a snapshot of the old table while
257 // the close swaps m_triggerEffects out; an effect it starts after that
258 // swap would be recorded in a list nobody stops. It checks this under the
259 // same lock and stops such an effect itself. Compared only against a
260 // table the caller still holds a reference to, so the address cannot have
261 // been reused.
262 const Triggers* m_triggerEffectsOwner = nullptr;
263 void FireFrameTriggers();
264 void ForgetTriggers();
265
266 // Sequence-fired presets (SEQUENCE_STARTED/SEQUENCE_STOPPED and the FSEQ
267 // frame-triggered commands) must never run at the call site: the open/close
268 // paths hold m_sequenceLock (often recursively, from callers that also hold
269 // it) and SendSequenceData runs on the channel output thread under
270 // outputThreadLock, so a preset that touches the playlist deadlocks against
271 // a playlist thread opening a sequence (m_playlistMutex -> m_sequenceLock
272 // vs m_sequenceLock -> m_playlistMutex). They are queued here and drained
273 // in FIFO order by a main-loop timer with no sequence locks held.
274 std::mutex m_pendingPresetsLock;
275 std::list<std::pair<std::string, std::map<std::string, std::string>>> m_pendingPresets;
276 // Effects to stop, drained with the presets for the same reason.
277 std::list<std::string> m_pendingEffectStops;
278
279 public:
280 // Also used by outputs that fire presets from the output thread (the
281 // control channel output), for the same reason.
282 void queuePreset(const std::string& preset, std::map<std::string, std::string>&& keywords);
283
284 void ReadFramesLoop();
285
286 private:
287 // Whether m_seqFile and m_triggers are set, so the per-frame checks
288 // (IsSequenceRunning(), FireFrameTriggers()) need no lock: an
289 // AtomicSharedPtr load is a mutex and a refcount round trip, about 0.5 us
290 // on an AM335x, and the output thread asks several times a frame. Both
291 // pointers are stored only through setSeqFile()/setTriggers(), which keep
292 // these in step. Declared last so no existing member moves.
293 std::atomic<bool> m_haveSeqFile{ false };
294 std::atomic<bool> m_haveTriggers{ false };
295 void setSeqFile(std::shared_ptr<FSEQFile> f);
296 void setTriggers(std::shared_ptr<Triggers> t);
297 };
298
299 extern Sequence* sequence;
300