| 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 | /* | ||
| 14 | * This Channel Output implements the data packet portion of the DDP | ||
| 15 | * protocol. Discovery is not currently supported. Data packets are | ||
| 16 | * sent to port 4048 UDP or TCP. This implementation uses UDP. | ||
| 17 | * | ||
| 18 | * The full DPP spec is currently located at http://www.3waylabs.com/ddp/ | ||
| 19 | * | ||
| 20 | * The data packet format is as follows: (mostly copied from above URL) | ||
| 21 | * | ||
| 22 | * byte 0: Flags: V V x T S R Q P | ||
| 23 | * V V: 2-bits for protocol version number (01 for this file) | ||
| 24 | * x: reserved | ||
| 25 | * T: timecode field added to end of header | ||
| 26 | * if T & P are set, Push at specified time | ||
| 27 | * S: Storage. If set, data from Storage, not data-field. | ||
| 28 | * R: Reply flag, marks reply to Query packet. | ||
| 29 | * always set when any packet is sent by a Display. | ||
| 30 | * if Reply, Q flag is ignored. | ||
| 31 | * Q: Query flag, requests len data from ID at offset | ||
| 32 | * (no data sent). if clear, is a Write buffer packet | ||
| 33 | * P: Push flag, for display synchronization, or marks | ||
| 34 | * last packet of Reply | ||
| 35 | * | ||
| 36 | * byte 1: Flags: x x x x n n n n | ||
| 37 | * x: reserved for future use (set to zero) | ||
| 38 | * nnnn: sequence number from 1-15, or zero if not used. | ||
| 39 | * a sender can send duplicate packets with the same | ||
| 40 | * sequence number and DDP header for redundancy. | ||
| 41 | * a receiver can ignore duplicates received back-to-back. | ||
| 42 | * the sequence number is ignored if zero. | ||
| 43 | * | ||
| 44 | * byte 2: data type: S x t t t b b b | ||
| 45 | * S: 0 = standard types, 1 = custom | ||
| 46 | * x: reserved for future use (set to zero) | ||
| 47 | * ttt: type, 0 = greyscale, 1 = rgb, 2 = hsl? | ||
| 48 | * bbb: bits per pixel: | ||
| 49 | * 0=undefined, 1=1, 2=4, 3=8, 4=16, 5=24, 6=32 | ||
| 50 | * | ||
| 51 | * byte 3: Source or Destination ID | ||
| 52 | * 0 = reserved | ||
| 53 | * 1 = default output device | ||
| 54 | * 2=249 custom IDs, (possibly defined via JSON config) ????? | ||
| 55 | * 246 = JSON control (read/write) | ||
| 56 | * 250 = JSON config (read/write) | ||
| 57 | * 251 = JSON status (read only) | ||
| 58 | * 254 = DMX transit | ||
| 59 | * 255 = all devices | ||
| 60 | * | ||
| 61 | * byte 4-7: data offset in bytes (in units based on data-type. | ||
| 62 | * ie: RGB=3 bytes=1 unit) or bytes?? 32-bit number, MSB first | ||
| 63 | * | ||
| 64 | * byte 8-9: data length in bytes (size of data field when writing) | ||
| 65 | * 16-bit number, MSB first | ||
| 66 | * for Queries, this specifies size of data to read, | ||
| 67 | * no data field follows header. | ||
| 68 | * | ||
| 69 | * if T flag, header extended 4 bytes for timecode field (not counted in | ||
| 70 | * data length) | ||
| 71 | * | ||
| 72 | * byte 10-13: timecode | ||
| 73 | * | ||
| 74 | * if no T flag, data starts at byte 10, otherwise byte 14 | ||
| 75 | * | ||
| 76 | * byte 10 or 14: start of data | ||
| 77 | * | ||
| 78 | */ | ||
| 79 | |||
| 80 | #include "fpp-pch.h" | ||
| 81 | |||
| 82 | #include "fpp-json.h" | ||
| 83 | |||
| 84 | #include <arpa/inet.h> | ||
| 85 | #include <netinet/in.h> | ||
| 86 | #include <sys/socket.h> | ||
| 87 | #include <sys/time.h> | ||
| 88 | #include <sys/types.h> | ||
| 89 | #include <ctype.h> | ||
| 90 | #include <errno.h> | ||
| 91 | #include <netdb.h> | ||
| 92 | #include <signal.h> | ||
| 93 | |||
| 94 | #include "../Warnings.h" | ||
| 95 | #include "ChannelOutputSetup.h" | ||
| 96 | #include "../log.h" | ||
| 97 | |||
| 98 | #include "DDP.h" | ||
| 99 | |||
| 100 | #define DDP_HEADER_LEN 10 | ||
| 101 | #define DDP_SYNCPACKET_LEN 10 | ||
| 102 | |||
| 103 | #define DDP_FLAGS1_VER 0xc0 // version mask | ||
| 104 | #define DDP_FLAGS1_VER1 0x40 // version=1 | ||
| 105 | #define DDP_FLAGS1_PUSH 0x01 | ||
| 106 | #define DDP_FLAGS1_QUERY 0x02 | ||
| 107 | #define DDP_FLAGS1_REPLY 0x04 | ||
| 108 | #define DDP_FLAGS1_STORAGE 0x08 | ||
| 109 | #define DDP_FLAGS1_TIME 0x10 | ||
| 110 | |||
| 111 | #define DDP_ID_DISPLAY 1 | ||
| 112 | #define DDP_ID_CONFIG 250 | ||
| 113 | #define DDP_ID_STATUS 251 | ||
| 114 | |||
| 115 | //1440 channels per packet | ||
| 116 | #define DDP_CHANNELS_PER_PACKET 1440 | ||
| 117 | |||
| 118 | #define DDP_PACKET_LEN (DDP_HEADER_LEN + DDP_CHANNELS_PER_PACKET) | ||
| 119 | |||
| 120 | static const std::string DDPTYPE = "DDP"; | ||
| 121 | |||
| 122 | 9 | const std::string& DDPOutputData::GetOutputTypeString() const { | |
| 123 | 9 | return DDPTYPE; | |
| 124 | } | ||
| 125 | |||
| 126 | 696 | DDPOutputData::DDPOutputData(const Json::Value& config) : | |
| 127 | UDPOutputData(config), | ||
| 128 | 696 | sequenceNumber(1) { | |
| 129 | 696 | memset((char*)&ddpAddress, 0, sizeof(sockaddr_in)); | |
| 130 | 696 | ddpAddress.sin_family = AF_INET; | |
| 131 | 696 | ddpAddress.sin_port = htons(DDP_PORT); | |
| 132 |
1/2✓ Branch 0 taken 696 times.
✗ Branch 1 not taken.
|
696 | if (active) { |
| 133 |
1/2✓ Branch 0 taken 696 times.
✗ Branch 1 not taken.
|
696 | ddpAddress.sin_addr.s_addr = toInetAddr(ipAddress, valid); |
| 134 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 696 times.
|
696 | if (!valid) { |
| 135 | ✗ | AddOutputConfigWarning(14, "Could not resolve host name " + ipAddress + " - disabling output"); | |
| 136 | ✗ | active = false; | |
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | 696 | pktCount = channelCount / DDP_CHANNELS_PER_PACKET; | |
| 141 |
2/2✓ Branch 0 taken 693 times.
✓ Branch 1 taken 3 times.
|
696 | if (channelCount % DDP_CHANNELS_PER_PACKET) { |
| 142 | 693 | pktCount++; | |
| 143 | } | ||
| 144 | |||
| 145 | 696 | ddpIovecs = (struct iovec*)calloc(pktCount * 2, sizeof(struct iovec)); | |
| 146 | 696 | ddpBuffers = (unsigned char**)calloc(pktCount, sizeof(unsigned char*)); | |
| 147 | |||
| 148 | 696 | int chan = startChannel - 1; | |
| 149 |
2/2✓ Branch 0 taken 695 times.
✓ Branch 1 taken 1 times.
|
696 | if (type == 5) { |
| 150 | 695 | chan = 0; | |
| 151 | } | ||
| 152 |
2/2✓ Branch 0 taken 3285 times.
✓ Branch 1 taken 696 times.
|
3981 | for (int x = 0; x < pktCount; x++) { |
| 153 | 3285 | ddpBuffers[x] = (unsigned char*)calloc(1, DDP_HEADER_LEN); | |
| 154 | |||
| 155 | // use scatter/gather for the packet. One IOV will contain | ||
| 156 | // the header, the second will point into the raw channel data | ||
| 157 | // and will be set at output time. This avoids any memcpy. | ||
| 158 | 3285 | ddpIovecs[x * 2].iov_base = ddpBuffers[x]; | |
| 159 | 3285 | ddpIovecs[x * 2].iov_len = DDP_HEADER_LEN; | |
| 160 | 3285 | ddpIovecs[x * 2 + 1].iov_base = nullptr; | |
| 161 | |||
| 162 | 3285 | ddpBuffers[x][0] = DDP_FLAGS1_VER1; | |
| 163 | 3285 | ddpBuffers[x][2] = 0; | |
| 164 | 3285 | ddpBuffers[x][3] = DDP_ID_DISPLAY; | |
| 165 | 3285 | int pktSize = DDP_CHANNELS_PER_PACKET; | |
| 166 |
2/2✓ Branch 0 taken 696 times.
✓ Branch 1 taken 2589 times.
|
3285 | if (x == (pktCount - 1)) { |
| 167 | 696 | ddpBuffers[x][0] = DDP_FLAGS1_VER1 | DDP_FLAGS1_PUSH; | |
| 168 | //last packet | ||
| 169 |
2/2✓ Branch 0 taken 693 times.
✓ Branch 1 taken 3 times.
|
696 | if (channelCount % DDP_CHANNELS_PER_PACKET) { |
| 170 | 693 | pktSize = channelCount % DDP_CHANNELS_PER_PACKET; | |
| 171 | } | ||
| 172 | } | ||
| 173 | 3285 | ddpIovecs[x * 2 + 1].iov_len = pktSize; | |
| 174 | |||
| 175 | //offset | ||
| 176 | 3285 | ddpBuffers[x][4] = (chan & 0xFF000000) >> 24; | |
| 177 | 3285 | ddpBuffers[x][5] = (chan & 0xFF0000) >> 16; | |
| 178 | 3285 | ddpBuffers[x][6] = (chan & 0xFF00) >> 8; | |
| 179 | 3285 | ddpBuffers[x][7] = (chan & 0xFF); | |
| 180 | |||
| 181 | //size | ||
| 182 | 3285 | ddpBuffers[x][8] = (pktSize & 0xFF00) >> 8; | |
| 183 | 3285 | ddpBuffers[x][9] = pktSize & 0xFF; | |
| 184 | |||
| 185 | 3285 | chan += pktSize; | |
| 186 | } | ||
| 187 | 696 | } | |
| 188 | 2784 | DDPOutputData::~DDPOutputData() { | |
| 189 |
2/2✓ Branch 0 taken 3285 times.
✓ Branch 1 taken 696 times.
|
7962 | for (int x = 0; x < pktCount; x++) { |
| 190 | 6570 | free(ddpBuffers[x]); | |
| 191 | } | ||
| 192 | 1392 | free(ddpBuffers); | |
| 193 | 1392 | free(ddpIovecs); | |
| 194 | 2784 | } | |
| 195 | |||
| 196 | 87486 | void DDPOutputData::PrepareData(unsigned char* channelData, UDPOutputMessages& msgs) { | |
| 197 | 87486 | sequencePending = false; | |
| 198 |
3/6✓ Branch 0 taken 87486 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87486 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 87486 times.
✗ Branch 5 not taken.
|
87486 | if (valid && active) { |
| 199 | 87486 | int start = 0; | |
| 200 | // numbered from the last frame sent, not the last one prepared | ||
| 201 | 87486 | char seq = sequenceNumber; | |
| 202 | struct mmsghdr msg; | ||
| 203 | 87486 | memset(&msg, 0, sizeof(msg)); | |
| 204 | |||
| 205 | 87486 | msg.msg_hdr.msg_name = &ddpAddress; | |
| 206 | 87486 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 207 | 87486 | bool anySkipped = false; | |
| 208 | 87486 | bool allSkipped = true; | |
| 209 |
2/2✓ Branch 0 taken 214306 times.
✓ Branch 1 taken 87486 times.
|
301792 | for (int p = 0; p < pktCount; p++) { |
| 210 |
1/2✓ Branch 0 taken 214306 times.
✗ Branch 1 not taken.
|
214306 | bool nto = NeedToOutputFrame(channelData, startChannel - 1, start, ddpIovecs[p * 2 + 1].iov_len); |
| 211 |
5/6✓ Branch 0 taken 4000 times.
✓ Branch 1 taken 210306 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 3996 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
|
214306 | if (!nto && (p == (pktCount - 1)) && !allSkipped) { |
| 212 | // at least one packet is not a duplicate, we need to send the last | ||
| 213 | // packet so that the sync flag is sent | ||
| 214 | ✗ | nto = true; | |
| 215 | } | ||
| 216 |
2/2✓ Branch 0 taken 210306 times.
✓ Branch 1 taken 4000 times.
|
214306 | if (nto) { |
| 217 | 210306 | msg.msg_hdr.msg_iov = &ddpIovecs[p * 2]; | |
| 218 | 210306 | msg.msg_hdr.msg_iovlen = 2; | |
| 219 | 210306 | msg.msg_len = ddpIovecs[p * 2 + 1].iov_len + DDP_HEADER_LEN; | |
| 220 | |||
| 221 |
2/4✓ Branch 0 taken 210306 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 210306 times.
✗ Branch 3 not taken.
|
210306 | msgs[ddpAddress.sin_addr.s_addr].push_back(msg); |
| 222 | |||
| 223 | 210306 | unsigned char* header = ddpBuffers[p]; | |
| 224 | 210306 | header[1] = seq & 0xF; | |
| 225 |
2/2✓ Branch 0 taken 196696 times.
✓ Branch 1 taken 13610 times.
|
210306 | seq = seq == 15 ? 1 : seq + 1; |
| 226 | |||
| 227 | // set the pointer to the channelData for the universe | ||
| 228 | 210306 | ddpIovecs[p * 2 + 1].iov_base = (void*)(&channelData[startChannel - 1 + start]); | |
| 229 | 210306 | allSkipped = false; | |
| 230 | } else { | ||
| 231 | 4000 | anySkipped = true; | |
| 232 | } | ||
| 233 | 214306 | start += ddpIovecs[p * 2 + 1].iov_len; | |
| 234 | } | ||
| 235 | 87486 | pendingSequenceNumber = seq; | |
| 236 | 87486 | sequencePending = true; | |
| 237 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 87482 times.
|
87486 | if (anySkipped) { |
| 238 | 4 | skippedFrames++; | |
| 239 | } else { | ||
| 240 | 87482 | skippedFrames = 0; | |
| 241 | } | ||
| 242 |
2/2✓ Branch 0 taken 87482 times.
✓ Branch 1 taken 4 times.
|
87486 | if (!allSkipped) { |
| 243 |
1/2✓ Branch 0 taken 87482 times.
✗ Branch 1 not taken.
|
87482 | SaveFrame(&channelData[startChannel - 1], start); |
| 244 | } | ||
| 245 | } | ||
| 246 | 87486 | } | |
| 247 | 83979 | void DDPOutputData::FrameSent() { | |
| 248 |
2/2✓ Branch 0 taken 83399 times.
✓ Branch 1 taken 580 times.
|
83979 | if (sequencePending) { |
| 249 | 83399 | sequenceNumber = pendingSequenceNumber; | |
| 250 | 83399 | sequencePending = false; | |
| 251 | } | ||
| 252 | 83979 | } | |
| 253 | 696 | void DDPOutputData::DumpConfig() { | |
| 254 | 696 | LogDebug(VB_CHANNELOUT, "DDP: %s %d:%d:%d:%d %s\n", | |
| 255 | description.c_str(), | ||
| 256 | active, | ||
| 257 | startChannel, | ||
| 258 | channelCount, | ||
| 259 | type, | ||
| 260 | ipAddress.c_str()); | ||
| 261 | 696 | } | |
| 262 |