FastLED 3.9.15
Loading...
Searching...
No Matches
pixel_stream.cpp.hpp
Go to the documentation of this file.
1
3#include "fl/log/log.h"
4#include "fl/stl/limits.h"
5#include "fl/stl/noexcept.h"
6
7#define DBG FL_DBG
8
9namespace fl {
10namespace video {
11
12// FLED v1 header layout (12 bytes). See
13// https://github.com/zackees/ledmapper/blob/main/docs/fled-format.md
14namespace {
15constexpr fl::u8 kFledMagic[4] = {'F', 'L', 'E', 'D'};
16constexpr fl::u8 kFledVersionV1 = 1;
19// Defensive cap on the embedded JSON to bound the worst-case heap
20// allocation if a malformed file claims a giant json_length. Real
21// screenmaps run ~500 B – ~50 KB; 1 MiB is well above that.
22constexpr fl::size_t kFledMaxJsonBytes = 1u * 1024u * 1024u;
23} // namespace
24
25PixelStream::PixelStream(int bytes_per_frame)
26 : mbytesPerFrame(bytes_per_frame), mType(kFile) {}
27
29
31 close();
32 mHandle = h;
35 // Probe seekability: if seek-to-start succeeds, this is a seekable file.
37 if (mType == kFile) {
38 // Try to detect a FLED v1 container. Anything that fails the header
39 // check (wrong magic, wrong version, unsupported pixel_format,
40 // truncated) falls back to legacy headerless RGB — zero behavior
41 // change for existing files.
42 if (mHandle->size() >= kFledHeaderBytes) {
43 char hdr[kFledHeaderBytes];
44 fl::size_t got = mHandle->read(hdr, kFledHeaderBytes);
45 bool isFled = got == kFledHeaderBytes
46 && static_cast<fl::u8>(hdr[0]) == kFledMagic[0]
47 && static_cast<fl::u8>(hdr[1]) == kFledMagic[1]
48 && static_cast<fl::u8>(hdr[2]) == kFledMagic[2]
49 && static_cast<fl::u8>(hdr[3]) == kFledMagic[3]
50 && static_cast<fl::u8>(hdr[4]) == kFledVersionV1
51 && static_cast<fl::u8>(hdr[5]) == kFledPixelFormatRgb8;
52 if (isFled) {
53 const fl::u32 jsonLen =
54 static_cast<fl::u32>(static_cast<fl::u8>(hdr[8]))
55 | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[9])) << 8)
56 | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[10])) << 16)
57 | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[11])) << 24);
58 // Cap the JSON length against a defensive maximum BEFORE
59 // doing any size arithmetic — guards against a malformed
60 // file declaring a multi-gigabyte json_length that would
61 // either overflow the offset calc or trigger a huge resize.
62 const fl::size_t jsonLenSz = static_cast<fl::size_t>(jsonLen);
63 const fl::size_t fileSize = mHandle->size();
64 const bool jsonInRange = jsonLenSz <= kFledMaxJsonBytes
65 && jsonLenSz <= fileSize - kFledHeaderBytes;
66 if (jsonInRange) {
67 mEmbeddedScreenMapJson.resize(static_cast<fl::size>(jsonLenSz));
68 fl::size_t jr = jsonLenSz > 0
69 ? mHandle->read(&mEmbeddedScreenMapJson[0], jsonLenSz)
70 : 0;
71 if (jr == jsonLenSz) {
72 mPayloadOffset = kFledHeaderBytes + jsonLenSz;
73 // Stream is now positioned at the first frame byte.
74 return mHandle->available();
75 }
76 // JSON slurp short-read — abandon FLED interpretation.
78 }
79 }
80 // Not a FLED file (or header rejected). Rewind so subsequent
81 // reads see the file from byte 0 as raw RGB triplets.
82 mHandle->seek(0, seek_dir::beg);
83 }
84 return mHandle->available();
85 }
86 return mHandle->available(mbytesPerFrame);
87}
88
90 mHandle.reset();
91}
92
94
96 return mHandle->read(&dst->r, 1) && mHandle->read(&dst->g, 1) &&
97 mHandle->read(&dst->b, 1);
98}
99
101 if (mType == kStreaming) {
102 return mHandle->available(mbytesPerFrame);
103 }
104 return mHandle->available();
105}
106
107bool PixelStream::atEnd() const {
108 if (mType == kStreaming) {
109 return false;
110 }
111 return !mHandle->available();
112}
113
115 if (!frame) {
116 return false;
117 }
118 if (mType == kFile && !framesRemaining()) {
119 return false;
120 }
121 size_t n = mHandle->readRGB8(frame->rgb());
122 if (mType == kFile) {
123 DBG("pos: " << mHandle->pos());
124 }
125 return n * 3 == size_t(mbytesPerFrame);
126}
127
128bool PixelStream::hasFrame(fl::u32 frameNumber) {
129 if (mType == kStreaming) {
130 // Streaming handle doesn't support seeking
131 DBG("Not implemented and therefore always returns true");
132 return true;
133 }
134 // Use size_t throughout so frameNumber * bytesPerFrame doesn't overflow
135 // u32 for high-LED-count grids past ~1M frames.
136 fl::size_t total_bytes = mHandle->size();
137 fl::size_t frameBytes = static_cast<fl::size_t>(frameNumber)
138 * static_cast<fl::size_t>(mbytesPerFrame);
139 fl::size_t target = mPayloadOffset + frameBytes;
140 return target < total_bytes;
141}
142
143bool PixelStream::readFrameAt(fl::u32 frameNumber, Frame *frame) {
144 if (mType == kStreaming) {
145 // Streaming handle doesn't support seeking
146 FL_DBG("Streaming handle doesn't support seeking");
147 return false;
148 }
149 fl::size_t frameBytes = static_cast<fl::size_t>(frameNumber)
150 * static_cast<fl::size_t>(mbytesPerFrame);
151 mHandle->seek(mPayloadOffset + frameBytes);
152 if (mHandle->bytesLeft() == 0) {
153 return false;
154 }
155 size_t read =
156 mHandle->readRGB8(frame->rgb()) * 3;
157
158 bool ok = int(read) == mbytesPerFrame;
159 if (!ok) {
160 DBG("readFrameAt failed - read: "
161 << read << ", mbytesPerFrame: " << mbytesPerFrame << ", frame:"
162 << frameNumber << ", left: " << mHandle->bytesLeft());
163 }
164 return ok;
165}
166
168 if (mbytesPerFrame == 0)
169 return 0;
170 i32 bytes_left = bytesRemaining();
171 if (bytes_left <= 0) {
172 return 0;
173 }
174 return bytes_left / mbytesPerFrame;
175}
176
178 if (mType == kStreaming) {
179 return -1;
180 }
181 fl::size_t pos = mHandle->pos();
182 if (pos < mPayloadOffset) return 0;
183 return static_cast<i32>((pos - mPayloadOffset) / mbytesPerFrame);
184}
185
187 if (mType == kStreaming) {
188 // Use (max)() to prevent macro expansion by Arduino.h's max macro
190 }
191 return mHandle->bytesLeft();
192}
193
197
199 if (mType == kStreaming) {
200 return false;
201 }
202 // Rewind to the start of the payload, not the start of the file —
203 // skips the FLED header on container-formatted streams.
204 mHandle->seek(mPayloadOffset);
205 return true;
206}
207
211
215
219
220size_t PixelStream::readBytes(u8 *dst, size_t len) {
221 u16 bytesRead = 0;
222 if (mType == kStreaming) {
223 while (bytesRead < len && mHandle->available(len)) {
224 if (mHandle->read(dst + bytesRead, 1)) {
225 bytesRead++;
226 } else {
227 break;
228 }
229 }
230 } else {
231 while (bytesRead < len && mHandle->available()) {
232 if (mHandle->read(dst + bytesRead, 1)) {
233 bytesRead++;
234 } else {
235 break;
236 }
237 }
238 }
239 return bytesRead;
240}
241
242} // namespace video
243} // namespace fl
uint8_t pos
Definition Blur.ino:11
fl::Video video(NUM_LEDS, 2.0f)
fl::span< CRGB > rgb()
Definition frame.h:42
PixelStream(int bytes_per_frame)
virtual ~PixelStream() FL_NOEXCEPT
fl::filebuf_ptr mHandle
bool begin(fl::filebuf_ptr h)
bool hasEmbeddedScreenMap() const FL_NOEXCEPT
bool hasFrame(fl::u32 frameNumber)
const fl::string & embeddedScreenMapJson() const FL_NOEXCEPT
bool readFrameAt(fl::u32 frameNumber, Frame *frame)
size_t readBytes(u8 *dst, size_t len)
bool readFrame(Frame *frame)
fl::string mEmbeddedScreenMapJson
#define FL_DBG
Definition log.h:388
Centralized logging categories for FastLED hardware interfaces and subsystems.
unsigned char u8
Definition s16x16x4.h:132
__SIZE_TYPE__ size_t
Definition s16x16x4.h:16
unsigned char u8
Definition stdint.h:131
int read()
fl::size size_t
Definition s16x16x4.h:223
fl::shared_ptr< filebuf > filebuf_ptr
Definition idecoder.h:15
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
static constexpr T max() FL_NOEXCEPT
Definition limits.h:108
#define DBG
Definition time.cpp.hpp:7