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