GCC Code Coverage Report


Directory: ./
File: Sequence.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 747 808 92.5%
Functions: 47 53 88.7%
Branches: 776 1242 62.5%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #ifndef PLATFORM_OSX
16 #include <asm-generic/hugetlb_encode.h>
17 #endif
18 #include <sys/mman.h>
19
20 #include <algorithm>
21 #include <atomic>
22 #include <chrono>
23 #include <cstdint>
24 #include <list>
25 #include <map>
26 #include <mutex>
27 #include <set>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <string>
32 #include <thread>
33 #include <utility>
34 #include <vector>
35
36 #include "MultiSync.h"
37 #include "Player.h"
38 #include "Timers.h"
39 #include "Plugins.h"
40 #include "Warnings.h"
41 #include "common.h"
42 #include "effects.h"
43 #include "log.h"
44 #include "settings.h"
45 #include "channeloutput/ChannelOutputSetup.h"
46 #include "channeloutput/channeloutputthread.h"
47 #include "channeltester/ChannelTester.h"
48 #include "commands/Commands.h"
49 #include "mediaoutput/GStreamerOut.h"
50 #include "overlays/PixelOverlay.h"
51 #include "playlist/Playlist.h"
52
53 #include "Sequence.h"
54
55 using namespace std::literals;
56 using namespace std::chrono_literals;
57 using namespace std::literals::chrono_literals;
58
59 #define SEQUENCE_CACHE_FRAMECOUNT 40
60
61 Sequence* sequence = NULL;
62 // The pause and single-step flags are set by commands on the main loop and
63 // read by the output thread. atomic_ref rather than atomic members: this
64 // class's layout is compiled into plugins (see Sequence.h).
65 71601 static std::atomic_ref<int> seqFlag(int& flag) {
66 71601 return std::atomic_ref<int>(flag);
67 }
68
69 1888 Sequence::Sequence() :
70 1888 m_seqMSDuration(0),
71 1888 m_seqMSElapsed(0),
72 1888 m_seqMSRemaining(0),
73 1888 m_seqFile(nullptr),
74 1888 m_seqStarting(0),
75 1888 m_seqPaused(0),
76 1888 m_seqStepTime(50),
77 1888 m_seqSingleStep(0),
78 1888 m_seqSingleStepBack(0),
79 1888 m_seqRefreshRate(20),
80 1888 m_remoteBlankCount(0),
81 1888 m_numSeek(0),
82 1888 m_readThread(nullptr),
83 1888 m_lastFrameRead(-1),
84 1888 m_doneRead(false),
85 1888 m_shuttingDown(false),
86 1888 m_lastFrameData(nullptr),
87 1888 m_dataProcessed(false),
88
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 m_seqFilename(""),
89 // Allocated here, not on the first bridged packet: the receive thread
90 // allocating it while the output thread tested the pointer was a race.
91 // calloc'd pages cost nothing until something is written to them.
92 1888 m_bridgeData((uint8_t*)calloc(1, FPPD_MAX_CHANNEL_NUM)),
93 9440 m_seqData(nullptr) {
94 #ifndef PLATFORM_OSX
95 1888 m_seqData = (char*)mmap(NULL, FPPD_MAX_CHANNEL_NUM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | HUGETLB_FLAG_ENCODE_2MB, -1, 0);
96 #endif
97
3/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1814 times.
✓ Branch 3 taken 74 times.
1888 if (m_seqData == nullptr || m_seqData == MAP_FAILED) {
98 1814 m_seqData = (char*)mmap(NULL, FPPD_MAX_CHANNEL_NUM, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
99
1/2
✓ Branch 0 taken 1814 times.
✗ Branch 1 not taken.
1814 LogDebug(VB_SEQUENCE, "Not using Huge Pages for Sequence Data %p\n", m_seqData);
100 } else {
101
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 LogDebug(VB_SEQUENCE, "Using Huge Pages for Sequence Data %p\n", m_seqData);
102 }
103 1888 memset(m_seqData, 0, FPPD_MAX_CHANNEL_NUM);
104
2/2
✓ Branch 0 taken 7552 times.
✓ Branch 1 taken 1888 times.
9440 for (int x = 0; x < 4; x++) {
105 7552 m_seqData[FPPD_OFF_CHANNEL + x] = 0;
106 7552 m_seqData[FPPD_WHITE_CHANNEL] = 0xFF;
107 }
108
109
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 m_blankBetweenSequences = getSettingInt("blankBetweenSequences");
110
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
1888 setBridgePrioritySetting(getSetting("bridgeDataPriority", "Warn If Sequence Running"));
111
112
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
7712 registerSettingsListener("sequence", "blankBetweenSequences",
113 2048 [this](const std::string& value) {
114 ✗ m_blankBetweenSequences = getSettingInt("blankBetweenSequences");
115 ✗ });
116
3/6
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
7552 registerSettingsListener("sequence", "bridgeDataPriority",
117 2048 [this](const std::string& value) {
118 ✗ setBridgePrioritySetting(value);
119 ✗ });
120 1888 }
121
122 1867 Sequence::~Sequence() {
123 1867 m_shuttingDown = true;
124 1867 frameLoadSignal.notify_all();
125
2/2
✓ Branch 0 taken 356 times.
✓ Branch 1 taken 1511 times.
1867 if (m_readThread) {
126 356 m_readThread->join();
127
1/2
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
356 delete m_readThread;
128 }
129 1867 SetLastFrameData(nullptr);
130 1867 clearCaches();
131 1867 setSeqFile(nullptr);
132
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 if (m_bridgeData) {
133 1867 free(m_bridgeData);
134 }
135 1867 munmap(m_seqData, FPPD_MAX_CHANNEL_NUM); // the size mmap()ed in the constructor
136 1867 }
137
138 1888 void Sequence::setBridgePrioritySetting(const std::string& value) {
139 1888 m_prioritize_sequence_over_bridge = false;
140 1888 m_warn_if_bridging = false;
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
1888 if (value == "Prioritize Sequence") {
142 ✗ m_prioritize_sequence_over_bridge = true;
143
2/2
✓ Branch 0 taken 1887 times.
✓ Branch 1 taken 1 times.
1888 } else if (value == "Warn If Sequence Running") {
144 1887 m_prioritize_sequence_over_bridge = true;
145 1887 m_warn_if_bridging = true;
146 }
147 1888 }
148
149 4848 void Sequence::clearCaches() {
150
2/2
✓ Branch 0 taken 13203 times.
✓ Branch 1 taken 4848 times.
18051 while (!frameCache.empty()) {
151
1/2
✓ Branch 0 taken 13203 times.
✗ Branch 1 not taken.
13203 if (frameCache.front() != m_lastFrameData)
152
1/2
✓ Branch 0 taken 13203 times.
✗ Branch 1 not taken.
13203 delete frameCache.front();
153 13203 frameCache.pop_front();
154 }
155
2/2
✓ Branch 0 taken 9705 times.
✓ Branch 1 taken 4848 times.
14553 while (!pastFrameCache.empty()) {
156
2/2
✓ Branch 0 taken 8678 times.
✓ Branch 1 taken 1027 times.
9705 if (pastFrameCache.front() != m_lastFrameData)
157
1/2
✓ Branch 0 taken 8678 times.
✗ Branch 1 not taken.
8678 delete pastFrameCache.front();
158 9705 pastFrameCache.pop_front();
159 }
160 4848 }
161
162 132290 void Sequence::SetLastFrameData(FSEQFile::FrameData* data) {
163
2/2
✓ Branch 0 taken 66169 times.
✓ Branch 1 taken 66121 times.
132290 if (m_lastFrameData == data)
164 66169 return;
165
166
2/2
✓ Branch 0 taken 65358 times.
✓ Branch 1 taken 763 times.
66121 if (m_lastFrameData) {
167 65358 bool found = false;
168
2/2
✓ Branch 0 taken 1253480 times.
✓ Branch 1 taken 65358 times.
1318838 for (auto const& i : pastFrameCache) {
169
2/2
✓ Branch 0 taken 64310 times.
✓ Branch 1 taken 1189170 times.
1253480 if (i == m_lastFrameData)
170 64310 found = true;
171 }
172
173
2/2
✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 64310 times.
65358 if (!found) {
174
2/2
✓ Branch 0 taken 453 times.
✓ Branch 1 taken 1048 times.
1501 for (auto const& i : frameCache) {
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 453 times.
453 if (i == m_lastFrameData)
176 ✗ found = true;
177 }
178 }
179
180
2/2
✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 64310 times.
65358 if (!found)
181
1/2
✓ Branch 0 taken 1048 times.
✗ Branch 1 not taken.
1048 delete m_lastFrameData;
182 }
183
184 66121 m_lastFrameData = data;
185
2/2
✓ Branch 0 taken 65358 times.
✓ Branch 1 taken 763 times.
66121 m_lastFrameNumber = data ? (int64_t)data->frame : -1;
186 }
187
188 /*
189 *
190 */
191
192 356 void ReadSequenceDataThread(Sequence* sequence) {
193 356 sequence->ReadFramesLoop();
194 356 }
195 356 void Sequence::ReadFramesLoop() {
196
2/4
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 356 times.
✗ Branch 3 not taken.
356 SetThreadName("FPP-ReadFrames");
197
1/2
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
356 std::unique_lock<std::mutex> lock(frameCacheLock);
198 while (true) {
199
2/2
✓ Branch 0 taken 356 times.
✓ Branch 1 taken 213206 times.
213562 if (m_shuttingDown) {
200 356 return;
201 }
202 213206 int cacheSize = frameCache.size();
203
1/2
✓ Branch 0 taken 213206 times.
✗ Branch 1 not taken.
213206 std::shared_ptr<FSEQFile> seqFilePtr = m_seqFile.load();
204
10/10
✓ Branch 0 taken 114592 times.
✓ Branch 1 taken 98614 times.
✓ Branch 2 taken 114002 times.
✓ Branch 3 taken 590 times.
✓ Branch 4 taken 97658 times.
✓ Branch 5 taken 16344 times.
✓ Branch 6 taken 79740 times.
✓ Branch 7 taken 17918 times.
✓ Branch 8 taken 79740 times.
✓ Branch 9 taken 133466 times.
213206 if (frameCache.size() < SEQUENCE_CACHE_FRAMECOUNT && m_seqStarting < 2 && seqFilePtr && !m_doneRead) {
205 79740 uint32_t frame = (m_lastFrameRead + 1);
206
2/2
✓ Branch 0 taken 78926 times.
✓ Branch 1 taken 814 times.
79740 if (frame < seqFilePtr->getNumFrames()) {
207
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 lock.unlock();
208
209
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 long long start = GetTimeMS();
210
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 std::unique_lock<std::mutex> readlock(readFileLock);
211
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 long long lockt = GetTimeMS();
212
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 std::shared_ptr<FSEQFile> file = m_seqFile.load();
213 78926 FSEQFile::FrameData* fd = nullptr;
214
5/6
✓ Branch 0 taken 78915 times.
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78915 times.
✓ Branch 4 taken 78915 times.
✓ Branch 5 taken 11 times.
78926 if (m_doneRead || file == nullptr) {
215 // memset(fd->data, 0, maxChanToRead);
216 } else {
217
1/2
✓ Branch 0 taken 78915 times.
✗ Branch 1 not taken.
78915 fd = file->getFrame(frame);
218 }
219
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 long long unlock = GetTimeMS();
220
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 readlock.unlock();
221
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 long long end = GetTimeMS();
222 78926 long long total = end - start;
223
4/4
✓ Branch 0 taken 78925 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 78914 times.
78926 if (total > 20 || fd == nullptr) {
224 12 uint32_t lfr = m_lastFrameRead;
225 12 int lt = lockt - start;
226 12 int ul = end - unlock;
227 12 int gf = unlock - lockt;
228
229
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogDebug(VB_SEQUENCE, "Problem reading frame %d: %X Time: %d ms Last: %d Lock: %d GetFrame: %d Unlock: %d\n",
230 frame, fd, ((int)total), lfr, lt, gf, ul);
231 }
232
233
1/2
✓ Branch 0 taken 78926 times.
✗ Branch 1 not taken.
78926 lock.lock();
234
2/2
✓ Branch 0 taken 78915 times.
✓ Branch 1 taken 11 times.
78926 if (fd) {
235
2/2
✓ Branch 0 taken 78890 times.
✓ Branch 1 taken 25 times.
78915 if (m_lastFrameRead == (frame - 1)) {
236 78890 m_lastFrameRead = frame;
237
1/2
✓ Branch 0 taken 78890 times.
✗ Branch 1 not taken.
78890 frameCache.push_back(fd);
238
239
1/2
✓ Branch 0 taken 78890 times.
✗ Branch 1 not taken.
78890 lock.unlock();
240 78890 frameLoadedSignal.notify_all();
241
2/4
✓ Branch 0 taken 78890 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 78890 times.
✗ Branch 3 not taken.
78890 std::this_thread::sleep_for(1ms);
242
1/2
✓ Branch 0 taken 78890 times.
✗ Branch 1 not taken.
78890 lock.lock();
243 } else {
244 // a skip is in progress, we don't need this frame anymore
245
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 delete fd;
246 }
247 }
248 78926 } else {
249 814 m_doneRead = true;
250
1/2
✓ Branch 0 taken 814 times.
✗ Branch 1 not taken.
814 lock.unlock();
251 814 frameLoadedSignal.notify_all();
252
2/4
✓ Branch 0 taken 814 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 814 times.
✗ Branch 3 not taken.
814 std::this_thread::sleep_for(1ms);
253
1/2
✓ Branch 0 taken 814 times.
✗ Branch 1 not taken.
814 lock.lock();
254 }
255 } else {
256
2/4
✓ Branch 0 taken 133466 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 133466 times.
✗ Branch 3 not taken.
133466 frameLoadSignal.wait_for(lock, 25ms);
257 }
258 426412 }
259 356 }
260
261 2918 inline void checkForReplacementFile(const std::string &fname) {
262
1/2
✓ Branch 0 taken 2918 times.
✗ Branch 1 not taken.
2918 std::string replaceFileName = fname + ".replace";
263
2/4
✓ Branch 0 taken 2918 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2918 times.
2918 if (FileExists(replaceFileName)) {
264 ✗ unlink(fname.c_str());
265 ✗ rename(replaceFileName.c_str(), fname.c_str());
266 }
267 2918 }
268
269 // Warning ID / lifetime for "sequence file does not exist". The warning is
270 // re-armed on every sync packet for the missing file, so this is the grace
271 // period after the player goes quiet (or after it sends a stop, which closes
272 // the sequence and clears the warning outright), not the total display time.
273 static constexpr int MISSING_SEQUENCE_WARNING_ID = 25;
274 static constexpr int MISSING_SEQUENCE_WARNING_TIMEOUT = 30;
275
276 // Sequence names already reported missing or invalid, so a playlist that keeps
277 // coming round to them (or a master that keeps naming one in sync packets)
278 // logs each once instead of every time: a repeating playlist of missing
279 // sequences wrote some 150 KB of log every 5 s. A name leaves the set when
280 // it opens. File-static (guarded by m_sequenceLock) because Sequence is
281 // plugin-visible.
282 static std::set<std::string> reportedMissingSequences;
283
284 753 static bool FirstReportOfMissing(const std::string& name) {
285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 753 times.
753 if (reportedMissingSequences.size() > 256) {
286 ✗ reportedMissingSequences.clear();
287 }
288
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
753 return reportedMissingSequences.insert(name).second;
289 }
290
291 765 void Sequence::SetMissingSequenceWarning(const std::string& filename, const std::string& warning) {
292 765 ClearMissingSequenceWarning();
293 765 m_missingSeqFilename = filename;
294 765 m_missingSeqWarning = warning;
295
2/4
✓ Branch 0 taken 765 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 765 times.
✗ Branch 3 not taken.
765 WarningHolder::AddWarningTimeout(MISSING_SEQUENCE_WARNING_TIMEOUT, MISSING_SEQUENCE_WARNING_ID, warning);
296 765 }
297
298 166 void Sequence::RefreshMissingSequenceWarning() {
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 166 times.
166 if (m_missingSeqWarning.empty()) {
300 ✗ return;
301 }
302 // AddWarningTimeout only ever pushes an existing warning's timeout later,
303 // so this keeps it up for as long as the sync packets keep arriving.
304
2/4
✓ Branch 0 taken 166 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 166 times.
✗ Branch 3 not taken.
332 WarningHolder::AddWarningTimeout(MISSING_SEQUENCE_WARNING_TIMEOUT, MISSING_SEQUENCE_WARNING_ID, m_missingSeqWarning);
305 }
306
307 3045 void Sequence::ClearMissingSequenceWarning() {
308
2/2
✓ Branch 0 taken 2285 times.
✓ Branch 1 taken 760 times.
3045 if (m_missingSeqWarning.empty()) {
309 2285 return;
310 }
311
2/4
✓ Branch 0 taken 760 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 760 times.
✗ Branch 3 not taken.
760 WarningHolder::RemoveWarning(MISSING_SEQUENCE_WARNING_ID, m_missingSeqWarning);
312 760 m_missingSeqWarning.clear();
313 760 m_missingSeqFilename.clear();
314 }
315
316 1954 int Sequence::OpenSequenceFile(const std::string& filename, int startFrame, int startSecond) {
317
1/2
✓ Branch 0 taken 1954 times.
✗ Branch 1 not taken.
1954 LogDebug(VB_SEQUENCE, "OpenSequenceFile(%s, %d, %d)\n", filename.c_str(), startFrame, startSecond);
318
319
2/4
✓ Branch 0 taken 1954 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1954 times.
1954 if (filename == "") {
320 ✗ LogErr(VB_SEQUENCE, "Empty Sequence Filename!\n", filename.c_str());
321 ✗ return 0;
322 }
323
324 // Sequence names are flat files inside the sequences directory -- they
325 // arrive from playlists, play commands, and multisync sync packets, so a
326 // rogue remote or crafted playlist must not escape via ../ (which would
327 // reach checkForReplacementFile()'s unlink/rename below), pick absolute
328 // paths, or smuggle NULs past the c_str() calls. Reject up front, before
329 // any teardown, the same way empty names are rejected.
330
2/2
✓ Branch 0 taken 1890 times.
✓ Branch 1 taken 60 times.
3904 if (filename.find_first_of("/\\") != std::string::npos ||
331
5/6
✓ Branch 0 taken 1950 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1890 times.
✓ Branch 4 taken 64 times.
✓ Branch 5 taken 1890 times.
3904 filename.find("..") != std::string::npos ||
332 1890 filename.find('\0') != std::string::npos) {
333 // The missing-sequence warning state is guarded by m_sequenceLock.
334 // Leave m_seqStarting alone: a sequence that is open but not yet
335 // started must not be flipped to "running" by a rejected request.
336
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
337
3/4
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 59 times.
64 if (FirstReportOfMissing(filename)) {
338
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 LogErr(VB_SEQUENCE, "Invalid Sequence Filename '%s'\n", filename.c_str());
339 } else {
340
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 LogDebug(VB_SEQUENCE, "Invalid Sequence Filename '%s'\n", filename.c_str());
341 }
342
3/6
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 64 times.
✗ Branch 5 not taken.
64 SetMissingSequenceWarning(filename, "Invalid sequence filename " + filename + "\n");
343 64 return 0;
344 64 }
345
346 1890 size_t bytesRead = 0;
347
1/2
✓ Branch 0 taken 1890 times.
✗ Branch 1 not taken.
1890 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
348 1890 m_endedSeqFilename.clear();
349 1890 const uint64_t openGeneration = ++m_openGeneration;
350 // A pending frame skip counts from the old file's position (see
351 // SkipFrames()).
352
1/2
✓ Branch 0 taken 1890 times.
✗ Branch 1 not taken.
1890 CancelFrameSkip();
353
354
3/4
✓ Branch 0 taken 1890 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1888 times.
1890 if (m_seqFile.load()) {
355
5/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (m_seqFilename == filename && m_seqStarting) {
356 // same filename AND we haven't started yet, we can continue
357 1 return 1;
358 }
359 }
360
361
2/4
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1889 times.
1889 if (IsSequenceRunning())
362 ✗ CloseSequenceFile();
363
364 // Tear down any existing m_seqFile. m_seqFile is an atomic<shared_ptr<FSEQFile>>, so
365 // resetting it here cannot free the file out from under an in-flight getFrame() in the
366 // ReadFramesLoop thread: that thread holds its own shared_ptr copy and the FSEQFile is
367 // only destroyed once the last holder releases it.
368
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 std::unique_lock<std::mutex> readLock(readFileLock);
369
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 std::unique_lock<std::mutex> lock(frameCacheLock);
370
3/4
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1888 times.
1889 if (std::shared_ptr<FSEQFile> old = m_seqFile.load()) {
371
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string fn = old->getFilename();
372
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 setSeqFile(nullptr);
373
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 ForgetTriggers();
374
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 checkForReplacementFile(fn);
375 1890 }
376
377 1889 m_seqStarting = 2;
378 1889 m_doneRead = false;
379 1889 m_lastFrameRead = -1;
380
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1880 times.
1889 if (startFrame) {
381 9 m_lastFrameRead = startFrame - 1;
382 }
383
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 clearCaches();
384
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 lock.unlock();
385
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 readLock.unlock();
386
387 1889 seqFlag(m_seqPaused).store(0);
388 1889 m_seqMSDuration = 0;
389 1889 m_seqMSElapsed = 0;
390 1889 m_seqMSRemaining = 0;
391
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 SetChannelOutputFrameNumber(m_lastFrameRead + 1);
392
2/2
✓ Branch 0 taken 356 times.
✓ Branch 1 taken 1533 times.
1889 if (m_readThread == nullptr) {
393
3/8
✓ Branch 0 taken 356 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 356 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 356 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
356 m_readThread = new std::thread(ReadSequenceDataThread, this);
394 }
395
396 {
397
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 std::unique_lock<std::mutex> nameLock(m_seqFilenameLock);
398
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 m_seqFilename = filename;
399 1889 }
400
1/2
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
1889 m_seqSyncName = filename;
401
402 char tmpFilename[2048];
403 unsigned char tmpData[2048];
404
4/8
✓ Branch 0 taken 1889 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1889 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1889 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1889 times.
✗ Branch 7 not taken.
1889 std::string fullPath = FPP_DIR_SEQUENCE("/" + filename);
405
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1888 times.
1889 if (fullPath.size() >= sizeof(tmpFilename)) {
406
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_SEQUENCE, "Sequence Filename too long (%u chars)\n", (unsigned)fullPath.size());
407
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 SetMissingSequenceWarning(filename, "Invalid sequence filename " + filename + "\n");
408 1 m_seqStarting = 0;
409 1 return 0;
410 }
411 1888 memcpy(tmpFilename, fullPath.c_str(), fullPath.size() + 1);
412
413
3/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✓ Branch 3 taken 1849 times.
1888 if (getFPPmode() == REMOTE_MODE)
414
2/4
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 CheckForHostSpecificFile(getSetting("HostName").c_str(), tmpFilename);
415
416
2/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
1888 checkForReplacementFile(std::string(tmpFilename));
417
418
3/4
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 701 times.
✓ Branch 3 taken 1187 times.
1888 if (!FileExists(tmpFilename)) {
419
1/2
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
701 std::string warning = "Sequence file ";
420
1/2
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
701 warning += tmpFilename;
421
1/2
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
701 warning += " does not exist\n";
422
423
3/4
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 689 times.
701 if (getFPPmode() == REMOTE_MODE) {
424
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogDebug(VB_SEQUENCE, "Sequence file %s does not exist\n", tmpFilename);
425
426 // Look for fallback.fseq instead
427
3/6
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 strcpy(tmpFilename, FPP_DIR_SEQUENCE("/fallback.fseq").c_str());
428
429
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 times.
12 if (!FileExists(tmpFilename)) {
430
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 LogDebug(VB_SEQUENCE, "Fallback Sequence file %s does not exist\n", tmpFilename);
431
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 SetMissingSequenceWarning(filename, warning);
432 11 m_seqStarting = 0;
433 11 return 0;
434
435 } else {
436 {
437
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::unique_lock<std::mutex> nameLock(m_seqFilenameLock);
438
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_seqFilename = "fallback.fseq";
439 1 }
440
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_SEQUENCE, "Playing Fallback Sequence file %s\n", tmpFilename);
441 }
442
443 } else {
444
3/4
✓ Branch 0 taken 689 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 682 times.
689 if (FirstReportOfMissing(filename)) {
445
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 LogErr(VB_SEQUENCE, "Sequence file %s does not exist\n", tmpFilename);
446 } else {
447
1/2
✓ Branch 0 taken 682 times.
✗ Branch 1 not taken.
682 LogDebug(VB_SEQUENCE, "Sequence file %s does not exist\n", tmpFilename);
448 }
449
1/2
✓ Branch 0 taken 689 times.
✗ Branch 1 not taken.
689 SetMissingSequenceWarning(filename, warning);
450 689 m_seqStarting = 0;
451 689 return 0;
452 }
453
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 700 times.
701 }
454
455 // We have a file to play (the requested one, or the fallback)
456
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 ClearMissingSequenceWarning();
457
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 reportedMissingSequences.erase(filename);
458
459
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 1155 times.
1188 if (multiSync->isMultiSyncEnabled()) {
460
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 seqLock.unlock();
461
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 multiSync->SendSeqOpenPacket(filename);
462
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 seqLock.lock();
463
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 32 times.
33 if (openGeneration != m_openGeneration) {
464
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_SEQUENCE, "Open of %s superseded by a later open while sending the open packet\n", filename.c_str());
465 1 return 0;
466 }
467 }
468
469
1/2
✓ Branch 0 taken 1187 times.
✗ Branch 1 not taken.
1187 setSeqFile(nullptr);
470
2/4
✓ Branch 0 taken 1187 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1187 times.
✗ Branch 3 not taken.
1187 FSEQFile* seqFile = FSEQFile::openFSEQFile(tmpFilename);
471
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1186 times.
1187 if (seqFile == NULL) {
472
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_SEQUENCE, "Error opening sequence file: %s. FSEQFile::openFSEQFile returned NULL\n",
473 tmpFilename);
474 1 m_seqStarting = 0;
475 1 return 0;
476 }
477
478 1186 m_seqStepTime = seqFile->getStepTime();
479 1186 m_seqRefreshRate = 1000.0f / m_seqStepTime;
480
481
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1173 times.
1186 if (startSecond >= 0) {
482 13 int frame = startSecond * 1000;
483 13 frame /= seqFile->getStepTime();
484 13 m_lastFrameRead = frame - 1;
485
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (m_lastFrameRead < -1)
486 ✗ m_lastFrameRead = -1;
487 }
488
489 // prepareRead() copies the ranges into its own member before returning, so
490 // it's safe to pass the temporary snapshot directly -- it lives through the
491 // full expression.
492
2/4
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1186 times.
✗ Branch 3 not taken.
1186 seqFile->prepareRead(*GetOutputRangesSnapshot(), startFrame < 0 ? 0 : startFrame);
493 // Calculate duration
494 1186 m_seqMSRemaining = seqFile->getNumFrames() * seqFile->getStepTime();
495 2372 m_seqMSDuration = m_seqMSRemaining.load();
496 1186 m_seqMSElapsed = 0;
497
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 SetChannelOutputRefreshRate(m_seqRefreshRate);
498
499 // start reading frames
500
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 lock.lock();
501
2/4
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1186 times.
✗ Branch 3 not taken.
1186 setSeqFile(std::shared_ptr<FSEQFile>(seqFile));
502
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 lock.unlock();
503 1186 m_seqStarting = 1; // beyond header, read loop can start reading frames
504 1186 frameLoadSignal.notify_all();
505 1186 seqFlag(m_seqPaused).store(0);
506 1186 seqFlag(m_seqSingleStep).store(0);
507 1186 seqFlag(m_seqSingleStepBack).store(0);
508 1186 m_dataProcessed = false;
509
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 ProcessVariableHeaders();
510
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 ReadSequenceData(true);
511
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 seqLock.unlock();
512
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 StartChannelOutputThread();
513
514 1186 m_numSeek = 0;
515
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 LogDebug(VB_SEQUENCE, "seqRefreshRate : %f\n", m_seqRefreshRate);
516
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
2372 LogDebug(VB_SEQUENCE, "seqMSDuration : %d\n", m_seqMSDuration.load());
517
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
2372 LogDebug(VB_SEQUENCE, "seqMSRemaining : %d\n", m_seqMSRemaining.load());
518 1186 return 1;
519 1890 }
520 1186 void Sequence::ProcessVariableHeaders() {
521
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 std::shared_ptr<FSEQFile> seqFile = m_seqFile.load();
522
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1186 times.
1186 if (!seqFile) {
523 ✗ return;
524 }
525
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 auto trig = std::make_shared<Triggers>();
526
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 1186 times.
1224 for (auto& vh : seqFile->getVariableHeaders()) {
527
5/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
38 if (vh.code[0] != 'F' || (vh.code[1] != 'C' && vh.code[1] != 'E')) {
528 31 continue;
529 }
530 // This walks a blob that came straight out of a file, so every read is
531 // bounds-checked against its end. It used to trust the block's own
532 // fields: the entry count and range count were read (unaligned) with no
533 // size check and the command/IP strings with a bare std::string(char*),
534 // so one truncated or non-v1 FC/FE block ran strlen() off the end of the
535 // vector and killed fppd while opening the sequence, before a single
536 // frame played. Nothing here is worth crashing a show over - a header
537 // that does not parse is dropped and the sequence still runs.
538
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 const std::vector<uint8_t>& vhd = vh.getData();
539 10 const uint8_t* data = vhd.data();
540 10 const uint8_t* const end = data + vhd.size();
541
542 10029 auto readU32 = [&data, end](uint32_t& out) {
543
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10026 times.
10029 if ((size_t)(end - data) < sizeof(out)) {
544 3 return false;
545 }
546 10026 memcpy(&out, data, sizeof(out)); // also drops the unaligned cast
547 10026 data += sizeof(out);
548 10026 return true;
549 10 };
550 16 auto readStr = [&data, end](std::string& out) {
551 16 const uint8_t* nul = (const uint8_t*)memchr(data, 0, end - data);
552
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 14 times.
16 if (nul == nullptr) {
553 2 return false;
554 }
555 14 out.assign((const char*)data, nul - data);
556 14 data = nul + 1;
557 14 return true;
558 10 };
559
560 10 uint32_t count = 0;
561 10 std::string ips;
562 // version flag, only understand v1 right now
563
9/12
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 7 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 7 times.
10 if (data == end || *data++ != 1 || !readU32(count) || !readStr(ips)) {
564 3 continue;
565 }
566 // TODO - process ips to see if we are actually supposed to run these commands/effects
567 // Not supported in xLights yet
568
569 7 bool truncated = false;
570
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
14 for (uint32_t x = 0; x < count && !truncated; x++) {
571 8 std::string cmd;
572 8 uint32_t cc = 0;
573
6/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 7 times.
8 if (!readStr(cmd) || !readU32(cc)) {
574 1 truncated = true;
575 1 break;
576 }
577 // The name is stored once (an _END twin for commands) and each
578 // range records only its index; see Triggers.
579 7 const uint32_t idx = (uint32_t)trig->names.size();
580
2/2
✓ Branch 0 taken 5006 times.
✓ Branch 1 taken 6 times.
5012 for (uint32_t cc1 = 0; cc1 < cc; cc1++) {
581 5006 uint32_t start = 0;
582 5006 uint32_t stop = 0;
583
5/6
✓ Branch 0 taken 5006 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 5005 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 5005 times.
5006 if (!readU32(start) || !readU32(stop)) {
584 1 truncated = true;
585 1 break;
586 }
587
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4999 times.
5005 if (cc1 == 0) {
588
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 trig->names.push_back(cmd);
589
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (vh.code[1] == 'C') {
590
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 trig->names.push_back(cmd + "_END");
591 }
592 }
593
2/2
✓ Branch 0 taken 5003 times.
✓ Branch 1 taken 2 times.
5005 if (vh.code[1] == 'C') {
594
2/4
✓ Branch 0 taken 5003 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5003 times.
✗ Branch 3 not taken.
5003 trig->commandPresets[start].push_back(idx);
595
2/4
✓ Branch 0 taken 5003 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5003 times.
✗ Branch 3 not taken.
5003 trig->commandPresets[stop].push_back(idx + 1);
596 } else {
597
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 trig->effectsOn[start].push_back(idx);
598
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 trig->effectsOff[stop].push_back(idx);
599 }
600 }
601
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 }
602
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (truncated) {
603
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogWarn(VB_SEQUENCE, "Truncated '%c%c' variable header in %s, ignoring the rest of it\n",
604 vh.code[0], vh.code[1], m_seqFilename.c_str());
605 }
606
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 }
607
7/8
✓ Branch 0 taken 1183 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1181 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1181 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 1181 times.
1186 if (!trig->commandPresets.empty() || !trig->effectsOn.empty() || !trig->effectsOff.empty()) {
608 5 m_lastTriggeredFrame = -1;
609
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 setTriggers(trig);
610 }
611
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 }
612
613 // Drop the current file's triggers (open/close). Effects the file started
614 // are stopped from the main loop: this runs under m_sequenceLock, and on the
615 // output thread when a sequence reaches its end, and StopEffect() can blank
616 // the outputs.
617 1030 void Sequence::ForgetTriggers() {
618
1/2
✓ Branch 0 taken 1030 times.
✗ Branch 1 not taken.
1030 setTriggers(nullptr);
619 1030 m_lastTriggeredFrame = -1;
620 1030 std::vector<std::string> running;
621 {
622 // Clearing the owner in the same critical section as the swap is what
623 // closes the window for an effect FireFrameTriggers() is starting
624 // right now: it either lands in `running` or sees the table closed.
625
1/2
✓ Branch 0 taken 1030 times.
✗ Branch 1 not taken.
1030 std::unique_lock<std::mutex> l(m_triggerEffectsLock);
626 1030 m_triggerEffectsOwner = nullptr;
627 1030 running.swap(m_triggerEffects);
628 1030 }
629
2/2
✓ Branch 0 taken 1029 times.
✓ Branch 1 taken 1 times.
1030 if (running.empty())
630 1029 return;
631 {
632
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::unique_lock<std::mutex> l(m_pendingPresetsLock);
633
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_pendingEffectStops.insert(m_pendingEffectStops.end(), running.begin(), running.end());
634 1 }
635
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
3 Timers::INSTANCE.addTimer("SequencePresets", GetTimeMS(), [this]() {
636 1 drainPendingPresets();
637 1 });
638
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1029 times.
1030 }
639
640 2215 void Sequence::queuePreset(const std::string& preset, std::map<std::string, std::string>&& keywords) {
641
2/2
✓ Branch 0 taken 2184 times.
✓ Branch 1 taken 31 times.
2215 if (!CommandManager::INSTANCE.HasPreset(preset)) {
642 2184 return;
643 }
644 {
645
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 std::unique_lock<std::mutex> l(m_pendingPresetsLock);
646
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 m_pendingPresets.emplace_back(preset, std::move(keywords));
647 31 }
648 // One shared timer name: a queue while an entry is already armed just
649 // refreshes it, and the drain empties the whole FIFO, so nothing is lost
650 // to the replace-same-name semantics. The drain runs on the main loop
651 // with no sequence locks held; a preset that re-enters the sequence (or
652 // queues further presets) is therefore safe.
653
3/6
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
93 Timers::INSTANCE.addTimer("SequencePresets", GetTimeMS(), [this]() {
654 27 drainPendingPresets();
655 27 });
656 }
657
658 1895 void Sequence::drainPendingPresets() {
659
1/2
✓ Branch 0 taken 1895 times.
✗ Branch 1 not taken.
1895 std::unique_lock<std::mutex> l(m_pendingPresetsLock);
660
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1895 times.
1896 while (!m_pendingEffectStops.empty()) {
661 1 std::string eff = std::move(m_pendingEffectStops.front());
662 1 m_pendingEffectStops.pop_front();
663
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 l.unlock();
664
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 StopEffect(eff);
665
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 l.lock();
666 1 }
667
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1895 times.
1926 while (!m_pendingPresets.empty()) {
668 31 auto p = std::move(m_pendingPresets.front());
669 31 m_pendingPresets.pop_front();
670
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 l.unlock();
671
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
31 CommandManager::INSTANCE.TriggerPreset(p.first, p.second);
672
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 l.lock();
673 31 }
674 1895 }
675
676 2042 void Sequence::StartSequence() {
677 // Callers reach this without m_sequenceLock (the command handler and the
678 // playlist), so the name has to be snapshotted rather than read raw.
679
1/2
✓ Branch 0 taken 2042 times.
✗ Branch 1 not taken.
2042 std::string seqName = GetSeqFilenameCopy();
680
8/14
✓ Branch 0 taken 2042 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2042 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2042 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1181 times.
✓ Branch 7 taken 861 times.
✓ Branch 8 taken 2042 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1181 times.
✓ Branch 11 taken 861 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
2042 if (!IsSequenceRunning() && m_seqFile.load()) {
681
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1149 times.
1181 if (multiSync->isMultiSyncEnabled()) {
682
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 ResetChannelOutputFrameNumber();
683
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 multiSync->SendSeqSyncStartPacket(seqName);
684 }
685 1181 m_seqStarting = 0;
686
1/2
✓ Branch 0 taken 1181 times.
✗ Branch 1 not taken.
1181 SetChannelOutputRefreshRate(m_seqRefreshRate);
687
1/2
✓ Branch 0 taken 1181 times.
✗ Branch 1 not taken.
1181 StartChannelOutputThread();
688
689 // Only on an actual start. It used to be queued on every call, and a
690 // remote whose copy of the sequence is missing calls this for every
691 // sync packet: the user's SEQUENCE_STARTED preset ran several times a
692 // second for the whole song, with no matching SEQUENCE_STOPPED.
693 //
694 // Deferred to the main loop: callers can hold m_sequenceLock
695 // (recursively, via StartSequence(filename, frame)), and a preset that
696 // starts/stops a playlist takes m_playlistMutex - the reverse of the
697 // playlist thread's m_playlistMutex -> m_sequenceLock order.
698
5/10
✓ Branch 0 taken 1181 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1181 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1181 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1181 times.
✓ Branch 7 taken 1181 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5905 queuePreset("SEQUENCE_STARTED", { { "SEQUENCE_NAME", seqName } });
699 }
700
1/6
✓ Branch 0 taken 1181 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
3223 }
701
702 252 void Sequence::StartSequence(const std::string& filename, int frameNumber) {
703
1/2
✓ Branch 0 taken 252 times.
✗ Branch 1 not taken.
252 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
704
8/8
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 195 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 240 times.
252 if ((frameNumber > 0) && (filename == m_endedSeqFilename) && (frameNumber >= m_endedSeqIgnoreFrom)) {
705 // A sync packet for a sequence that already played to its end here
706 // (see m_endedSeqFilename). A start (frame 0) or an open replays it,
707 // and so does a sync packet for a frame before the last two seconds
708 // (see m_endedSeqIgnoreFrom).
709 12 return;
710 }
711
6/6
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 187 times.
✓ Branch 2 taken 52 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 52 times.
✓ Branch 5 taken 188 times.
240 if ((sequence->m_seqFilename != filename) && (m_seqSyncName != filename)) {
712
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 CloseSequenceFile();
713
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 OpenSequenceFile(filename, frameNumber);
714
7/12
✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 166 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 166 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 188 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 166 times.
✓ Branch 9 taken 22 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
188 } else if (!m_seqFile.load() && m_missingSeqFilename == filename) {
715 // The file is still missing and the player is still syncing to it, so
716 // keep the warning up instead of letting it time out mid-sequence.
717
1/2
✓ Branch 0 taken 166 times.
✗ Branch 1 not taken.
166 RefreshMissingSequenceWarning();
718 }
719
7/8
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 193 times.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 46 times.
✓ Branch 6 taken 194 times.
✓ Branch 7 taken 46 times.
240 if ((sequence->m_seqFilename == filename) || (sequence->m_seqFilename == "fallback.fseq")) {
720
1/2
✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
194 StartSequence();
721
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 29 times.
194 if (frameNumber != 0) {
722
1/2
✓ Branch 0 taken 165 times.
✗ Branch 1 not taken.
165 SeekSequenceFile(frameNumber);
723 }
724 }
725
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 12 times.
252 }
726
727 262 void Sequence::SeekSequenceFile(int frameNumber) {
728
1/2
✓ Branch 0 taken 262 times.
✗ Branch 1 not taken.
262 LogDebug(VB_SEQUENCE, "SeekSequenceFile(%d)\n", frameNumber);
729
730
1/2
✓ Branch 0 taken 262 times.
✗ Branch 1 not taken.
262 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
731
3/4
✓ Branch 0 taken 262 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✓ Branch 3 taken 99 times.
262 if (!IsSequenceRunning()) {
732
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 LogDebug(VB_SEQUENCE, "No sequence is running\n");
733 163 return;
734 }
735
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 seqLock.unlock();
736
737
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 std::unique_lock<std::mutex> lock(frameCacheLock);
738
5/6
✓ Branch 0 taken 447 times.
✓ Branch 1 taken 99 times.
✓ Branch 2 taken 447 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 447 times.
✓ Branch 5 taken 99 times.
546 while (!pastFrameCache.empty() && frameNumber >= pastFrameCache.back()->frame) {
739 // Going backwords but frame is cached, we'll push the old frames
740
1/2
✓ Branch 0 taken 447 times.
✗ Branch 1 not taken.
447 frameCache.push_front(pastFrameCache.back());
741 447 pastFrameCache.pop_back();
742 }
743
6/6
✓ Branch 0 taken 846 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 758 times.
✓ Branch 3 taken 88 times.
✓ Branch 4 taken 758 times.
✓ Branch 5 taken 99 times.
857 while (!frameCache.empty() && frameCache.front()->frame < frameNumber) {
744
2/2
✓ Branch 0 taken 737 times.
✓ Branch 1 taken 21 times.
758 if (frameCache.front() != m_lastFrameData)
745
1/2
✓ Branch 0 taken 737 times.
✗ Branch 1 not taken.
737 delete frameCache.front();
746 758 frameCache.pop_front();
747 }
748
4/6
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 88 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 99 times.
99 if (!frameCache.empty() && frameNumber < frameCache.front()->frame) {
749 ✗ clearCaches();
750 }
751
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 88 times.
99 if (frameCache.empty()) {
752
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 LogDebug(VB_SEQUENCE, "Seeking to %d. Last read is %d\n", frameNumber, (int)m_lastFrameRead);
753 11 m_lastFrameRead = frameNumber - 1;
754 11 frameLoadSignal.notify_all();
755
756
6/8
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 3 times.
11 if ((frameNumber < 100) && (getFPPmode() == REMOTE_MODE)) {
757 8 m_numSeek++;
758
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (m_numSeek > 6) {
759 // Timed: it is re-raised while the skipping continues and
760 // otherwise ages out, rather than staying up until a restart.
761
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
8 WarningHolder::AddWarningTimeout(600, 17, "Multiple Frame Skips During Playback - Likely Slow Network");
762 }
763 }
764 }
765
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 lock.unlock();
766 99 frameLoadSignal.notify_all();
767
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 163 times.
262 }
768
769 97 void Sequence::SkipFrames(std::atomic_int& skip) {
770
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
771
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
194 if (int frames = skip.exchange(0, std::memory_order_relaxed)) {
772
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 SeekSequenceFile(channelOutputFrame.load(std::memory_order_relaxed) + frames + 1);
773 }
774 97 }
775
776 32840 std::string Sequence::GetSeqFilenameCopy() const {
777
1/2
✓ Branch 0 taken 32840 times.
✗ Branch 1 not taken.
32840 std::unique_lock<std::mutex> nameLock(m_seqFilenameLock);
778
1/2
✓ Branch 0 taken 32840 times.
✗ Branch 1 not taken.
65680 return m_seqFilename;
779 32840 }
780
781 // The flag is set after its pointer is stored and cleared before the pointer
782 // is dropped, so a reader that sees it set finds the pointer set too, as far
783 // as any unlocked check can promise that.
784 5270 void Sequence::setSeqFile(std::shared_ptr<FSEQFile> f) {
785
2/2
✓ Branch 0 taken 1186 times.
✓ Branch 1 taken 4084 times.
5270 if (f) {
786
1/2
✓ Branch 0 taken 1186 times.
✗ Branch 1 not taken.
1186 m_seqFile.store(std::move(f));
787 1186 m_haveSeqFile.store(true, std::memory_order_release);
788 } else {
789 4084 m_haveSeqFile.store(false, std::memory_order_release);
790
1/2
✓ Branch 0 taken 4084 times.
✗ Branch 1 not taken.
4084 m_seqFile.store(nullptr);
791 }
792 5270 }
793
794 1035 void Sequence::setTriggers(std::shared_ptr<Triggers> t) {
795
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1030 times.
1035 if (t) {
796 {
797
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::unique_lock<std::mutex> l(m_triggerEffectsLock);
798 5 m_triggerEffectsOwner = t.get();
799 5 }
800
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 m_triggers.store(std::move(t));
801 5 m_haveTriggers.store(true, std::memory_order_release);
802 } else {
803 1030 m_haveTriggers.store(false, std::memory_order_release);
804
1/2
✓ Branch 0 taken 1030 times.
✗ Branch 1 not taken.
1030 m_triggers.store(nullptr);
805 }
806 1035 }
807
808 1706936 int Sequence::IsSequenceRunning(void) {
809
6/6
✓ Branch 0 taken 1120287 times.
✓ Branch 1 taken 586649 times.
✓ Branch 2 taken 1115980 times.
✓ Branch 3 taken 4307 times.
✓ Branch 4 taken 1115980 times.
✓ Branch 5 taken 590956 times.
2827223 if (m_haveSeqFile.load(std::memory_order_acquire) && !m_seqStarting.load(std::memory_order_acquire))
810 1115980 return 1;
811
812 590956 return 0;
813 }
814
815 2011 int Sequence::IsSequenceRunning(const std::string& filename) {
816 2011 int result = 0;
817
818
1/2
✓ Branch 0 taken 2011 times.
✗ Branch 1 not taken.
2011 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
819
7/8
✓ Branch 0 taken 383 times.
✓ Branch 1 taken 1628 times.
✓ Branch 2 taken 383 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✓ Branch 5 taken 358 times.
✓ Branch 6 taken 1653 times.
✓ Branch 7 taken 358 times.
2011 if ((sequence->m_seqFilename == filename) || (!filename.empty() && (sequence->m_seqSyncName == filename)))
820
1/2
✓ Branch 0 taken 1653 times.
✗ Branch 1 not taken.
1653 result = sequence->IsSequenceRunning();
821
822 2011 return result;
823 2011 }
824
825 65047 void Sequence::BlankSequenceData(bool clearBridge) {
826
1/2
✓ Branch 0 taken 65047 times.
✗ Branch 1 not taken.
65047 LogExcess(VB_SEQUENCE, "BlankSequenceData()\n");
827
1/2
✓ Branch 0 taken 65047 times.
✗ Branch 1 not taken.
65047 auto ranges = GetOutputRangesSnapshot();
828
2/2
✓ Branch 0 taken 65101 times.
✓ Branch 1 taken 65047 times.
130148 for (auto& a : *ranges) {
829 65101 memset(&m_seqData[a.first], 0, a.second);
830 }
831
3/4
✓ Branch 0 taken 65047 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3981 times.
✓ Branch 3 taken 61066 times.
65047 if (m_bridgeData && clearBridge) {
832
1/2
✓ Branch 0 taken 3981 times.
✗ Branch 1 not taken.
3981 std::unique_lock<std::mutex> lock(m_bridgeRangesLock);
833
2/2
✓ Branch 0 taken 3995 times.
✓ Branch 1 taken 3981 times.
7976 for (auto& a : *ranges) {
834 3995 memset(&m_bridgeData[a.first], 0, a.second);
835 }
836 3981 m_bridgeRanges.clear();
837 3981 m_hasBridgeRanges.store(false, std::memory_order_relaxed);
838 3981 }
839
840 65047 m_dataProcessed = false;
841
842
1/2
✓ Branch 0 taken 65047 times.
✗ Branch 1 not taken.
65047 std::unique_lock<std::mutex> lock(frameCacheLock);
843
1/2
✓ Branch 0 taken 65047 times.
✗ Branch 1 not taken.
65047 SetLastFrameData(nullptr);
844 65047 }
845
846 31 int Sequence::SequenceIsPaused(void) {
847 31 return seqFlag(m_seqPaused).load();
848 }
849
850 21 void Sequence::ToggleSequencePause(void) {
851 21 seqFlag(m_seqPaused).fetch_xor(1);
852 21 }
853
854 31 void Sequence::SingleStepSequence(void) {
855 31 seqFlag(m_seqSingleStep).store(1);
856 31 }
857
858 // Broken per issue #986
859 ✗ void Sequence::SingleStepSequenceBack(void) {
860 ✗ seqFlag(m_seqSingleStepBack).store(1);
861 ✗ }
862
863 128325 void Sequence::ReadSequenceData(bool forceFirstFrame) {
864
1/2
✓ Branch 0 taken 128325 times.
✗ Branch 1 not taken.
128325 LogExcess(VB_SEQUENCE, "ReadSequenceData()\n");
865
1/2
✓ Branch 0 taken 128325 times.
✗ Branch 1 not taken.
128325 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
866
6/6
✓ Branch 0 taken 127139 times.
✓ Branch 1 taken 1186 times.
✓ Branch 2 taken 68 times.
✓ Branch 3 taken 127071 times.
✓ Branch 4 taken 68 times.
✓ Branch 5 taken 128257 times.
128325 if (!forceFirstFrame && m_seqStarting) {
867 68 return;
868 }
869
870
7/8
✓ Branch 0 taken 128257 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64879 times.
✓ Branch 3 taken 63378 times.
✓ Branch 4 taken 60 times.
✓ Branch 5 taken 64819 times.
✓ Branch 6 taken 60 times.
✓ Branch 7 taken 128197 times.
128257 if ((IsSequenceRunning()) && seqFlag(m_seqPaused).load(std::memory_order_relaxed)) {
871
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 20 times.
60 if (seqFlag(m_seqSingleStep).exchange(0)) {
872 // one frame forward: read it as if playing
873
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 } else if (seqFlag(m_seqSingleStepBack).exchange(0)) {
874 ✗ std::unique_lock<std::mutex> lock(frameCacheLock);
875 ✗ if (!pastFrameCache.empty()) {
876 ✗ frameCache.push_front(pastFrameCache.back());
877 ✗ pastFrameCache.pop_back();
878 ✗ } else if (frameCache.empty()) {
879 ✗ m_lastFrameRead = -1;
880 ✗ frameLoadSignal.notify_all();
881 } else {
882 ✗ int f = frameCache.front()->frame - 1;
883 ✗ m_lastFrameRead = f - 1;
884 ✗ if (m_lastFrameRead < -1) {
885 ✗ m_lastFrameRead = -1;
886 }
887 ✗ clearCaches();
888 ✗ frameLoadSignal.notify_all();
889 }
890 ✗ } else {
891 40 return;
892 }
893 }
894
895
7/8
✓ Branch 0 taken 127031 times.
✓ Branch 1 taken 1186 times.
✓ Branch 2 taken 127031 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 64839 times.
✓ Branch 5 taken 62192 times.
✓ Branch 6 taken 66025 times.
✓ Branch 7 taken 62192 times.
128217 if (forceFirstFrame || IsSequenceRunning()) {
896 66025 m_remoteBlankCount = 0;
897
898
1/2
✓ Branch 0 taken 66025 times.
✗ Branch 1 not taken.
66025 std::unique_lock<std::mutex> lock(frameCacheLock);
899
6/6
✓ Branch 0 taken 2028 times.
✓ Branch 1 taken 63997 times.
✓ Branch 2 taken 1422 times.
✓ Branch 3 taken 606 times.
✓ Branch 4 taken 1422 times.
✓ Branch 5 taken 64603 times.
66025 if (frameCache.empty() && !m_doneRead) {
900 // wait up to the step time, if we don't have the frame, bail
901 1422 frameLoadSignal.notify_all();
902
1/2
✓ Branch 0 taken 1422 times.
✗ Branch 1 not taken.
1422 frameLoadedSignal.wait_for(lock, std::chrono::milliseconds(m_seqStepTime - 1));
903 }
904
2/2
✓ Branch 0 taken 65376 times.
✓ Branch 1 taken 649 times.
66025 if (!frameCache.empty()) {
905 65376 FSEQFile::FrameData* data = frameCache.front();
906 65376 frameCache.pop_front();
907
2/2
✓ Branch 0 taken 55224 times.
✓ Branch 1 taken 10152 times.
65376 if (pastFrameCache.size() > 20) {
908
1/2
✓ Branch 0 taken 55224 times.
✗ Branch 1 not taken.
55224 if (pastFrameCache.front() != m_lastFrameData)
909
1/2
✓ Branch 0 taken 55224 times.
✗ Branch 1 not taken.
55224 delete pastFrameCache.front();
910 55224 pastFrameCache.pop_front();
911 }
912
1/2
✓ Branch 0 taken 65376 times.
✗ Branch 1 not taken.
65376 pastFrameCache.push_back(data);
913
1/2
✓ Branch 0 taken 65376 times.
✗ Branch 1 not taken.
65376 SetLastFrameData(data);
914
1/2
✓ Branch 0 taken 65376 times.
✗ Branch 1 not taken.
65376 lock.unlock();
915 65376 frameLoadSignal.notify_all();
916
917
1/2
✓ Branch 0 taken 65376 times.
✗ Branch 1 not taken.
65376 data->readFrame((uint8_t*)m_seqData, FPPD_MAX_CHANNELS);
918
1/2
✓ Branch 0 taken 65376 times.
✗ Branch 1 not taken.
65376 SetChannelOutputFrameNumber(data->frame);
919 // Values for status and the playlist; nothing is published
920 // through them, so no ordering is needed.
921 65376 const int elapsed = data->frame * m_seqStepTime;
922 65376 m_seqMSElapsed.store(elapsed, std::memory_order_relaxed);
923 130752 m_seqMSRemaining.store(m_seqMSDuration.load(std::memory_order_relaxed) - elapsed, std::memory_order_relaxed);
924 65376 m_dataProcessed.store(false, std::memory_order_release);
925
2/2
✓ Branch 0 taken 606 times.
✓ Branch 1 taken 43 times.
649 } else if (m_doneRead) {
926
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 lock.unlock();
927 1212 m_seqMSElapsed = m_seqMSDuration.load();
928 606 m_seqMSRemaining = 0;
929 // Remember it by the name sync packets use (see m_seqSyncName).
930 606 std::string ended;
931 606 int ignoreFrom = 0;
932 {
933
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
934
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 606 times.
✓ Branch 2 taken 606 times.
✗ Branch 3 not taken.
606 ended = m_seqSyncName.empty() ? m_seqFilename : m_seqSyncName;
935
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 if (m_seqStepTime > 0) {
936 1212 ignoreFrom = std::max(0, (m_seqMSDuration.load() - 2000) / m_seqStepTime);
937 }
938 606 }
939
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 CloseSequenceFile();
940
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
941
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 m_endedSeqFilename = ended;
942 606 m_endedSeqIgnoreFrom = ignoreFrom;
943 606 } else {
944
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 2 times.
43 if (m_lastFrameRead > 0) {
945 // we'll have the read thread discard the frame
946 41 m_lastFrameRead++;
947
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41 times.
41 if (!pastFrameCache.empty()) {
948 // and copy the last frame data
949 ✗ SetLastFrameData(pastFrameCache.back());
950 ✗ pastFrameCache.back()->readFrame((uint8_t*)m_seqData, FPPD_MAX_CHANNELS);
951 ✗ m_dataProcessed = false;
952 }
953 }
954
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 lock.unlock();
955 43 frameLoadSignal.notify_all();
956 }
957 66025 } else {
958
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62192 times.
62192 if (m_blankBetweenSequences) {
959 ✗ BlankSequenceData();
960
3/4
✓ Branch 0 taken 62192 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 136 times.
✓ Branch 3 taken 62056 times.
62192 } else if (getFPPmode() == REMOTE_MODE) {
961 // on a remote, we will get a "stop" and then a "start" a short time later
962 // we don't want to blank immediately (unless the master tells us to)
963 // so we don't get black blinks on the remote. We'll wait the equivalent
964 // of 5 frames to then blank
965
2/2
✓ Branch 0 taken 73 times.
✓ Branch 1 taken 63 times.
136 if (m_remoteBlankCount > 5) {
966
1/2
✓ Branch 0 taken 73 times.
✗ Branch 1 not taken.
73 BlankSequenceData();
967 } else {
968 63 m_remoteBlankCount++;
969 }
970
7/10
✓ Branch 0 taken 62056 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60993 times.
✓ Branch 3 taken 1063 times.
✓ Branch 4 taken 60993 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60993 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 60993 times.
✓ Branch 9 taken 1063 times.
62056 } else if (!Player::INSTANCE.IsPlaying() && (getFPPmode() & PLAYER_MODE)) {
971 // Player, but not playlist running (so not between sequences)
972 // yet we are likely outputting something (overlay, etc...) Need to blank
973
1/2
✓ Branch 0 taken 60993 times.
✗ Branch 1 not taken.
60993 BlankSequenceData();
974 }
975 }
976
2/2
✓ Branch 0 taken 128217 times.
✓ Branch 1 taken 108 times.
128325 }
977
978 138483 void Sequence::ProcessSequenceData(int ms) {
979
2/2
✓ Branch 0 taken 6481 times.
✓ Branch 1 taken 132002 times.
138483 if (m_dataProcessed.load(std::memory_order_acquire)) {
980 // we shouldn't normally be reprocessing the same data, so
981 // if we are then see if we can start with a pristine copy
982
1/2
✓ Branch 0 taken 6481 times.
✗ Branch 1 not taken.
6481 std::unique_lock<std::mutex> lock(frameCacheLock);
983
2/2
✓ Branch 0 taken 808 times.
✓ Branch 1 taken 5673 times.
6481 if (m_lastFrameData)
984
1/2
✓ Branch 0 taken 808 times.
✗ Branch 1 not taken.
808 m_lastFrameData->readFrame((uint8_t*)m_seqData, FPPD_MAX_CHANNELS);
985 6481 }
986
987
5/6
✓ Branch 0 taken 138483 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4078 times.
✓ Branch 3 taken 134405 times.
✓ Branch 4 taken 4078 times.
✓ Branch 5 taken 134405 times.
138483 if (m_bridgeData && hasBridgeData()) {
988 // Prune expired ranges and merge the live ones, all under the lock.
989 // m_bridgeRanges is keyed by startChannel so the scratch vector comes
990 // out sorted. WriteBridgeData() copies each packet in under the same
991 // lock, so a packet can no longer land halfway through this copy and
992 // send a universe half old, half new; the receive path waits at most
993 // for one merge's memcpys.
994
1/2
✓ Branch 0 taken 4078 times.
✗ Branch 1 not taken.
4078 uint64_t nt = GetMonotonicTimeMS(); // expiries are monotonic (CommitBridgeRanges)
995 // The scratch vector is shared by every caller (the output thread and
996 // the inline SendBlankingData path), so it is cleared, filled and read
997 // only under the lock: a clear() racing another caller's emplace_back
998 // or iteration corrupts the vector.
999
1/2
✓ Branch 0 taken 4078 times.
✗ Branch 1 not taken.
4078 std::unique_lock<std::mutex> bridgesLock(m_bridgeRangesLock);
1000 4078 m_bridgeRangeScratch.clear();
1001 4078 auto rit = m_bridgeRanges.begin();
1002
2/2
✓ Branch 0 taken 15952 times.
✓ Branch 1 taken 4078 times.
20030 while (rit != m_bridgeRanges.end()) {
1003 15952 BridgeRangeData& rd = rit->second;
1004 15952 uint32_t len = 0;
1005 15952 auto it = rd.expires.begin();
1006
2/2
✓ Branch 0 taken 15954 times.
✓ Branch 1 taken 15952 times.
31906 while (it != rd.expires.end()) {
1007
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 15942 times.
15954 if (it->second < nt) {
1008
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 it = rd.expires.erase(it);
1009 } else {
1010 15942 len = std::max(len, it->first);
1011 15942 ++it;
1012 }
1013 }
1014
2/2
✓ Branch 0 taken 15942 times.
✓ Branch 1 taken 10 times.
15952 if (len > 0) {
1015
1/2
✓ Branch 0 taken 15942 times.
✗ Branch 1 not taken.
15942 m_bridgeRangeScratch.emplace_back(rd.startChannel, len);
1016 15942 ++rit;
1017 } else {
1018
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 rit = m_bridgeRanges.erase(rit);
1019 }
1020 }
1021 4078 m_hasBridgeRanges.store(!m_bridgeRanges.empty(), std::memory_order_relaxed);
1022
1023 // copy the latest bridge data to the sequence data
1024 4078 uint32_t curStart = 0xFFFFFFFF;
1025 4078 uint32_t nextStart = 0xFFFFFFFF;
1026
2/2
✓ Branch 0 taken 15942 times.
✓ Branch 1 taken 4078 times.
20020 for (auto& a : m_bridgeRangeScratch) {
1027
2/2
✓ Branch 0 taken 4070 times.
✓ Branch 1 taken 11872 times.
15942 if (curStart == 0xFFFFFFFF) {
1028 4070 curStart = a.first;
1029 4070 nextStart = a.first + a.second;
1030
2/2
✓ Branch 0 taken 3988 times.
✓ Branch 1 taken 7884 times.
11872 } else if (a.first > nextStart) {
1031 3988 memcpy(&m_seqData[curStart], &m_bridgeData[curStart], nextStart - curStart);
1032 3988 curStart = a.first;
1033 3988 nextStart = a.first + a.second;
1034 } else {
1035 // Adjacent or overlapping: extend the run, but never shrink it.
1036 // This used to set it to the end of 'a', which cut the run
1037 // short when 'a' lay entirely inside it (one input's universe
1038 // inside another's range), dropping the earlier range's tail.
1039 7884 nextStart = std::max(nextStart, a.first + a.second);
1040 }
1041 }
1042
2/2
✓ Branch 0 taken 4070 times.
✓ Branch 1 taken 8 times.
4078 if (curStart != 0xFFFFFFFF) {
1043 4070 memcpy(&m_seqData[curStart], &m_bridgeData[curStart], nextStart - curStart);
1044 }
1045
1/2
✓ Branch 0 taken 4078 times.
✗ Branch 1 not taken.
4078 bridgesLock.unlock();
1046 4078 }
1047 138483 PluginManager::INSTANCE.modifySequenceData(ms, (uint8_t*)m_seqData);
1048
1049
2/2
✓ Branch 0 taken 1033 times.
✓ Branch 1 taken 137450 times.
138483 if (IsEffectRunning())
1050 1033 OverlayEffects(m_seqData);
1051
1052 138483 if (
1053 #ifdef HAS_GSTREAMER
1054
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 138387 times.
138483 GStreamerOutput::IsOverlayingVideo()
1055 #else
1056 false
1057 #endif
1058 ) {
1059 #ifdef HAS_GSTREAMER
1060 96 GStreamerOutput::ProcessVideoOverlay(ms);
1061 #endif
1062 }
1063
2/2
✓ Branch 0 taken 25608 times.
✓ Branch 1 taken 112875 times.
138483 if (PixelOverlayManager::INSTANCE.hasActiveOverlays()) {
1064 25608 PixelOverlayManager::INSTANCE.doOverlays((uint8_t*)m_seqData);
1065 }
1066
1067
2/2
✓ Branch 0 taken 30015 times.
✓ Branch 1 taken 108468 times.
138483 if (ChannelTester::INSTANCE.Testing())
1068 30015 ChannelTester::INSTANCE.OverlayTestData(m_seqData);
1069
1070 138483 PluginManager::INSTANCE.modifyChannelData(ms, (uint8_t*)m_seqData);
1071
1072 138483 PrepareChannelData(m_seqData);
1073 138483 m_dataProcessed.store(true, std::memory_order_release);
1074 138483 }
1075
1076 131548 void Sequence::FireFrameTriggers() {
1077 // Triggers belong to frames, not to sends. This runs on every output
1078 // send, and used to fire whatever frame was current: the first frame's
1079 // triggers went off two or three times (it is sent before the sequence
1080 // starts, and a remote resends it until the start packet arrives), a held
1081 // or paused frame re-fired every send, and a frame skipped by a catch-up
1082 // jump -- often the one carrying an _END preset -- never fired. Fire each
1083 // frame's triggers once, only once the sequence has started, including the
1084 // frames a small jump skipped; a large jump is a seek (a remote joining
1085 // mid-song) and fires only the frame it lands on.
1086 // Most files have no triggers: skip the locked load on every frame.
1087
2/2
✓ Branch 0 taken 131384 times.
✓ Branch 1 taken 164 times.
131548 if (!m_haveTriggers.load(std::memory_order_acquire))
1088 131385 return;
1089
1/2
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
164 std::shared_ptr<Triggers> trig = m_triggers.load();
1090 164 int64_t frame = m_lastFrameNumber.load();
1091
6/8
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 163 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 163 times.
164 if (!trig || (frame < 0) || m_seqStarting)
1092 1 return;
1093 163 int64_t last = m_lastTriggeredFrame.load();
1094
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
163 if (frame == last)
1095 ✗ return;
1096
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 int64_t maxCatchup = (m_seqStepTime > 0) ? (2000 / m_seqStepTime) : 40;
1097
2/4
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
163 int64_t from = ((frame < last) || ((frame - last) > maxCatchup)) ? frame : last + 1;
1098 163 m_lastTriggeredFrame = frame;
1099
1100
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 for (auto p = trig->commandPresets.lower_bound((uint32_t)from);
1101
6/6
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 80 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 163 times.
169 (p != trig->commandPresets.end()) && (p->first <= (uint64_t)frame); ++p) {
1102 // This runs on the channel output thread while the open/close
1103 // paths can be reassigning the name on another thread.
1104
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::string seqName = GetSeqFilenameCopy();
1105
2/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
12 std::map<std::string, std::string> keywords({ { "SEQUENCE_NAME", seqName } });
1106 6 uint32_t elapsedMS = p->first * (uint32_t)m_seqStepTime;
1107 6 uint32_t totalSeconds = elapsedMS / 1000;
1108 char timeBuf[16];
1109 6 snprintf(timeBuf, sizeof(timeBuf), "%02u:%02u.%03u",
1110 totalSeconds / 60, totalSeconds % 60, elapsedMS % 1000);
1111
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 for (uint32_t idx : p->second) {
1112 6 const std::string& cmd = trig->names[idx];
1113
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 LogDebug(VB_COMMAND, "Sequence \"%s\" @ %s triggering preset \"%s\"\n", seqName.c_str(), timeBuf, cmd.c_str());
1114 // Deferred to the main loop: this thread holds
1115 // outputThreadLock for the whole frame, so running a
1116 // command here couples the output thread to every lock
1117 // any command can take (playlist, scheduler via
1118 // %VAR:fpp_*%, media...). The preset fires on the next
1119 // main-loop tick, a few ms after the frame it was
1120 // authored on.
1121
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 queuePreset(cmd, std::map<std::string, std::string>(keywords));
1122 }
1123 6 }
1124
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 for (auto e = trig->effectsOn.lower_bound((uint32_t)from);
1125
6/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 160 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 163 times.
165 (e != trig->effectsOn.end()) && (e->first <= (uint64_t)frame); ++e) {
1126
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (uint32_t idx : e->second) {
1127 2 const std::string& eff = trig->names[idx];
1128
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 StartEffect(eff, 0, 1);
1129
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_lock<std::mutex> l(m_triggerEffectsLock);
1130
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (m_triggerEffectsOwner == trig.get()) {
1131
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_triggerEffects.push_back(eff);
1132 } else {
1133 // The file closed (ForgetTriggers) while this frame was
1134 // firing, after it took the running list: nothing else will
1135 // stop this effect, so stop it here, as effectsOff does.
1136
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 l.unlock();
1137
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 StopEffect(eff);
1138 }
1139 2 }
1140 }
1141
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 for (auto e = trig->effectsOff.lower_bound((uint32_t)from);
1142
4/6
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 121 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 163 times.
163 (e != trig->effectsOff.end()) && (e->first <= (uint64_t)frame); ++e) {
1143 ✗ for (uint32_t idx : e->second) {
1144 ✗ const std::string& eff = trig->names[idx];
1145 ✗ StopEffect(eff);
1146 ✗ std::unique_lock<std::mutex> l(m_triggerEffectsLock);
1147 ✗ auto it = std::find(m_triggerEffects.begin(), m_triggerEffects.end(), eff);
1148 ✗ if (it != m_triggerEffects.end())
1149 ✗ m_triggerEffects.erase(it);
1150 ✗ }
1151 }
1152
4/10
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
170 }
1153
1154 131548 void Sequence::SendSequenceData() {
1155 131548 FireFrameTriggers();
1156 131548 SendChannelData(m_seqData);
1157 131548 }
1158
1159 3981 void Sequence::SendBlankingData(void) {
1160
1/2
✓ Branch 0 taken 3981 times.
✗ Branch 1 not taken.
3981 LogDebug(VB_SEQUENCE, "SendBlankingData()\n");
1161
2/4
✓ Branch 0 taken 3981 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3981 times.
✗ Branch 3 not taken.
3981 std::this_thread::sleep_for(5ms);
1162
1163
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 3914 times.
3981 if (multiSync->isMultiSyncEnabled())
1164
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 multiSync->SendBlankingDataPacket();
1165
1166
1/2
✓ Branch 0 taken 3981 times.
✗ Branch 1 not taken.
3981 BlankSequenceData(true);
1167
1168 // Normally the output thread sends it. With output disabled (the outputs
1169 // API's "disable") that thread keeps running and preparing every frame on
1170 // the same buffers, so it is asked to send this one anyway rather than
1171 // having this thread prepare and send alongside it. Waiting for its lock
1172 // instead deadlocked: callers hold m_sequenceLock (CloseSequenceFile(),
1173 // and the open/start paths that close), which the output thread takes in
1174 // ReadSequenceData() while holding outputThreadLock.
1175
7/10
✓ Branch 0 taken 3981 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3801 times.
✓ Branch 3 taken 180 times.
✓ Branch 4 taken 3801 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 180 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3346 times.
✓ Branch 9 taken 635 times.
3981 if (ChannelOutputThreadIsEnabled() ? ForceChannelOutputNow() : ForceChannelOutputSendNow()) {
1176 3346 return;
1177 }
1178
1179 // The output thread isn't running, or is on its way out and no longer
1180 // preparing frames: drive the outputs from this thread. Hold off a
1181 // replacement output thread from starting mid-send, but never wait on its
1182 // lock: an exiting thread holds it while its OUTPUTS_DISABLED presets run,
1183 // and those can stop a playlist, which needs m_sequenceLock -- held by our
1184 // caller. Not on the output thread itself, which already holds it, nor
1185 // when this send re-enters itself through OverlayEffects() on the same
1186 // thread: the lock is a plain mutex. Then hold off an output reload's
1187 // teardown, which would otherwise free an output mid-send.
1188 static thread_local int inlineSendDepth = 0;
1189 635 std::unique_lock<std::mutex> threadLock(outputThreadLock, std::defer_lock);
1190
4/8
✓ Branch 0 taken 635 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 635 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 635 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 635 times.
✗ Branch 7 not taken.
635 if (inlineSendDepth == 0 && !IsChannelOutputThread()) {
1191
7/8
✓ Branch 0 taken 11821 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11199 times.
✓ Branch 3 taken 622 times.
✓ Branch 4 taken 11186 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 11186 times.
✓ Branch 7 taken 635 times.
11821 for (int tries = 0; !threadLock.try_lock() && tries < 50; ++tries) {
1192
4/10
✓ Branch 0 taken 11186 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11186 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11186 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 11186 times.
11186 if (ChannelOutputThreadIsEnabled() ? ForceChannelOutputNow() : ForceChannelOutputSendNow()) {
1193 ✗ return; // a replacement came up; it sends the blank
1194 }
1195
2/4
✓ Branch 0 taken 11186 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11186 times.
✗ Branch 3 not taken.
11186 std::this_thread::sleep_for(1ms);
1196 }
1197 }
1198
1/2
✓ Branch 0 taken 635 times.
✗ Branch 1 not taken.
635 std::unique_lock<std::recursive_mutex> inlineLock(inlineOutputLock);
1199 struct Depth {
1200 635 Depth() { inlineSendDepth++; }
1201 635 ~Depth() { inlineSendDepth--; }
1202 635 } depth;
1203
1/2
✓ Branch 0 taken 635 times.
✗ Branch 1 not taken.
635 ProcessSequenceData(0);
1204
1/2
✓ Branch 0 taken 635 times.
✗ Branch 1 not taken.
635 SendSequenceData();
1205
1/2
✓ Branch 0 taken 635 times.
✗ Branch 1 not taken.
635 }
1206
1207 29 void Sequence::CloseIfOpen(const std::string& filename) {
1208
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
1209
6/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 25 times.
✓ Branch 5 taken 4 times.
29 if ((m_seqFilename == filename) || (m_seqSyncName == filename))
1210
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 CloseSequenceFile();
1211 29 }
1212
1213 1092 void Sequence::CloseSequenceFile(void) {
1214 // These two reads happen before m_sequenceLock is taken below, so they need
1215 // the snapshot; callers arrive here from the playlist, MultiSync and the
1216 // API threads.
1217
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 std::string seqName = GetSeqFilenameCopy();
1218
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 LogDebug(VB_SEQUENCE, "CloseSequenceFile() %s\n", seqName.c_str());
1219
1220
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1073 times.
1092 if (multiSync->isMultiSyncEnabled())
1221
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 multiSync->SendSeqSyncStopPacket(seqName);
1222
1223
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 std::unique_lock<std::recursive_mutex> seqLock(m_sequenceLock);
1224
1225 // The player told us to stop (or we're moving on); a file that never
1226 // opened is no longer worth warning about.
1227
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 ClearMissingSequenceWarning();
1228
1229
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 std::unique_lock<std::mutex> readLock(readFileLock);
1230
3/4
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1029 times.
✓ Branch 3 taken 63 times.
1092 if (std::shared_ptr<FSEQFile> old = m_seqFile.load()) {
1231
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 std::string fn = old->getFilename();
1232
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 std::unique_lock<std::mutex> fclock(frameCacheLock);
1233
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 setSeqFile(nullptr);
1234
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 fclock.unlock();
1235
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 checkForReplacementFile(fn);
1236
1237 // Deferred to the main loop: this runs under m_sequenceLock +
1238 // readFileLock (and on the channel output thread when the sequence
1239 // ends via ReadSequenceData), so firing the preset here inverts
1240 // against the playlist thread's m_playlistMutex -> m_sequenceLock.
1241
2/2
✓ Branch 0 taken 1026 times.
✓ Branch 1 taken 3 times.
1029 if (!m_seqStarting) {
1242 // Only for a sequence that started, to pair with SEQUENCE_STARTED
1243 // (which fires only on an actual start). Closing one that was
1244 // opened and never started -- the next playlist entry prepared,
1245 // then a stop -- used to fire a STOPPED with no STARTED.
1246
5/10
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1026 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1026 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1026 times.
✓ Branch 7 taken 1026 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5130 queuePreset("SEQUENCE_STOPPED", { { "SEQUENCE_NAME", m_seqFilename } });
1247 }
1248
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 ForgetTriggers();
1249 2121 }
1250
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 readLock.unlock();
1251
1252
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 std::unique_lock<std::mutex> lock(frameCacheLock);
1253
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 clearCaches();
1254 1092 m_doneRead = true;
1255 1092 m_lastFrameRead = -1;
1256
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 lock.unlock();
1257 1092 frameLoadedSignal.notify_all();
1258
1259 {
1260
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 std::unique_lock<std::mutex> nameLock(m_seqFilenameLock);
1261
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 m_seqFilename = "";
1262 1092 }
1263 1092 m_seqSyncName.clear();
1264 1092 seqFlag(m_seqPaused).store(0);
1265
1266
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 if ((!IsEffectRunning()) &&
1267
3/4
✓ Branch 0 taken 1091 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1011 times.
✓ Branch 3 taken 80 times.
1091 ((getFPPmode() != REMOTE_MODE) &&
1268
7/8
✓ Branch 0 taken 1091 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1011 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 614 times.
✓ Branch 5 taken 397 times.
✓ Branch 6 taken 397 times.
✓ Branch 7 taken 695 times.
2798 (Player::INSTANCE.GetStatus() != FPP_STATUS_PLAYLIST_PLAYING)) ||
1269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 695 times.
695 (m_blankBetweenSequences)) {
1270
1/2
✓ Branch 0 taken 397 times.
✗ Branch 1 not taken.
397 SendBlankingData();
1271 }
1272
1/6
✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2118 }
1273
1274 49392 bool Sequence::WriteBridgeData(uint8_t* data, int startChannel, int len) {
1275
3/4
✓ Branch 0 taken 49392 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 49371 times.
49392 if (this->IsSequenceRunning()) {
1276
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 if (m_warn_if_bridging) {
1277
3/6
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
80 WarningHolder::AddWarningTimeout(60 ,11, "Received bridging data while sequence is running.");
1278 }
1279
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 if (m_prioritize_sequence_over_bridge) {
1280 20 return false;
1281 }
1282 }
1283
1284
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49372 times.
49372 if (!m_bridgeData) {
1285 ✗ return false;
1286 }
1287 // Guard against out-of-range bridging data. The bound is the real
1288 // channels, FPPD_MAX_CHANNELS: the bytes after them are the reserved OFF
1289 // and WHITE channels, which outputs read as "always 0" for null pixels and
1290 // smart-receiver lead-in/out, and bridged data merged into them turned
1291 // those on. Unsigned arithmetic avoids overflow.
1292
2/4
✓ Branch 0 taken 49372 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49372 times.
✗ Branch 3 not taken.
49372 if (startChannel < 0 || len < 0 ||
1293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49372 times.
49372 ((unsigned long long)startChannel + (unsigned long long)len) > (unsigned long long)FPPD_MAX_CHANNELS) {
1294 ✗ return false;
1295 }
1296 // Under the ranges lock, which ProcessSequenceData() holds while it
1297 // copies the bridge buffer out: a packet landing mid-copy sent a universe
1298 // half old, half new.
1299
1/2
✓ Branch 0 taken 49372 times.
✗ Branch 1 not taken.
49372 std::unique_lock<std::mutex> lock(m_bridgeRangesLock);
1300 49372 memcpy(&m_bridgeData[startChannel], data, len);
1301 49372 return true;
1302 49372 }
1303
1304 17715 void Sequence::CommitBridgeRanges(const std::vector<std::pair<uint32_t, uint32_t>>& ranges, uint64_t expireMS) {
1305
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17715 times.
17715 if (ranges.empty()) {
1306 ✗ return;
1307 }
1308
1/2
✓ Branch 0 taken 17715 times.
✗ Branch 1 not taken.
17715 std::unique_lock<std::mutex> lock(m_bridgeRangesLock);
1309
2/2
✓ Branch 0 taken 49372 times.
✓ Branch 1 taken 17715 times.
67087 for (auto& r : ranges) {
1310
1/2
✓ Branch 0 taken 49372 times.
✗ Branch 1 not taken.
49372 auto& a = m_bridgeRanges[r.first];
1311 49372 a.startChannel = r.first;
1312
1/2
✓ Branch 0 taken 49372 times.
✗ Branch 1 not taken.
49372 a.expires[r.second] = expireMS;
1313 }
1314 17715 m_hasBridgeRanges.store(true, std::memory_order_relaxed);
1315
1/2
✓ Branch 0 taken 17715 times.
✗ Branch 1 not taken.
17715 lock.unlock();
1316
1317 17715 setDataNotProcessed();
1318 17715 }
1319
1320 ✗ void Sequence::SetBridgeData(uint8_t* data, int startChannel, int len, uint64_t expireMS) {
1321 ✗ if (!WriteBridgeData(data, startChannel, len)) {
1322 ✗ return;
1323 }
1324 ✗ std::unique_lock<std::mutex> lock(m_bridgeRangesLock);
1325 ✗ auto& a = m_bridgeRanges[startChannel];
1326 ✗ a.startChannel = startChannel;
1327 ✗ a.expires[len] = expireMS;
1328 ✗ m_hasBridgeRanges.store(true, std::memory_order_relaxed);
1329 ✗ lock.unlock();
1330
1331 ✗ setDataNotProcessed();
1332 ✗ }
1333
1334 631287 bool Sequence::hasBridgeData() {
1335 631287 return m_hasBridgeRanges.load(std::memory_order_relaxed);
1336 }
1337