Skip to content

Commit 6ec092a

Browse files
add ImgViewerMjpeg_ESP32P4.ino example
1 parent 8c4c4a7 commit 6ec092a

File tree

3 files changed

+375
-0
lines changed

3 files changed

+375
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*******************************************************************************
2+
* ESP32 SIMD Motion JPEG Image Viewer
3+
* This is a simple Motion JPEG image viewer example using ESP32 SIMD
4+
* Image Source: https://www.pexels.com/video/earth-rotating-video-856356/
5+
* cropped: x: 598 y: 178 width: 720 height: 720 resized: 240x240
6+
* ffmpeg -i "Pexels Videos 3931.mp4" -ss 0 -t 20.4s -vf "reverse,setpts=0.5*PTS,fps=10,vflip,hflip,rotate=90,crop=720:720:178:598,scale=240:240:flags=lanczos" -q:v 11 earth.mjpeg
7+
*
8+
* Dependent libraries:
9+
* ESP32_JPEG: https://github.com/esp-arduino-libs/ESP32_JPEG.git
10+
*
11+
* Setup steps:
12+
* 1. Change your LCD parameters in Arduino_GFX setting
13+
* 2. Upload Motion JPEG file
14+
* FFat/LittleFS:
15+
* upload FFat (FatFS) data with ESP32 Sketch Data Upload:
16+
* ESP32: https://github.com/lorol/arduino-esp32fs-plugin
17+
* SD:
18+
* Most Arduino system built-in support SD file system.
19+
******************************************************************************/
20+
#define ROOT "/root"
21+
#define MJPEG_FILENAME ROOT "/earth.mjpeg"
22+
#define MJPEG_OUTPUT_SIZE (240 * 240 * 2) // memory for a output image frame
23+
#define MJPEG_BUFFER_SIZE (MJPEG_OUTPUT_SIZE / 10) // memory for a single JPEG frame
24+
25+
/*******************************************************************************
26+
* Start of Arduino_GFX setting
27+
******************************************************************************/
28+
#include <Arduino_GFX_Library.h>
29+
30+
#define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin
31+
32+
/* More dev device declaration: https://github.com/moononournation/Arduino_GFX/wiki/Dev-Device-Declaration */
33+
#if defined(DISPLAY_DEV_KIT)
34+
Arduino_GFX *gfx = create_default_Arduino_GFX();
35+
#else /* !defined(DISPLAY_DEV_KIT) */
36+
37+
/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
38+
Arduino_DataBus *bus = create_default_Arduino_DataBus();
39+
40+
/* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */
41+
Arduino_GFX *gfx = new Arduino_ILI9341(bus, DF_GFX_RST, 3 /* rotation */, false /* IPS */);
42+
43+
#endif /* !defined(DISPLAY_DEV_KIT) */
44+
/*******************************************************************************
45+
* End of Arduino_GFX setting
46+
******************************************************************************/
47+
48+
#include <FFat.h>
49+
#include <LittleFS.h>
50+
#include <SPIFFS.h>
51+
#include <SD.h>
52+
#include <SD_MMC.h>
53+
54+
#include "MjpegClass.h"
55+
static MjpegClass mjpeg;
56+
57+
/* variables */
58+
static int total_frames = 0;
59+
static unsigned long total_read_video = 0;
60+
static unsigned long total_decode_video = 0;
61+
static unsigned long total_show_video = 0;
62+
static unsigned long start_ms, curr_ms;
63+
static int16_t x = -1, y = -1, w = -1, h = -1;
64+
65+
void setup()
66+
{
67+
#ifdef DEV_DEVICE_INIT
68+
DEV_DEVICE_INIT();
69+
#endif
70+
71+
Serial.begin(115200);
72+
// Serial.setDebugOutput(true);
73+
// while(!Serial);
74+
Serial.println("Arduino_GFX Motion JPEG SIMD Decoder Image Viewer example");
75+
76+
// Init Display
77+
if (!gfx->begin())
78+
{
79+
Serial.println("gfx->begin() failed!");
80+
}
81+
gfx->fillScreen(RGB565_BLACK);
82+
83+
#ifdef GFX_BL
84+
pinMode(GFX_BL, OUTPUT);
85+
digitalWrite(GFX_BL, HIGH);
86+
#endif
87+
88+
// if (!FFat.begin(false, ROOT))
89+
if (!LittleFS.begin(false, ROOT))
90+
// if (!SPIFFS.begin(false, ROOT))
91+
// SPI.begin(12 /* CLK */, 13 /* D0/MISO */, 11 /* CMD/MOSI */);
92+
// if (!SD.begin(10 /* CS */, SPI, 80000000L, ROOT))
93+
// pinMode(10 /* CS */, OUTPUT);
94+
// digitalWrite(SD_CS, HIGH);
95+
// SD_MMC.setPins(12 /* CLK */, 11 /* CMD/MOSI */, 13 /* D0/MISO */);
96+
// if (!SD_MMC.begin(ROOT, true /* mode1bit */, false /* format_if_mount_failed */, SDMMC_FREQ_DEFAULT))
97+
// SD_MMC.setPins(12 /* CLK */, 11 /* CMD/MOSI */, 13 /* D0/MISO */, 14 /* D1 */, 15 /* D2 */, 10 /* D3/CS */);
98+
// if (!SD_MMC.begin(ROOT, false /* mode1bit */, false /* format_if_mount_failed */, SDMMC_FREQ_HIGHSPEED))
99+
{
100+
Serial.println(F("ERROR: File System Mount Failed!"));
101+
gfx->println(F("ERROR: File System Mount Failed!"));
102+
}
103+
else
104+
{
105+
Serial.println(F("MJPEG start"));
106+
107+
start_ms = millis();
108+
curr_ms = millis();
109+
if (!mjpeg.setup(MJPEG_FILENAME))
110+
{
111+
Serial.println(F("mjpeg.setup() failed!"));
112+
}
113+
else
114+
{
115+
while (mjpeg.readMjpegBuf())
116+
{
117+
// Read video
118+
total_read_video += millis() - curr_ms;
119+
curr_ms = millis();
120+
121+
// Play video
122+
mjpeg.decodeJpg();
123+
total_decode_video += millis() - curr_ms;
124+
curr_ms = millis();
125+
126+
if (x == -1)
127+
{
128+
w = mjpeg.getWidth();
129+
h = mjpeg.getHeight();
130+
x = (w > gfx->width()) ? 0 : ((gfx->width() - w) / 2);
131+
y = (h > gfx->height()) ? 0 : ((gfx->height() - h) / 2);
132+
}
133+
gfx->draw16bitRGBBitmap(x, y, mjpeg.getOutBuf(), w, h);
134+
total_show_video += millis() - curr_ms;
135+
136+
curr_ms = millis();
137+
total_frames++;
138+
}
139+
int time_used = millis() - start_ms;
140+
Serial.println(F("MJPEG end"));
141+
142+
float fps = 1000.0 * total_frames / time_used;
143+
Serial.printf("Arduino_GFX ESP32 SIMD MJPEG decoder\n\n");
144+
Serial.printf("Frame size: %d x %d\n", mjpeg.getWidth(), mjpeg.getHeight());
145+
Serial.printf("Total frames: %d\n", total_frames);
146+
Serial.printf("Time used: %d ms\n", time_used);
147+
Serial.printf("Average FPS: %0.1f\n", fps);
148+
Serial.printf("Read MJPEG: %lu ms (%0.1f %%)\n", total_read_video, 100.0 * total_read_video / time_used);
149+
Serial.printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video, 100.0 * total_decode_video / time_used);
150+
Serial.printf("Show video: %lu ms (%0.1f %%)\n", total_show_video, 100.0 * total_show_video / time_used);
151+
152+
gfx->setCursor(0, 0);
153+
gfx->printf("Arduino_GFX ESP32 SIMD MJPEG decoder\n\n");
154+
gfx->printf("Frame size: %d x %d\n", mjpeg.getWidth(), mjpeg.getHeight());
155+
gfx->printf("Total frames: %d\n", total_frames);
156+
gfx->printf("Time used: %d ms\n", time_used);
157+
gfx->printf("Average FPS: %0.1f\n", fps);
158+
gfx->printf("Read MJPEG: %lu ms (%0.1f %%)\n", total_read_video, 100.0 * total_read_video / time_used);
159+
gfx->printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video, 100.0 * total_decode_video / time_used);
160+
gfx->printf("Show video: %lu ms (%0.1f %%)\n", total_show_video, 100.0 * total_show_video / time_used);
161+
162+
mjpeg.close();
163+
}
164+
}
165+
}
166+
167+
void loop()
168+
{
169+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*******************************************************************************
2+
* ESP32_JPEG Wrapper Class
3+
*
4+
* Dependent libraries:
5+
* ESP32_JPEG: https://github.com/esp-arduino-libs/ESP32_JPEG.git
6+
******************************************************************************/
7+
#pragma once
8+
9+
#if defined(ESP32)
10+
11+
#define READ_BUFFER_SIZE 1024
12+
13+
#include <driver/jpeg_decode.h>
14+
15+
class MjpegClass
16+
{
17+
public:
18+
bool setup(const char *path)
19+
{
20+
_input = fopen(path, "r");
21+
_inputindex = 0;
22+
23+
if (!_read_buf)
24+
{
25+
_read_buf = (uint8_t *)malloc(READ_BUFFER_SIZE);
26+
}
27+
28+
if (!_read_buf)
29+
{
30+
return false;
31+
}
32+
33+
jpeg_decode_engine_cfg_t decode_eng_cfg = {
34+
.intr_priority = 0,
35+
.timeout_ms = 40,
36+
};
37+
ESP_ERROR_CHECK(jpeg_new_decoder_engine(&decode_eng_cfg, &_decoder_engine));
38+
39+
jpeg_decode_cfg_t decode_cfg_rgb = {
40+
.output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
41+
.rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
42+
};
43+
44+
size_t tx_buffer_size;
45+
size_t rx_buffer_size;
46+
47+
jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
48+
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
49+
};
50+
51+
jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
52+
.buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
53+
};
54+
55+
uint32_t out_buf_size = MJPEG_OUTPUT_SIZE;
56+
uint32_t bit_stream_size = MJPEG_BUFFER_SIZE;
57+
_mjpeg_buf = (uint8_t *)jpeg_alloc_decoder_mem(bit_stream_size, &tx_mem_cfg, &tx_buffer_size);
58+
_output_buf = (uint16_t *)jpeg_alloc_decoder_mem(out_buf_size, &rx_mem_cfg, &rx_buffer_size);
59+
60+
return true;
61+
}
62+
63+
bool readMjpegBuf()
64+
{
65+
if (_inputindex == 0)
66+
{
67+
_buf_read = fread(_read_buf, 1, READ_BUFFER_SIZE, _input);
68+
_inputindex += _buf_read;
69+
}
70+
_mjpeg_buf_offset = 0;
71+
int i = 0;
72+
bool found_FFD8 = false;
73+
while ((_buf_read > 0) && (!found_FFD8))
74+
{
75+
i = 0;
76+
while ((i < _buf_read) && (!found_FFD8))
77+
{
78+
if ((_read_buf[i] == 0xFF) && (_read_buf[i + 1] == 0xD8)) // JPEG header
79+
{
80+
// Serial.printf("Found FFD8 at: %d.\n", i);
81+
found_FFD8 = true;
82+
}
83+
++i;
84+
}
85+
if (found_FFD8)
86+
{
87+
--i;
88+
}
89+
else
90+
{
91+
_buf_read = fread(_read_buf, 1, READ_BUFFER_SIZE, _input);
92+
}
93+
}
94+
uint8_t *_p = _read_buf + i;
95+
_buf_read -= i;
96+
bool found_FFD9 = false;
97+
if (_buf_read > 0)
98+
{
99+
i = 3;
100+
while ((_buf_read > 0) && (!found_FFD9))
101+
{
102+
if ((_mjpeg_buf_offset > 0) && (_mjpeg_buf[_mjpeg_buf_offset - 1] == 0xFF) && (_p[0] == 0xD9)) // JPEG trailer
103+
{
104+
// Serial.printf("Found FFD9 at: %d.\n", i);
105+
found_FFD9 = true;
106+
}
107+
else
108+
{
109+
while ((i < _buf_read) && (!found_FFD9))
110+
{
111+
if ((_p[i] == 0xFF) && (_p[i + 1] == 0xD9)) // JPEG trailer
112+
{
113+
found_FFD9 = true;
114+
++i;
115+
}
116+
++i;
117+
}
118+
}
119+
120+
// Serial.printf("i: %d\n", i);
121+
memcpy(_mjpeg_buf + _mjpeg_buf_offset, _p, i);
122+
_mjpeg_buf_offset += i;
123+
size_t o = _buf_read - i;
124+
if (o > 0)
125+
{
126+
// Serial.printf("o: %d\n", o);
127+
memcpy(_read_buf, _p + i, o);
128+
_buf_read = fread(_read_buf + o, 1, READ_BUFFER_SIZE - o, _input);
129+
_p = _read_buf;
130+
_inputindex += _buf_read;
131+
_buf_read += o;
132+
// Serial.printf("_buf_read: %d\n", _buf_read);
133+
}
134+
else
135+
{
136+
_buf_read = fread(_read_buf, 1, READ_BUFFER_SIZE, _input);
137+
_p = _read_buf;
138+
_inputindex += _buf_read;
139+
}
140+
i = 0;
141+
}
142+
if (found_FFD9)
143+
{
144+
return true;
145+
}
146+
}
147+
148+
return false;
149+
}
150+
151+
bool decodeJpg()
152+
{
153+
_remain = _mjpeg_buf_offset;
154+
155+
jpeg_decode_picture_info_t header_info;
156+
ESP_ERROR_CHECK(jpeg_decoder_get_info(_mjpeg_buf, _remain, &header_info));
157+
_w = header_info.width;
158+
_h = header_info.height;
159+
uint32_t out_size;
160+
jpeg_decode_cfg_t decode_cfg_rgb = {
161+
.output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
162+
.rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
163+
};
164+
ESP_ERROR_CHECK(jpeg_decoder_process(_decoder_engine, &decode_cfg_rgb, (const uint8_t *)_mjpeg_buf, _remain, (uint8_t *)_output_buf, MJPEG_OUTPUT_SIZE, &out_size));
165+
166+
return true;
167+
}
168+
169+
int16_t getWidth()
170+
{
171+
return _w;
172+
}
173+
174+
int16_t getHeight()
175+
{
176+
return _h;
177+
}
178+
179+
uint16_t *getOutBuf()
180+
{
181+
return _output_buf;
182+
}
183+
184+
void close()
185+
{
186+
fclose(_input);
187+
}
188+
189+
private:
190+
FILE *_input;
191+
uint8_t *_mjpeg_buf;
192+
uint16_t *_output_buf;
193+
194+
uint8_t *_read_buf;
195+
int32_t _mjpeg_buf_offset = 0;
196+
197+
jpeg_decoder_handle_t _decoder_engine;
198+
199+
int16_t _w = 0, _h = 0;
200+
201+
int32_t _inputindex = 0;
202+
int32_t _buf_read;
203+
int32_t _remain = 0;
204+
};
205+
206+
#endif // defined(ESP32)
Binary file not shown.

0 commit comments

Comments
 (0)