Skip to content

Commit 9e27574

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 rounds the captured length up to a half-buffer, 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 half-buffer **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 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 67f83d5 commit 9e27574

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
@@ -68,16 +68,10 @@ static portMUX_TYPE g_psram_dma_lock = portMUX_INITIALIZER_UNLOCKED;
6868
#ifndef CAM_SOI_PROBE_BYTES
6969
#define CAM_SOI_PROBE_BYTES 32
7070
#endif
71-
72-
/* Number of bytes copied to SRAM for EOI validation when capturing
73-
* directly to PSRAM. Tunable to probe more of the frame tail if needed. */
74-
#ifndef CAM_EOI_PROBE_BYTES
75-
#define CAM_EOI_PROBE_BYTES 32
76-
#endif
77-
7871
/*
79-
* PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on the
80-
* SOI probe region so cached reads see the data written by DMA.
72+
* PSRAM DMA may bypass the CPU cache. Always call esp_cache_msync() on
73+
* PSRAM regions that the CPU will read so cached reads see the data written
74+
* by DMA.
8175
*/
8276

8377
static inline size_t dcache_line_size(void)
@@ -129,12 +123,19 @@ static inline void cam_drop_psram_cache(void *addr, size_t len)
129123
#define CAM_WARN_THROTTLE(counter, first) do { (void)(counter); } while (0)
130124
#endif
131125

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

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

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

165-
int offset = -1;
166-
uint8_t *dptr = (uint8_t *)inbuf + length - JPEG_EOI_MARKER_LEN;
167-
while (dptr > inbuf) {
168-
if (memcmp(dptr, &JPEG_EOI_MARKER, JPEG_EOI_MARKER_LEN) == 0) {
169-
offset = dptr - inbuf;
170-
//ESP_LOGW(TAG, "EOI: %d", length - (offset + 2));
171-
return offset;
166+
if (search_forward) {
167+
/* Scan forward to honor the earliest marker in the buffer. This avoids
168+
* returning an EOI that belongs to a larger previous frame when the tail
169+
* of that frame still resides in PSRAM. JPEG data is pseudo random, so
170+
* the first marker byte appears rarely; test four positions per load to
171+
* reduce memory traffic. */
172+
const uint8_t *pat = JPEG_EOI_BYTES;
173+
const uint32_t A = pat[0] * 0x01010101u;
174+
const uint32_t ONE = 0x01010101u;
175+
const uint32_t HIGH = 0x80808080u;
176+
uint32_t i = 0;
177+
while (i + 4 <= length) {
178+
uint32_t w;
179+
memcpy(&w, inbuf + i, 4); /* unaligned load is allowed */
180+
uint32_t x = w ^ A; /* identify bytes equal to first marker byte */
181+
uint32_t m = (~x & (x - ONE)) & HIGH; /* mask has high bit set for candidate bytes */
182+
while (m) { /* handle only candidates to avoid unnecessary memcmp calls */
183+
unsigned off = __builtin_ctz(m) >> 3;
184+
uint32_t pos = i + off;
185+
if (pos + JPEG_EOI_MARKER_LEN <= length &&
186+
memcmp(inbuf + pos, pat, JPEG_EOI_MARKER_LEN) == 0) {
187+
return pos;
188+
}
189+
m &= m - 1; /* clear processed candidate */
190+
}
191+
i += 4;
192+
}
193+
for (; i + JPEG_EOI_MARKER_LEN <= length; i++) {
194+
if (memcmp(inbuf + i, pat, JPEG_EOI_MARKER_LEN) == 0) {
195+
return i;
196+
}
197+
}
198+
return -1;
199+
}
200+
201+
const uint8_t *dptr = inbuf + length - JPEG_EOI_MARKER_LEN;
202+
while (dptr >= inbuf) {
203+
if (memcmp(dptr, JPEG_EOI_BYTES, JPEG_EOI_MARKER_LEN) == 0) {
204+
return dptr - inbuf;
205+
}
206+
if (dptr == inbuf) {
207+
break;
172208
}
173209
dptr--;
174210
}
@@ -707,27 +743,26 @@ camera_fb_t *cam_take(TickType_t timeout)
707743
/* find the end marker for JPEG. Data after that can be discarded */
708744
int offset_e = -1;
709745
if (cam_obj->psram_mode) {
710-
size_t probe_len = dma_buffer->len;
711-
if (probe_len > CAM_EOI_PROBE_BYTES) {
712-
probe_len = CAM_EOI_PROBE_BYTES;
713-
}
714-
if (probe_len == 0) {
746+
/* Search forward from (JPEG_EOI_MARKER_LEN - 1) bytes before the final
747+
* DMA block. We prefer forward search to pick the earliest EOI in the
748+
* last half-buffer, avoiding stale markers from a larger prior frame. */
749+
size_t probe_len = eoi_probe_window(cam_obj->dma_half_buffer_size,
750+
dma_buffer->len);
751+
if (probe_len < JPEG_EOI_MARKER_LEN) {
715752
goto skip_eoi_check;
716753
}
717-
cam_drop_psram_cache(dma_buffer->buf + dma_buffer->len - probe_len, probe_len);
718-
719-
uint8_t eoi_probe[CAM_EOI_PROBE_BYTES];
720-
memcpy(eoi_probe, dma_buffer->buf + dma_buffer->len - probe_len, probe_len);
721-
int off = cam_verify_jpeg_eoi(eoi_probe, probe_len);
754+
uint8_t *probe_start = dma_buffer->buf + dma_buffer->len - probe_len;
755+
cam_drop_psram_cache(probe_start, probe_len);
756+
int off = cam_verify_jpeg_eoi(probe_start, probe_len, true);
722757
if (off >= 0) {
723758
offset_e = dma_buffer->len - probe_len + off;
724759
}
725760
} else {
726-
offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len);
761+
offset_e = cam_verify_jpeg_eoi(dma_buffer->buf, dma_buffer->len, false);
727762
}
728763

729764
if (offset_e >= 0) {
730-
dma_buffer->len = offset_e + sizeof(JPEG_EOI_MARKER);
765+
dma_buffer->len = offset_e + JPEG_EOI_MARKER_LEN;
731766
if (cam_obj->psram_mode) {
732767
/* DMA may bypass cache, ensure full frame is visible */
733768
cam_drop_psram_cache(dma_buffer->buf, dma_buffer->len);

0 commit comments

Comments
 (0)