| 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 "fpp-json.h" | ||
| 16 | |||
| 17 | #include "../common.h" | ||
| 18 | #include "../log.h" | ||
| 19 | |||
| 20 | #include "TestPatternBase.h" | ||
| 21 | #include "channeloutput/ChannelOutputSetup.h" | ||
| 22 | #include "overlays/PixelOverlay.h" | ||
| 23 | #include "overlays/PixelOverlayModel.h" | ||
| 24 | |||
| 25 | /* | ||
| 26 | * | ||
| 27 | */ | ||
| 28 | 191 | TestPatternBase::TestPatternBase() : | |
| 29 | 191 | m_testEnabled(0), | |
| 30 |
1/2✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
|
191 | m_testPatternName("basePattern"), |
| 31 | 191 | m_minChannels(2), | |
| 32 | 191 | m_testData(NULL), | |
| 33 | 191 | m_nextCycleTime(0), | |
| 34 | 191 | m_cycleMS(0), | |
| 35 | 191 | m_channelCount(0), | |
| 36 | 382 | m_configChanged(0) { | |
| 37 |
1/2✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
|
191 | LogExcess(VB_CHANNELOUT, "TestPatternBase::TestPatternBase()\n"); |
| 38 | |||
| 39 | // Give room for an extra RGBW Pixel to make coding test patterns easier | ||
| 40 |
1/2✓ Branch 0 taken 191 times.
✗ Branch 1 not taken.
|
191 | m_testData = new char[FPPD_MAX_CHANNELS + 4]; |
| 41 | 191 | } | |
| 42 | |||
| 43 | /* | ||
| 44 | * | ||
| 45 | */ | ||
| 46 | 242 | TestPatternBase::~TestPatternBase() { | |
| 47 | 242 | LogExcess(VB_CHANNELOUT, "TestPatternBase::~TestPatternBase()\n"); | |
| 48 | |||
| 49 |
1/2✓ Branch 0 taken 121 times.
✗ Branch 1 not taken.
|
242 | delete[] m_testData; |
| 50 | 242 | } | |
| 51 | |||
| 52 | /* | ||
| 53 | * | ||
| 54 | */ | ||
| 55 | ✗ | int TestPatternBase::Init(std::string configStr) { | |
| 56 | ✗ | Json::Value config; | |
| 57 | ✗ | if (!LoadJsonFromString(configStr, config)) { | |
| 58 | ✗ | LogErr(VB_CHANNELOUT, | |
| 59 | "Error parsing Test Pattern config string: '%s'\n", | ||
| 60 | configStr.c_str()); | ||
| 61 | ✗ | return 0; | |
| 62 | } | ||
| 63 | |||
| 64 | ✗ | return Init(config); | |
| 65 | ✗ | } | |
| 66 | |||
| 67 | /* | ||
| 68 | * | ||
| 69 | */ | ||
| 70 | 194 | int TestPatternBase::Init(Json::Value config) { | |
| 71 |
2/4✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 194 times.
✗ Branch 3 not taken.
|
194 | int cycleMS = JsonInt(config, "cycleMS"); |
| 72 |
2/2✓ Branch 0 taken 189 times.
✓ Branch 1 taken 5 times.
|
194 | if (m_cycleMS != cycleMS) { |
| 73 | 189 | m_cycleMS = cycleMS; | |
| 74 | 189 | m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000); | |
| 75 | } | ||
| 76 | |||
| 77 |
2/2✓ Branch 0 taken 163 times.
✓ Branch 1 taken 31 times.
|
194 | if (config.isMember("channelSet")) { |
| 78 |
3/6✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
|
489 | std::string channelSet = JsonString(config, "channelSet"); |
| 79 |
2/2✓ Branch 0 taken 162 times.
✓ Branch 1 taken 1 times.
|
163 | if (m_channelSetStr != channelSet) { |
| 80 |
1/2✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
|
162 | m_channelSetStr = channelSet; |
| 81 |
2/4✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
|
162 | SetChannelSet(m_channelSetStr); |
| 82 | 162 | m_configChanged = 1; | |
| 83 | } | ||
| 84 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
194 | } else if (m_channelSetStr != "") { |
| 85 | ✗ | m_channelSetStr = ""; | |
| 86 | ✗ | m_configChanged = 1; | |
| 87 | ✗ | m_channelSet.clear(); | |
| 88 | } | ||
| 89 | |||
| 90 |
2/2✓ Branch 0 taken 163 times.
✓ Branch 1 taken 31 times.
|
194 | if (m_configChanged) { |
| 91 | 163 | SetupTest(); | |
| 92 | } | ||
| 93 | |||
| 94 | 194 | m_testEnabled = 1; | |
| 95 | |||
| 96 | 194 | DumpConfig(); | |
| 97 | |||
| 98 | 194 | return 1; | |
| 99 | } | ||
| 100 | |||
| 101 | /* | ||
| 102 | * | ||
| 103 | */ | ||
| 104 | 200 | int TestPatternBase::SetChannelSet(std::string channelSetStr) { | |
| 105 | 200 | int start = 0; | |
| 106 | 200 | int end = 0; | |
| 107 | |||
| 108 | 200 | int max = 0; | |
| 109 |
1/2✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
|
200 | auto outputRanges = GetOutputRangesSnapshot(); |
| 110 |
2/2✓ Branch 0 taken 200 times.
✓ Branch 1 taken 200 times.
|
400 | for (auto& a : *outputRanges) { |
| 111 | 200 | int e = a.first + a.second - 1; | |
| 112 | 200 | max = std::max(max, e); | |
| 113 | } | ||
| 114 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 181 times.
|
200 | if (max < 8) { |
| 115 | 19 | max = 8; | |
| 116 | } | ||
| 117 | |||
| 118 | 200 | m_channelSet.clear(); | |
| 119 | 200 | m_channelCount = 0; | |
| 120 | 200 | m_channelSetRanges = outputRanges; | |
| 121 | |||
| 122 | // Ranges may be separated by ';' or ','. Only ';' was ever parsed, but the | ||
| 123 | // value the API advertises for this argument - GetOutputRangesAsString(), | ||
| 124 | // published as the ChannelRange contents by /api/fppd/testing/tests/<pattern> | ||
| 125 | // - joins its ranges with ',', so a caller sending that value back got only | ||
| 126 | // the first range: the rest were swallowed by the atoi() below without any | ||
| 127 | // error. A token is looked up as a model name before being split on ',' so | ||
| 128 | // a model whose name contains one still resolves. | ||
| 129 | 200 | std::vector<std::string> ranges; | |
| 130 |
3/4✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
✓ Branch 3 taken 200 times.
|
403 | for (const auto& tok : split(channelSetStr, ';')) { |
| 131 |
4/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 202 times.
✓ Branch 2 taken 202 times.
✓ Branch 3 taken 1 times.
|
204 | if (tok.find(',') == std::string::npos || |
| 132 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | PixelOverlayManager::INSTANCE.getModel(tok) != nullptr) { |
| 133 |
1/2✓ Branch 0 taken 202 times.
✗ Branch 1 not taken.
|
202 | ranges.push_back(tok); |
| 134 | } else { | ||
| 135 |
3/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
|
4 | for (const auto& sub : split(tok, ',')) { |
| 136 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | ranges.push_back(sub); |
| 137 | 1 | } | |
| 138 | } | ||
| 139 | 200 | } | |
| 140 | |||
| 141 |
2/2✓ Branch 0 taken 205 times.
✓ Branch 1 taken 200 times.
|
405 | for (int r = 0; r < ranges.size(); r++) { |
| 142 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | auto model = PixelOverlayManager::INSTANCE.getModelShared(ranges[r]); |
| 143 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 205 times.
|
205 | if (model != nullptr) { |
| 144 | ✗ | start = model->getStartChannel(); | |
| 145 | ✗ | end = start + model->getChannelCount() - 1; | |
| 146 | } else { | ||
| 147 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | std::vector<std::string> parts = split(ranges[r], '-'); |
| 148 | |||
| 149 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | if (parts.size()) { |
| 150 | // atoi() wrapped "3000000000" to a negative start, which the | ||
| 151 | // checks below never caught, so OverlayTestData() copied to | ||
| 152 | // ~1.3 GB before the channel buffer every frame. Parse wide | ||
| 153 | // and clamp into int range; a negative value is rejected below. | ||
| 154 | 410 | auto channelNum = [](const std::string& v) { | |
| 155 | 410 | long long n = strtoll(v.c_str(), nullptr, 10); | |
| 156 |
1/2✓ Branch 0 taken 410 times.
✗ Branch 1 not taken.
|
410 | return (int)std::clamp<long long>(n, -1, INT_MAX); |
| 157 | }; | ||
| 158 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | start = channelNum(parts[0]); |
| 159 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | if (start > 0) |
| 160 | 205 | start--; | |
| 161 | |||
| 162 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | if (parts.size() > 1) |
| 163 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | end = channelNum(parts[1]); |
| 164 | else | ||
| 165 | ✗ | end = start; | |
| 166 | |||
| 167 |
1/2✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
|
205 | if (end > 0) |
| 168 | 205 | end--; | |
| 169 | |||
| 170 |
2/2✓ Branch 0 taken 45 times.
✓ Branch 1 taken 160 times.
|
205 | if (end > max) |
| 171 | 45 | end = max; | |
| 172 | } | ||
| 173 | 205 | } | |
| 174 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 200 times.
|
205 | if (end < start) { |
| 175 | 5 | end = start; | |
| 176 | } | ||
| 177 | // Only end was clamped to the outputs, so a range starting past them | ||
| 178 | // ("99999999-99999999") put the copy in OverlayTestData() that far | ||
| 179 | // past the channel buffer: corruption, or a SIGSEGV on the output | ||
| 180 | // thread. Such a range has nothing to test; drop it. The total is | ||
| 181 | // capped at the test buffer too, which overlapping ranges could | ||
| 182 | // otherwise outgrow. | ||
| 183 |
3/4✓ Branch 0 taken 205 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 201 times.
|
205 | if (start < 0 || start > max) { |
| 184 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | LogWarn(VB_CHANNELOUT, "Test channel range '%s' is outside the output channels (1-%d), ignoring it\n", |
| 185 | ranges[r].c_str(), max + 1); | ||
| 186 | 4 | continue; | |
| 187 | } | ||
| 188 | 201 | int room = (int)FPPD_MAX_CHANNELS - m_channelCount; | |
| 189 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 201 times.
|
201 | if (room <= 0) { |
| 190 | ✗ | break; | |
| 191 | } | ||
| 192 | 201 | end = std::min(end, start + room - 1); | |
| 193 |
1/2✓ Branch 0 taken 201 times.
✗ Branch 1 not taken.
|
201 | m_channelSet.push_back(std::make_pair(start, end)); |
| 194 | 201 | m_channelCount += end - start + 1; | |
| 195 |
2/3✓ Branch 0 taken 201 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
205 | } |
| 196 | |||
| 197 | 200 | return 1; | |
| 198 | 200 | } | |
| 199 | |||
| 200 | /* | ||
| 201 | * | ||
| 202 | */ | ||
| 203 | 170 | int TestPatternBase::SetupTest(void) { | |
| 204 | 170 | m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000); | |
| 205 | |||
| 206 | 170 | return 1; | |
| 207 | } | ||
| 208 | |||
| 209 | /* | ||
| 210 | * | ||
| 211 | */ | ||
| 212 | 30015 | int TestPatternBase::OverlayTestData(char* channelData) { | |
| 213 | 30015 | int count = 0; | |
| 214 | 30015 | int copied = 0; | |
| 215 | |||
| 216 |
2/2✓ Branch 0 taken 339 times.
✓ Branch 1 taken 29676 times.
|
30015 | if (!m_testEnabled) { |
| 217 | // Clear channels being tested | ||
| 218 |
2/2✓ Branch 0 taken 252 times.
✓ Branch 1 taken 339 times.
|
591 | for (int s = 0; s < m_channelSet.size(); s++) { |
| 219 | 252 | count = m_channelSet[s].second - m_channelSet[s].first + 1; | |
| 220 | |||
| 221 | 252 | bzero(channelData + m_channelSet[s].first, count); | |
| 222 | } | ||
| 223 | |||
| 224 | 339 | return 1; | |
| 225 | } | ||
| 226 | |||
| 227 | // The channel set was clipped to the outputs as they were when it was | ||
| 228 | // resolved, so resolve it again once they change. Resolved only when | ||
| 229 | // its text changed, a test started before its outputs were loaded (or | ||
| 230 | // while they were being reloaded) stayed clipped to the 1-8 placeholder | ||
| 231 | // there is with no outputs: 1-150 lit channels 1-9 for the whole test. | ||
| 232 |
9/12✓ Branch 0 taken 26115 times.
✓ Branch 1 taken 3561 times.
✓ Branch 2 taken 26115 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 38 times.
✓ Branch 5 taken 26077 times.
✓ Branch 6 taken 26115 times.
✓ Branch 7 taken 3561 times.
✓ Branch 8 taken 38 times.
✓ Branch 9 taken 29638 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
29676 | if (!m_channelSetStr.empty() && GetOutputRangesSnapshot() != m_channelSetRanges) { |
| 233 |
1/2✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
|
38 | auto was = m_channelSet; |
| 234 |
2/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
|
38 | SetChannelSet(m_channelSetStr); |
| 235 |
3/4✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 31 times.
|
38 | if (m_channelSet != was) { |
| 236 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | SetupTest(); |
| 237 | } | ||
| 238 | 38 | } | |
| 239 | |||
| 240 |
2/2✓ Branch 0 taken 26061 times.
✓ Branch 1 taken 29676 times.
|
55737 | for (int s = 0; s < m_channelSet.size(); s++) { |
| 241 | 26061 | count = m_channelSet[s].second - m_channelSet[s].first + 1; | |
| 242 | |||
| 243 | 26061 | memcpy(channelData + m_channelSet[s].first, m_testData + copied, | |
| 244 | count); | ||
| 245 | |||
| 246 | 26061 | copied += count; | |
| 247 | } | ||
| 248 | |||
| 249 | // On the monotonic clock: on the wall clock a step back held the pattern | ||
| 250 | // still for as long as the step. Nothing to cycle when every range was | ||
| 251 | // dropped (the patterns' shifts take a channel off the count, and would | ||
| 252 | // underflow). | ||
| 253 |
6/6✓ Branch 0 taken 26064 times.
✓ Branch 1 taken 3612 times.
✓ Branch 2 taken 6696 times.
✓ Branch 3 taken 19368 times.
✓ Branch 4 taken 6696 times.
✓ Branch 5 taken 22980 times.
|
29676 | if (m_channelCount > 0 && GetMonotonicTimeMicros() >= m_nextCycleTime) { |
| 254 | 6696 | CycleData(); | |
| 255 | 6696 | m_nextCycleTime = GetMonotonicTimeMicros() + (m_cycleMS * 1000); | |
| 256 | } | ||
| 257 | |||
| 258 | 29676 | return 1; | |
| 259 | } | ||
| 260 | |||
| 261 | /* | ||
| 262 | * | ||
| 263 | */ | ||
| 264 | 194 | void TestPatternBase::DumpConfig(void) { | |
| 265 | 194 | LogDebug(VB_CHANNELOUT, "TestPatternBase::DumpConfig()\n"); | |
| 266 | 194 | LogDebug(VB_CHANNELOUT, " cycleMS : %d\n", m_cycleMS); | |
| 267 | 194 | LogDebug(VB_CHANNELOUT, " channelSet : %s\n", m_channelSetStr.c_str()); | |
| 268 | 194 | LogDebug(VB_CHANNELOUT, " channelCount: %d\n", m_channelCount); | |
| 269 | |||
| 270 |
2/2✓ Branch 0 taken 164 times.
✓ Branch 1 taken 194 times.
|
358 | for (int s = 0; s < m_channelSet.size(); s++) { |
| 271 | 164 | LogDebug(VB_CHANNELOUT, " : %d-%d\n", | |
| 272 | m_channelSet[s].first + 1, m_channelSet[s].second + 1); | ||
| 273 | } | ||
| 274 | 194 | } | |
| 275 |