Skip to content

Commit 38bb948

Browse files
committed
cam_hal: PSRAM: forward-scan last half-buffer to find JPEG EOI reliably
When `psram_mode` is active, `cam_take()` used to copy only 32 bytes from PSRAM and scan backward for the JPEG EOI (FF D9). Because DMA copies in dma_node_buffer_size length, the true EOI often falls earlier and was missed, leading to repeated "NO-EOI" resets. This change: - Drops the 32-byte SRAM copy; scan directly in PSRAM after cache invalidation. - Searches the last dma_node_buffer_size **plus 1 byte** (marker_len-1), starting 1 byte before the final block to catch split EOIs. - Scans **forward** so we pick the earliest EOI in the current tail and avoid stale markers from a larger previous frame. - Uses a fast byte-hunt for 0xFF with a 2-byte verification for "FF D9". SRAM DMA mode keeps the simple backward check (we only accept EOI at offset 0 there), so no extra complexity or perf work is added. Net effect: robust EOI detection without garbage tails and fewer spurious "NO-EOI" resets in PSRAM captures.
1 parent a60c650 commit 38bb948

File tree

1 file changed

+66
-31
lines changed

1 file changed

+66
-31
lines changed

driver/cam_hal.c

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,10 @@ static portMUX_TYPE g_psram_dma_lock = portMUX_INITIALIZER_UNLOCKED;
6969
#ifndef CAM_SOI_PROBE_BYTES
7070
#define CAM_SOI_PROBE_BYTES 32
7171
#endif
72-
73-
/* Number of bytes copied to SRAM for EOI validation when capturing
74-
* directly to PSRAM. Tunable to probe more of the frame tail if needed. */
75-
#ifndef CAM_EOI_PROBE_BYTES
76-
#define CAM_EOI_PROBE_BYTES 32
77-
#endif
78-
7972
/*
80-
* PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on the
81-
* SOI probe region so cached reads see the data written by DMA.
73+
* PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on
74+
* PSRAM regions that the CPU will read so cached reads see the data written
75+
* by DMA.
8276
*/
8377

8478
static inline size_t dcache_line_size(void)
@@ -130,12 +124,19 @@ static inline void cam_drop_psram_cache(void *addr, size_t len)
130124
#define CAM_WARN_THROTTLE(counter, first) do { (void)(counter); } while (0)
131125
#endif
132126

133-
/* JPEG markers in little-endian order (ESP32). */
127+
/* JPEG markers (byte-order independent). */
134128
static const uint8_t JPEG_SOI_MARKER[] = {0xFF, 0xD8, 0xFF}; /* SOI = FF D8 FF */
135129
#define JPEG_SOI_MARKER_LEN (3)
136-
static const uint16_t JPEG_EOI_MARKER = 0xD9FF; /* EOI = FF D9 */
130+
static const uint8_t JPEG_EOI_BYTES[] = {0xFF, 0xD9}; /* EOI = FF D9 */
137131
#define JPEG_EOI_MARKER_LEN (2)
138132

133+
/* Compute the scan window for JPEG EOI detection in PSRAM. */
134+
static inline size_t eoi_probe_window(size_t half, size_t frame_len)
135+
{
136+
size_t w = half + (JPEG_EOI_MARKER_LEN - 1);
137+
return w > frame_len ? frame_len : w;
138+
}
139+
139140
static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length)
140141
{
141142
static uint16_t warn_soi_miss_cnt = 0;
@@ -157,19 +158,54 @@ static int cam_verify_jpeg_soi(const uint8_t *inbuf, uint32_t length)
157158
return -1;
158159
}
159160

