FastLED 3.9.15
Loading...
Searching...
No Matches
software_decoder.cpp.hpp
Go to the documentation of this file.
1#include "software_decoder.h"
2#include "fl/stl/cstddef.h"
3#include "fl/stl/noexcept.h"
4#include "fl/stl/utility.h"
5#include "fl/stl/string.h"
7#include "fl/stl/cstring.h" // for fl::memset() and fl::memcpy()
8
9// Include stdio for FILE type needed by pl_mpeg
10#include "fl/stl/stdio.h"
11
12
13// Declare pl_mpeg types and function prototypes manually
14// Forward declarations and types from driver.h
16
17
18
19
20namespace fl {
21namespace third_party {
22
23// Helper function to convert YUV to RGB
24static void yuv_to_rgb(const fl::third_party::plm_frame_t* frame, fl::u8* rgb_buffer) FL_NOEXCEPT {
25 fl::u32 width = frame->width;
26 fl::u32 height = frame->height;
27
28 // YUV to RGB conversion coefficients (ITU-R BT.601 standard, scaled by 1000)
29 const fl::i32 YUV_TO_RGB_MATRIX[9] = {
30 1164, 0, 1596, // Y, U, V coefficients for R
31 1164, -391, -813, // Y, U, V coefficients for G
32 1164, 2017, 0 // Y, U, V coefficients for B (fixed: 2018 -> 2017)
33 };
34
35 for (fl::u32 y = 0; y < height; y++) {
36 for (fl::u32 x = 0; x < width; x++) {
37 // Get Y, U, V values
38 fl::u32 y_index = y * frame->y.width + x;
39 fl::u32 uv_x = x / 2;
40 fl::u32 uv_y = y / 2;
41 fl::u32 uv_index = uv_y * frame->cr.width + uv_x;
42
43 fl::i32 Y = frame->y.data[y_index] - 16;
44 fl::i32 U = frame->cb.data[uv_index] - 128;
45 fl::i32 V = frame->cr.data[uv_index] - 128;
46
47 // Convert to RGB
48 fl::i32 R = (YUV_TO_RGB_MATRIX[0] * Y + YUV_TO_RGB_MATRIX[1] * U + YUV_TO_RGB_MATRIX[2] * V) / 1000;
49 fl::i32 G = (YUV_TO_RGB_MATRIX[3] * Y + YUV_TO_RGB_MATRIX[4] * U + YUV_TO_RGB_MATRIX[5] * V) / 1000;
50 fl::i32 B = (YUV_TO_RGB_MATRIX[6] * Y + YUV_TO_RGB_MATRIX[7] * U + YUV_TO_RGB_MATRIX[8] * V) / 1000;
51
52 // Clamp values
53 R = R < 0 ? 0 : (R > 255 ? 255 : R);
54 G = G < 0 ? 0 : (G > 255 ? 255 : G);
55 B = B < 0 ? 0 : (B > 255 ? 255 : B);
56
57 // Store RGB values
58 fl::u32 rgb_index = (y * width + x) * 3;
59 rgb_buffer[rgb_index + 0] = static_cast<fl::u8>(R);
60 rgb_buffer[rgb_index + 1] = static_cast<fl::u8>(G);
61 rgb_buffer[rgb_index + 2] = static_cast<fl::u8>(B);
62 }
63 }
64}
65
66// MPEG1 decoder internal data structure
68 // pl_mpeg decoder instance
70
71 // Video properties
72 fl::u16 width = 0;
73 fl::u16 height = 0;
74 fl::u16 frameRate = 0;
75
76 // Input buffer for pl_mpeg
78 fl::size inputSize = 0;
79 fl::size totalSize = 0;
80
81 // Current decoded frame data (RGB)
83 fl::size rgbFrameSize = 0;
84
85 // Decoder state
86 bool headerParsed = false;
87 bool initialized = false;
88 bool hasNewFrame = false;
89
90 // Frame timing
91 double lastFrameTime = 0.0;
92 double targetFrameDuration = 1.0/30.0; // Default 30fps
93};
94
95// Static callback wrapper for pl_mpeg
97 FL_UNUSED(plm_ptr);
98 auto* decoder = static_cast<SoftwareMpeg1Decoder*>(user);
99
100 if (decoder && decoder->decoderData_ && frame) {
101 decoder->decoderData_->hasNewFrame = true;
102 decoder->decoderData_->lastFrameTime = frame->time;
103
104 // Convert YUV to RGB and store in buffer
105 if (decoder->decoderData_->rgbFrameBuffer.get()) {
106 yuv_to_rgb(frame, decoder->decoderData_->rgbFrameBuffer.get());
107 }
108 }
109}
110
111// Static callback for audio decoding
113 FL_UNUSED(plm_ptr);
114 auto* decoder = static_cast<SoftwareMpeg1Decoder*>(user);
115
116 if (!decoder || !samples || !decoder->config_.audioCallback) {
117 return;
118 }
119
120 // Convert float samples to i16 PCM
121 // pl_mpeg provides interleaved stereo samples as floats in range [-1.0, 1.0]
123 pcm.reserve(samples->count * 2); // stereo
124
125 for (unsigned i = 0; i < samples->count * 2; i++) {
126 float sample = samples->interleaved[i];
127 // Clamp and convert to i16 range
128 sample = sample < -1.0f ? -1.0f : (sample > 1.0f ? 1.0f : sample);
129 pcm.push_back(static_cast<fl::i16>(sample * 32767.0f));
130 }
131
132 // Create audio::Sample with timestamp in milliseconds
133 fl::u32 timestampMs = static_cast<fl::u32>(samples->time * 1000.0);
134 audio::Sample audioSample(fl::span<const fl::i16>(pcm.data(), pcm.size()), timestampMs);
135
136 // Call the user's audio callback
137 decoder->config_.audioCallback(audioSample);
138}
139
141 : config_(config)
143 // Set target frame duration based on config
144 if (config_.targetFps > 0) {
145 decoderData_->targetFrameDuration = 1.0 / config_.targetFps;
146 }
147}
148
153
155 if (!stream) {
156 setError("Invalid filebuf provided");
157 return false;
158 }
159
160 stream_ = stream;
161 hasError_ = false;
162 errorMessage_.clear();
163 endOfStream_ = false;
165
166 if (!initializeDecoder()) {
167 return false;
168 }
169
170 ready_ = true;
171 return true;
172}
173
176 ready_ = false;
177 stream_.reset();
178}
179
181 if (msg && hasError_) {
182 *msg = errorMessage_;
183 }
184 return hasError_;
185}
186
188 if (!ready_) {
189 return DecodeResult::Error;
190 }
191
192 if (hasError_) {
193 return DecodeResult::Error;
194 }
195
196 if (endOfStream_) {
198 }
199
200 if (!decodeNextFrame()) {
201 if (!hasError_) {
202 endOfStream_ = true;
204 }
205 return DecodeResult::Error;
206 }
207
209}
210
212 if (config_.mode == Mpeg1Config::Streaming && !config_.immediateMode && !frameBuffer_.empty() && currentFrameIndex_ > 0) {
214 return result;
215 }
216 if (currentFrame_) {
218 return result;
219 }
220 // Return an invalid frame if no frame has been decoded yet
221 return Frame(0);
222}
223
227
229 // For streaming mode, we don't know total frames in advance
230 return 0;
231}
232
233bool SoftwareMpeg1Decoder::seek(fl::u32 frameIndex) FL_NOEXCEPT {
234 (void)frameIndex; // Suppress unused parameter warning
235 // Seeking not supported in this simplified implementation
236 return false;
237}
238
240 return decoderData_->width;
241}
242
244 return decoderData_->height;
245}
246
248 return decoderData_->frameRate;
249}
250
252 if (!stream_) {
253 setError("No input stream available");
254 return false;
255 }
256
257 // Read the entire stream into memory for pl_mpeg
258 // First, estimate the size by reading chunks
259 const fl::size CHUNK_SIZE = 8192;
260 fl::vector<fl::u8> tempBuffer;
261
262 fl::u8 chunk[CHUNK_SIZE];
263 fl::size bytesRead;
264 do {
265 bytesRead = stream_->read(chunk, CHUNK_SIZE);
266 if (bytesRead > 0) {
267 for (fl::size i = 0; i < bytesRead; ++i) {
268 tempBuffer.push_back(chunk[i]);
269 }
270 }
271 } while (bytesRead == CHUNK_SIZE);
272
273 if (tempBuffer.empty()) {
274 setError("Empty input stream - no data available");
275 return false;
276 }
277
278 // Copy to our buffer
279 decoderData_->totalSize = tempBuffer.size();
280 decoderData_->inputBuffer.reset(new fl::u8[decoderData_->totalSize]);
281 fl::memcpy(decoderData_->inputBuffer.get(), tempBuffer.data(), decoderData_->totalSize);
282
283 // Create pl_mpeg instance with memory buffer
285 decoderData_->inputBuffer.get(),
286 decoderData_->totalSize,
287 0 // Don't free when done - we manage the memory
288 );
289
290 if (!decoderData_->plmpeg) {
291 setError("Failed to create pl_mpeg decoder instance");
292 return false;
293 }
294
295 // Enable/disable audio decoding based on config
296 if (config_.skipAudio || !config_.audioCallback) {
298 } else {
302 }
303
304 // Set looping if requested
305 fl::third_party::plm_set_loop(decoderData_->plmpeg, config_.looping ? 1 : 0);
306
307 // Set up video decode callback BEFORE any decoding to ensure callbacks work
310
311 // Try to get headers - for multiplexed streams with audio, this may require decoding
312 // some frames before audio headers are found
313 // Note: Video callback must be set first so we can capture frame dimensions
315 // Temporarily allocate a minimal buffer for header decoding
316 // We'll reallocate properly once we know dimensions
317 fl::size temp_buffer_size = static_cast<fl::size>(1920ul * 1080ul * 3ul); // Max reasonable size for header decode
318 decoderData_->rgbFrameBuffer.reset(new fl::u8[temp_buffer_size]);
319
320 // Decode one frame to get headers
321 fl::third_party::plm_decode(decoderData_->plmpeg, decoderData_->targetFrameDuration);
322 }
323
324 // Check if we at least have video - video headers are mandatory
327 setError("Failed to parse MPEG1 headers");
328 return false;
329 }
330
331 // Audio headers might not be present yet if audio packets come later in the stream
332 // This is OK - audio will work once we encounter audio packets during decode
333
334 // Get video properties
335 decoderData_->width = static_cast<fl::u16>(fl::third_party::plm_get_width(decoderData_->plmpeg));
336 decoderData_->height = static_cast<fl::u16>(fl::third_party::plm_get_height(decoderData_->plmpeg));
337 decoderData_->frameRate = static_cast<fl::u16>(fl::third_party::plm_get_framerate(decoderData_->plmpeg));
338
339 if (decoderData_->width == 0 || decoderData_->height == 0) {
340 setError("Invalid video dimensions from MPEG1 stream");
341 return false;
342 }
343
344 // Now allocate properly sized buffers based on actual video dimensions
346 decoderData_->initialized = true;
347 decoderData_->headerParsed = true;
348 return true;
349}
350
352 // This is now handled by pl_mpeg in initializeDecoder()
353 return decoderData_->headerParsed;
354}
355
357 if (!decoderData_->headerParsed || !decoderData_->plmpeg) {
358 return false;
359 }
360
361 // Reset the new frame flag
362 decoderData_->hasNewFrame = false;
363
364 // Decode using pl_mpeg
365 fl::third_party::plm_decode(decoderData_->plmpeg, decoderData_->targetFrameDuration);
366
367 // Check if we have reached the end
369 return false;
370 }
371
372 // If we have a new frame, create the Frame objects
373 if (decoderData_->hasNewFrame) {
374 return decodeFrame();
375 }
376
377 return false;
378}
379
381 // This is now handled by pl_mpeg internally
382 return true;
383}
384
386 // The actual frame decoding is handled by pl_mpeg via the callback
387 // This method just updates our Frame objects with the decoded data
388
389 if (!decoderData_->hasNewFrame || !decoderData_->rgbFrameBuffer.get()) {
390 return false;
391 }
392
393 // Calculate timestamp in milliseconds
394 fl::u32 timestampMs = static_cast<fl::u32>(decoderData_->lastFrameTime * 1000.0);
395
396 // Update current frame
397 if (config_.mode == Mpeg1Config::Streaming && !config_.immediateMode && !frameBuffer_.empty()) {
398 fl::u8 bufferIndex = currentFrameIndex_ % config_.bufferFrames;
399 // Create a new Frame using shared_ptr
400 frameBuffer_[bufferIndex] = fl::make_shared<Frame>(decoderData_->rgbFrameBuffer.get(),
401 decoderData_->width,
402 decoderData_->height,
404 timestampMs);
405 lastDecodedIndex_ = bufferIndex;
406 } else {
407 // Create a new frame as shared_ptr (for SingleFrame mode or immediate mode)
408 currentFrame_ = fl::make_shared<Frame>(decoderData_->rgbFrameBuffer.get(),
409 decoderData_->width,
410 decoderData_->height,
412 timestampMs);
413 }
414
416 return true;
417}
418
420 fl::size frameSize = decoderData_->width * decoderData_->height * 3; // RGB888
421 decoderData_->rgbFrameSize = frameSize;
422
423 // Allocate RGB frame buffer for converted frames
424 decoderData_->rgbFrameBuffer.reset(new fl::u8[frameSize]);
425
426 if (config_.mode == Mpeg1Config::Streaming && !config_.immediateMode) {
427 frameBuffer_.resize(config_.bufferFrames);
428 for (fl::u8 i = 0; i < config_.bufferFrames; ++i) {
429 // Create Frame objects with shared_ptr (initially empty)
431 }
432 }
433}
434
436 if (decoderData_) {
437 // Destroy pl_mpeg instance
438 if (decoderData_->plmpeg) {
440 decoderData_->plmpeg = nullptr;
441 }
442
443 decoderData_->initialized = false;
444 decoderData_->headerParsed = false;
445 decoderData_->hasNewFrame = false;
446 decoderData_->inputBuffer.reset();
447 decoderData_->rgbFrameBuffer.reset();
448 }
449
450 // Clean up frame buffers
451 frameBuffer_.clear();
452}
453
455 hasError_ = true;
456 errorMessage_ = message;
457 ready_ = false;
458}
459
460// IDecoder audio interface implementations
462 if (!decoderData_ || !decoderData_->plmpeg) {
463 return false;
464 }
466}
467
469 config_.audioCallback = callback;
470
471 // If decoder is already initialized, update the callback
472 if (decoderData_ && decoderData_->plmpeg) {
473 if (callback && !config_.skipAudio) {
477 } else {
479 }
480 }
481}
482
484 if (!decoderData_ || !decoderData_->plmpeg) {
485 return 0;
486 }
488}
489
490} // namespace third_party
491} // namespace fl
Frame getCurrentFrame() FL_NOEXCEPT override
bool begin(fl::filebuf_ptr stream) FL_NOEXCEPT override
int getAudioSampleRate() const FL_NOEXCEPT override
void setError(const fl::string &message) FL_NOEXCEPT
bool hasAudio() const FL_NOEXCEPT override
bool hasMoreFrames() const FL_NOEXCEPT override
SoftwareMpeg1Decoder(const Mpeg1Config &config) FL_NOEXCEPT
static void audioDecodeCallback(fl::third_party::plm_t *plm, fl::third_party::plm_samples_t *samples, void *user) FL_NOEXCEPT
void setAudioCallback(AudioFrameCallback callback) FL_NOEXCEPT override
fl::u32 getFrameCount() const FL_NOEXCEPT override
static void videoDecodeCallback(fl::third_party::plm_t *plm, fl::third_party::plm_frame_t *frame, void *user) FL_NOEXCEPT
bool hasError(fl::string *msg=nullptr) const FL_NOEXCEPT override
bool seek(fl::u32 frameIndex) FL_NOEXCEPT override
fl::vector< fl::shared_ptr< Frame > > frameBuffer_
DecodeResult decode() FL_NOEXCEPT override
fl::size size() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
T * data() FL_NOEXCEPT
Definition vector.h:619
void reserve(fl::size n) FL_NOEXCEPT
Definition vector.h:591
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
unsigned char u8
Definition coder.h:132
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, void *user) FL_NOEXCEPT
Definition pl_mpeg.hpp:464
void plm_decode(plm_t *self, double seconds) FL_NOEXCEPT
Definition pl_mpeg.hpp:474
int plm_get_samplerate(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:416
int plm_get_width(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:388
int plm_get_height(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:394
plm_t * plm_create_with_memory(uint8_t *bytes, size_t length, int free_when_done) FL_NOEXCEPT
Definition pl_mpeg.hpp:244
void plm_set_audio_enabled(plm_t *self, int enabled) FL_NOEXCEPT
Definition pl_mpeg.hpp:344
int plm_get_num_audio_streams(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:412
void plm_set_loop(plm_t *self, int loop) FL_NOEXCEPT
Definition pl_mpeg.hpp:456
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, void *user) FL_NOEXCEPT
Definition pl_mpeg.hpp:469
double plm_get_framerate(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:400
void plm_destroy(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:296
int plm_has_headers(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:312
static void yuv_to_rgb(const fl::third_party::plm_frame_t *frame, fl::u8 *rgb_buffer) FL_NOEXCEPT
int plm_has_ended(plm_t *self) FL_NOEXCEPT
Definition pl_mpeg.hpp:460
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
FL_DISABLE_WARNING_PUSH unsigned char * B
u8 u8 height
Definition blur.h:186
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
Definition sample.cpp.hpp:9
u8 width
Definition blur.h:186
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
fl::shared_ptr< filebuf > filebuf_ptr
Definition idecoder.h:15
DecodeResult
Definition idecoder.h:18
fl::function< void(const audio::Sample &)> AudioFrameCallback
Definition idecoder.h:28
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_UNUSED(x)
#define FL_NOEXCEPT