ESP-IDF 5.5 + LVGL 9 firmware that plays random public cameras from insecam.org on the T-Embed's 320x170 screen, live MJPEG where available, a new camera every 20 seconds. Knob steps through cameras, LED ring shows the countdown. Auto-detects the original vs CC1101 board, reuses Wi-Fi credentials from NVS and falls back to a captive-portal setup hotspot.
99 lines
3.2 KiB
C
99 lines
3.2 KiB
C
#include "jpegframe.h"
|
|
#include <string.h>
|
|
|
|
enum { SEEK_FF, SEEK_D8, MARK_FF, MARK, LEN_HI, LEN_LO, SEG, ENTROPY, ENTROPY_FF };
|
|
|
|
void jpegframe_init(jpegframe_t *f, uint8_t *out, size_t out_cap)
|
|
{
|
|
memset(f, 0, sizeof *f);
|
|
f->out = out;
|
|
f->out_cap = out_cap;
|
|
}
|
|
|
|
void jpegframe_reset(jpegframe_t *f)
|
|
{
|
|
f->state = SEEK_FF;
|
|
f->out_len = 0;
|
|
f->skipped = 0;
|
|
}
|
|
|
|
static inline bool put(jpegframe_t *f, uint8_t b)
|
|
{
|
|
if (f->out_len >= f->out_cap) return false;
|
|
f->out[f->out_len++] = b;
|
|
return true;
|
|
}
|
|
|
|
size_t jpegframe_feed(jpegframe_t *f, const uint8_t *in, size_t n, bool *complete, bool *overflow)
|
|
{
|
|
*complete = false;
|
|
*overflow = false;
|
|
size_t i = 0;
|
|
while (i < n) {
|
|
uint8_t b = in[i++];
|
|
switch (f->state) {
|
|
case SEEK_FF:
|
|
if (b == 0xFF) f->state = SEEK_D8; else f->skipped++;
|
|
break;
|
|
case SEEK_D8:
|
|
if (b == 0xD8) { f->out_len = 0; put(f, 0xFF); put(f, 0xD8); f->state = MARK_FF; }
|
|
else if (b != 0xFF) { f->state = SEEK_FF; f->skipped += 2; }
|
|
break;
|
|
case MARK_FF:
|
|
if (!put(f, b)) goto overflow;
|
|
if (b == 0xFF) f->state = MARK;
|
|
else { f->state = SEEK_FF; } // corrupt: resync
|
|
break;
|
|
case MARK:
|
|
if (!put(f, b)) goto overflow;
|
|
if (b == 0xFF) break; // fill byte
|
|
f->marker = b;
|
|
if (b == 0xD9) { *complete = true; f->state = SEEK_FF; return i; }
|
|
if (b == 0xD8 || b == 0x01 || (b >= 0xD0 && b <= 0xD7)) f->state = MARK_FF;
|
|
else f->state = LEN_HI;
|
|
break;
|
|
case LEN_HI:
|
|
if (!put(f, b)) goto overflow;
|
|
f->remaining = (uint32_t)b << 8;
|
|
f->state = LEN_LO;
|
|
break;
|
|
case LEN_LO:
|
|
if (!put(f, b)) goto overflow;
|
|
f->remaining |= b;
|
|
if (f->remaining < 2) { f->state = SEEK_FF; break; }
|
|
f->remaining -= 2;
|
|
f->state = f->remaining ? SEG : (f->marker == 0xDA ? ENTROPY : MARK_FF);
|
|
break;
|
|
case SEG: {
|
|
// bulk copy the segment payload
|
|
size_t take = n - (i - 1);
|
|
if (take > f->remaining) take = f->remaining;
|
|
if (f->out_len + take > f->out_cap) goto overflow;
|
|
memcpy(f->out + f->out_len, in + i - 1, take);
|
|
f->out_len += take;
|
|
f->remaining -= take;
|
|
i += take - 1;
|
|
if (!f->remaining) f->state = f->marker == 0xDA ? ENTROPY : MARK_FF;
|
|
break;
|
|
}
|
|
case ENTROPY:
|
|
if (!put(f, b)) goto overflow;
|
|
if (b == 0xFF) f->state = ENTROPY_FF;
|
|
break;
|
|
case ENTROPY_FF:
|
|
if (!put(f, b)) goto overflow;
|
|
if (b == 0x00 || (b >= 0xD0 && b <= 0xD7)) f->state = ENTROPY; // stuffed byte / restart
|
|
else if (b == 0xFF) break;
|
|
else if (b == 0xD9) { *complete = true; f->state = SEEK_FF; return i; }
|
|
else { f->marker = b; f->state = LEN_HI; } // DHT/DQT/SOS of a later scan
|
|
break;
|
|
}
|
|
}
|
|
return i;
|
|
overflow:
|
|
*overflow = true;
|
|
f->state = SEEK_FF;
|
|
f->out_len = 0;
|
|
return i;
|
|
}
|