GCC Code Coverage Report


Directory: ./
File: fseq/FSEQFile.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 66 70 94.3%
Functions: 25 30 83.3%
Branches: 8 10 80.0%

Line Branch Exec Source
1 #pragma once
2
3 // PLUGIN ABI: native plugins compile against this header and are loaded with
4 // dlopen(), so parts of it are baked into binaries FPP did not build.
5 // - FSEQFile, V1FSEQFile, V2FSEQFile and FrameData vtables: plugins call
6 // their virtuals (a mid-vtable virtual added here once only escaped
7 // because an unrelated bump landed a week later).
8 // - Their data members: plugins use inline setters that write protected
9 // fields, and read V2FSEQFile::m_sparseRanges.
10 // - VariableHeader, which plugins construct and push by value.
11 // Any such change must bump FPP_PLUGIN_API_VERSION in Plugin.h. Without the
12 // bump, a plugin built against the previous header still loads and silently
13 // uses the old layout - FPP 10.2 shipped exactly that and crash-looped fppd.
14 // See .claude/PLUGIN-ABI.md for which plugins use what.
15
16 #include <stdio.h>
17 #include <cstdint>
18 #include <string>
19 #include <vector>
20
21 class FSEQFile {
22 public:
23 class VariableHeader {
24 public:
25 4 VariableHeader() :
26 4 fseqFile(nullptr) {
27 4 code[0] = code[1] = 0;
28 4 offset = 0;
29 4 length = 0;
30 4 extendedData = false;
31 4 }
32 111 VariableHeader(FSEQFile* fseq) :
33 111 fseqFile(fseq),
34 111 offset(0),
35 111 length(0) {
36 111 code[0] = code[1] = 0;
37 111 extendedData = false;
38 111 }
39 153 VariableHeader(const VariableHeader& cp) :
40 153 data(cp.data) {
41 153 code[0] = cp.code[0];
42 153 code[1] = cp.code[1];
43 153 extendedData = cp.extendedData;
44 153 offset = cp.offset;
45 153 length = cp.length;
46 153 fseqFile = cp.fseqFile;
47 153 }
48 295 ~VariableHeader() {}
49
50 uint8_t code[2];
51 bool extendedData = false;
52
53 105 void resizeData(size_t sz) {
54 105 data.resize(sz);
55 105 length = 0;
56 105 offset = 0;
57 105 }
58 197 const uint32_t getDataLength() const {
59
1/2
✓ Branch 0 taken 197 times.
✗ Branch 1 not taken.
197 if (!data.empty()) {
60 197 return (uint32_t)data.size();
61 }
62 ✗ return length;
63 }
64 8 void setDataLocation(uint64_t off, uint32_t len) {
65 8 offset = off;
66 8 length = len;
67 8 }
68 29 const std::vector<uint8_t>& getData() const {
69 29 loadData();
70 29 return data;
71 }
72 161 std::vector<uint8_t>& getData() {
73 161 loadData();
74 161 return data;
75 }
76
77
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 101 times.
114 uint64_t getExtDataOffset() const { return extendedData ? offset : 0; }
78
79 private:
80 void loadData() const;
81 mutable std::vector<uint8_t> data;
82 uint64_t offset;
83 uint32_t length;
84 FSEQFile* fseqFile;
85 };
86
87 class FrameData {
88 public:
89 125137 FrameData(uint32_t f) :
90 125137 frame(f){};
91 250274 virtual ~FrameData(){};
92
93 virtual bool readFrame(uint8_t* data, uint32_t maxChannels) = 0;
94
95 uint32_t frame;
96 };
97
98 enum CompressionType {
99 none,
100 zstd,
101 zlib
102 };
103 constexpr static const char* CompressionTypeStrings[] = { "none", "zstd", "zlib" };
104
105 protected:
106 // open file for reading
107 FSEQFile(const std::string& fn, FILE* file, const std::vector<uint8_t>& header);
108 // open file for writing
109 FSEQFile(const std::string& fn);
110
111 public:
112 virtual ~FSEQFile();
113
114 static FSEQFile* openFSEQFile(const std::string& fn);
115
116 static FSEQFile* createFSEQFile(const std::string& fn,
117 int version,
118 CompressionType ct = CompressionType::zstd,
119 int level = -99);
120 // utility methods
121 static std::string getMediaFilename(const std::string& fn);
122 std::string getMediaFilename() const;
123 ✗ uint32_t getTotalTimeMS() const { return m_seqNumFrames * m_seqStepTime; }
124
125 void parseVariableHeaders(const std::vector<uint8_t>& header, int start);
126
127 // How the caller intends to read this file. This is about the access
128 // pattern, not the hardware: it says what the reader is allowed to do ahead
129 // of the frame being asked for.
130 enum class ReadPattern {
131 // Serve one frame at a time with the smallest possible latency and
132 // memory footprint - what playback needs, especially on a small single
133 // core device that cannot hold the sequence. The default.
134 Streaming,
135 // Every frame will be read in order, front to back, as fast as
136 // possible, and the caller has already allocated room for what it
137 // needs. The reader may then decompress whole blocks ahead of the
138 // requested frame, in parallel, at the cost of a bounded amount of
139 // extra memory.
140 Bulk
141 };
142 // Must be set before prepareRead()/getFrame() to have any effect.
143 38 virtual void setReadPattern(ReadPattern p) { m_readPattern = p; }
144 27 ReadPattern getReadPattern() const { return m_readPattern; }
145
146 // prepare to start reading. The ranges will be the list of channel ranges that
147 // are actually needed for each frame. The reader can optimize to only
148 // read those frames.
149 ✗ virtual void prepareRead(const std::vector<std::pair<uint32_t, uint32_t>>& ranges, uint32_t startFrame = 0) {}
150
151 // For reading data from the fseq file, returns an object can
152 // provide the necessary data in a timely fashion for the given frame
153 // It may not be used right away and will be deleted at some point in the future
154 virtual FrameData* getFrame(uint32_t frame) = 0;
155
156 // For writing to the fseq file
157 2 virtual void enableMinorVersionFeatures(uint8_t ver) {}
158 virtual void initializeFromFSEQ(const FSEQFile& fseq);
159 virtual void writeHeader() = 0;
160 virtual void addFrame(uint32_t frame,
161 const uint8_t* data) = 0;
162 virtual void finalize();
163
164 virtual void dumpInfo(bool indent = false);
165
166 124545 uint32_t getNumFrames() const { return m_seqNumFrames; }
167 2551 int getStepTime() const { return m_seqStepTime; }
168 533382 uint32_t getChannelCount() const { return m_seqChannelCount; }
169 65 int getVersionMajor() const { return m_seqVersionMajor; }
170 18 int getVersionMinor() const { return m_seqVersionMinor; }
171 18 uint64_t getUniqueId() const { return m_uniqueId; }
172 1048 const std::string& getFilename() const { return m_filename; }
173
174 virtual uint32_t getMaxChannel() const = 0;
175 1722 const std::vector<VariableHeader>& getVariableHeaders() const { return m_variableHeaders; }
176
177 void setNumFrames(uint32_t f) { m_seqNumFrames = f; }
178 void setStepTime(int st) { m_seqStepTime = st; }
179 void setChannelCount(int cc) { m_seqChannelCount = cc; }
180 void addVariableHeader(const VariableHeader& header) { m_variableHeaders.push_back(header); }
181
182 ✗ const std::vector<uint8_t>& getMemoryBuffer() const { return m_memoryBuffer; }
183 uint64_t getMemoryBufferPos() const { return m_memoryBufferPos; }
184
185 protected:
186 std::string m_filename;
187 uint64_t m_uniqueId;
188 uint32_t m_seqNumFrames;
189 uint32_t m_seqChannelCount;
190 int m_seqStepTime;
191 int m_seqVersionMajor;
192 int m_seqVersionMinor;
193 ReadPattern m_readPattern = ReadPattern::Streaming;
194
195 std::vector<VariableHeader> m_variableHeaders;
196
197 protected:
198 uint64_t m_seqFileSize;
199 uint64_t m_seqChanDataOffset;
200
201 int seek(uint64_t location, int origin);
202 uint64_t tell();
203 uint64_t write(const void* ptr, uint64_t size);
204 uint64_t read(void* ptr, uint64_t size);
205 void preload(uint64_t pos, uint64_t size);
206
207 private:
208 FILE* volatile m_seqFile;
209 std::vector<uint8_t> m_memoryBuffer;
210 uint64_t m_memoryBufferPos;
211 };
212
213 class V1FSEQFile : public FSEQFile {
214 public:
215 V1FSEQFile(const std::string& fn, FILE* file, const std::vector<uint8_t>& header);
216 V1FSEQFile(const std::string& fn);
217
218 virtual ~V1FSEQFile();
219
220 virtual void prepareRead(const std::vector<std::pair<uint32_t, uint32_t>>& ranges, uint32_t startFrame = 0) override;
221 virtual FrameData* getFrame(uint32_t frame) override;
222
223 virtual void writeHeader() override;
224 virtual void addFrame(uint32_t frame,
225 const uint8_t* data) override;
226 virtual void finalize() override;
227
228 virtual uint32_t getMaxChannel() const override;
229
230 // The ranges to read and the data size needed to read the ranges
231 std::vector<std::pair<uint32_t, uint32_t>> m_rangesToRead;
232 uint32_t m_dataBlockSize;
233 };
234
235 class V2Handler;
236
237 class V2FSEQFile : public FSEQFile {
238 public:
239 V2FSEQFile(const std::string& fn, FILE* file, const std::vector<uint8_t>& header);
240 V2FSEQFile(const std::string& fn, CompressionType ct, int cl);
241
242 virtual ~V2FSEQFile();
243
244 virtual void prepareRead(const std::vector<std::pair<uint32_t, uint32_t>>& ranges, uint32_t startFrame = 0) override;
245 virtual FrameData* getFrame(uint32_t frame) override;
246
247 virtual void writeHeader() override;
248 virtual void addFrame(uint32_t frame,
249 const uint8_t* data) override;
250 virtual void finalize() override;
251
252 virtual void dumpInfo(bool indent = false) override;
253
254 virtual uint32_t getMaxChannel() const override;
255
256 36 virtual void enableMinorVersionFeatures(uint8_t ver) override {
257 36 m_seqVersionMinor = ver;
258
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 9 times.
36 if (ver == 0) {
259 27 m_allowExtendedBlocks = false;
260 }
261
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 27 times.
36 if (ver >= 1) {
262 9 m_allowExtendedBlocks = true;
263 }
264 36 }
265
266 // The fixed header holds at most 255 sparse ranges. From minor version 3 a longer
267 // table also goes in an 'SR' extended data block; below that, the ranges are
268 // merged across their smallest gaps until they fit.
269 static void fitSparseRangesToHeader(std::vector<std::pair<uint32_t, uint32_t>>& ranges);
270
271 17 [[nodiscard]] std::string CompressionTypeString() const {
272
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
34 return CompressionTypeStrings[(int)m_compressionType];
273 }
274
275 CompressionType m_compressionType;
276 int m_compressionLevel;
277 std::vector<std::pair<uint32_t, uint32_t>> m_sparseRanges;
278 std::vector<std::pair<uint32_t, uint32_t>> m_rangesToRead;
279 std::vector<std::pair<uint32_t, uint64_t>> m_frameOffsets;
280 uint32_t m_dataBlockSize;
281 bool m_allowExtendedBlocks;
282
283 private:
284 void createHandler();
285
286 V2Handler* m_handler;
287 friend class V2Handler;
288 };
289