diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 2e30e24957..fcb80547bd 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -321,6 +321,27 @@ config OV2640_REGDEBUG endif +config GOLDFISH_CAMERA + bool "Goldfish camera support" + depends on VIDEO + default n + +if GOLDFISH_CAMERA + +config GOLDFISH_CAMERA_PIPE_PATH + string "Goldfish camera pipe path" + default "/dev/goldfish_pipe" + +config GOLDFISH_CAMERA_PRIORITY + int "Goldfish camera thread priority" + default 224 + +config GOLDFISH_CAMERA_STACKSIZE + int "Goldfish camera stack size" + default DEFAULT_TASK_STACKSIZE + +endif + source "drivers/video/vnc/Kconfig" source "drivers/video/mipidsi/Kconfig" diff --git a/drivers/video/Make.defs b/drivers/video/Make.defs index ad41c026fa..b40305ee28 100644 --- a/drivers/video/Make.defs +++ b/drivers/video/Make.defs @@ -58,6 +58,10 @@ endif endif +ifeq ($(CONFIG_GOLDFISH_CAMERA),y) + CSRCS += goldfish_camera.c +endif + include video/vnc/Make.defs include video/mipidsi/Make.defs diff --git a/drivers/video/goldfish_camera.c b/drivers/video/goldfish_camera.c new file mode 100644 index 0000000000..55996c11af --- /dev/null +++ b/drivers/video/goldfish_camera.c @@ -0,0 +1,780 @@ +/**************************************************************************** + * drivers/video/goldfish_camera.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define GOLDFISH_CAMERA_MAX_NUMBER 8 +#define GOLDFISH_CAMERA_SIZE_MAX_NUMBER 32 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct +{ + uint16_t width; + uint16_t height; +} goldfish_camera_size_t; + +typedef struct +{ + char name[32]; + uint32_t channel; + uint32_t pix; + char dir[32]; + goldfish_camera_size_t size[GOLDFISH_CAMERA_SIZE_MAX_NUMBER]; +} goldfish_camera_info_t; + +typedef struct +{ + struct imgdata_s data; + struct imgsensor_s sensor; + imgdata_capture_t capture_cb; + FAR void *capture_arg; + uint32_t buf_size; + FAR uint8_t *next_buf; + struct file file; + sem_t run; + goldfish_camera_info_t info; + bool streaming; + pid_t pid; +} goldfish_camera_priv_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Video image sensor operations */ + +static bool goldfish_camera_is_available(FAR struct imgsensor_s *sensor); +static int goldfish_camera_init(FAR struct imgsensor_s *sensor); +static int goldfish_camera_uninit(FAR struct imgsensor_s *sensor); +static FAR const char * +goldfish_camera_get_driver_name(FAR struct imgsensor_s *sensor); +static int +goldfish_camera_validate_frame_setting(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type, + uint8_t nr_datafmt, + FAR imgsensor_format_t *datafmts, + FAR imgsensor_interval_t *interval); +static int goldfish_camera_start_capture(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type, + uint8_t nr_datafmt, + FAR imgsensor_format_t *datafmts, + FAR imgsensor_interval_t *interval); +static int goldfish_camera_stop_capture(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type); + +/* Video image data operations */ + +static int goldfish_camera_data_init(FAR struct imgdata_s *data); +static int goldfish_camera_data_uninit(FAR struct imgdata_s *data); +static int +goldfish_camera_data_validate_frame_setting(FAR struct imgdata_s *data, + uint8_t nr_datafmt, + FAR imgdata_format_t *datafmt, + FAR imgdata_interval_t *interval); +static int +goldfish_camera_data_start_capture(FAR struct imgdata_s *data, + uint8_t nr_datafmt, + FAR imgdata_format_t *datafmt, + FAR imgdata_interval_t *interval, + imgdata_capture_t callback, + FAR void *arg); +static int goldfish_camera_data_stop_capture(FAR struct imgdata_s *data); +static int goldfish_camera_data_set_buf(FAR struct imgdata_s *data, + FAR uint8_t *addr, + uint32_t size); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Communication service for qemu-pipe */ + +static const char g_qemudservice[] = "qemud"; + +/* Camera service. */ + +static const char g_cameraservice[] = "camera"; + +static const struct imgsensor_ops_s g_goldfish_camera_ops = +{ + .is_available = goldfish_camera_is_available, + .init = goldfish_camera_init, + .uninit = goldfish_camera_uninit, + .get_driver_name = goldfish_camera_get_driver_name, + .validate_frame_setting = goldfish_camera_validate_frame_setting, + .start_capture = goldfish_camera_start_capture, + .stop_capture = goldfish_camera_stop_capture, +}; + +static const struct imgdata_ops_s g_goldfish_camera_data_ops = +{ + .init = goldfish_camera_data_init, + .uninit = goldfish_camera_data_uninit, + .set_buf = goldfish_camera_data_set_buf, + .validate_frame_setting = goldfish_camera_data_validate_frame_setting, + .start_capture = goldfish_camera_data_start_capture, + .stop_capture = goldfish_camera_data_stop_capture, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int goldfish_camera_read_fully(FAR struct file *file, + FAR void *buffer, + size_t size) +{ + FAR char *p = (FAR char *)buffer; + + while (size > 0) + { + ssize_t n = file_read(file, p, size); + if (n < 0) + { + return n; + } + + p += n; + size -= n; + } + + return 0; +} + +static int goldfish_camera_write_fully(FAR struct file *file, + FAR const void *buffer, + size_t size) +{ + FAR const char *p = (FAR const char *)buffer; + + while (size > 0) + { + ssize_t n = file_write(file, p, size); + if (n < 0) + { + return n; + } + + p += n; + size -= n; + } + + return 0; +} + +static int goldfish_camera_send(FAR struct file *file, + FAR const char *format, + ...) +{ + char buf[256]; + va_list ap; + int len; + + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + + if (len < 0) + { + return len; + } + + return goldfish_camera_write_fully(file, buf, len + 1); +} + +static ssize_t goldfish_camera_recv(FAR struct file *file, + FAR void **data) +{ + char buf[9]; + size_t payload_size; + bool empty = !*data; + int ret; + + ret = goldfish_camera_read_fully(file, buf, 8); + if (ret < 0) + { + return ret; + } + + buf[8] = '\0'; + payload_size = strtoul(buf, NULL, 16); + + if (payload_size < 3) + { + return -EINVAL; + } + + ret = goldfish_camera_read_fully(file, buf, 3); + if (ret < 0) + { + return ret; + } + + if (memcmp(buf, "ok", 2) != 0) + { + return -EINVAL; + } + + if (buf[2] == '\0') + { + return 0; + } + + payload_size -= 3; + if (payload_size == 0) + { + return 0; + } + + if (empty) + { + *data = kmm_malloc(payload_size); + if (*data == NULL) + { + return -ENOMEM; + } + } + + ret = goldfish_camera_read_fully(file, *data, payload_size); + if (ret < 0) + { + if (empty) + { + kmm_free(*data); + } + + return ret; + } + + return payload_size; +} + +static ssize_t goldfish_camera_get_list(FAR goldfish_camera_priv_t **priv, + size_t size) +{ + FAR void *cameradata = NULL; + struct file file; + FAR char *data; + size_t count = 0; + int ret; + + /* Queries list of cameras connected to the host. */ + + ret = file_open(&file, + CONFIG_GOLDFISH_CAMERA_PIPE_PATH, + O_RDWR); + if (ret < 0) + { + verr("Failed to open: %s: %d\n", + CONFIG_GOLDFISH_CAMERA_PIPE_PATH, ret); + return ret; + } + + ret = goldfish_camera_send(&file, + "pipe:%s:%s", + g_qemudservice, + g_cameraservice); + if (ret < 0) + { + goto out; + } + + ret = goldfish_camera_send(&file, "list"); + if (ret < 0) + { + goto out; + } + + ret = goldfish_camera_recv(&file, &cameradata); + if (ret < 0) + { + goto out; + } + + /* Parse string + * + * For example: + * 'name=virtualscene channel=0 pix=876758866 dir=back framedims=640x480' + * + * Use the strstr function to parse name/channel/pix/dir/framedims + * of multiple devices by "name"; + * + * Use the strchr function to parse multiple size by ','; + */ + + data = (FAR char *)cameradata; + + while ((data = strstr(data, "name")) != NULL) + { + char sizelist[256]; + FAR char *sizedata = sizelist; + size_t sizecount = 0; + + priv[count] = kmm_zalloc(sizeof(goldfish_camera_priv_t)); + if (priv[count] == NULL) + { + verr("Failed to allocate instance\n"); + ret = -ENOMEM; + goto out; + } + + sscanf(data, + "name=%s channel=%"SCNu32" pix=%"SCNu32" dir=%s framedims=%s", + priv[count]->info.name, + &priv[count]->info.channel, + &priv[count]->info.pix, + priv[count]->info.dir, + sizelist); + + sscanf(sizedata, + "%"SCNu16"x%"SCNu16"", + &priv[count]->info.size[sizecount].width, + &priv[count]->info.size[sizecount].height); + + while ((sizedata = strchr(sizedata, ',')) != NULL) + { + if (++sizecount >= GOLDFISH_CAMERA_SIZE_MAX_NUMBER) + { + ret = -E2BIG; + goto err; + } + + sizedata++; + sscanf(sizedata, + "%"SCNu16"x%"SCNu16"", + &priv[count]->info.size[sizecount].width, + &priv[count]->info.size[sizecount].height); + } + + if (++count >= size) + { + ret = -E2BIG; + goto err; + } + + data++; + } + + ret = count; + goto out; + +err: + do + { + if (priv[count]) + { + kmm_free(priv[count]); + } + } + while (count--); + +out: + file_close(&file); + + if (cameradata) + { + kmm_free(cameradata); + } + + return ret; +} + +static int goldfish_camera_thread(int argc, FAR char *argv[]) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *) + ((uintptr_t)strtoul(argv[1], NULL, 16)); + int ret; + + while (1) + { + if (nxsem_wait(&priv->run) < 0) + { + continue; + } + + if (!priv->streaming) + { + return 0; + } + + ret = goldfish_camera_send(&priv->file, + "frame video=%"PRIu32" preview=0 " + "whiteb=1,1,1 expcomp=1 time=0", + priv->buf_size); + + if (ret < 0) + { + return ret; + } + +reload: + ret = goldfish_camera_recv(&priv->file, + (FAR void **)&priv->next_buf); + if (ret < 0) + { + return ret; + } + else if (ret == 0) + { + goto reload; + } + else + { + struct timespec ts; + struct timeval tv; + + DEBUGASSERT(ret == priv->buf_size); + + clock_systime_timespec(&ts); + TIMESPEC_TO_TIMEVAL(&tv, &ts); + priv->capture_cb(0, priv->buf_size, &tv, priv->capture_arg); + } + } +} + +/* Helper functions */ + +static uint32_t imgdata_fmt_to_v4l2(uint32_t pixelformat) +{ + switch (pixelformat) + { + case IMGDATA_PIX_FMT_YUV420P: + return V4L2_PIX_FMT_YUV420; + + case IMGDATA_PIX_FMT_YUYV: + return V4L2_PIX_FMT_YUYV; + + case IMGDATA_PIX_FMT_JPEG_WITH_SUBIMG: + return V4L2_PIX_FMT_JPEG; + + case IMGDATA_PIX_FMT_JPEG: + return V4L2_PIX_FMT_JPEG; + + case IMGDATA_PIX_FMT_RGB565: + return V4L2_PIX_FMT_RGB565; + + case IMGDATA_PIX_FMT_UYVY: + return V4L2_PIX_FMT_UYVY; + + default: + + /* Unsupported format */ + + return 0; + } +} + +/* Sensor op functions are mostly dummy */ + +static bool goldfish_camera_is_available(FAR struct imgsensor_s *sensor) +{ + return access(CONFIG_GOLDFISH_CAMERA_PIPE_PATH, F_OK) == 0; +} + +static int goldfish_camera_init(FAR struct imgsensor_s *sensor) +{ + return 0; +} + +static int goldfish_camera_uninit(FAR struct imgsensor_s *sensor) +{ + return 0; +} + +static FAR const char * +goldfish_camera_get_driver_name(FAR struct imgsensor_s *sensor) +{ + FAR goldfish_camera_priv_t *priv = container_of(sensor, + goldfish_camera_priv_t, + sensor); + return priv->info.name; +} + +static int +goldfish_camera_validate_frame_setting(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type, + uint8_t nr_fmt, + FAR imgsensor_format_t *fmt, + FAR imgsensor_interval_t *interval) +{ + return 0; +} + +static int goldfish_camera_start_capture(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type, + uint8_t nr_fmt, + FAR imgsensor_format_t *fmt, + FAR imgsensor_interval_t *interval) +{ + return 0; +} + +static int goldfish_camera_stop_capture(FAR struct imgsensor_s *sensor, + imgsensor_stream_type_t type) +{ + return 0; +} + +/* Data op functions do all the real work */ + +static int goldfish_camera_data_init(FAR struct imgdata_s *data) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + FAR char *argv[2]; + char arg1[32]; + int ret; + + ret = file_open(&priv->file, + CONFIG_GOLDFISH_CAMERA_PIPE_PATH, + O_RDWR); + if (ret < 0) + { + verr("Failed to open: %s: %d\n", + CONFIG_GOLDFISH_CAMERA_PIPE_PATH, ret); + return ret; + } + + nxsem_init(&priv->run, 0, 0); + priv->streaming = true; + + ret = goldfish_camera_send(&priv->file, + "pipe:%s:%s:name=%s", + g_qemudservice, + g_cameraservice, + priv->info.name); + if (ret < 0) + { + goto err; + } + + ret = goldfish_camera_send(&priv->file, "connect"); + if (ret < 0) + { + goto err; + } + + snprintf(arg1, sizeof(arg1), "%p", priv); + argv[0] = arg1; + argv[1] = NULL; + + ret = kthread_create("goldfish_camera_thread", + CONFIG_GOLDFISH_CAMERA_PRIORITY, + CONFIG_GOLDFISH_CAMERA_STACKSIZE, + goldfish_camera_thread, argv); + if (ret < 0) + { + goto err; + } + + priv->pid = ret; + return 0; + +err: + nxsem_destroy(&priv->run); + priv->streaming = false; + file_close(&priv->file); + return ret; +} + +static int goldfish_camera_data_uninit(FAR struct imgdata_s *data) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + int ret; + + ret = goldfish_camera_send(&priv->file, "disconnect"); + if (ret < 0) + { + return ret; + } + + priv->streaming = false; + nxsem_post(&priv->run); + nxsched_waitpid(priv->pid, NULL, 0, 0); + + nxsem_destroy(&priv->run); + file_close(&priv->file); + + return 0; +} + +static int goldfish_camera_data_validate_buf(FAR uint8_t *addr, + uint32_t size) +{ + if (!addr || ((uintptr_t)addr & 0x1f)) + { + return -EINVAL; + } + + return 0; +} + +static int goldfish_camera_data_set_buf(FAR struct imgdata_s *data, + FAR uint8_t *addr, + uint32_t size) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + int ret; + + ret = goldfish_camera_data_validate_buf(addr, size); + if (ret < 0) + { + return ret; + } + + priv->next_buf = addr; + priv->buf_size = size; + + nxsem_post(&priv->run); + + return 0; +} + +static int +goldfish_camera_data_validate_frame_setting(FAR struct imgdata_s *data, + uint8_t nr_datafmt, + FAR imgdata_format_t *datafmt, + FAR imgdata_interval_t *interval) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + int i; + + for (i = 0; i < GOLDFISH_CAMERA_SIZE_MAX_NUMBER; i++) + { + if (datafmt[IMGDATA_FMT_MAIN].width == priv->info.size[i].width && + datafmt[IMGDATA_FMT_MAIN].height == priv->info.size[i].height && + imgdata_fmt_to_v4l2(datafmt[IMGDATA_FMT_MAIN].pixelformat) + == priv->info.pix) + { + return 0; + } + } + + return -ENOTSUP; +} + +static int +goldfish_camera_data_start_capture(FAR struct imgdata_s *data, + uint8_t nr_datafmt, + FAR imgdata_format_t *datafmt, + FAR imgdata_interval_t *interval, + imgdata_capture_t callback, + FAR void *arg) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + int ret; + + ret = goldfish_camera_send(&priv->file, + "start dim=%dx%d pix=%d", + datafmt[IMGDATA_FMT_MAIN].width, + datafmt[IMGDATA_FMT_MAIN].height, + imgdata_fmt_to_v4l2( + datafmt[IMGDATA_FMT_MAIN].pixelformat)); + if (ret < 0) + { + return ret; + } + + priv->capture_cb = callback; + priv->capture_arg = arg; + + return 0; +} + +static int goldfish_camera_data_stop_capture(FAR struct imgdata_s *data) +{ + FAR goldfish_camera_priv_t *priv = (FAR goldfish_camera_priv_t *)data; + int ret; + + ret = goldfish_camera_send(&priv->file, "stop"); + if (ret < 0) + { + return ret; + } + + priv->capture_cb = NULL; + priv->capture_arg = NULL; + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int goldfish_camera_initialize(void) +{ + FAR goldfish_camera_priv_t *priv[GOLDFISH_CAMERA_MAX_NUMBER]; + FAR struct imgsensor_s *sensor; + char devpath[32]; + ssize_t number; + ssize_t i; + + number = goldfish_camera_get_list(priv, GOLDFISH_CAMERA_MAX_NUMBER); + if (number < 0) + { + return number; + } + + for (i = 0; i < number; i++) + { + priv[i]->sensor.ops = &g_goldfish_camera_ops; + priv[i]->data.ops = &g_goldfish_camera_data_ops; + + sensor = &priv[i]->sensor; + + if (i == 0) + { + snprintf(devpath, sizeof(devpath), "/dev/video"); + } + else + { + snprintf(devpath, sizeof(devpath), "/dev/video%zd", i); + } + + video_register(devpath, + &priv[i]->data, + &sensor, + 1); + } + + return 0; +} diff --git a/include/nuttx/video/goldfish_camera.h b/include/nuttx/video/goldfish_camera.h new file mode 100644 index 0000000000..07a538edc9 --- /dev/null +++ b/include/nuttx/video/goldfish_camera.h @@ -0,0 +1,63 @@ +/**************************************************************************** + * include/nuttx/video/goldfish_camera.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_VIDEO_GOLDFISH_CAMERA_H +#define __INCLUDE_NUTTX_VIDEO_GOLDFISH_CAMERA_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_GOLDFISH_CAMERA +int goldfish_camera_initialize(void); +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_VIDEO_GOLDFISH_CAMERA_H */