| /**************************************************************************** |
| * drivers/video/goldfish_camera.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <debug.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <nuttx/nuttx.h> |
| #include <nuttx/kthread.h> |
| #include <nuttx/video/imgsensor.h> |
| #include <nuttx/video/imgdata.h> |
| #include <nuttx/video/video.h> |
| #include <nuttx/video/v4l2_cap.h> |
| |
| /**************************************************************************** |
| * 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, |
| uint8_t nr_datafmts, |
| FAR imgdata_format_t *datafmts, |
| 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 | O_CLOEXEC); |
| 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); |
| |
| if (priv->capture_cb == NULL) |
| { |
| return 0; |
| } |
| |
| 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 | O_CLOEXEC); |
| 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", |
| SCHED_PRIORITY_DEFAULT, |
| CONFIG_DEFAULT_TASK_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); |
| |
| 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, |
| uint8_t nr_datafmts, |
| FAR imgdata_format_t *datafmts, |
| 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); |
| } |
| |
| capture_register(devpath, &priv[i]->data, &sensor, 1); |
| } |
| |
| return 0; |
| } |