@@ -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
8478static 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 ). */
134128static 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+
139140static 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