Improve camera

This commit is contained in:
Thomas Farstrike
2025-11-25 07:59:48 +01:00
parent 7e4585e91e
commit 31e61e7d88
3 changed files with 64 additions and 146 deletions
+59 -141
View File
@@ -25,6 +25,7 @@ static const mp_obj_type_t webcam_type;
typedef struct _webcam_obj_t {
mp_obj_base_t base;
int fd;
char device[64]; // Device path (e.g., "/dev/video0")
void *buffers[NUM_BUFFERS];
size_t buffer_length;
int frame_count;
@@ -147,8 +148,11 @@ static void save_raw_rgb565(const char *filename, uint16_t *data, int width, int
fclose(fp);
}
static int init_webcam(webcam_obj_t *self, const char *device) {
//WEBCAM_DEBUG_PRINT("webcam.c: init_webcam\n");
static int init_webcam(webcam_obj_t *self, const char *device, int width, int height) {
// Store device path for later use (e.g., reconfigure)
strncpy(self->device, device, sizeof(self->device) - 1);
self->device[sizeof(self->device) - 1] = '\0';
self->fd = open(device, O_RDWR);
if (self->fd < 0) {
WEBCAM_DEBUG_PRINT("Cannot open device: %s\n", strerror(errno));
@@ -157,8 +161,8 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(self->fd, VIDIOC_S_FMT, &fmt) < 0) {
@@ -167,6 +171,10 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
return -errno;
}
// Store actual format (driver may adjust dimensions)
width = fmt.fmt.pix.width;
height = fmt.fmt.pix.height;
struct v4l2_requestbuffers req = {0};
req.count = NUM_BUFFERS;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
@@ -215,9 +223,9 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
self->frame_count = 0;
// Store the input dimensions from V4L2 format
self->input_width = WIDTH;
self->input_height = HEIGHT;
// Store the input dimensions (actual values from V4L2, may be adjusted by driver)
self->input_width = width;
self->input_height = height;
// Initialize output dimensions with defaults if not already set
if (self->output_width == 0) self->output_width = OUTPUT_WIDTH;
@@ -323,13 +331,25 @@ static mp_obj_t capture_frame(mp_obj_t self_in, mp_obj_t format) {
}
}
static mp_obj_t webcam_init(size_t n_args, const mp_obj_t *args) {
mp_arg_check_num(n_args, 0, 0, 1, false);
static mp_obj_t webcam_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_device, ARG_width, ARG_height };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_device, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = WIDTH} },
{ MP_QSTR_height, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = HEIGHT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
const char *device = "/dev/video0";
if (n_args == 1) {
device = mp_obj_str_get_str(args[0]);
if (args[ARG_device].u_obj != MP_OBJ_NULL) {
device = mp_obj_str_get_str(args[ARG_device].u_obj);
}
int width = args[ARG_width].u_int;
int height = args[ARG_height].u_int;
webcam_obj_t *self = m_new_obj(webcam_obj_t);
self->base.type = &webcam_type;
self->fd = -1;
@@ -340,14 +360,14 @@ static mp_obj_t webcam_init(size_t n_args, const mp_obj_t *args) {
self->output_width = 0; // Will use default OUTPUT_WIDTH in init_webcam
self->output_height = 0; // Will use default OUTPUT_HEIGHT in init_webcam
int res = init_webcam(self, device);
int res = init_webcam(self, device, width, height);
if (res < 0) {
mp_raise_OSError(-res);
}
return MP_OBJ_FROM_PTR(self);
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(webcam_init_obj, 0, 1, webcam_init);
MP_DEFINE_CONST_FUN_OBJ_KW(webcam_init_obj, 0, webcam_init);
static mp_obj_t webcam_deinit(mp_obj_t self_in) {
webcam_obj_t *self = MP_OBJ_TO_PTR(self_in);
@@ -373,11 +393,10 @@ MP_DEFINE_CONST_FUN_OBJ_2(webcam_capture_frame_obj, webcam_capture_frame);
static mp_obj_t webcam_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
/*
* Reconfigure webcam resolution.
* Reconfigure webcam resolution by reinitializing.
*
* Supports changing both INPUT resolution (V4L2 capture format) and
* OUTPUT resolution (conversion buffers). If input resolution changes,
* this will stop streaming, reconfigure V4L2, and restart streaming.
* This elegantly reuses deinit_webcam() and init_webcam() instead of
* duplicating V4L2 setup code.
*
* Parameters:
* input_width, input_height: V4L2 capture resolution (optional)
@@ -412,142 +431,41 @@ static mp_obj_t webcam_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_m
if (new_output_height == 0) new_output_height = self->output_height;
// Validate dimensions
if (new_input_width <= 0 || new_input_height <= 0 || new_input_width > 1920 || new_input_height > 1920) {
if (new_input_width <= 0 || new_input_height <= 0 || new_input_width > 3840 || new_input_height > 2160) {
mp_raise_ValueError(MP_ERROR_TEXT("Invalid input dimensions"));
}
if (new_output_width <= 0 || new_output_height <= 0 || new_output_width > 1920 || new_output_height > 1920) {
if (new_output_width <= 0 || new_output_height <= 0 || new_output_width > 3840 || new_output_height > 2160) {
mp_raise_ValueError(MP_ERROR_TEXT("Invalid output dimensions"));
}
bool input_changed = (new_input_width != self->input_width || new_input_height != self->input_height);
bool output_changed = (new_output_width != self->output_width || new_output_height != self->output_height);
// If input resolution changed, need to reconfigure V4L2
if (input_changed) {
WEBCAM_DEBUG_PRINT("Reconfiguring V4L2: %dx%d -> %dx%d\n",
self->input_width, self->input_height,
new_input_width, new_input_height);
// 1. Stop streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(self->fd, VIDIOC_STREAMOFF, &type) < 0) {
WEBCAM_DEBUG_PRINT("STREAMOFF failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
// 2. Unmap old buffers
for (int i = 0; i < NUM_BUFFERS; i++) {
if (self->buffers[i] != MAP_FAILED && self->buffers[i] != NULL) {
munmap(self->buffers[i], self->buffer_length);
self->buffers[i] = MAP_FAILED;
}
}
// 3. Set new V4L2 format
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = new_input_width;
fmt.fmt.pix.height = new_input_height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(self->fd, VIDIOC_S_FMT, &fmt) < 0) {
WEBCAM_DEBUG_PRINT("S_FMT failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
// Verify format was set (driver may adjust dimensions)
if (fmt.fmt.pix.width != new_input_width || fmt.fmt.pix.height != new_input_height) {
WEBCAM_DEBUG_PRINT("Warning: Driver adjusted format to %dx%d\n",
fmt.fmt.pix.width, fmt.fmt.pix.height);
new_input_width = fmt.fmt.pix.width;
new_input_height = fmt.fmt.pix.height;
}
// 4. Request new buffers
struct v4l2_requestbuffers req = {0};
req.count = NUM_BUFFERS;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(self->fd, VIDIOC_REQBUFS, &req) < 0) {
WEBCAM_DEBUG_PRINT("REQBUFS failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
// 5. Map new buffers
for (int i = 0; i < NUM_BUFFERS; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(self->fd, VIDIOC_QUERYBUF, &buf) < 0) {
WEBCAM_DEBUG_PRINT("QUERYBUF failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
self->buffer_length = buf.length;
self->buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, self->fd, buf.m.offset);
if (self->buffers[i] == MAP_FAILED) {
WEBCAM_DEBUG_PRINT("mmap failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
}
// 6. Queue buffers
for (int i = 0; i < NUM_BUFFERS; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(self->fd, VIDIOC_QBUF, &buf) < 0) {
WEBCAM_DEBUG_PRINT("QBUF failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
}
// 7. Restart streaming
if (ioctl(self->fd, VIDIOC_STREAMON, &type) < 0) {
WEBCAM_DEBUG_PRINT("STREAMON failed: %s\n", strerror(errno));
mp_raise_OSError(errno);
}
// Update stored input dimensions
self->input_width = new_input_width;
self->input_height = new_input_height;
// Check if anything changed
if (new_input_width == self->input_width &&
new_input_height == self->input_height &&
new_output_width == self->output_width &&
new_output_height == self->output_height) {
return mp_const_none; // Nothing to do
}
// If output resolution changed (or input changed which may affect output), reallocate output buffers
if (output_changed || input_changed) {
// Free old buffers
free(self->gray_buffer);
free(self->rgb565_buffer);
WEBCAM_DEBUG_PRINT("Reconfiguring webcam: %dx%d -> %dx%d (input), %dx%d -> %dx%d (output)\n",
self->input_width, self->input_height, new_input_width, new_input_height,
self->output_width, self->output_height, new_output_width, new_output_height);
// Update dimensions
self->output_width = new_output_width;
self->output_height = new_output_height;
// Remember device path before deinit (which closes fd)
char device[64];
strncpy(device, self->device, sizeof(device));
// Allocate new buffers
self->gray_buffer = (unsigned char *)malloc(self->output_width * self->output_height * sizeof(unsigned char));
self->rgb565_buffer = (uint16_t *)malloc(self->output_width * self->output_height * sizeof(uint16_t));
// Set desired output dimensions before reinit
self->output_width = new_output_width;
self->output_height = new_output_height;
if (!self->gray_buffer || !self->rgb565_buffer) {
free(self->gray_buffer);
free(self->rgb565_buffer);
self->gray_buffer = NULL;
self->rgb565_buffer = NULL;
mp_raise_OSError(MP_ENOMEM);
}
// Clean shutdown and reinitialize with new input dimensions
deinit_webcam(self);
int res = init_webcam(self, device, new_input_width, new_input_height);
if (res < 0) {
mp_raise_OSError(-res);
}
WEBCAM_DEBUG_PRINT("Webcam reconfigured: input %dx%d, output %dx%d\n",
self->input_width, self->input_height,
self->output_width, self->output_height);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(webcam_reconfigure_obj, 1, webcam_reconfigure);
@@ -144,11 +144,10 @@ class CameraApp(Activity):
else:
print("camera app: no internal camera found, trying webcam on /dev/video0")
try:
self.cam = webcam.init("/dev/video0")
# Initialize webcam with desired resolution directly
print(f"Initializing webcam at {self.width}x{self.height}")
self.cam = webcam.init("/dev/video0", width=self.width, height=self.height)
self.use_webcam = True
# Reconfigure webcam to use saved resolution
print(f"Reconfiguring webcam to {self.width}x{self.height}")
webcam.reconfigure(self.cam, output_width=self.width, output_height=self.height)
except Exception as e:
print(f"camera app: webcam exception: {e}")
if self.cam:
@@ -266,6 +266,7 @@ best fit on battery power:
2482 is 4.180
2470 is 4.170
2457 is 4.147
# 2444 is 4.12
2433 is 4.109
2429 is 4.102
2393 is 4.044
@@ -280,7 +281,7 @@ def adc_to_voltage(adc_value):
Calibration data shows linear relationship: voltage = -0.0016237 * adc + 8.2035
This is ~10x more accurate than simple scaling (error ~0.01V vs ~0.1V).
"""
return (-0.0016237 * adc_value + 8.2035)
return (0.001651* adc_value + 0.08709)
mpos.battery_voltage.init_adc(13, adc_to_voltage)