@@ -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
8377static 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 ). */
133127static 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+
138139static 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