.plans/mpeg.md
This document outlines the design and implementation strategy for adding MPEG1 and JPEG decoder capabilities to FastLED through a new fl/codec.h header. The implementation will provide efficient video and image decoding for LED display applications, leveraging hardware acceleration where available while maintaining software fallback options for broad platform compatibility.
The fl/codec.h module will provide unified codec functionality for FastLED, enabling:
| Platform | JPEG Hardware | MPEG1 Hardware | Notes |
|---|---|---|---|
| ESP32 | ROM TJpgDec | None | ~5KB flash savings with ROM decoder |
| ESP32-S3 | ROM TJpgDec + PIE/SIMD | None | ~52ms for 320x180 JPEG @ 240MHz* |
| ESP32-C3 | ROM TJpgDec | None | RISC-V architecture |
| ESP32-C5 | ROM TJpgDec | None | RISC-V architecture |
| ESP32-C6 | ROM TJpgDec | None | RISC-V with vector extensions |
| ESP32-C61 | ROM TJpgDec | None | RISC-V architecture |
| ESP32-P4 | Full JPEG Codec + PIE | None | Up to 571 fps decoder, 142 fps encoder |
Key Findings:
| Feature | Support | Details |
|---|---|---|
| JPEG Hardware | None | Software only |
| MPEG1 Hardware | None | Software only |
| DSP Instructions | Yes | Accelerates signal processing |
| FPU | 64-bit double | Full hardware float/double support |
| Clock Speed | 600MHz | Dual-issue superscalar |
Key Findings:
| Platform | JPEG Hardware | MPEG1 Hardware | Notes |
|---|---|---|---|
| STM32F4 | None | None | Software only via LibJPEG |
| STM32F7 | Hardware JPEG | None | ~4ms for 640x480 encode* |
| STM32H7 | Hardware JPEG + DMA2D | None | 100fps @ 640x480 with YCbCr→RGB acceleration |
Key Findings:
Based on FastLED's audio_input cross-platform plugin pattern, the codec system will use:
namespace fl {
namespace codec {
// Configuration objects (similar to AudioConfig pattern)
struct JpegConfig {
enum Quality { Low, Medium, High };
enum OutputFormat { RGB565, RGB888, RGBA8888 };
Quality quality = Medium;
OutputFormat format = RGB888;
bool useHardwareAcceleration = true;
size_t maxWidth = 1920;
size_t maxHeight = 1080;
JpegConfig() = default;
JpegConfig(Quality q, OutputFormat fmt = RGB888) : quality(q), format(fmt) {}
};
struct Mpeg1Config {
enum FrameMode { SingleFrame, Streaming };
FrameMode mode = Streaming;
uint16_t targetFps = 30;
bool looping = false;
bool skipAudio = true;
size_t bufferFrames = 2;
Mpeg1Config() = default;
Mpeg1Config(FrameMode m, uint16_t fps = 30) : mode(m), targetFps(fps) {}
};
// Base decoder interface (similar to IAudioInput)
class IDecoder {
public:
virtual ~IDecoder() = default;
// Control methods
virtual bool begin(fl::ByteStream* stream) = 0;
virtual void end() = 0;
virtual bool isReady() const = 0;
virtual bool error(fl::string* msg = nullptr) = 0;
// Decoding methods
virtual DecodeResult decode() = 0;
virtual Frame getCurrentFrame() = 0;
virtual bool hasMoreFrames() const = 0;
};
// Factory interface (similar to IAudioInput::create pattern)
class ICodecFactory {
public:
virtual ~ICodecFactory() = default;
virtual fl::shared_ptr<IDecoder> createJpegDecoder(const JpegConfig& config, fl::string* error_message = nullptr) = 0;
virtual fl::shared_ptr<IDecoder> createMpeg1Decoder(const Mpeg1Config& config, fl::string* error_message = nullptr) = 0;
virtual bool supportsJpeg() const = 0;
virtual bool supportsMpeg1() const = 0;
};
// Static factory methods (similar to IAudioInput::create)
class Codec {
public:
static fl::shared_ptr<IDecoder> createJpegDecoder(const JpegConfig& config, fl::string* error_message = nullptr);
static fl::shared_ptr<IDecoder> createMpeg1Decoder(const Mpeg1Config& config, fl::string* error_message = nullptr);
static bool isJpegSupported();
static bool isMpeg1Supported();
};
} // namespace codec
} // namespace fl
Following the audio_input pattern, codec implementations will be organized as:
// In fl/codec.cpp - main dispatcher
#if FASTLED_USES_ESP32_CODEC
#include "platforms/esp/32/codec/codec_impl.hpp"
#elif FASTLED_USES_STM32_CODEC
#include "platforms/stm32/codec/codec_impl.hpp"
#elif FASTLED_USES_TEENSY_CODEC
#include "platforms/teensy/codec/codec_impl.hpp"
#else
#include "platforms/codec_null.hpp"
#endif
// Platform detection (similar to audio_input.cpp)
fl::shared_ptr<IDecoder> platform_create_jpeg_decoder(const JpegConfig& config, fl::string* error_message) FL_LINK_WEAK;
fl::shared_ptr<IDecoder> platform_create_mpeg1_decoder(const Mpeg1Config& config, fl::string* error_message) FL_LINK_WEAK;
// Weak default implementations
FL_LINK_WEAK
fl::shared_ptr<IDecoder> platform_create_jpeg_decoder(const JpegConfig& config, fl::string* error_message) {
if (error_message) {
*error_message = "JPEG decoding not supported on this platform.";
}
return fl::make_shared<fl::NullDecoder>();
}
// platforms/esp/32/codec/codec_impl.hpp
namespace fl {
fl::shared_ptr<IDecoder> esp32_create_jpeg_decoder(const JpegConfig& config, fl::string* error_message) {
#if CONFIG_IDF_TARGET_ESP32P4
// Use hardware JPEG codec
return fl::make_shared<ESP32P4JpegDecoder>(config);
#elif defined(ESP32) || defined(ESP32S3) || defined(ESP32C3)
// Use ROM TJpgDec decoder
return fl::make_shared<ESP32RomJpegDecoder>(config);
#else
if (error_message) {
*error_message = "JPEG not supported on this ESP32 variant";
}
return fl::make_shared<NullDecoder>();
#endif
}
fl::shared_ptr<IDecoder> esp32_create_mpeg1_decoder(const Mpeg1Config& config, fl::string* error_message) {
// All ESP32 variants use software MPEG1 decoder
return fl::make_shared<SoftwareMpeg1Decoder>(config);
}
} // namespace fl
Following the audio_input pattern, the codec API provides clean factory methods and configuration objects:
// Simple JPEG decoding with automatic platform selection
fl::codec::JpegConfig config(fl::codec::JpegConfig::Medium, fl::codec::JpegConfig::RGB888);
fl::string error_msg;
auto decoder = fl::codec::Codec::createJpegDecoder(config, &error_msg);
if (!decoder) {
FL_WARN("Failed to create JPEG decoder: %s", error_msg.c_str());
return;
}
fl::ByteStreamPtr stream = fl::ByteStream::openFile("image.jpg");
if (decoder->begin(stream)) {
while (decoder->hasMoreFrames()) {
if (decoder->decode() == fl::codec::DecodeResult::Success) {
fl::codec::Frame frame = decoder->getCurrentFrame();
// Copy frame data to LEDs
memcpy(leds, frame.pixels, frame.width * frame.height * 3);
FastLED.show();
}
}
decoder->end();
}
// MPEG1 video playback
fl::codec::Mpeg1Config videoConfig(fl::codec::Mpeg1Config::Streaming, 25);
videoConfig.looping = true;
videoConfig.skipAudio = true;
auto videoDecoder = fl::codec::Codec::createMpeg1Decoder(videoConfig, &error_msg);
if (videoDecoder) {
videoDecoder->begin(videoStream);
// Decode frames in loop...
}
// Check platform capabilities before configuration
if (fl::codec::Codec::isJpegSupported()) {
fl::codec::JpegConfig config;
// Platform will automatically choose best decoder:
// - ESP32-P4: Hardware JPEG codec
// - ESP32-S3: ROM TJpgDec with PIE optimizations
// - ESP32/ESP32-C3: ROM TJpgDec
// - STM32H7: Hardware JPEG + DMA2D
// - Others: Software fallback
config.useHardwareAcceleration = true;
auto decoder = fl::codec::Codec::createJpegDecoder(config);
}
// Configuration objects allow platform-specific tuning
fl::codec::Mpeg1Config videoConfig;
videoConfig.bufferFrames = 3; // More buffering on high-RAM platforms
videoConfig.targetFps = 30; // Adjusted based on CPU capability
// Error handling follows audio_input pattern
fl::string error_details;
auto decoder = fl::codec::Codec::createMpeg1Decoder(videoConfig, &error_details);
if (!decoder) {
FL_WARN("MPEG1 not supported: %s", error_details.c_str());
// Graceful degradation to static images
}
// Streaming mode for memory-constrained devices
fl::codec::JpegConfig lowMemConfig;
lowMemConfig.quality = fl::codec::JpegConfig::Low;
lowMemConfig.format = fl::codec::JpegConfig::RGB565; // Use less memory
lowMemConfig.maxWidth = 320; // Limit resolution
lowMemConfig.maxHeight = 240;
auto decoder = fl::codec::Codec::createJpegDecoder(lowMemConfig);
// Block-based processing (similar to audio readAll pattern)
while (decoder->hasMoreFrames()) {
if (decoder->decode() == fl::codec::DecodeResult::Success) {
fl::codec::Frame frame = decoder->getCurrentFrame();
// Process frame in smaller chunks if needed
processFrameBlocks(frame);
}
}
| Platform | Typical RAM | Recommended Strategy |
|---|---|---|
| AVR | 2-8KB | Not supported |
| ESP32 | 320KB+ | Full frame buffering |
| ESP32-C3 | 400KB | Streaming with small buffer |
| Teensy 4.x | 1MB | Multiple frame buffers |
| STM32F4 | 128KB | Block-based streaming |
| STM32H7 | 1MB | Hardware DMA transfers |
// Minimal memory mode - decode directly to LED buffer
class DirectDecoder {
static constexpr size_t BLOCK_SIZE = 64; // 8x8 pixels
uint8_t blockBuffer[BLOCK_SIZE * 3]; // RGB888
void decodeToLEDs(CRGB* leds, size_t count) {
// Decode blocks and directly write to LED array
}
};
// Frame buffer mode - for smooth playback
class BufferedDecoder {
fl::scoped_array<uint8_t> frameBuffer[2]; // Double buffering
size_t activeBuffer = 0;
void swapBuffers() {
activeBuffer = 1 - activeBuffer;
}
};
| Use Case | Resolution | Target FPS | Platform |
|---|---|---|---|
| LED Matrix Display | 64x64 | 30 | ESP32-S3 |
| LED Strip Animation | 300 pixels | 60 | Teensy 4.x |
| Video Wall | 128x128 | 25 | STM32H7 |
| Low-res Video | 160x120 | 15 | ESP32-C3 |
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32-S3 PIE instructions for YCbCr→RGB conversion
// Note: Requires 16-byte memory alignment
void ycbcr_to_rgb_pie(uint8_t* __attribute__((aligned(16))) ycbcr,
uint8_t* __attribute__((aligned(16))) rgb) {
// PIE/SIMD implementation using ee.* instructions
// Limited instruction set - no shift right logical,
// no add/multiply with widening
// Accelerates color conversion step of JPEG decoding
}
#endif
#ifdef STM32H7
class STM32H7JpegDecoder : public JpegDecoder {
JPEG_HandleTypeDef hjpeg;
DMA2D_HandleTypeDef hdma2d;
bool accelerate(const uint8_t* in, uint8_t* out, const DecodeParams& params) override {
HAL_JPEG_Decode(&hjpeg, in, inSize, out, outSize, timeout);
// Use DMA2D for YCbCr to RGB conversion
HAL_DMA2D_Start(&hdma2d, ...);
return true;
}
};
#endif
#if defined(TEENSY40) || defined(TEENSY41)
// Utilize ARM DSP instructions
void dct_accelerated(float* data) {
// Use arm_cfft_f32 from CMSIS-DSP
arm_cfft_f32(&arm_cfft_sR_f32_len64, data, 0, 1);
}
#endif
Current Status: The fl/codec.h module foundation has been successfully implemented with a solid architecture that follows FastLED design patterns. The core API structure is complete, and the framework for both JPEG and MPEG1 decoding is in place.
Progress Summary:
Key Achievements:
Next Phase Focus: Complete the actual decoder implementations, starting with ESP32 JPEG support and software MPEG1 decoding, followed by platform-specific hardware acceleration optimizations.