@@ -50,12 +50,25 @@ static void yuyv_to_rgb565(unsigned char *yuyv, uint16_t *rgb565, int in_width,
5050 for (int x = 0 ; x < out_width ; x ++ ) {
5151 int src_x = (int )(x * x_ratio ) + crop_x_offset ;
5252 int src_y = (int )(y * y_ratio ) + crop_y_offset ;
53- int src_index = (src_y * in_width + src_x ) * 2 ;
54-
55- int y0 = yuyv [src_index ];
56- int u = yuyv [src_index + 1 ];
57- int v = yuyv [src_index + 3 ];
5853
54+ // YUYV format: Y0 U Y1 V (4 bytes for 2 pixels)
55+ // Ensure we're aligned to even pixel boundary
56+ int src_x_even = (src_x / 2 ) * 2 ;
57+ int src_base_index = (src_y * in_width + src_x_even ) * 2 ;
58+
59+ // Extract Y, U, V values
60+ int y0 ;
61+ if (src_x % 2 == 0 ) {
62+ // Even pixel: use Y0
63+ y0 = yuyv [src_base_index ];
64+ } else {
65+ // Odd pixel: use Y1
66+ y0 = yuyv [src_base_index + 2 ];
67+ }
68+ int u = yuyv [src_base_index + 1 ];
69+ int v = yuyv [src_base_index + 3 ];
70+
71+ // YUV to RGB conversion (ITU-R BT.601)
5972 int c = y0 - 16 ;
6073 int d = u - 128 ;
6174 int e = v - 128 ;
@@ -64,10 +77,12 @@ static void yuyv_to_rgb565(unsigned char *yuyv, uint16_t *rgb565, int in_width,
6477 int g = (298 * c - 100 * d - 208 * e + 128 ) >> 8 ;
6578 int b = (298 * c + 516 * d + 128 ) >> 8 ;
6679
80+ // Clamp to valid range
6781 r = r < 0 ? 0 : (r > 255 ? 255 : r );
6882 g = g < 0 ? 0 : (g > 255 ? 255 : g );
6983 b = b < 0 ? 0 : (b > 255 ? 255 : b );
7084
85+ // Convert to RGB565
7186 uint16_t r5 = (r >> 3 ) & 0x1F ;
7287 uint16_t g6 = (g >> 2 ) & 0x3F ;
7388 uint16_t b5 = (b >> 3 ) & 0x1F ;
@@ -91,8 +106,23 @@ static void yuyv_to_grayscale(unsigned char *yuyv, unsigned char *gray, int in_w
91106 for (int x = 0 ; x < out_width ; x ++ ) {
92107 int src_x = (int )(x * x_ratio ) + crop_x_offset ;
93108 int src_y = (int )(y * y_ratio ) + crop_y_offset ;
94- int src_index = (src_y * in_width + src_x ) * 2 ;
95- gray [y * out_width + x ] = yuyv [src_index ];
109+
110+ // YUYV format: Y0 U Y1 V (4 bytes for 2 pixels)
111+ // Ensure we're aligned to even pixel boundary
112+ int src_x_even = (src_x / 2 ) * 2 ;
113+ int src_base_index = (src_y * in_width + src_x_even ) * 2 ;
114+
115+ // Extract Y value
116+ unsigned char y_val ;
117+ if (src_x % 2 == 0 ) {
118+ // Even pixel: use Y0
119+ y_val = yuyv [src_base_index ];
120+ } else {
121+ // Odd pixel: use Y1
122+ y_val = yuyv [src_base_index + 2 ];
123+ }
124+
125+ gray [y * out_width + x ] = y_val ;
96126 }
97127 }
98128}
@@ -342,14 +372,25 @@ static mp_obj_t webcam_capture_frame(mp_obj_t self_in, mp_obj_t format) {
342372MP_DEFINE_CONST_FUN_OBJ_2 (webcam_capture_frame_obj , webcam_capture_frame );
343373
344374static mp_obj_t webcam_reconfigure (size_t n_args , const mp_obj_t * pos_args , mp_map_t * kw_args ) {
345- // NOTE: This function only changes OUTPUT resolution (what Python receives).
346- // The INPUT resolution (what the webcam captures from V4L2) remains fixed at 640x480.
347- // The conversion functions will crop/scale from input to output resolution.
348- // TODO: Add support for changing input resolution (requires V4L2 reinit)
349-
350- enum { ARG_self , ARG_output_width , ARG_output_height };
375+ /*
376+ * Reconfigure webcam resolution.
377+ *
378+ * Supports changing both INPUT resolution (V4L2 capture format) and
379+ * OUTPUT resolution (conversion buffers). If input resolution changes,
380+ * this will stop streaming, reconfigure V4L2, and restart streaming.
381+ *
382+ * Parameters:
383+ * input_width, input_height: V4L2 capture resolution (optional)
384+ * output_width, output_height: Output buffer resolution (optional)
385+ *
386+ * If not specified, dimensions remain unchanged.
387+ */
388+
389+ enum { ARG_self , ARG_input_width , ARG_input_height , ARG_output_width , ARG_output_height };
351390 static const mp_arg_t allowed_args [] = {
352391 { MP_QSTR_self , MP_ARG_REQUIRED | MP_ARG_OBJ , {.u_obj = MP_OBJ_NULL } },
392+ { MP_QSTR_input_width , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = 0 } },
393+ { MP_QSTR_input_height , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = 0 } },
353394 { MP_QSTR_output_width , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = 0 } },
354395 { MP_QSTR_output_height , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = 0 } },
355396 };
@@ -360,26 +401,135 @@ static mp_obj_t webcam_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_m
360401 webcam_obj_t * self = MP_OBJ_TO_PTR (args [ARG_self ].u_obj );
361402
362403 // Get new dimensions (keep current if not specified)
363- int new_width = args [ARG_output_width ].u_int ;
364- int new_height = args [ARG_output_height ].u_int ;
404+ int new_input_width = args [ARG_input_width ].u_int ;
405+ int new_input_height = args [ARG_input_height ].u_int ;
406+ int new_output_width = args [ARG_output_width ].u_int ;
407+ int new_output_height = args [ARG_output_height ].u_int ;
365408
366- if (new_width == 0 ) new_width = self -> output_width ;
367- if (new_height == 0 ) new_height = self -> output_height ;
409+ if (new_input_width == 0 ) new_input_width = self -> input_width ;
410+ if (new_input_height == 0 ) new_input_height = self -> input_height ;
411+ if (new_output_width == 0 ) new_output_width = self -> output_width ;
412+ if (new_output_height == 0 ) new_output_height = self -> output_height ;
368413
369414 // Validate dimensions
370- if (new_width <= 0 || new_height <= 0 || new_width > 1920 || new_height > 1920 ) {
415+ if (new_input_width <= 0 || new_input_height <= 0 || new_input_width > 1920 || new_input_height > 1920 ) {
416+ mp_raise_ValueError (MP_ERROR_TEXT ("Invalid input dimensions" ));
417+ }
418+ if (new_output_width <= 0 || new_output_height <= 0 || new_output_width > 1920 || new_output_height > 1920 ) {
371419 mp_raise_ValueError (MP_ERROR_TEXT ("Invalid output dimensions" ));
372420 }
373421
374- // If dimensions changed, reallocate buffers
375- if (new_width != self -> output_width || new_height != self -> output_height ) {
422+ bool input_changed = (new_input_width != self -> input_width || new_input_height != self -> input_height );
423+ bool output_changed = (new_output_width != self -> output_width || new_output_height != self -> output_height );
424+
425+ // If input resolution changed, need to reconfigure V4L2
426+ if (input_changed ) {
427+ WEBCAM_DEBUG_PRINT ("Reconfiguring V4L2: %dx%d -> %dx%d\n" ,
428+ self -> input_width , self -> input_height ,
429+ new_input_width , new_input_height );
430+
431+ // 1. Stop streaming
432+ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
433+ if (ioctl (self -> fd , VIDIOC_STREAMOFF , & type ) < 0 ) {
434+ WEBCAM_DEBUG_PRINT ("STREAMOFF failed: %s\n" , strerror (errno ));
435+ mp_raise_OSError (errno );
436+ }
437+
438+ // 2. Unmap old buffers
439+ for (int i = 0 ; i < NUM_BUFFERS ; i ++ ) {
440+ if (self -> buffers [i ] != MAP_FAILED && self -> buffers [i ] != NULL ) {
441+ munmap (self -> buffers [i ], self -> buffer_length );
442+ self -> buffers [i ] = MAP_FAILED ;
443+ }
444+ }
445+
446+ // 3. Set new V4L2 format
447+ struct v4l2_format fmt = {0 };
448+ fmt .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
449+ fmt .fmt .pix .width = new_input_width ;
450+ fmt .fmt .pix .height = new_input_height ;
451+ fmt .fmt .pix .pixelformat = V4L2_PIX_FMT_YUYV ;
452+ fmt .fmt .pix .field = V4L2_FIELD_ANY ;
453+
454+ if (ioctl (self -> fd , VIDIOC_S_FMT , & fmt ) < 0 ) {
455+ WEBCAM_DEBUG_PRINT ("S_FMT failed: %s\n" , strerror (errno ));
456+ mp_raise_OSError (errno );
457+ }
458+
459+ // Verify format was set (driver may adjust dimensions)
460+ if (fmt .fmt .pix .width != new_input_width || fmt .fmt .pix .height != new_input_height ) {
461+ WEBCAM_DEBUG_PRINT ("Warning: Driver adjusted format to %dx%d\n" ,
462+ fmt .fmt .pix .width , fmt .fmt .pix .height );
463+ new_input_width = fmt .fmt .pix .width ;
464+ new_input_height = fmt .fmt .pix .height ;
465+ }
466+
467+ // 4. Request new buffers
468+ struct v4l2_requestbuffers req = {0 };
469+ req .count = NUM_BUFFERS ;
470+ req .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
471+ req .memory = V4L2_MEMORY_MMAP ;
472+
473+ if (ioctl (self -> fd , VIDIOC_REQBUFS , & req ) < 0 ) {
474+ WEBCAM_DEBUG_PRINT ("REQBUFS failed: %s\n" , strerror (errno ));
475+ mp_raise_OSError (errno );
476+ }
477+
478+ // 5. Map new buffers
479+ for (int i = 0 ; i < NUM_BUFFERS ; i ++ ) {
480+ struct v4l2_buffer buf = {0 };
481+ buf .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
482+ buf .memory = V4L2_MEMORY_MMAP ;
483+ buf .index = i ;
484+
485+ if (ioctl (self -> fd , VIDIOC_QUERYBUF , & buf ) < 0 ) {
486+ WEBCAM_DEBUG_PRINT ("QUERYBUF failed: %s\n" , strerror (errno ));
487+ mp_raise_OSError (errno );
488+ }
489+
490+ self -> buffer_length = buf .length ;
491+ self -> buffers [i ] = mmap (NULL , buf .length , PROT_READ | PROT_WRITE ,
492+ MAP_SHARED , self -> fd , buf .m .offset );
493+
494+ if (self -> buffers [i ] == MAP_FAILED ) {
495+ WEBCAM_DEBUG_PRINT ("mmap failed: %s\n" , strerror (errno ));
496+ mp_raise_OSError (errno );
497+ }
498+ }
499+
500+ // 6. Queue buffers
501+ for (int i = 0 ; i < NUM_BUFFERS ; i ++ ) {
502+ struct v4l2_buffer buf = {0 };
503+ buf .type = V4L2_BUF_TYPE_VIDEO_CAPTURE ;
504+ buf .memory = V4L2_MEMORY_MMAP ;
505+ buf .index = i ;
506+
507+ if (ioctl (self -> fd , VIDIOC_QBUF , & buf ) < 0 ) {
508+ WEBCAM_DEBUG_PRINT ("QBUF failed: %s\n" , strerror (errno ));
509+ mp_raise_OSError (errno );
510+ }
511+ }
512+
513+ // 7. Restart streaming
514+ if (ioctl (self -> fd , VIDIOC_STREAMON , & type ) < 0 ) {
515+ WEBCAM_DEBUG_PRINT ("STREAMON failed: %s\n" , strerror (errno ));
516+ mp_raise_OSError (errno );
517+ }
518+
519+ // Update stored input dimensions
520+ self -> input_width = new_input_width ;
521+ self -> input_height = new_input_height ;
522+ }
523+
524+ // If output resolution changed (or input changed which may affect output), reallocate output buffers
525+ if (output_changed || input_changed ) {
376526 // Free old buffers
377527 free (self -> gray_buffer );
378528 free (self -> rgb565_buffer );
379529
380530 // Update dimensions
381- self -> output_width = new_width ;
382- self -> output_height = new_height ;
531+ self -> output_width = new_output_width ;
532+ self -> output_height = new_output_height ;
383533
384534 // Allocate new buffers
385535 self -> gray_buffer = (unsigned char * )malloc (self -> output_width * self -> output_height * sizeof (unsigned char ));
@@ -392,10 +542,12 @@ static mp_obj_t webcam_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_m
392542 self -> rgb565_buffer = NULL ;
393543 mp_raise_OSError (MP_ENOMEM );
394544 }
395-
396- WEBCAM_DEBUG_PRINT ("Webcam reconfigured to %dx%d\n" , self -> output_width , self -> output_height );
397545 }
398546
547+ WEBCAM_DEBUG_PRINT ("Webcam reconfigured: input %dx%d, output %dx%d\n" ,
548+ self -> input_width , self -> input_height ,
549+ self -> output_width , self -> output_height );
550+
399551 return mp_const_none ;
400552}
401553MP_DEFINE_CONST_FUN_OBJ_KW (webcam_reconfigure_obj , 1 , webcam_reconfigure );
0 commit comments