| 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 <netinet/in.h> | ||
| 19 | #include <sys/socket.h> | ||
| 20 | #include <sys/time.h> | ||
| 21 | #include <sys/types.h> | ||
| 22 | #include <ctype.h> | ||
| 23 | #include <errno.h> | ||
| 24 | #include <netdb.h> | ||
| 25 | #include <signal.h> | ||
| 26 | |||
| 27 | #include "../Warnings.h" | ||
| 28 | #include "ChannelOutputSetup.h" | ||
| 29 | #include "../log.h" | ||
| 30 | |||
| 31 | #include "KiNet.h" | ||
| 32 | |||
| 33 | #define KINET_V1_PACKET_HEADERLEN 21 | ||
| 34 | #define KINET_V2_PACKET_HEADERLEN 24 | ||
| 35 | |||
| 36 | #define KINET_V1_TYPE 6 | ||
| 37 | #define KINET_V2_TYPE 7 | ||
| 38 | |||
| 39 | static const uint8_t V1_HEADER[KINET_V1_PACKET_HEADERLEN] = { | ||
| 40 | 0x04, 0x01, 0xdc, 0x4a, // magic[4] 0401dc4a | ||
| 41 | 0x01, 0x00, // ver[2] 0100 | ||
| 42 | 0x01, 0x01, // type[2] | ||
| 43 | 0x00, 0x00, 0x00, 0x00, // seq[4] | ||
| 44 | 0x00, // port | ||
| 45 | 0x00, // padding | ||
| 46 | 0x00, 0x00, // flags | ||
| 47 | 0xFF, 0xFF, 0xFF, 0xFF, // time | ||
| 48 | 0x00 // uni | ||
| 49 | }; | ||
| 50 | static const uint8_t V2_HEADER[KINET_V2_PACKET_HEADERLEN] = { | ||
| 51 | 0x04, 0x01, 0xdc, 0x4a, // magic[4] 0401dc4a | ||
| 52 | 0x01, 0x00, // ver[2] 0100 | ||
| 53 | 0x08, 0x01, // type[2] | ||
| 54 | 0x00, 0x00, 0x00, 0x00, // seq[4] | ||
| 55 | 0xFF, 0xFF, 0xFF, 0xFF, // time | ||
| 56 | 0x00, // port | ||
| 57 | 0x00, // sequenceNum | ||
| 58 | 0x00, 0x00, // flags? | ||
| 59 | 0x00, 0x02, | ||
| 60 | 0x00, 0x00 | ||
| 61 | }; | ||
| 62 | |||
| 63 | static const std::string KINETTYPE = "KiNet"; | ||
| 64 | |||
| 65 | ✗ | const std::string& KiNetOutputData::GetOutputTypeString() const { | |
| 66 | ✗ | return KINETTYPE; | |
| 67 | } | ||
| 68 | |||
| 69 | 2 | KiNetOutputData::KiNetOutputData(const Json::Value& config) : | |
| 70 | 2 | UDPOutputData(config) { | |
| 71 | 2 | memset((char*)&kinetAddress, 0, sizeof(sockaddr_in)); | |
| 72 | 2 | kinetAddress.sin_family = AF_INET; | |
| 73 | 2 | kinetAddress.sin_port = htons(KINET_PORT); | |
| 74 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (active) { |
| 75 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | kinetAddress.sin_addr.s_addr = toInetAddr(ipAddress, valid); |
| 76 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!valid) { |
| 77 | ✗ | AddOutputConfigWarning(14, "Could not resolve host name " + ipAddress + " - disabling output"); | |
| 78 | ✗ | active = false; | |
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | port = config["id"].asInt(); |
| 83 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if (config.isMember("universeCount")) { |
| 84 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | portCount = config["universeCount"].asInt(); |
| 85 | } | ||
| 86 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (portCount < 1) { |
| 87 | ✗ | portCount = 1; | |
| 88 | } | ||
| 89 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (portCount > 255) { |
| 90 | ✗ | portCount = 255; | |
| 91 | } | ||
| 92 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (channelCount > 512) { |
| 93 | ✗ | channelCount = 512; | |
| 94 | } | ||
| 95 | |||
| 96 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | sentSequence.assign(portCount, 0); |
| 97 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | pendingSequence.assign(portCount, 0); |
| 98 | 2 | kinetIovecs = (struct iovec*)calloc(portCount * 2, sizeof(struct iovec)); | |
| 99 | 2 | kinetBuffers = (unsigned char**)calloc(portCount, sizeof(unsigned char*)); | |
| 100 | |||
| 101 | 2 | int chan = startChannel - 1; | |
| 102 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (int x = 0; x < portCount; x++) { |
| 103 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (type == KINET_V1_TYPE) { |
| 104 | 3 | kinetBuffers[x] = (unsigned char*)malloc(KINET_V1_PACKET_HEADERLEN); | |
| 105 | 3 | memcpy(kinetBuffers[x], V1_HEADER, KINET_V1_PACKET_HEADERLEN); | |
| 106 | 3 | kinetBuffers[x][12] = port + x; | |
| 107 | } else { | ||
| 108 | 3 | kinetBuffers[x] = (unsigned char*)malloc(KINET_V2_PACKET_HEADERLEN); | |
| 109 | 3 | memcpy(kinetBuffers[x], V2_HEADER, KINET_V2_PACKET_HEADERLEN); | |
| 110 | 3 | kinetBuffers[x][16] = port + x; | |
| 111 | } | ||
| 112 | |||
| 113 | // use scatter/gather for the packet. One IOV will contain | ||
| 114 | // the header, the second will point into the raw channel data | ||
| 115 | // and will be set at output time. This avoids any memcpy. | ||
| 116 | 6 | kinetIovecs[x * 2].iov_base = kinetBuffers[x]; | |
| 117 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | kinetIovecs[x * 2].iov_len = type == KINET_V1_TYPE ? KINET_V1_PACKET_HEADERLEN : KINET_V2_PACKET_HEADERLEN; |
| 118 | 6 | kinetIovecs[x * 2 + 1].iov_base = nullptr; | |
| 119 | 6 | kinetIovecs[x * 2 + 1].iov_len = channelCount; | |
| 120 | |||
| 121 | 6 | chan += channelCount; | |
| 122 | } | ||
| 123 | 2 | } | |
| 124 | 8 | KiNetOutputData::~KiNetOutputData() { | |
| 125 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
16 | for (int x = 0; x < portCount; x++) { |
| 126 | 12 | free(kinetBuffers[x]); | |
| 127 | } | ||
| 128 | 4 | free(kinetBuffers); | |
| 129 | 4 | free(kinetIovecs); | |
| 130 | 8 | } | |
| 131 | |||
| 132 | 4 | void KiNetOutputData::GetRequiredChannelRange(int& min, int& max) { | |
| 133 | 4 | min = startChannel - 1; | |
| 134 | 4 | max = startChannel + (channelCount * portCount) - 1; | |
| 135 | 4 | } | |
| 136 | |||
| 137 | 138 | void KiNetOutputData::PrepareData(unsigned char* channelData, UDPOutputMessages& msgs) { | |
| 138 |
3/6✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 138 times.
✗ Branch 5 not taken.
|
138 | if (valid && active) { |
| 139 | 138 | int start = 0; | |
| 140 | struct mmsghdr msg; | ||
| 141 | 138 | memset(&msg, 0, sizeof(msg)); | |
| 142 | |||
| 143 | 138 | msg.msg_hdr.msg_name = &kinetAddress; | |
| 144 | 138 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 145 | 138 | bool anySkipped = false; | |
| 146 | 138 | bool allSkipped = true; | |
| 147 |
2/2✓ Branch 0 taken 414 times.
✓ Branch 1 taken 138 times.
|
552 | for (int p = 0; p < portCount; p++) { |
| 148 |
1/2✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
|
414 | bool nto = NeedToOutputFrame(channelData, startChannel - 1, start, kinetIovecs[p * 2 + 1].iov_len); |
| 149 |
1/2✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
|
414 | if (nto) { |
| 150 | 414 | msg.msg_hdr.msg_iov = &kinetIovecs[p * 2]; | |
| 151 | 414 | msg.msg_hdr.msg_iovlen = 2; | |
| 152 | 414 | msg.msg_len = kinetIovecs[p * 2 + 1].iov_len + kinetIovecs[p * 2].iov_len; | |
| 153 | |||
| 154 |
2/4✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 414 times.
✗ Branch 3 not taken.
|
414 | msgs[kinetAddress.sin_addr.s_addr].push_back(msg); |
| 155 | |||
| 156 |
2/2✓ Branch 0 taken 207 times.
✓ Branch 1 taken 207 times.
|
414 | if (type == KINET_V2_TYPE) { |
| 157 | // Numbered from the last packet sent, not the last one | ||
| 158 | // prepared: a frame prepared twice before it went out (as | ||
| 159 | // one is when playback starts) skipped a number on the wire. | ||
| 160 | 207 | uint32_t seq = sentSequence[p] + 1; | |
| 161 | 207 | pendingSequence[p] = seq; | |
| 162 | 207 | kinetBuffers[p][8] = seq & 0xFF; | |
| 163 | 207 | kinetBuffers[p][9] = (seq >> 8) & 0xFF; | |
| 164 | 207 | kinetBuffers[p][10] = (seq >> 16) & 0xFF; | |
| 165 | 207 | kinetBuffers[p][11] = (seq >> 24) & 0xFF; | |
| 166 | } | ||
| 167 | // set the pointer to the channelData for the universe | ||
| 168 | 414 | kinetIovecs[p * 2 + 1].iov_base = (void*)(&channelData[startChannel - 1 + start]); | |
| 169 | 414 | allSkipped = false; | |
| 170 | } else { | ||
| 171 | ✗ | anySkipped = true; | |
| 172 | } | ||
| 173 | 414 | start += kinetIovecs[p * 2 + 1].iov_len; | |
| 174 | } | ||
| 175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 138 times.
|
138 | if (anySkipped) { |
| 176 | ✗ | skippedFrames++; | |
| 177 | } else { | ||
| 178 | 138 | skippedFrames = 0; | |
| 179 | } | ||
| 180 |
1/2✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
|
138 | if (!allSkipped) { |
| 181 |
1/2✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
|
138 | SaveFrame(&channelData[startChannel - 1], start); |
| 182 | } | ||
| 183 | } | ||
| 184 | 138 | } | |
| 185 | 132 | void KiNetOutputData::FrameSent() { | |
| 186 | 132 | sentSequence = pendingSequence; | |
| 187 | 132 | } | |
| 188 | |||
| 189 | 2 | void KiNetOutputData::DumpConfig() { | |
| 190 | 2 | LogDebug(VB_CHANNELOUT, "KiNet: %s %d:%d:%d:%d %s\n", | |
| 191 | description.c_str(), | ||
| 192 | active, | ||
| 193 | startChannel, | ||
| 194 | channelCount, | ||
| 195 | type, | ||
| 196 | ipAddress.c_str()); | ||
| 197 | 2 | } | |
| 198 |