incubator-nuttx/drivers/video/goldfish_camera.c

786 lines
20 KiB
C

/****************************************************************************
* 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 <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>
/****************************************************************************
* 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 | 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,
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;
}