| 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 GPL v2 as described in the | ||
| 10 | * included LICENSE.GPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | |||
| 17 | #include <arpa/inet.h> | ||
| 18 | #include <net/if.h> | ||
| 19 | #include <netinet/in.h> | ||
| 20 | #include <sys/ioctl.h> | ||
| 21 | #include <sys/socket.h> | ||
| 22 | #include <sys/time.h> | ||
| 23 | #include <ctype.h> | ||
| 24 | #include <errno.h> | ||
| 25 | #include <math.h> | ||
| 26 | #include <netdb.h> | ||
| 27 | #include <signal.h> | ||
| 28 | #include <string.h> | ||
| 29 | #include <strings.h> | ||
| 30 | #include <unistd.h> | ||
| 31 | |||
| 32 | #include "../Warnings.h" | ||
| 33 | #include "../log.h" | ||
| 34 | |||
| 35 | #include "ArtNet.h" | ||
| 36 | #include "ChannelOutputSetup.h" | ||
| 37 | #include "channeloutputthread.h" | ||
| 38 | #include "e131bridge.h" | ||
| 39 | #include "fpp.h" | ||
| 40 | |||
| 41 | #define ARTNET_HEADER_LENGTH 18 | ||
| 42 | #define ARTNET_SYNC_PACKET_LENGTH 14 | ||
| 43 | |||
| 44 | #define ARTNET_DEST_PORT 6454 | ||
| 45 | |||
| 46 | #define ARTNET_SEQUENCE_INDEX 12 | ||
| 47 | #define ARTNET_UNIVERSE_INDEX 14 | ||
| 48 | #define ARTNET_LENGTH_INDEX 16 | ||
| 49 | |||
| 50 | #define ARTNET_TYPE_BROADCAST 2 | ||
| 51 | #define ARTNET_TYPE_UNICAST_ARTNETPORT 3 | ||
| 52 | #define ARTNET_TYPE_UNICAST 9 | ||
| 53 | |||
| 54 | const char ArtNetHeader[] = { | ||
| 55 | 'A', 'r', 't', '-', 'N', 'e', 't', 0x00, // 8-byte ID | ||
| 56 | 0x00, // Opcode Low | ||
| 57 | 0x50, // Opcode High | ||
| 58 | 0x00, // Protocol Version High | ||
| 59 | 0x0E, // Protocol Version Low | ||
| 60 | 0x00, // Sequence # | ||
| 61 | 0x00, // Physical # | ||
| 62 | 0x00, // Universe Number Low | ||
| 63 | 0x00, // Universe Number High (7 bits) | ||
| 64 | 0x00, // Length High (length always an even number) | ||
| 65 | 0x00 // Length Low | ||
| 66 | }; | ||
| 67 | |||
| 68 | const char ArtNetSyncPacket[] = { | ||
| 69 | 'A', 'r', 't', '-', 'N', 'e', 't', 0x00, // 8-byte ID | ||
| 70 | 0x00, // Opcode Low | ||
| 71 | 0x52, // Opcode High | ||
| 72 | 0x00, // Protocol Version High | ||
| 73 | 0x0E, // Protocol Version Low | ||
| 74 | 0x00, // Aux1 | ||
| 75 | 0x00 // Aux2 | ||
| 76 | }; | ||
| 77 | |||
| 78 | static struct iovec ArtNetSyncIovecs = { (void*)ArtNetSyncPacket, ARTNET_SYNC_PACKET_LENGTH }; | ||
| 79 | static struct sockaddr_in ArtNetSyncAddress; | ||
| 80 | |||
| 81 | static const std::string ARTNETTYPE = "ArtNet"; | ||
| 82 | |||
| 83 | ✗ | const std::string& ArtNetOutputData::GetOutputTypeString() const { | |
| 84 | ✗ | return ARTNETTYPE; | |
| 85 | } | ||
| 86 | |||
| 87 | 26 | ArtNetOutputData::ArtNetOutputData(const Json::Value& config) : | |
| 88 | UDPOutputData(config), | ||
| 89 | 26 | sequenceNumber(1), | |
| 90 | 26 | universeCount(1) { | |
| 91 | 26 | memset((char*)&anAddress, 0, sizeof(sockaddr_in)); | |
| 92 | 26 | anAddress.sin_family = AF_INET; | |
| 93 | 26 | anAddress.sin_port = htons(ARTNET_DEST_PORT); | |
| 94 | |||
| 95 | 26 | memset((char*)&ArtNetSyncAddress, 0, sizeof(sockaddr_in)); | |
| 96 | 26 | ArtNetSyncAddress.sin_family = AF_INET; | |
| 97 | 26 | ArtNetSyncAddress.sin_port = htons(ARTNET_DEST_PORT); | |
| 98 | 26 | ArtNetSyncAddress.sin_addr.s_addr = inet_addr("255.255.255.255"); | |
| 99 | |||
| 100 |
2/4✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
|
26 | universe = config["id"].asInt(); |
| 101 |
2/4✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
|
26 | priority = config["priority"].asInt(); |
| 102 |
3/4✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 4 times.
|
26 | if (config.isMember("universeCount")) { |
| 103 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
22 | universeCount = config["universeCount"].asInt(); |
| 104 | } | ||
| 105 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (universeCount < 1) { |
| 106 | ✗ | universeCount = 1; | |
| 107 | } | ||
| 108 | |||
| 109 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
|
26 | switch (type) { |
| 110 | ✗ | case ARTNET_TYPE_BROADCAST: // Multicast | |
| 111 | ✗ | ipAddress = ""; | |
| 112 | ✗ | break; | |
| 113 | 26 | case ARTNET_TYPE_UNICAST_ARTNETPORT: // UnicastAddress | |
| 114 | case ARTNET_TYPE_UNICAST: // UnicastAddress | ||
| 115 |
2/4✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
|
26 | ipAddress = config["address"].asString(); |
| 116 | 26 | break; | |
| 117 | } | ||
| 118 | |||
| 119 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (type == ARTNET_TYPE_BROADCAST) { |
| 120 | ✗ | anAddress.sin_addr.s_addr = inet_addr("255.255.255.255"); | |
| 121 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | } else if (active) { |
| 122 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | anAddress.sin_addr.s_addr = toInetAddr(ipAddress, valid); |
| 123 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
|
26 | if (!valid) { |
| 124 | ✗ | AddOutputConfigWarning(14, "Could not resolve host name " + ipAddress + " - disabling output"); | |
| 125 | ✗ | active = false; | |
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | anIovecs.resize(universeCount * 2); |
| 130 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | anHeaders.resize(universeCount); |
| 131 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 26 times.
|
53 | for (int x = 0; x < universeCount; x++) { |
| 132 | 27 | unsigned char* anBuffer = (unsigned char*)malloc(ARTNET_HEADER_LENGTH); | |
| 133 | 27 | anHeaders[x] = anBuffer; | |
| 134 | |||
| 135 | 27 | memcpy(anBuffer, ArtNetHeader, ARTNET_HEADER_LENGTH); | |
| 136 | |||
| 137 | 27 | int uni = universe + x; | |
| 138 | |||
| 139 | 27 | anBuffer[ARTNET_UNIVERSE_INDEX] = (char)(uni % 256); | |
| 140 | 27 | anBuffer[ARTNET_UNIVERSE_INDEX + 1] = (char)(uni / 256); | |
| 141 | |||
| 142 | 27 | anBuffer[ARTNET_LENGTH_INDEX] = (char)(channelCount / 256); | |
| 143 | 27 | anBuffer[ARTNET_LENGTH_INDEX + 1] = (char)(channelCount % 256); | |
| 144 | |||
| 145 | // use scatter/gather for the packet. One IOV will contain | ||
| 146 | // the header, the second will point into the raw channel data | ||
| 147 | // and will be set at output time. This avoids any memcpy. | ||
| 148 | 27 | anIovecs[x * 2].iov_base = anBuffer; | |
| 149 | 27 | anIovecs[x * 2].iov_len = ARTNET_HEADER_LENGTH; | |
| 150 | 27 | anIovecs[x * 2 + 1].iov_base = nullptr; | |
| 151 | 27 | anIovecs[x * 2 + 1].iov_len = channelCount; | |
| 152 | } | ||
| 153 | 26 | } | |
| 154 | |||
| 155 | 104 | ArtNetOutputData::~ArtNetOutputData() { | |
| 156 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 26 times.
|
106 | for (auto a : anHeaders) { |
| 157 | 54 | free(a); | |
| 158 | } | ||
| 159 | 104 | } | |
| 160 | |||
| 161 | 52 | bool ArtNetOutputData::IsPingable() { | |
| 162 |
3/4✓ Branch 0 taken 46 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
|
52 | return type == ARTNET_TYPE_UNICAST || type == ARTNET_TYPE_UNICAST_ARTNETPORT; |
| 163 | } | ||
| 164 | |||
| 165 | // The shared ArtNet socket for `key`, or -1 while it can't be opened (the | ||
| 166 | // port is taken; CreateArtNetSocket() warns about that once and retries now | ||
| 167 | // and then). Never hand the sender -1: every frame it would fail on it. | ||
| 168 | 8601 | static int ArtNetSocketFor(UDPOutputMessages& messages, unsigned int key) { | |
| 169 | 8601 | int sock = messages.GetSocket(key); | |
| 170 |
2/2✓ Branch 0 taken 922 times.
✓ Branch 1 taken 7679 times.
|
8601 | if (sock == -1) { |
| 171 | // we MAY be bridging ArtNet so we need to use that same socket | ||
| 172 | 922 | sock = CreateArtNetSocket(); | |
| 173 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 914 times.
|
922 | if (sock != -1) { |
| 174 | 8 | messages.ForceSocket(key, sock, true); | |
| 175 | } | ||
| 176 | } | ||
| 177 | 8601 | return sock; | |
| 178 | } | ||
| 179 | |||
| 180 | 4495 | void ArtNetOutputData::PrepareData(unsigned char* channelData, UDPOutputMessages& messages) { | |
| 181 | 4495 | sequencePending = false; | |
| 182 |
3/6✓ Branch 0 taken 4495 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4495 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4495 times.
✗ Branch 5 not taken.
|
4495 | if (valid && active) { |
| 183 | 4495 | ArtNetSocketFor(messages, ARTNET_SYNC_KEY); | |
| 184 | |||
| 185 | 4495 | int key = ARTNET_MESSAGES_KEY; | |
| 186 |
2/2✓ Branch 0 taken 389 times.
✓ Branch 1 taken 4106 times.
|
4495 | if (type == ARTNET_TYPE_UNICAST) { |
| 187 | 389 | key = anAddress.sin_addr.s_addr; | |
| 188 | } else { | ||
| 189 | // ALL ArtNet messages must go out on the same socket | ||
| 190 | // and the socket MUST have the source port of ARTNET_DEST_PORT | ||
| 191 | // as per the ArtNet protocol | ||
| 192 | // Use a reserved key that won't collide with IP addresses | ||
| 193 |
2/2✓ Branch 0 taken 355 times.
✓ Branch 1 taken 3751 times.
|
4106 | if (ArtNetSocketFor(messages, ARTNET_MESSAGES_KEY) == -1) { |
| 194 | 355 | return; | |
| 195 | } | ||
| 196 | } | ||
| 197 | 4140 | unsigned char* cur = channelData + startChannel - 1; | |
| 198 | 4140 | int start = 0; | |
| 199 | 4140 | bool anySkipped = false; | |
| 200 | 4140 | bool allSkipped = true; | |
| 201 | |||
| 202 | 4140 | std::vector<struct mmsghdr>& msgs = messages[key]; | |
| 203 |
2/2✓ Branch 0 taken 4209 times.
✓ Branch 1 taken 4140 times.
|
8349 | for (int x = 0; x < universeCount; x++) { |
| 204 |
2/2✓ Branch 0 taken 4109 times.
✓ Branch 1 taken 100 times.
|
4209 | if (NeedToOutputFrame(channelData, startChannel - 1, start, channelCount)) { |
| 205 | struct mmsghdr msg; | ||
| 206 | 4109 | memset(&msg, 0, sizeof(msg)); | |
| 207 | 4109 | msg.msg_hdr.msg_name = &anAddress; | |
| 208 | 4109 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 209 | 4109 | msg.msg_hdr.msg_iov = &anIovecs[x * 2]; | |
| 210 | 4109 | msg.msg_hdr.msg_iovlen = 2; | |
| 211 | 4109 | msg.msg_len = channelCount + ARTNET_HEADER_LENGTH; | |
| 212 |
1/2✓ Branch 0 taken 4109 times.
✗ Branch 1 not taken.
|
4109 | msgs.push_back(msg); |
| 213 | |||
| 214 | 4109 | anHeaders[x][ARTNET_SEQUENCE_INDEX] = sequenceNumber; | |
| 215 | 4109 | anIovecs[x * 2 + 1].iov_base = (void*)cur; | |
| 216 | 4109 | allSkipped = false; | |
| 217 | } else { | ||
| 218 | 100 | anySkipped = true; | |
| 219 | } | ||
| 220 | 4209 | cur += channelCount; | |
| 221 | 4209 | start += channelCount; | |
| 222 | } | ||
| 223 | 4140 | pendingSequenceNumber = sequenceNumber + 1; | |
| 224 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4140 times.
|
4140 | if (pendingSequenceNumber == 0) { |
| 225 | ✗ | pendingSequenceNumber++; | |
| 226 | } | ||
| 227 | 4140 | sequencePending = true; | |
| 228 | // the counter forces a full resend once 10 frames in a row skipped | ||
| 229 | // something; without the reset, after the first 10 skips dedup was | ||
| 230 | // off for the rest of the run | ||
| 231 |
2/2✓ Branch 0 taken 100 times.
✓ Branch 1 taken 4040 times.
|
4140 | if (anySkipped) { |
| 232 | 100 | skippedFrames++; | |
| 233 | } else { | ||
| 234 | 4040 | skippedFrames = 0; | |
| 235 | } | ||
| 236 |
2/2✓ Branch 0 taken 4040 times.
✓ Branch 1 taken 100 times.
|
4140 | if (!allSkipped) { |
| 237 | 4040 | SaveFrame(&channelData[startChannel - 1], start); | |
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | 4495 | void ArtNetOutputData::PostPrepareData(unsigned char* channelData, UDPOutputMessages& msgs) { | |
| 242 |
6/8✓ Branch 0 taken 4495 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4495 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3936 times.
✓ Branch 5 taken 559 times.
✓ Branch 6 taken 3936 times.
✓ Branch 7 taken 559 times.
|
4495 | if (valid && active && msgs.GetSocket(ARTNET_SYNC_KEY) != -1) { |
| 243 |
3/4✓ Branch 0 taken 3936 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3558 times.
✓ Branch 3 taken 378 times.
|
3936 | for (auto msg : msgs[ARTNET_SYNC_KEY]) { |
| 244 |
1/2✓ Branch 0 taken 3558 times.
✗ Branch 1 not taken.
|
3558 | if (msg.msg_hdr.msg_iov == &ArtNetSyncIovecs) { |
| 245 | // already added, skip | ||
| 246 | 3558 | return; | |
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | struct mmsghdr msg; | ||
| 251 | 378 | memset(&msg, 0, sizeof(msg)); | |
| 252 | |||
| 253 | 378 | msg.msg_hdr.msg_name = &ArtNetSyncAddress; | |
| 254 | 378 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 255 | 378 | msg.msg_hdr.msg_iov = &ArtNetSyncIovecs; | |
| 256 | 378 | msg.msg_hdr.msg_iovlen = 1; | |
| 257 | 378 | msg.msg_len = ARTNET_SYNC_PACKET_LENGTH; | |
| 258 |
2/4✓ Branch 0 taken 378 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 378 times.
✗ Branch 3 not taken.
|
378 | msgs[ARTNET_SYNC_KEY].push_back(msg); |
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | 4290 | void ArtNetOutputData::FrameSent() { | |
| 263 |
2/2✓ Branch 0 taken 4088 times.
✓ Branch 1 taken 202 times.
|
4290 | if (sequencePending) { |
| 264 | 4088 | sequenceNumber = pendingSequenceNumber; | |
| 265 | 4088 | sequencePending = false; | |
| 266 | } | ||
| 267 | 4290 | } | |
| 268 | |||
| 269 | 52 | void ArtNetOutputData::GetRequiredChannelRange(int& min, int& max) { | |
| 270 | 52 | min = startChannel - 1; | |
| 271 | 52 | max = startChannel + (channelCount * universeCount) - 1; | |
| 272 | 52 | } | |
| 273 | |||
| 274 | 26 | void ArtNetOutputData::DumpConfig() { | |
| 275 | 26 | LogDebug(VB_CHANNELOUT, "ArtNet Universe: %s %d:%d:%d:%d:%d:%d %s\n", | |
| 276 | description.c_str(), | ||
| 277 | active, | ||
| 278 | universe, | ||
| 279 | universeCount, | ||
| 280 | startChannel, | ||
| 281 | channelCount, | ||
| 282 | type, | ||
| 283 | ipAddress.c_str()); | ||
| 284 | 26 | } | |
| 285 |