GCC Code Coverage Report


Directory: ./
File: channeloutput/DDP.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 87 90 96.7%
Functions: 6 6 100.0%
Branches: 39 56 69.6%

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