160-
static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length)
161+
static int cam_verify_jpeg_eoi(const uint8_t *inbuf, uint32_t length, bool search_forward)
161162
{
162163
if (length < JPEG_EOI_MARKER_LEN) {
163164
return -1;
164165
}
165166

166-
int offset = -1;
167-
uint8_t *dptr = (uint8_t *)inbuf + length - JPEG_EOI_MARKER_LEN;
168-
while (dptr > inbuf) {
169-
if (memcmp(dptr, &JPEG_EOI_MARKER, JPEG_EOI_MARKER_LEN) == 0) {
170-
offset = dptr - inbuf;
171-
//ESP_LOGW(TAG, "EOI: %d", length - (offset + 2));
172-
return offset;
167+
if (search_forward) {
168+
/* Scan forward to honor the earliest marker in the buffer. This avoids
169+
* returning an EOI that belongs to a larger previous frame when the tail
170+
* of that frame still resides in PSRAM. JPEG data is pseudo random, so
171+
* the first marker byte appears rarely; test four positions per load to
172+
* reduce memory traffic. */
173+
const uint8_t *pat = JPEG_EOI_BYTES;
174+
const uint32_t A = pat[0] * 0x01010101u;
175+
const uint32_t ONE = 0x01010101u;
176+
const uint32_t HIGH = 0x80808080u;
177+
uint32_t i = 0;
178+
while (i + 4 <= length) {
179+
uint32_t w;
180+
memcpy(&w, inbuf + i, 4); /* unaligned load is allowed */
181+
uint32_t x = w ^ A; /* identify bytes equal to first marker byte */
182+
uint32_t m = (~x & (x - ONE)) & HIGH; /* mask has high bit set for candidate bytes */
183+
while (m) { /* handle only candidates to avoid unnecessary memcmp calls */
184+
unsigned off = __builtin_ctz(m) >> 3;
185+
uint32_t pos = i + off;
186+
if (pos + JPEG_EOI_MARKER_LEN <= length &&
187+
memcmp(inbuf + pos, pat, JPEG_EOI_MARKER_LEN) == 0) {
188+
return pos;
189+
}
190+
m &= m - 1; /* clear processed candidate */
191+
}
192+
i += 4;
193+
}
194+
for (; i + JPEG_EOI_MARKER_LEN <= length; i++) {
195+
if (memcmp(inbuf + i, pat, JPEG_EOI_MARKER_LEN) == 0) {
196+
return i;
197+
}
198+
}
199+
return -1;
200+
}
201+
202+
const uint8_t *dptr = inbuf + length - JPEG_EOI_MARKER_LEN;
203+
while (dptr >= inbuf) {
204+
if (memcmp(dptr, JPEG_EOI_BYTES, JPEG_EOI_MARKER_LEN) == 0) {
205+
return dptr - inbuf;
206+
}
207+
if (dptr == inbuf) {
208+
break;
173209
}
174210
dptr--;
175211
}
@@ -696,27 +732,26 @@ camera_fb_t *cam_take(TickType_t timeout)
696732
/* find the end marker for JPEG. Data after that can be discarded */
697733
int offset_e = -1;
698734
if (cam_obj->psram_mode) {
699-
size_t probe_len = dma_buffer->len;
700-
if (probe_len > CAM_EOI_PROBE_BYTES) {
701-
probe_len = CAM_EOI_PROBE_BYTES;
702-
}
703-
if (probe_len == 0) {
735+
/* Search forward from (JPEG_EOI_MARKER_LEN - 1) bytes before the final
736+
* DMA block. We prefer forward search to pick the earliest EOI in the
737+
* last DMA node, avoiding stale markers from a larger prior frame. */
738+
size_t probe_len = eoi_probe_window(cam_obj->dma_node_buffer_size,
739+
dma_buffer->len);
740+
if (probe_len < JPEG_EOI_MARKER_LEN) {
704741
goto skip_eoi_check;
705742
}
706-
cam_drop_psram_cache(dma_buffer->buf + dma_buffer->len - probe_len, probe_len);
707-
708-
uint8_t eoi_probe[CAM_EOI_PROBE_BYTES];
709-
memcpy(eoi_probe, dma_buffer->buf + dma_buffer->len - probe_len, probe_len);
710-
int off = cam_verify_jpeg_eoi(eoi_probe, probe_len);
743+
uint8_t *probe_start = dma_buffer->buf + dma_buffer->len - probe_len;
744+
cam_drop_psram_cache(probe_start, probe_len);
745+
int off = cam_verify_jpeg_eoi(probe_start, probe_len, true);
711746
if (off >= 0) {
712747
offset_e = dma_buffer->len - probe_len + off;
713748
}
714749
} else {
715-
offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len);
750+
offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len, false);
716751
}
717752

718753
if (offset_e >= 0) {
719-
dma_buffer->len = offset_e + sizeof(JPEG_EOI_MARKER);
754+
dma_buffer->len = offset_e + JPEG_EOI_MARKER_LEN;
720755
if (cam_obj->psram_mode) {
721756
/* DMA may bypass cache, ensure full frame is visible */
722757
cam_drop_psram_cache(dma_buffer->buf, dma_buffer->len);

0 commit comments

Comments
 (0)