| 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 | #include <shared_mutex> | ||
| 16 | |||
| 17 | #include "fpp-json.h" | ||
| 18 | |||
| 19 | #include <dlfcn.h> | ||
| 20 | #include <errno.h> | ||
| 21 | #include <pthread.h> | ||
| 22 | #include <stdio.h> | ||
| 23 | #include <stdlib.h> | ||
| 24 | #include <string.h> | ||
| 25 | |||
| 26 | #include <algorithm> | ||
| 27 | #include <sstream> | ||
| 28 | #include <string> | ||
| 29 | |||
| 30 | #include "ChannelOutput.h" | ||
| 31 | #include "ChannelOutputSetup.h" | ||
| 32 | #include "Sequence.h" | ||
| 33 | #include "channeloutputthread.h" | ||
| 34 | #include "ThreadedChannelOutput.h" | ||
| 35 | #include "Warnings.h" | ||
| 36 | #include "common.h" | ||
| 37 | #include "log.h" | ||
| 38 | #include "settings.h" | ||
| 39 | #include "../AtomicSharedPtr.h" | ||
| 40 | #include "../FileMonitor.h" | ||
| 41 | #include "../OutputMonitor.h" | ||
| 42 | #include "../Plugin.h" | ||
| 43 | #include "../Plugins.h" | ||
| 44 | #include "../config.h" | ||
| 45 | #include "../MultiSync.h" | ||
| 46 | #include "../e131bridge.h" | ||
| 47 | |||
| 48 | #include "processors/OutputProcessor.h" | ||
| 49 | |||
| 50 | ///////////////////////////////////////////////////////////////////////////// | ||
| 51 | |||
| 52 | class FPPChannelOutputInstance { | ||
| 53 | public: | ||
| 54 | 1279 | FPPChannelOutputInstance() {} | |
| 55 | 1279 | ~FPPChannelOutputInstance() {} | |
| 56 | |||
| 57 | unsigned int startChannel = 0; | ||
| 58 | unsigned int channelCount = 0; | ||
| 59 | ChannelOutput* output = nullptr; | ||
| 60 | std::string sourceFile; | ||
| 61 | |||
| 62 | std::atomic<FPPChannelOutputInstance*> prev; | ||
| 63 | std::atomic<FPPChannelOutputInstance*> next; | ||
| 64 | }; | ||
| 65 | |||
| 66 | std::atomic<unsigned long> channelOutputFrame{ 0 }; | ||
| 67 | std::atomic<float> mediaElapsedSeconds{ 0.0f }; | ||
| 68 | |||
| 69 | std::atomic<FPPChannelOutputInstance*> channelOutputs; | ||
| 70 | std::atomic<FPPChannelOutputInstance*> lastChannelOutput; | ||
| 71 | 1202 | inline void addChannelOutput(FPPChannelOutputInstance* inst) { | |
| 72 | // the output thread walks this list under outputThreadLock; linking a node | ||
| 73 | // in takes several stores, so publish them all before it can see any | ||
| 74 |
1/2✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
|
1202 | std::unique_lock<std::mutex> lock = LockOutputThread(); |
| 75 | // An output added while the output thread is already up -- a config reload, | ||
| 76 | // which is what an xLights auto-upload does -- missed the StartingOutput() | ||
| 77 | // that thread made when it came up, and would otherwise run in the stopped | ||
| 78 | // state for as long as it lives: BBShiftPanel leaves its PRUs parked, so | ||
| 79 | // the panels stay dark, and PCA9685 never leaves sleep. The thread sets | ||
| 80 | // ThreadIsRunning and makes its own StartingOutput() call under this same | ||
| 81 | // lock, so checking here cannot land in between the two and either miss | ||
| 82 | // the call or make it twice. | ||
| 83 |
6/8✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1202 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✓ Branch 5 taken 1054 times.
✓ Branch 6 taken 148 times.
✓ Branch 7 taken 1054 times.
|
1202 | if (inst->output && ChannelOutputThreadIsRunning()) { |
| 84 |
2/4✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
|
148 | LogDebug(VB_CHANNELOUT, "Output thread already running, starting output on new %s\n", |
| 85 | inst->output->GetOutputType().c_str()); | ||
| 86 |
1/2✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
|
148 | inst->output->StartingOutput(); |
| 87 | } | ||
| 88 | 1202 | inst->prev = lastChannelOutput.load(); | |
| 89 |
2/2✓ Branch 0 taken 167 times.
✓ Branch 1 taken 1035 times.
|
1202 | if (lastChannelOutput) { |
| 90 | 167 | lastChannelOutput.load()->next = inst; | |
| 91 | } else { | ||
| 92 | 1035 | channelOutputs = inst; | |
| 93 | } | ||
| 94 | 1202 | lastChannelOutput = inst; | |
| 95 | 1202 | } | |
| 96 | |||
| 97 | OutputProcessors outputProcessors; | ||
| 98 | |||
| 99 | ✗ | bool HasChannelOutputs() { | |
| 100 | ✗ | return channelOutputs.load() != nullptr; | |
| 101 | } | ||
| 102 | |||
| 103 | // This and GetOutputTypes() below walk the list from HTTP/MultiSync threads. | ||
| 104 | // They can't take outputThreadLock: the output thread holds it across | ||
| 105 | // Sequence::SendSequenceData(), which runs FSEQ-triggered commands, and a | ||
| 106 | // command that reached back into either of these would deadlock the output | ||
| 107 | // thread. They read under outputListFreeLock instead, which the reload path | ||
| 108 | // takes last, only while it unlinks instances, and which these walks hold | ||
| 109 | // alone -- a leaf, so nothing can wait on it in a cycle. Without it they | ||
| 110 | // could read an instance the reload was freeing. The reload frees what it | ||
| 111 | // unlinked only after letting go: an output's Close() takes other locks | ||
| 112 | // (UDPOutput's removes its NetworkMonitor callback, whose dispatch takes | ||
| 113 | // MultiSync's lock, which is held around these walks), and the walk that | ||
| 114 | // could still see an unlinked instance is one the exclusive lock waited out. | ||
| 115 | static std::shared_mutex outputListFreeLock; | ||
| 116 | |||
| 117 | 173632 | bool HasUniverseOutputs() { | |
| 118 |
3/6✓ Branch 0 taken 173632 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 173632 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 173632 times.
✗ Branch 5 not taken.
|
173632 | std::string universeFile = FPP_DIR_CONFIG("/co-universes.json"); |
| 119 |
1/2✓ Branch 0 taken 173632 times.
✗ Branch 1 not taken.
|
173632 | std::shared_lock<std::shared_mutex> l(outputListFreeLock); |
| 120 |
2/2✓ Branch 0 taken 76191 times.
✓ Branch 1 taken 98441 times.
|
174632 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 121 |
2/2✓ Branch 0 taken 75191 times.
✓ Branch 1 taken 1000 times.
|
76191 | if (inst->sourceFile == universeFile) { |
| 122 | 75191 | return true; | |
| 123 | } | ||
| 124 | } | ||
| 125 | 98441 | return false; | |
| 126 | 173632 | } | |
| 127 | |||
| 128 | static std::map<std::string, std::set<std::string>> outputLoadWarnings; | ||
| 129 | // Warnings the outputs themselves raise while a config file's outputs are | ||
| 130 | // being initialised (AddOutputConfigWarning), retracted with that file's other | ||
| 131 | // load warnings when it is next loaded. Main loop only, like the loader. | ||
| 132 | static std::map<std::string, std::set<std::pair<int, std::string>>> outputConfigWarnings; | ||
| 133 | static std::string outputConfigWarningFile; | ||
| 134 | |||
| 135 | 78 | void AddOutputConfigWarning(int id, const std::string& warning) { | |
| 136 |
1/2✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
|
78 | WarningHolder::AddWarning(id, warning); |
| 137 |
2/2✓ Branch 0 taken 76 times.
✓ Branch 1 taken 2 times.
|
78 | if (!outputConfigWarningFile.empty()) { |
| 138 |
3/6✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
76 | outputConfigWarnings[outputConfigWarningFile].insert({ id, warning }); |
| 139 | } | ||
| 140 | 78 | } | |
| 141 | |||
| 142 | using OutputRangeVec = std::vector<std::pair<uint32_t, uint32_t>>; | ||
| 143 | // Snapshots published by ComputeOutputRanges() and handed out by | ||
| 144 | // GetOutputRangesSnapshot(). Both are seeded here with the {0,8} default so a | ||
| 145 | // snapshot is never null and never empty -- readers never mutate these, so no | ||
| 146 | // lock is needed to see a consistent list while ComputeOutputRanges() | ||
| 147 | // publishes a new one from another thread. | ||
| 148 | static AtomicSharedPtr<const OutputRangeVec> outputRanges(std::make_shared<const OutputRangeVec>(OutputRangeVec{ { 0, 8 } })); | ||
| 149 | static AtomicSharedPtr<const OutputRangeVec> preciseOutputRanges(std::make_shared<const OutputRangeVec>(OutputRangeVec{ { 0, 8 } })); | ||
| 150 | |||
| 151 | // Returns a snapshot of the current ranges; the caller should hold the | ||
| 152 | // returned shared_ptr for the duration of any iteration over it rather than | ||
| 153 | // calling this repeatedly. Never null, never empty. | ||
| 154 | 118458 | std::shared_ptr<const OutputRangeVec> GetOutputRangesSnapshot(bool precise) { | |
| 155 |
2/2✓ Branch 0 taken 118374 times.
✓ Branch 1 taken 84 times.
|
118458 | return precise ? preciseOutputRanges.load() : outputRanges.load(); |
| 156 | } | ||
| 157 | |||
| 158 | // Plugin-compatibility API (fpp-brightness and fpp-Capture call this; the | ||
| 159 | // return type is not part of the C++ name mangling, so changing it would let | ||
| 160 | // an already-compiled plugin misread the return value rather than fail to | ||
| 161 | // link). Each calling thread gets its own copy of the snapshot, so the | ||
| 162 | // reference stays valid and immutable for that thread until IT calls again -- | ||
| 163 | // a recompute on another thread can no longer mutate a list a plugin is | ||
| 164 | // iterating. In-tree code should use GetOutputRangesSnapshot() instead. | ||
| 165 | ✗ | const OutputRangeVec& GetOutputRanges(bool precise) { | |
| 166 | // One copy per flavor, so holding the precise and non-precise references | ||
| 167 | // at the same time works, as it did when these were two distinct vectors. | ||
| 168 | ✗ | static thread_local OutputRangeVec copies[2]; | |
| 169 | ✗ | OutputRangeVec& copy = copies[precise ? 1 : 0]; | |
| 170 | ✗ | copy = *GetOutputRangesSnapshot(precise); | |
| 171 | ✗ | return copy; | |
| 172 | } | ||
| 173 | |||
| 174 | 84 | std::string GetOutputRangesAsString(bool precise, bool oneBased) { | |
| 175 | 84 | std::string ret; | |
| 176 | 84 | bool first = true; | |
| 177 |
1/2✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
|
84 | int offset = oneBased ? 1 : 0; |
| 178 |
1/2✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
|
84 | auto ranges = GetOutputRangesSnapshot(precise); |
| 179 |
2/2✓ Branch 0 taken 84 times.
✓ Branch 1 taken 84 times.
|
168 | for (auto& a : *ranges) { |
| 180 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
|
84 | if (!first) { |
| 181 | ✗ | ret += ","; | |
| 182 | } else { | ||
| 183 | 84 | first = false; | |
| 184 | } | ||
| 185 |
3/6✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 84 times.
✗ Branch 5 not taken.
|
84 | ret += std::to_string(a.first + offset) + "-" + std::to_string(a.first + a.second - 1 + offset); |
| 186 | } | ||
| 187 |
2/4✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 84 times.
|
84 | if (ret == "") { |
| 188 | ✗ | ret = "1-8"; | |
| 189 | } | ||
| 190 | 84 | return ret; | |
| 191 | 84 | } | |
| 192 | |||
| 193 | // we'll sort the ranges that the outputs have registered and combine any overlaps | ||
| 194 | // or close ranges to keep the range list smaller | ||
| 195 | 4294 | static void sortRanges(std::vector<std::pair<uint32_t, uint32_t>>& rngs, bool gaps) { | |
| 196 | 4294 | std::map<uint32_t, uint32_t> ranges; | |
| 197 | // sort | ||
| 198 |
2/2✓ Branch 0 taken 3223 times.
✓ Branch 1 taken 4294 times.
|
7517 | for (auto& a : rngs) { |
| 199 |
1/2✓ Branch 0 taken 3223 times.
✗ Branch 1 not taken.
|
3223 | uint32_t cur = ranges[a.first]; |
| 200 |
2/2✓ Branch 0 taken 106 times.
✓ Branch 1 taken 3117 times.
|
3223 | if (cur) { |
| 201 |
1/2✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
|
106 | ranges[a.first] = std::max(a.second, cur); |
| 202 | } else { | ||
| 203 |
1/2✓ Branch 0 taken 3117 times.
✗ Branch 1 not taken.
|
3117 | ranges[a.first] = a.second; |
| 204 | } | ||
| 205 | } | ||
| 206 | 4294 | rngs.clear(); | |
| 207 | 4294 | std::pair<uint32_t, uint32_t> cur(FPPD_MAX_CHANNELS, FPPD_MAX_CHANNELS); | |
| 208 |
2/2✓ Branch 0 taken 2147 times.
✓ Branch 1 taken 2147 times.
|
4294 | uint32_t gapEliminate = gaps ? 1025 : 0; |
| 209 | |||
| 210 |
2/2✓ Branch 0 taken 3117 times.
✓ Branch 1 taken 4294 times.
|
7411 | for (auto& a : ranges) { |
| 211 |
2/2✓ Branch 0 taken 2288 times.
✓ Branch 1 taken 829 times.
|
3117 | if (cur.first == FPPD_MAX_CHANNELS) { |
| 212 | 2288 | cur.first = a.first; | |
| 213 | 2288 | cur.second = a.second; | |
| 214 | } else { | ||
| 215 |
2/2✓ Branch 0 taken 791 times.
✓ Branch 1 taken 38 times.
|
829 | if (a.first <= (cur.first + cur.second + gapEliminate)) { |
| 216 | // overlap or within 1025 channels of an overlap, need to combine | ||
| 217 | // if the two ranges are "close" (wthin 1025 channels) we'll combine | ||
| 218 | // as the overhead of doing ranges wouldn't benefit with a small gap | ||
| 219 | 791 | uint32_t max = cur.first + cur.second - 1; | |
| 220 | 791 | uint32_t amax = a.first + a.second - 1; | |
| 221 | 791 | max = std::max(max, amax); | |
| 222 | 791 | cur.second = max - cur.first + 1; | |
| 223 | } else { | ||
| 224 |
1/2✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
|
38 | rngs.push_back(cur); |
| 225 | 38 | cur.first = a.first; | |
| 226 | 38 | cur.second = a.second; | |
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 |
2/2✓ Branch 0 taken 2288 times.
✓ Branch 1 taken 2006 times.
|
4294 | if (cur.first != FPPD_MAX_CHANNELS) { |
| 231 |
1/2✓ Branch 0 taken 2288 times.
✗ Branch 1 not taken.
|
2288 | rngs.push_back(cur); |
| 232 | } | ||
| 233 |
2/2✓ Branch 0 taken 2006 times.
✓ Branch 1 taken 2288 times.
|
4294 | if (rngs.empty()) { |
| 234 | 2006 | cur.first = 0; | |
| 235 | 2006 | cur.second = 8; | |
| 236 |
1/2✓ Branch 0 taken 2006 times.
✗ Branch 1 not taken.
|
2006 | rngs.push_back(cur); |
| 237 | } | ||
| 238 | 4294 | } | |
| 239 | 1886 | static void addRange(uint32_t min, uint32_t max, std::vector<std::pair<uint32_t, uint32_t>>& prec, std::vector<std::pair<uint32_t, uint32_t>>& normal) { | |
| 240 | // Range lengths below are max - min + 1 in unsigned arithmetic, so an | ||
| 241 | // inverted range underflows to a ~4GB length that reaches the memset in | ||
| 242 | // BlankSequenceData(). The callers pass ints, so a negative max arrives | ||
| 243 | // here as a near-UINT32_MAX value that is not inverted -- bound it against | ||
| 244 | // the sequence buffer as well. Both checks are on the incoming values: | ||
| 245 | // max is aligned upward further down, which would hide an inversion. | ||
| 246 |
4/4✓ Branch 0 taken 1885 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1883 times.
|
1886 | if (max < min || max >= FPPD_MAX_CHANNELS) { |
| 247 | 3 | LogErr(VB_CHANNELOUT, "Ignoring invalid channel range %u - %u\n", min, max); | |
| 248 | 3 | return; | |
| 249 | } | ||
| 250 |
2/2✓ Branch 0 taken 2910 times.
✓ Branch 1 taken 1634 times.
|
4544 | for (auto& r : prec) { |
| 251 | 2910 | int rm = r.first + r.second - 1; | |
| 252 |
4/4✓ Branch 0 taken 2891 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 249 times.
✓ Branch 3 taken 2642 times.
|
2910 | if (min >= r.first && max <= rm) { |
| 253 | // within the range, don't add it | ||
| 254 | 249 | return; | |
| 255 | } | ||
| 256 | } | ||
| 257 |
1/2✓ Branch 0 taken 1634 times.
✗ Branch 1 not taken.
|
1634 | prec.emplace_back(min, max - min + 1); |
| 258 | |||
| 259 | // having the reads be aligned to intervals of 8 can help performance so | ||
| 260 | // we'll expand the range a bit to align things better | ||
| 261 | // round minimum down to interval of 8 | ||
| 262 | 1634 | min &= 0xFFFFFFF8; | |
| 263 | 1634 | max += 8; | |
| 264 | 1634 | max &= 0xFFFFFFF8; | |
| 265 | 1634 | max -= 1; | |
| 266 |
2/2✓ Branch 0 taken 2420 times.
✓ Branch 1 taken 1589 times.
|
4009 | for (auto& r : normal) { |
| 267 | 2420 | int rm = r.first + r.second - 1; | |
| 268 |
4/4✓ Branch 0 taken 2401 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 45 times.
✓ Branch 3 taken 2356 times.
|
2420 | if (min >= r.first && max <= rm) { |
| 269 | // within the range, don't add it | ||
| 270 | 45 | return; | |
| 271 | } | ||
| 272 | } | ||
| 273 |
1/2✓ Branch 0 taken 1589 times.
✗ Branch 1 not taken.
|
1589 | normal.emplace_back(min, max - min + 1); |
| 274 | } | ||
| 275 | |||
| 276 | 2147 | static void ComputeOutputRanges() { | |
| 277 | 2147 | std::vector<std::pair<uint32_t, uint32_t>> normal; | |
| 278 | 2147 | std::vector<std::pair<uint32_t, uint32_t>> precise; | |
| 279 | 2147 | FPPChannelOutputInstance* co = channelOutputs; | |
| 280 |
2/2✓ Branch 0 taken 1334 times.
✓ Branch 1 taken 2147 times.
|
3481 | while (co != nullptr) { |
| 281 |
1/2✓ Branch 0 taken 1334 times.
✗ Branch 1 not taken.
|
1334 | if (co->output) { |
| 282 |
2/4✓ Branch 0 taken 1334 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1334 times.
✗ Branch 3 not taken.
|
1334 | co->output->GetRequiredChannelRanges([&precise, &normal, &co](int m1, int m2) { |
| 283 |
2/4✓ Branch 0 taken 1783 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1783 times.
✗ Branch 3 not taken.
|
1783 | LogInfo(VB_CHANNELOUT, "%s: Determined range needed %d - %d\n", co->output->GetOutputType().c_str(), m1, m2); |
| 284 | 1783 | addRange(m1, m2, precise, normal); | |
| 285 | 1783 | }); | |
| 286 | } | ||
| 287 | 1334 | co = co->next; | |
| 288 | } | ||
| 289 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | outputProcessors.GetRequiredChannelRanges([&precise, &normal](int m1, int m2) { |
| 290 | 103 | LogInfo(VB_CHANNELOUT, "OutputProcessor: Determined range needed %d - %d\n", m1, m2); | |
| 291 | 103 | addRange(m1, m2, precise, normal); | |
| 292 | 103 | }); | |
| 293 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | sortRanges(normal, false); |
| 294 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | sortRanges(precise, true); |
| 295 |
2/2✓ Branch 0 taken 2161 times.
✓ Branch 1 taken 2147 times.
|
4308 | for (auto& r : precise) { |
| 296 |
1/2✓ Branch 0 taken 2161 times.
✗ Branch 1 not taken.
|
2161 | LogInfo(VB_CHANNELOUT, "Determined range needed %d - %d\n", r.first, r.first + r.second - 1); |
| 297 | } | ||
| 298 |
2/4✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2147 times.
✗ Branch 3 not taken.
|
2147 | preciseOutputRanges.store(std::make_shared<const OutputRangeVec>(std::move(precise))); |
| 299 |
2/4✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2147 times.
✗ Branch 3 not taken.
|
2147 | outputRanges.store(std::make_shared<const OutputRangeVec>(std::move(normal))); |
| 300 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | Bridge_OutputRangesChanged(); |
| 301 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | MultiSync::INSTANCE.Ping(); |
| 302 |
1/2✓ Branch 0 taken 2147 times.
✗ Branch 1 not taken.
|
2147 | MultiSync::INSTANCE.WriteRuntimeInfoFile(); |
| 303 | 2147 | } | |
| 304 | |||
| 305 | ///////////////////////////////////////////////////////////////////////////// | ||
| 306 | |||
| 307 | // in some of these cases, we could symlink the shlib | ||
| 308 | static std::map<std::string, std::string> OUTPUT_REMAPS = { | ||
| 309 | { "VirtualMatrix", "FBMatrix" }, | ||
| 310 | { "DMX-Pro", "USBDMX" }, | ||
| 311 | { "DMX-Open", "USBDMX" }, | ||
| 312 | { "Pixelnet-Lynx", "USBPixelnet" }, | ||
| 313 | { "Pixelnet-Open", "USBPixelnet" }, | ||
| 314 | { "universes", "UDPOutput" }, | ||
| 315 | // The RPIWS281X driver has been removed. DPIPixels drives the same pins and | ||
| 316 | // maps the old RPIWS281X string configs itself, so old configs load unchanged. | ||
| 317 | { "RPIWS281X", "DPIPixels" } | ||
| 318 | }; | ||
| 319 | |||
| 320 | bool skipOutputRangeCompute = false; | ||
| 321 | |||
| 322 | // Every output is closed through here. A ThreadedChannelOutput subclass's | ||
| 323 | // Close() frees its own resources (serial port, libusb handle, buffers, X | ||
| 324 | // display) and only then reaches ThreadedChannelOutput::Close(), which stops | ||
| 325 | // the worker - and the worker flushes any pending frame on its way out, into | ||
| 326 | // what was just freed. Stopping it first covers every subclass, plugins | ||
| 327 | // included, without relying on each one to order its Close() correctly. | ||
| 328 | 1203 | static void CloseChannelOutput(ChannelOutput* output) { | |
| 329 |
3/4✓ Branch 0 taken 1203 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 1143 times.
|
1203 | if (auto* threaded = dynamic_cast<ThreadedChannelOutput*>(output)) { |
| 330 | 60 | threaded->StopOutputThread(); | |
| 331 | } | ||
| 332 | 1203 | output->Close(); | |
| 333 | 1203 | } | |
| 334 | |||
| 335 | 9657 | static bool ReloadChannelOutputsForFile(const std::string& cfgFile) { | |
| 336 |
1/2✓ Branch 0 taken 9657 times.
✗ Branch 1 not taken.
|
9657 | Json::Value root; |
| 337 |
3/4✓ Branch 0 taken 9657 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1282 times.
✓ Branch 3 taken 8375 times.
|
9657 | if (FileExists(cfgFile)) { |
| 338 |
3/4✓ Branch 0 taken 1282 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1280 times.
|
1282 | if (!LoadJsonFromFile(cfgFile, root, JsonRoot::Object)) { |
| 339 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!skipOutputRangeCompute) { |
| 340 | // this is a reload, it could still be writing the file so we'll wait a bit | ||
| 341 | // and try again | ||
| 342 | ✗ | std::this_thread::sleep_for(std::chrono::milliseconds(5)); | |
| 343 | } | ||
| 344 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if (!LoadJsonFromFile(cfgFile, root, JsonRoot::Object)) { |
| 345 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | std::string warning = "Could not parse " + cfgFile + ". Some outputs may not work."; |
| 346 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | WarningHolder::AddWarning(26, warning); |
| 347 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | outputLoadWarnings[cfgFile].insert(warning); |
| 348 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_CHANNELOUT, "Error parsing %s\n", cfgFile.c_str()); |
| 349 | 2 | return false; | |
| 350 | 2 | } | |
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | 9655 | bool changed = false; | |
| 355 | { | ||
| 356 | // Unlink and destroy under outputThreadLock. PrepareChannelData() and | ||
| 357 | // SendChannelData() walk this list from the channel output thread, | ||
| 358 | // which holds that lock for the whole of its cycle; without it the | ||
| 359 | // walk can be sitting inside inst->output->PrepData() when the | ||
| 360 | // instance is freed here, and inst = inst->next then reads a freed | ||
| 361 | // node as well. This used to be a 100ms sleep, which is not a | ||
| 362 | // barrier at all -- a slow output cycle (the reports that led here had | ||
| 363 | // Send: 73ms against a 50ms loop) simply runs past it. | ||
| 364 | // | ||
| 365 | // Only the teardown is covered. Init() of the replacement outputs | ||
| 366 | // below can take a long time (compileMatrix.sh, PRU firmware loads) | ||
| 367 | // and holding the lock across it would stall output for that whole | ||
| 368 | // time; a half-populated list is harmless, an unlinked-but-live one is | ||
| 369 | // not. | ||
| 370 | 9655 | std::list<FPPChannelOutputInstance*> toDelete; | |
| 371 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | std::unique_lock<std::mutex> lock = LockOutputThread(); |
| 372 | // And against a blank being sent inline from another thread (see | ||
| 373 | // inlineOutputLock); that send doesn't hold outputThreadLock. | ||
| 374 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | std::unique_lock<std::recursive_mutex> inlineLock(inlineOutputLock); |
| 375 | // and against HasUniverseOutputs()/GetOutputTypes(); see there | ||
| 376 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | std::unique_lock<std::shared_mutex> freeLock(outputListFreeLock); |
| 377 |
2/2✓ Branch 0 taken 3723 times.
✓ Branch 1 taken 9655 times.
|
13378 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 378 | // remove everything in the list that was loaded from the cfgFile | ||
| 379 |
2/2✓ Branch 0 taken 204 times.
✓ Branch 1 taken 3519 times.
|
3723 | if (inst->sourceFile == cfgFile) { |
| 380 | 204 | changed = true; | |
| 381 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | toDelete.push_back(inst); |
| 382 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 196 times.
|
204 | if (inst->next) { |
| 383 | 8 | inst->next.load()->prev = inst->prev.load(); | |
| 384 | } | ||
| 385 |
2/2✓ Branch 0 taken 75 times.
✓ Branch 1 taken 129 times.
|
204 | if (inst->prev) { |
| 386 | 75 | inst->prev.load()->next = inst->next.load(); | |
| 387 | } | ||
| 388 |
2/2✓ Branch 0 taken 129 times.
✓ Branch 1 taken 75 times.
|
204 | if (inst == channelOutputs.load()) { |
| 389 | 129 | channelOutputs = inst->next.load(); | |
| 390 | } | ||
| 391 |
2/2✓ Branch 0 taken 196 times.
✓ Branch 1 taken 8 times.
|
204 | if (inst == lastChannelOutput.load()) { |
| 392 | 196 | lastChannelOutput = inst->prev.load(); | |
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | // Unlinked: no walk under it can reach these any more. Let go before | ||
| 397 | // closing them -- see HasUniverseOutputs(). | ||
| 398 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | freeLock.unlock(); |
| 399 |
3/4✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 218 times.
✓ Branch 3 taken 9437 times.
|
9655 | if (ChannelOutputThreadIsRunning()) { |
| 400 | // StoppingOutput() is the pair of the StartingOutput() the output | ||
| 401 | // thread makes when it comes up, so it is only owed to the outputs | ||
| 402 | // if that thread actually ran. | ||
| 403 |
2/2✓ Branch 0 taken 152 times.
✓ Branch 1 taken 218 times.
|
370 | for (auto inst : toDelete) { |
| 404 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | if (inst->output) { |
| 405 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | inst->output->StoppingOutput(); |
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
| 409 |
2/2✓ Branch 0 taken 204 times.
✓ Branch 1 taken 9655 times.
|
9859 | for (auto inst : toDelete) { |
| 410 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | if (inst->output) { |
| 411 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | CloseChannelOutput(inst->output); |
| 412 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | delete inst->output; |
| 413 | } | ||
| 414 |
1/2✓ Branch 0 taken 204 times.
✗ Branch 1 not taken.
|
204 | delete inst; |
| 415 | } | ||
| 416 | 9655 | } | |
| 417 |
3/4✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 9655 times.
|
9663 | for (auto& warning : outputLoadWarnings[cfgFile]) { |
| 418 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | WarningHolder::RemoveWarning(26, warning); |
| 419 | } | ||
| 420 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | outputLoadWarnings.erase(cfgFile); |
| 421 |
3/4✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 9655 times.
|
9676 | for (auto& [id, warning] : outputConfigWarnings[cfgFile]) { |
| 422 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
45 | WarningHolder::RemoveWarning(id, warning); |
| 423 | } | ||
| 424 |
1/2✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
|
9655 | outputConfigWarnings.erase(cfgFile); |
| 425 |
3/4✓ Branch 0 taken 9655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1280 times.
✓ Branch 3 taken 8375 times.
|
9655 | if (FileExists(cfgFile)) { |
| 426 | // indexing a non-array by position throws, out of this callback | ||
| 427 |
5/12✓ Branch 0 taken 1280 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1280 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1280 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1280 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1280 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
1280 | const Json::Value outputs = root["channelOutputs"].isArray() ? root["channelOutputs"] : Json::Value(Json::arrayValue); |
| 428 | 1280 | std::string type; | |
| 429 | 1280 | int start = 0; | |
| 430 | 1280 | int count = 0; | |
| 431 | |||
| 432 |
3/4✓ Branch 0 taken 2574 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1294 times.
✓ Branch 3 taken 1280 times.
|
2574 | for (int c = 0; c < outputs.size(); c++) { |
| 433 | 1294 | bool enabled = false; | |
| 434 | try { | ||
| 435 | // asInt()/asString() throw on a value of the wrong type | ||
| 436 | // (hand-edited or third-party configs), and this runs in the | ||
| 437 | // FileMonitor callback: an escaping exception ends fppd | ||
| 438 |
3/6✓ Branch 0 taken 1294 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1294 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1294 times.
✗ Branch 5 not taken.
|
1294 | type = outputs[c]["type"].asString(); |
| 439 |
4/6✓ Branch 0 taken 1294 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1294 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1293 times.
✓ Branch 5 taken 1 times.
|
1294 | enabled = outputs[c]["enabled"].asInt(); |
| 440 |
3/6✓ Branch 0 taken 1293 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1293 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1293 times.
✗ Branch 5 not taken.
|
1293 | start = outputs[c]["startChannel"].asInt(); |
| 441 |
3/6✓ Branch 0 taken 1293 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1293 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1293 times.
✗ Branch 5 not taken.
|
1293 | count = outputs[c]["channelCount"].asInt(); |
| 442 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | } catch (const std::exception& ex) { |
| 443 |
6/12✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
|
1 | std::string warning = "Could not read output " + std::to_string(c + 1) + " in " + cfgFile + " (" + ex.what() + ")"; |
| 444 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | WarningHolder::AddWarning(26, warning); |
| 445 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | outputLoadWarnings[cfgFile].insert(warning); |
| 446 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_CHANNELOUT, "%s\n", warning.c_str()); |
| 447 | 1 | continue; | |
| 448 | 1 | } | |
| 449 | |||
| 450 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1279 times.
|
1293 | if (!enabled) { |
| 451 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | LogDebug(VB_CHANNELOUT, "Skipping Disabled Channel Output: %s\n", type.c_str()); |
| 452 | 14 | continue; | |
| 453 | } | ||
| 454 | |||
| 455 | // internally we start channel counts at zero | ||
| 456 | 1279 | start -= 1; | |
| 457 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1278 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1279 | if (start < 0 && count <= 0) { |
| 458 | // nothing to send from it, but the start still becomes the | ||
| 459 | // offset SendData() is handed; -1 would wrap to UINT_MAX | ||
| 460 | 1 | start = 0; | |
| 461 | } | ||
| 462 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1279 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1279 | if (start < 0 && count > 0) { |
| 463 | // we have a negative channel number, but actually are supposed to be outputting data | ||
| 464 | // Skip this output as that's not valid | ||
| 465 | ✗ | std::string warning = "Could not initialize output type " + type + ". Invalid start channel."; | |
| 466 | ✗ | WarningHolder::AddWarning(26, warning); | |
| 467 | ✗ | outputLoadWarnings[cfgFile].insert(warning); | |
| 468 | ✗ | LogInfo(VB_CHANNELOUT, "%s\n", warning.c_str()); | |
| 469 | ✗ | continue; | |
| 470 | ✗ | } | |
| 471 | |||
| 472 |
2/4✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1279 times.
|
1279 | FPPChannelOutputInstance* channelOutput = new FPPChannelOutputInstance(); |
| 473 | |||
| 474 | 1279 | channelOutput->startChannel = start; | |
| 475 | 1279 | channelOutput->channelCount = count; | |
| 476 |
1/2✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
|
1279 | channelOutput->sourceFile = cfgFile; |
| 477 |
1/2✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
|
1279 | std::string libnamePfx = ""; |
| 478 | |||
| 479 | // First some Channel Outputs enabled everythwere | ||
| 480 |
3/4✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
✓ Branch 3 taken 1132 times.
|
1279 | if (type == "LEDPanelMatrix") { |
| 481 | // for LED matrices, the driver is determined by the subType | ||
| 482 |
1/2✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
|
147 | libnamePfx = "matrix-"; |
| 483 | // outside the try below: a subType of the wrong JSON type | ||
| 484 | // threw from asString() to std::terminate at startup. A | ||
| 485 | // non-string finds no driver, which is reported as usual. | ||
| 486 |
2/4✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
✗ Branch 3 not taken.
|
147 | const Json::Value& subType = outputs[c]["subType"]; |
| 487 |
7/12✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 146 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 146 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 146 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
149 | type = subType.isString() ? subType.asString() : std::string("(invalid subType)"); |
| 488 |
3/4✓ Branch 0 taken 1132 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 814 times.
✓ Branch 3 taken 318 times.
|
1132 | } else if (OUTPUT_REMAPS.find(type) != OUTPUT_REMAPS.end()) { |
| 489 |
2/4✓ Branch 0 taken 814 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 814 times.
✗ Branch 3 not taken.
|
814 | type = OUTPUT_REMAPS[type]; |
| 490 | } | ||
| 491 | |||
| 492 | try { | ||
| 493 | // the output's constructor raises warnings too, not just Init() | ||
| 494 | struct ConfigWarningScope { | ||
| 495 | 1279 | ConfigWarningScope(const std::string& f) { outputConfigWarningFile = f; } | |
| 496 | 1279 | ~ConfigWarningScope() { outputConfigWarningFile.clear(); } | |
| 497 |
1/2✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
|
1279 | } warningScope(cfgFile); |
| 498 |
3/6✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1279 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1279 times.
✗ Branch 5 not taken.
|
1279 | LogInfo(VB_CHANNELOUT, "Loading output plugin: type=%s, libname=%s\n", type.c_str(), ("fpp-co-" + libnamePfx + type).c_str()); |
| 499 |
6/10✓ Branch 0 taken 1279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1279 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1279 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1279 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1276 times.
✓ Branch 9 taken 3 times.
|
1279 | FPPPlugins::ChannelOutputPlugin* p = dynamic_cast<FPPPlugins::ChannelOutputPlugin*>(PluginManager::INSTANCE.findPlugin(libnamePfx + type, "fpp-co-" + libnamePfx + type)); |
| 500 |
2/2✓ Branch 0 taken 1276 times.
✓ Branch 1 taken 3 times.
|
1279 | if (p) { |
| 501 |
1/2✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
|
1276 | LogInfo(VB_CHANNELOUT, "Plugin found, creating output: start=%d, count=%d\n", start, count); |
| 502 |
1/2✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
|
1276 | channelOutput->output = p->createChannelOutput(start, count); |
| 503 |
1/2✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
|
1276 | if (channelOutput->output) { |
| 504 | // Pins the plugin: the output outlives it and is owned | ||
| 505 | // elsewhere, so the plugin cannot be unloaded. | ||
| 506 |
1/2✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
|
1276 | PluginManager::INSTANCE.noteChannelOutputCreated(p); |
| 507 | } | ||
| 508 | } else { | ||
| 509 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogWarn(VB_CHANNELOUT, "Plugin NOT found for type: %s\n", type.c_str()); |
| 510 | } | ||
| 511 |
2/2✓ Branch 0 taken 1276 times.
✓ Branch 1 taken 3 times.
|
1279 | if (channelOutput->output) { |
| 512 |
1/2✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
|
1276 | LogInfo(VB_CHANNELOUT, "Calling Init() on %s output\n", type.c_str()); |
| 513 |
4/6✓ Branch 0 taken 1276 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1276 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1272 times.
✓ Branch 5 taken 4 times.
|
1280 | bool initOK = channelOutput->output->Init(outputs[c]); |
| 514 |
2/2✓ Branch 0 taken 1203 times.
✓ Branch 1 taken 69 times.
|
1272 | if (initOK) { |
| 515 | // Every output reads (PrepData) or sends from exactly the | ||
| 516 | // ranges it reports here, straight out of the sequence | ||
| 517 | // buffer, which is FPPD_MAX_CHANNELS long. addRange() | ||
| 518 | // drops an out-of-bounds range from the read set, but the | ||
| 519 | // output itself would still walk off the end of the | ||
| 520 | // buffer every frame, so it must not be started at all. | ||
| 521 | 1203 | bool bad = false; | |
| 522 | 1203 | int badMin = 0; | |
| 523 | 1203 | int badMax = -1; | |
| 524 |
2/4✓ Branch 0 taken 1203 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1203 times.
✗ Branch 3 not taken.
|
1203 | channelOutput->output->GetRequiredChannelRanges([&bad, &badMin, &badMax](int m1, int m2) { |
| 525 | // Only a non-empty range outside the buffer: an | ||
| 526 | // inverted one is how an output with nothing to | ||
| 527 | // send reports. Before channel 1 is as bad as past | ||
| 528 | // the end: an output reads channelData[m1] either way. | ||
| 529 |
6/8✓ Branch 0 taken 1607 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1605 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1605 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1604 times.
|
1607 | if (!bad && m2 >= m1 && (m1 < 0 || m2 >= (int)FPPD_MAX_CHANNELS)) { |
| 530 | 1 | bad = true; | |
| 531 | 1 | badMin = m1; | |
| 532 | 1 | badMax = m2; | |
| 533 | } | ||
| 534 | 1607 | }); | |
| 535 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1202 times.
|
1203 | if (bad) { |
| 536 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | std::string warning = "Output type " + type + " needs channels " + |
| 537 |
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.
|
4 | std::to_string(badMin + 1) + "-" + std::to_string(badMax + 1) + |
| 538 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
4 | ", outside 1-" + std::to_string(FPPD_MAX_CHANNELS) + |
| 539 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | ". Output disabled."; |
| 540 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | WarningHolder::AddWarning(26, warning); |
| 541 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_CHANNELOUT, "%s\n", warning.c_str()); |
| 542 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | outputLoadWarnings[cfgFile].insert(warning); |
| 543 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | CloseChannelOutput(channelOutput->output); |
| 544 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | delete channelOutput->output; |
| 545 | 1 | channelOutput->output = nullptr; | |
| 546 | 1 | initOK = false; | |
| 547 | 1 | } | |
| 548 | } | ||
| 549 |
2/2✓ Branch 0 taken 1202 times.
✓ Branch 1 taken 70 times.
|
1272 | if (initOK) { |
| 550 |
1/2✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
|
1202 | LogInfo(VB_CHANNELOUT, "Init succeeded, adding to channel outputs\n"); |
| 551 |
1/2✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
|
1202 | addChannelOutput(channelOutput); |
| 552 |
1/2✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
|
1202 | LogDebug(VB_CHANNELOUT, "Configured %s Channel Output\n", type.c_str()); |
| 553 | 1202 | channelOutput = nullptr; | |
| 554 | 1202 | changed = true; | |
| 555 |
2/2✓ Branch 0 taken 69 times.
✓ Branch 1 taken 1 times.
|
70 | } else if (channelOutput->output) { |
| 556 |
2/4✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 69 times.
✗ Branch 3 not taken.
|
69 | std::string warning = "Could not initialize output type " + type + ". Check logs for details."; |
| 557 |
1/2✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
|
69 | WarningHolder::AddWarning(26, warning); |
| 558 |
1/2✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
|
69 | LogErr(VB_CHANNELOUT, warning); |
| 559 |
2/4✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 69 times.
✗ Branch 3 not taken.
|
69 | outputLoadWarnings[cfgFile].insert(warning); |
| 560 |
1/2✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
|
69 | delete channelOutput->output; |
| 561 | 69 | channelOutput->output = nullptr; | |
| 562 | 69 | } | |
| 563 | } else { | ||
| 564 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | std::string warning = "Could not create output type " + type + ". Check logs for details."; |
| 565 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | WarningHolder::AddWarning(26, warning); |
| 566 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | outputLoadWarnings[cfgFile].insert(warning); |
| 567 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogErr(VB_CHANNELOUT, warning); |
| 568 | 3 | } | |
| 569 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
1283 | } catch (const std::exception& ex) { |
| 570 |
4/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
|
4 | std::string warning = "Could not initialize output type " + type + ". (" + ex.what() + ")"; |
| 571 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | WarningHolder::AddWarning(26, warning); |
| 572 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | LogErr(VB_CHANNELOUT, warning); |
| 573 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | outputLoadWarnings[cfgFile].insert(warning); |
| 574 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if (channelOutput && channelOutput->output) { |
| 575 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | delete channelOutput->output; |
| 576 | 4 | channelOutput->output = nullptr; | |
| 577 | } | ||
| 578 | 4 | } | |
| 579 |
2/2✓ Branch 0 taken 77 times.
✓ Branch 1 taken 1202 times.
|
1279 | if (channelOutput) { |
| 580 | // if we didn't add the channel output, delete it | ||
| 581 |
1/2✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
|
77 | delete channelOutput; |
| 582 | } | ||
| 583 | 1279 | } | |
| 584 | 1280 | } | |
| 585 | 9655 | return changed; | |
| 586 | 9657 | } | |
| 587 | |||
| 588 | 1887 | int InitializeChannelOutputs(void) { | |
| 589 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | Json::Value root; |
| 590 | |||
| 591 | 1887 | channelOutputFrame = 0; | |
| 592 | |||
| 593 | // FIXME, build this list dynamically | ||
| 594 | 1887 | const char* configFiles[] = { | |
| 595 | "/co-universes.json", | ||
| 596 | "/co-other.json", | ||
| 597 | "/co-pwm.json", | ||
| 598 | #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64) | ||
| 599 | "/co-bbbStrings.json", | ||
| 600 | #else | ||
| 601 | "/co-pixelStrings.json", | ||
| 602 | #endif | ||
| 603 | "/channeloutputs.json", | ||
| 604 | NULL | ||
| 605 | }; | ||
| 606 | 1887 | skipOutputRangeCompute = true; | |
| 607 |
2/2✓ Branch 0 taken 9435 times.
✓ Branch 1 taken 1887 times.
|
11322 | for (int f = 0; configFiles[f]; f++) { |
| 608 |
3/6✓ Branch 0 taken 9435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9435 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9435 times.
✗ Branch 5 not taken.
|
18870 | std::string configFile = FPP_DIR_CONFIG(configFiles[f]); |
| 609 |
4/8✓ Branch 0 taken 9435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9435 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9435 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9435 times.
✗ Branch 7 not taken.
|
27505 | FileMonitor::INSTANCE.AddFile("ChannelOutputSetup", configFile, [configFile]() { |
| 610 | 9655 | LogDebug(VB_CHANNELOUT, "Reloading Channel Outputs for %s\n", configFile.c_str()); | |
| 611 | 9655 | bool changed = ReloadChannelOutputsForFile(configFile); | |
| 612 | #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64) | ||
| 613 | // On BBB platforms, if co-bbbStrings.json changes and we have BBBMatrix outputs, | ||
| 614 | // we need to reload channeloutputs.json too so compileMatrix.sh gets called | ||
| 615 |
6/6✓ Branch 0 taken 107 times.
✓ Branch 1 taken 463 times.
✓ Branch 2 taken 84 times.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 84 times.
✓ Branch 5 taken 486 times.
|
570 | if (changed && configFile.find("co-bbbStrings.json") != std::string::npos) { |
| 616 |
3/6✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 84 times.
✗ Branch 5 not taken.
|
84 | std::string channelOutputsFile = FPP_DIR_CONFIG("/channeloutputs.json"); |
| 617 |
3/4✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 82 times.
|
84 | if (FileExists(channelOutputsFile)) { |
| 618 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Json::Value root; |
| 619 |
5/10✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | if (LoadJsonFromFile(channelOutputsFile, root, JsonRoot::Object) && root.isMember("channelOutputs")) { |
| 620 |
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.
|
2 | for (int i = 0; i < root["channelOutputs"].size(); i++) { |
| 621 | // the BBBMatrix driver is the "LEDscapeMatrix" | ||
| 622 | // subtype, which is what the UI saves | ||
| 623 |
4/8✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
|
2 | const std::string subType = root["channelOutputs"][i]["subType"].asString(); |
| 624 |
4/10✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
4 | if (root["channelOutputs"][i]["type"].asString() == "LEDPanelMatrix" && |
| 625 |
6/16✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
|
6 | (subType == "LEDscapeMatrix" || subType == "BBBMatrix") && |
| 626 |
5/10✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
2 | root["channelOutputs"][i]["enabled"].asInt() == 1) { |
| 627 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogDebug(VB_CHANNELOUT, "BBBMatrix output detected, reloading %s to ensure PRU code is compiled\n", channelOutputsFile.c_str()); |
| 628 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ReloadChannelOutputsForFile(channelOutputsFile); |
| 629 | 2 | break; | |
| 630 | } | ||
| 631 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } |
| 632 | } | ||
| 633 | 2 | } | |
| 634 | 84 | } | |
| 635 | #endif | ||
| 636 |
4/4✓ Branch 0 taken 1208 times.
✓ Branch 1 taken 8447 times.
✓ Branch 2 taken 217 times.
✓ Branch 3 taken 991 times.
|
9655 | if (changed && !skipOutputRangeCompute) { |
| 637 | 217 | ComputeOutputRanges(); | |
| 638 | } | ||
| 639 | 9655 | }) | |
| 640 |
2/4✓ Branch 0 taken 9435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9435 times.
✗ Branch 3 not taken.
|
18870 | .TriggerFileChanged("ChannelOutputSetup", configFile); |
| 641 | 9435 | } | |
| 642 | 1887 | skipOutputRangeCompute = false; | |
| 643 | |||
| 644 | 1887 | int count = 0; | |
| 645 | 1887 | FPPChannelOutputInstance* co = channelOutputs; | |
| 646 |
2/2✓ Branch 0 taken 997 times.
✓ Branch 1 taken 1887 times.
|
2884 | while (co != nullptr) { |
| 647 | 997 | count++; | |
| 648 | 997 | co = co->next; | |
| 649 | } | ||
| 650 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | LogDebug(VB_CHANNELOUT, "%d Channel Outputs configured\n", count); |
| 651 |
3/6✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1887 times.
✗ Branch 5 not taken.
|
2047 | std::string opfilename = FPP_DIR_CONFIG("/outputprocessors.json"); |
| 652 |
4/8✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1887 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1887 times.
✗ Branch 7 not taken.
|
5501 | FileMonitor::INSTANCE.AddFile("outputprocessors.json", opfilename, [opfilename]() { |
| 653 |
1/2✓ Branch 0 taken 1970 times.
✗ Branch 1 not taken.
|
1970 | Json::Value newRoot; |
| 654 |
8/10✓ Branch 0 taken 1970 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 133 times.
✓ Branch 3 taken 1837 times.
✓ Branch 4 taken 133 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 40 times.
✓ Branch 7 taken 93 times.
✓ Branch 8 taken 40 times.
✓ Branch 9 taken 1930 times.
|
1970 | if (FileExists(opfilename) && !LoadJsonFromFile(opfilename, newRoot, JsonRoot::Object)) { |
| 655 | // probably caught part way through being | ||
| 656 | // written; one retry, as for the outputs, and | ||
| 657 | // otherwise keep the processors running now | ||
| 658 | // rather than dropping every one of them | ||
| 659 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); |
| 660 |
2/4✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 40 times.
✗ Branch 3 not taken.
|
40 | if (!LoadJsonFromFile(opfilename, newRoot, JsonRoot::Object)) { |
| 661 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | LogErr(VB_CHANNELOUT, "Could not parse %s, keeping the current output processors\n", opfilename.c_str()); |
| 662 | 40 | return; | |
| 663 | } | ||
| 664 | } | ||
| 665 |
1/2✓ Branch 0 taken 1930 times.
✗ Branch 1 not taken.
|
1930 | outputProcessors.loadFromJSON(newRoot); |
| 666 |
1/2✓ Branch 0 taken 1930 times.
✗ Branch 1 not taken.
|
1930 | ComputeOutputRanges(); |
| 667 |
2/2✓ Branch 0 taken 1930 times.
✓ Branch 1 taken 40 times.
|
1970 | }) |
| 668 |
2/4✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
|
3774 | .TriggerFileChanged("outputprocessors.json", opfilename); |
| 669 | 1887 | return 1; | |
| 670 | 1887 | } | |
| 671 | |||
| 672 | /* | ||
| 673 | * Set the channel output frame counter to a specific value | ||
| 674 | */ | ||
| 675 | 67265 | void SetChannelOutputFrameNumber(int frameNumber) { | |
| 676 | // Every frame; a value nothing else is published through. | ||
| 677 | 67265 | channelOutputFrame.store(frameNumber, std::memory_order_relaxed); | |
| 678 | 67265 | } | |
| 679 | |||
| 680 | /* | ||
| 681 | * Reset the output frame count | ||
| 682 | */ | ||
| 683 | 1965 | void ResetChannelOutputFrameNumber(void) { | |
| 684 | 1965 | channelOutputFrame = 0; | |
| 685 | 1965 | mediaElapsedSeconds = 0.0; | |
| 686 | 1965 | } | |
| 687 | |||
| 688 | 3651 | void OverlayOutputTestData(std::set<std::string> types, unsigned char* channelData, int cycleCnt, float percentOfCycle, int testType, const Json::Value& extraConfig) { | |
| 689 |
2/2✓ Branch 0 taken 3651 times.
✓ Branch 1 taken 3651 times.
|
7302 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 690 | 3651 | auto output = inst->output; | |
| 691 |
8/20✓ Branch 0 taken 3651 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3651 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3648 times.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3648 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3651 times.
✓ Branch 16 taken 3648 times.
✓ Branch 17 taken 3 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
3651 | if (output && output->SupportsTesting() && (types.empty() || types.find(output->GetOutputType()) != types.end())) { |
| 692 | 3648 | output->OverlayTestData(channelData, cycleCnt, percentOfCycle, testType, extraConfig); | |
| 693 | } | ||
| 694 | } | ||
| 695 | 3651 | } | |
| 696 | // see the note on HasUniverseOutputs() for the lock this walk takes | ||
| 697 | 1 | std::set<std::string> GetOutputTypes() { | |
| 698 | 1 | std::set<std::string> ret; | |
| 699 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::shared_lock<std::shared_mutex> l(outputListFreeLock); |
| 700 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 701 | 1 | auto output = inst->output; | |
| 702 |
4/8✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
|
1 | if (output && output->SupportsTesting()) { |
| 703 | ✗ | ret.insert(output->GetOutputType()); | |
| 704 | } | ||
| 705 | } | ||
| 706 | 1 | return ret; | |
| 707 | 1 | } | |
| 708 | // The two per-frame walks load each link with acquire rather than seq_cst: | ||
| 709 | // one barrier instead of two per node on an AM335x, and still enough to see | ||
| 710 | // the fields of a node addChannelOutput() linked in. | ||
| 711 | 138483 | int PrepareChannelData(char* channelData) { | |
| 712 | 138483 | outputProcessors.ProcessData((unsigned char*)channelData); | |
| 713 |
2/2✓ Branch 0 taken 140264 times.
✓ Branch 1 taken 138483 times.
|
278747 | for (auto inst = channelOutputs.load(std::memory_order_acquire); inst != nullptr; inst = inst->next.load(std::memory_order_acquire)) { |
| 714 | 140264 | auto output = inst->output; | |
| 715 |
1/2✓ Branch 0 taken 140264 times.
✗ Branch 1 not taken.
|
140264 | if (output) { |
| 716 | 140264 | output->PrepData((unsigned char*)channelData); | |
| 717 | } | ||
| 718 | } | ||
| 719 | 138483 | return 0; | |
| 720 | } | ||
| 721 | |||
| 722 | /* | ||
| 723 | * | ||
| 724 | */ | ||
| 725 | 131548 | int SendChannelData(const char* channelData) { | |
| 726 | 131548 | int i = 0; | |
| 727 | FPPChannelOutputInstance* inst; | ||
| 728 | |||
| 729 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 131548 times.
|
131548 | if (WillLog(LOG_DEBUG, VB_CHANNELDATA)) { |
| 730 | ✗ | uint32_t minimumNeededChannel = GetOutputRangesSnapshot(false)->at(0).first; | |
| 731 | char buf[128]; | ||
| 732 | ✗ | snprintf(buf, sizeof(buf), "Channel Data starting at channel %d", minimumNeededChannel); | |
| 733 | ✗ | HexDump(buf, &channelData[minimumNeededChannel], 16, VB_CHANNELDATA); | |
| 734 | } | ||
| 735 | |||
| 736 |
2/2✓ Branch 0 taken 135459 times.
✓ Branch 1 taken 131548 times.
|
267007 | for (auto inst = channelOutputs.load(std::memory_order_acquire); inst != nullptr; inst = inst->next.load(std::memory_order_acquire)) { |
| 737 | 135459 | auto output = inst->output; | |
| 738 |
1/2✓ Branch 0 taken 135459 times.
✗ Branch 1 not taken.
|
135459 | if (output) { |
| 739 | 135459 | output->SendData((unsigned char*)(channelData + inst->startChannel)); | |
| 740 | } | ||
| 741 | } | ||
| 742 | |||
| 743 | 131548 | return 0; | |
| 744 | } | ||
| 745 | |||
| 746 | /* | ||
| 747 | * | ||
| 748 | */ | ||
| 749 | 1956 | void StartingOutput(void) { | |
| 750 | 1956 | OutputMonitor::INSTANCE.AutoEnableOutputs(); | |
| 751 | |||
| 752 |
2/2✓ Branch 0 taken 1079 times.
✓ Branch 1 taken 1956 times.
|
3035 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 753 |
1/2✓ Branch 0 taken 1079 times.
✗ Branch 1 not taken.
|
1079 | if (inst->output) { |
| 754 | 1079 | inst->output->StartingOutput(); | |
| 755 | } | ||
| 756 | } | ||
| 757 | 1956 | } | |
| 758 | |||
| 759 | /* | ||
| 760 | * | ||
| 761 | */ | ||
| 762 | 1947 | void StoppingOutput(void) { | |
| 763 |
2/2✓ Branch 0 taken 1075 times.
✓ Branch 1 taken 1947 times.
|
3022 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 764 |
1/2✓ Branch 0 taken 1075 times.
✗ Branch 1 not taken.
|
1075 | if (inst->output) { |
| 765 | 1075 | inst->output->StoppingOutput(); | |
| 766 | } | ||
| 767 | } | ||
| 768 | 1947 | OutputMonitor::INSTANCE.AutoDisableOutputs(); | |
| 769 | 1947 | } | |
| 770 | |||
| 771 | /* | ||
| 772 | * | ||
| 773 | */ | ||
| 774 | 1867 | void CloseChannelOutputs(void) { | |
| 775 | // Deliberately not taking outputThreadLock, unlike the reload path: this | ||
| 776 | // runs at shutdown, after StopChannelOutputThread(), so there is nothing | ||
| 777 | // left to race with. If that thread did wedge and is still holding the | ||
| 778 | // lock, blocking here would trade a crash-exit for a hung daemon. | ||
| 779 |
2/2✓ Branch 0 taken 998 times.
✓ Branch 1 taken 1867 times.
|
2865 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 780 |
1/2✓ Branch 0 taken 998 times.
✗ Branch 1 not taken.
|
998 | if (inst->output) { |
| 781 | 998 | CloseChannelOutput(inst->output); | |
| 782 | } | ||
| 783 | } | ||
| 784 |
2/2✓ Branch 0 taken 998 times.
✓ Branch 1 taken 1867 times.
|
2865 | for (auto inst = channelOutputs.load(); inst != nullptr; inst = inst->next) { |
| 785 | 998 | auto output = inst->output; | |
| 786 |
1/2✓ Branch 0 taken 998 times.
✗ Branch 1 not taken.
|
998 | if (output) { |
| 787 | 998 | inst->output = nullptr; | |
| 788 |
1/2✓ Branch 0 taken 998 times.
✗ Branch 1 not taken.
|
998 | delete output; |
| 789 | } | ||
| 790 | } | ||
| 791 |
2/2✓ Branch 0 taken 998 times.
✓ Branch 1 taken 1867 times.
|
2865 | while (channelOutputs.load() != nullptr) { |
| 792 | 998 | FPPChannelOutputInstance* inst = channelOutputs.load(); | |
| 793 | 998 | channelOutputs = inst->next.load(); | |
| 794 |
1/2✓ Branch 0 taken 998 times.
✗ Branch 1 not taken.
|
998 | delete inst; |
| 795 | } | ||
| 796 | 1867 | } | |
| 797 |