nuttx-apps/examples/camera/camera_main.c
wangjianyu3 52b36deb13 examples/camera: use MMAP with USERPTR fallback and fix NX stride
Use MMAP buffer allocation by default so the driver can manage
DMA-capable memory with proper alignment and cache attributes.
Fall back to USERPTR if the driver does not support MMAP.

Also fix NX framebuffer stride calculation to use the actual
display width instead of hardcoded values.

Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
2026-04-09 10:34:06 -03:00

821 lines
23 KiB
C

/****************************************************************************
* apps/examples/camera/camera_main.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 <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
#include <nuttx/video/video.h>
#include <nuttx/video/v4l2_cap.h>
#include "camera_fileutil.h"
#include "camera_bkgd.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define IMAGE_JPG_SIZE (512*1024) /* 512kB for FullHD Jpeg file. */
#define IMAGE_RGB_SIZE (320*240*2) /* QVGA RGB565 */
#define VIDEO_BUFNUM (3)
#define STILL_BUFNUM (1)
#define MAX_CAPTURE_NUM (100)
#define DEFAULT_CAPTURE_NUM (10)
#define START_CAPTURE_TIME (5) /* seconds */
#define KEEP_VIDEO_TIME (10) /* seconds */
#define APP_STATE_BEFORE_CAPTURE (0)
#define APP_STATE_UNDER_CAPTURE (1)
#define APP_STATE_AFTER_CAPTURE (2)
#define CAMERA_DEV_PATH "/dev/video"
/****************************************************************************
* Private Types
****************************************************************************/
struct v_buffer
{
FAR uint32_t *start;
uint32_t length;
};
typedef struct v_buffer v_buffer_t;
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int camera_prepare(int fd, enum v4l2_buf_type type,
uint32_t buf_mode, uint32_t pixformat,
uint16_t hsize, uint16_t vsize,
FAR struct v_buffer **vbuf,
uint8_t buffernum, int buffersize,
FAR uint32_t *memory);
static void free_buffer(FAR struct v_buffer *buffers, uint8_t bufnum);
static int parse_arguments(int argc, FAR char *argv[],
FAR int *capture_num,
FAR enum v4l2_buf_type *type);
static int get_camimage(int fd, FAR struct v4l2_buffer *v4l2_buf,
enum v4l2_buf_type buf_type, uint32_t memory);
static int release_camimage(int fd, FAR struct v4l2_buffer *v4l2_buf);
static int start_stillcapture(int v_fd, enum v4l2_buf_type capture_type);
static int stop_stillcapture(int v_fd, enum v4l2_buf_type capture_type);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: camera_prepare()
*
* Description:
* Allocate frame buffer for camera and queue the allocated buffer
* into video driver.
****************************************************************************/
static int camera_prepare(int fd, enum v4l2_buf_type type,
uint32_t buf_mode, uint32_t pixformat,
uint16_t hsize, uint16_t vsize,
FAR struct v_buffer **vbuf,
uint8_t buffernum, int buffersize,
FAR uint32_t *memory)
{
int ret;
int cnt;
struct v4l2_format fmt =
{
0
};
struct v4l2_requestbuffers req =
{
0
};
struct v4l2_buffer buf =
{
0
};
/* VIDIOC_S_FMT set format */
fmt.type = type;
fmt.fmt.pix.width = hsize;
fmt.fmt.pix.height = vsize;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
fmt.fmt.pix.pixelformat = pixformat;
ret = ioctl(fd, VIDIOC_S_FMT, (uintptr_t)&fmt);
if (ret < 0)
{
printf("Failed to VIDIOC_S_FMT: errno = %d\n", errno);
return ret;
}
/* VIDIOC_REQBUFS: try MMAP first (driver-managed DMA buffers).
* Fall back to USERPTR if the driver does not support MMAP.
*/
req.type = type;
req.memory = V4L2_MEMORY_MMAP;
req.count = buffernum;
req.mode = buf_mode;
ret = ioctl(fd, VIDIOC_REQBUFS, (uintptr_t)&req);
if (ret < 0)
{
req.memory = V4L2_MEMORY_USERPTR;
ret = ioctl(fd, VIDIOC_REQBUFS, (uintptr_t)&req);
if (ret < 0)
{
printf("Failed to VIDIOC_REQBUFS: errno = %d\n", errno);
return ret;
}
}
*memory = req.memory;
/* Prepare v_buffer array to track buffer metadata */
*vbuf = malloc(sizeof(v_buffer_t) * buffernum);
if (!(*vbuf))
{
printf("Out of memory for array of v_buffer_t[%d]\n", buffernum);
return ERROR;
}
for (cnt = 0; cnt < buffernum; cnt++)
{
if (req.memory == V4L2_MEMORY_MMAP)
{
/* VIDIOC_QUERYBUF: get driver-allocated buffer metadata */
memset(&buf, 0, sizeof(v4l2_buffer_t));
buf.type = type;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = cnt;
ret = ioctl(fd, VIDIOC_QUERYBUF, (uintptr_t)&buf);
if (ret < 0)
{
printf("Failed to VIDIOC_QUERYBUF %d: errno = %d\n",
cnt, errno);
free(*vbuf);
*vbuf = NULL;
return ret;
}
(*vbuf)[cnt].start = NULL;
(*vbuf)[cnt].length = buf.length;
}
else
{
(*vbuf)[cnt].length = buffersize;
/* Note:
* VIDIOC_QBUF set buffer pointer.
* Buffer pointer must be 32bytes aligned.
*/
(*vbuf)[cnt].start = memalign(32, buffersize);
if (!(*vbuf)[cnt].start)
{
printf("Out of memory for image buffer of %d/%d\n",
cnt, buffernum);
/* Release allocated memory. */
while (cnt--)
{
free((*vbuf)[cnt].start);
}
free(*vbuf);
*vbuf = NULL;
return ERROR;
}
}
}
/* VIDIOC_QBUF enqueue buffer */
for (cnt = 0; cnt < buffernum; cnt++)
{
memset(&buf, 0, sizeof(v4l2_buffer_t));
buf.type = type;
buf.memory = req.memory;
buf.index = cnt;
buf.length = (*vbuf)[cnt].length;
if (req.memory == V4L2_MEMORY_USERPTR)
{
buf.m.userptr = (uintptr_t)(*vbuf)[cnt].start;
}
ret = ioctl(fd, VIDIOC_QBUF, (uintptr_t)&buf);
if (ret)
{
printf("Fail QBUF %d\n", errno);
free_buffer(*vbuf, buffernum);
*vbuf = NULL;
return ERROR;
}
}
/* VIDIOC_STREAMON start stream */
ret = ioctl(fd, VIDIOC_STREAMON, (uintptr_t)&type);
if (ret < 0)
{
printf("Failed to VIDIOC_STREAMON: errno = %d\n", errno);
free_buffer(*vbuf, buffernum);
*vbuf = NULL;
return ret;
}
return OK;
}
/****************************************************************************
* Name: free_buffer()
*
* Description:
* All free allocated memory of v_buffer.
****************************************************************************/
static void free_buffer(FAR struct v_buffer *buffers, uint8_t bufnum)
{
uint8_t cnt;
if (buffers)
{
for (cnt = 0; cnt < bufnum; cnt++)
{
if (buffers[cnt].start)
{
free(buffers[cnt].start);
}
}
free(buffers);
}
}
/****************************************************************************
* Name: parse_argument()
*
* Description:
* Parse and decode commandline arguments.
****************************************************************************/
static int parse_arguments(int argc, FAR char *argv[],
FAR int *capture_num,
FAR enum v4l2_buf_type *type)
{
if (argc == 1)
{
*capture_num = DEFAULT_CAPTURE_NUM;
*type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
}
else if (argc == 2)
{
if (strncmp(argv[1], "-jpg", 5) == 0)
{
*capture_num = DEFAULT_CAPTURE_NUM;
*type = V4L2_BUF_TYPE_STILL_CAPTURE;
}
else
{
*capture_num = atoi(argv[1]);
if (*capture_num < 0 || *capture_num > MAX_CAPTURE_NUM)
{
printf("Invalid capture num(%d). must be >=0 and <=%d\n",
*capture_num, MAX_CAPTURE_NUM);
return ERROR;
}
*type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
}
}
else if (argc == 3)
{
if (strncmp(argv[1], "-jpg", 5) == 0)
{
*capture_num = atoi(argv[2]);
if (*capture_num < 0 || *capture_num > MAX_CAPTURE_NUM)
{
printf("Invalid capture num(%d). must be >=0 and <=%d\n",
*capture_num, MAX_CAPTURE_NUM);
return ERROR;
}
*type = V4L2_BUF_TYPE_STILL_CAPTURE;
}
else
{
printf("Invalid argument 1 : %s\n", argv[1]);
return ERROR;
}
}
else
{
printf("Too many arguments\n");
return ERROR;
}
return OK;
}
/****************************************************************************
* Name: get_camimage()
*
* Description:
* DQBUF camera frame buffer from video driver with taken picture data.
****************************************************************************/
static int get_camimage(int fd, FAR struct v4l2_buffer *v4l2_buf,
enum v4l2_buf_type buf_type, uint32_t memory)
{
int ret;
/* VIDIOC_DQBUF acquires captured data. */
memset(v4l2_buf, 0, sizeof(v4l2_buffer_t));
v4l2_buf->type = buf_type;
v4l2_buf->memory = memory;
ret = ioctl(fd, VIDIOC_DQBUF, (uintptr_t)v4l2_buf);
if (ret)
{
printf("Fail DQBUF %d\n", errno);
return ERROR;
}
return OK;
}
/****************************************************************************
* Name: release_camimage()
*
* Description:
* Re-QBUF to set used frame buffer into video driver.
****************************************************************************/
static int release_camimage(int fd, FAR struct v4l2_buffer *v4l2_buf)
{
int ret;
/* VIDIOC_QBUF sets buffer pointer into video driver again. */
ret = ioctl(fd, VIDIOC_QBUF, (uintptr_t)v4l2_buf);
if (ret)
{
printf("Fail QBUF %d\n", errno);
return ERROR;
}
return OK;
}
/****************************************************************************
* Name: start_stillcapture()
*
* Description:
* Start STILL capture stream by TAKEPICT_START if buf_type is
* STILL_CAPTURE.
****************************************************************************/
static int start_stillcapture(int v_fd, enum v4l2_buf_type capture_type)
{
int ret;
if (capture_type == V4L2_BUF_TYPE_STILL_CAPTURE)
{
ret = ioctl(v_fd, VIDIOC_TAKEPICT_START, 0);
if (ret < 0)
{
printf("Failed to start taking picture\n");
return ERROR;
}
}
return OK;
}
/****************************************************************************
* Name: stop_stillcapture()
*
* Description:
* Stop STILL capture stream by TAKEPICT_STOP if buf_type is STILL_CAPTURE.
****************************************************************************/
static int stop_stillcapture(int v_fd, enum v4l2_buf_type capture_type)
{
int ret;
if (capture_type == V4L2_BUF_TYPE_STILL_CAPTURE)
{
ret = ioctl(v_fd, VIDIOC_TAKEPICT_STOP, false);
if (ret < 0)
{
printf("Failed to stop taking picture\n");
return ERROR;
}
}
return OK;
}
/****************************************************************************
* Name: get_imgsensor_name()
*
* Description:
* Get image sensor driver name by querying device capabilities.
****************************************************************************/
static FAR const char *get_imgsensor_name(int fd)
{
static struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, (uintptr_t)&cap);
return (FAR const char *)cap.driver;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main()
*
* Description:
* main routine of this example.
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int ret;
int v_fd;
int capture_num = DEFAULT_CAPTURE_NUM;
enum v4l2_buf_type capture_type = V4L2_BUF_TYPE_STILL_CAPTURE;
struct v4l2_buffer v4l2_buf;
FAR const char *save_dir;
FAR const char *sensor;
uint16_t w;
uint16_t h;
int is_eternal;
int app_state;
struct timeval start;
struct timeval now;
struct timeval delta;
struct timeval wait;
FAR struct v_buffer *buffers_video = NULL;
FAR struct v_buffer *buffers_still = NULL;
uint32_t video_memory = V4L2_MEMORY_USERPTR;
uint32_t still_memory = V4L2_MEMORY_USERPTR;
/* ===== Parse and Check arguments ===== */
ret = parse_arguments(argc, argv, &capture_num, &capture_type);
if (ret != OK)
{
printf("usage: %s ([-jpg]) ([capture num])\n", argv[0]);
return ERROR;
}
/* ===== Initialization Code ===== */
/* Initialize NX graphics subsystem to use LCD */
#ifdef CONFIG_EXAMPLES_CAMERA_OUTPUT_LCD
ret = nximage_initialize();
if (ret < 0)
{
printf("camera_main: Failed to get NX handle: %d\n", errno);
return ERROR;
}
#endif
/* Select storage to save image files */
save_dir = futil_initialize();
/* Initialize video driver to create a device file */
ret = capture_initialize(CAMERA_DEV_PATH);
if (ret != 0)
{
printf("ERROR: Failed to initialize video: errno = %d\n", errno);
goto exit_without_cleaning_videodriver;
}
/* Open the device file. */
v_fd = open(CAMERA_DEV_PATH, 0);
if (v_fd < 0)
{
printf("ERROR: Failed to open video.errno = %d\n", errno);
ret = ERROR;
goto exit_without_cleaning_buffer;
}
/* Prepare for STILL_CAPTURE stream.
*
* The video buffer mode is V4L2_BUF_MODE_FIFO mode.
* In this FIFO mode, if all VIDIOC_QBUFed frame buffers are captured image
* and no additional frame buffers are VIDIOC_QBUFed, the capture stops and
* waits for new VIDIOC_QBUFed frame buffer.
* And when new VIDIOC_QBUF is executed, the capturing is resumed.
*
* Allocate frame buffers for JPEG size (512KB).
* Set FULLHD size in ISX012 case, QUADVGA size in ISX019 case or other
* image sensors,
* Number of frame buffers is defined as STILL_BUFNUM(1).
* And all allocated memories are VIDIOC_QBUFed.
*/
if (capture_num != 0 && capture_type == V4L2_BUF_TYPE_STILL_CAPTURE)
{
/* Determine image size from connected image sensor name,
* because video driver does not support VIDIOC_ENUM_FRAMESIZES
* for now.
*/
sensor = get_imgsensor_name(v_fd);
if (strncmp(sensor, "ISX012", strlen("ISX012")) == 0)
{
w = VIDEO_HSIZE_FULLHD;
h = VIDEO_VSIZE_FULLHD;
}
else if (strncmp(sensor, "ISX019", strlen("ISX019")) == 0)
{
w = VIDEO_HSIZE_QUADVGA;
h = VIDEO_VSIZE_QUADVGA;
}
else
{
w = VIDEO_HSIZE_QUADVGA;
h = VIDEO_VSIZE_QUADVGA;
}
ret = camera_prepare(v_fd, V4L2_BUF_TYPE_STILL_CAPTURE,
V4L2_BUF_MODE_FIFO, V4L2_PIX_FMT_JPEG,
w, h,
&buffers_still, STILL_BUFNUM, IMAGE_JPG_SIZE,
&still_memory);
if (ret != OK)
{
goto exit_this_app;
}
}
/* Prepare for VIDEO_CAPTURE stream.
*
* The video buffer mode is V4L2_BUF_MODE_RING mode.
* In this RING mode, if all VIDIOC_QBUFed frame buffers are captured image
* and no additional frame buffers are VIDIOC_QBUFed, the capture continues
* as the oldest image in the V4L2_BUF_QBUFed frame buffer is reused in
* order from the captured frame buffer and a new camera image is
* recaptured.
*
* Allocate frame buffers for QVGA RGB565 size (320x240x2=150KB).
* Number of frame buffers is defined as VIDEO_BUFNUM(3).
* And all allocated memories are VIDIOC_QBUFed.
*/
ret = camera_prepare(v_fd, V4L2_BUF_TYPE_VIDEO_CAPTURE,
V4L2_BUF_MODE_RING, V4L2_PIX_FMT_RGB565,
VIDEO_HSIZE_QVGA, VIDEO_VSIZE_QVGA,
&buffers_video, VIDEO_BUFNUM, IMAGE_RGB_SIZE,
&video_memory);
if (ret != OK)
{
goto exit_this_app;
}
/* This application has 3 states.
*
* APP_STATE_BEFORE_CAPTURE:
* This state waits 5 seconds (defined as START_CAPTURE_TIME)
* with displaying preview (VIDEO_CAPTURE stream image) on LCD.
* After 5 seconds, state will be changed to APP_STATE_UNDER_CAPTURE.
*
* APP_STATE_UNDER_CAPTURE:
* This state will start taking picture and store the image into files.
* Number of taking pictures is set capture_num variable.
* It can be changed by command line argument.
* After finishing taking pictures, the state will be changed to
* APP_STATE_AFTER_CAPTURE.
*
* APP_STATE_AFTER_CAPTURE:
* This state waits 10 seconds (defined as KEEP_VIDEO_TIME)
* with displaying preview (VIDEO_CAPTURE stream image) on LCD.
* After 10 seconds, this application will be finished.
*
* Notice:
* If capture_num is set '0', state will stay APP_STATE_BEFORE_CAPTURE.
*/
app_state = APP_STATE_BEFORE_CAPTURE;
/* Show this application behavior. */
if (capture_num == 0)
{
is_eternal = 1;
printf("Start video this mode is eternal."
" (Non stop, non save files.)\n");
#ifndef CONFIG_EXAMPLES_CAMERA_OUTPUT_LCD
printf("This mode should be run with LCD display\n");
#endif
}
else
{
is_eternal = 0;
wait.tv_sec = START_CAPTURE_TIME;
wait.tv_usec = 0;
printf("Take %d pictures as %s file in %s after %d seconds.\n",
capture_num,
capture_type == V4L2_BUF_TYPE_STILL_CAPTURE ? "JPEG" : "RGB",
save_dir, START_CAPTURE_TIME);
printf(" After finishing taking pictures,"
" this app will be finished after %d seconds.\n",
KEEP_VIDEO_TIME);
}
gettimeofday(&start, NULL);
/* ===== Main Loop ===== */
while (1)
{
switch (app_state)
{
/* BEFORE_CAPTURE and AFTER_CAPTURE is waiting for expiring the
* time.
* In the meantime, Capturing VIDEO image to show pre-view on LCD.
*/
case APP_STATE_BEFORE_CAPTURE:
case APP_STATE_AFTER_CAPTURE:
ret = get_camimage(v_fd, &v4l2_buf,
V4L2_BUF_TYPE_VIDEO_CAPTURE, video_memory);
if (ret != OK)
{
goto exit_this_app;
}
#ifdef CONFIG_EXAMPLES_CAMERA_OUTPUT_LCD
nximage_draw((FAR void *)v4l2_buf.m.userptr,
VIDEO_HSIZE_QVGA, VIDEO_VSIZE_QVGA);
#endif
ret = release_camimage(v_fd, &v4l2_buf);
if (ret != OK)
{
goto exit_this_app;
}
if (!is_eternal)
{
gettimeofday(&now, NULL);
timersub(&now, &start, &delta);
if (timercmp(&delta, &wait, > /* For checkpatch */))
{
printf("Expire time is pasted. GoTo next state.\n");
if (app_state == APP_STATE_BEFORE_CAPTURE)
{
app_state = APP_STATE_UNDER_CAPTURE;
}
else
{
ret = OK;
goto exit_this_app;
}
}
}
break; /* Finish APP_STATE_BEFORE_CAPTURE or APP_STATE_AFTER_CAPTURE */
/* UNDER_CAPTURE is taking pictures until number of capture_num
* value.
* This state stays until finishing all pictures.
*/
case APP_STATE_UNDER_CAPTURE:
printf("Start capturing...\n");
ret = start_stillcapture(v_fd, capture_type);
if (ret != OK)
{
goto exit_this_app;
}
while (capture_num)
{
ret = get_camimage(v_fd, &v4l2_buf, capture_type,
capture_type ==
V4L2_BUF_TYPE_STILL_CAPTURE ?
still_memory : video_memory);
if (ret != OK)
{
goto exit_this_app;
}
futil_writeimage(
(FAR uint8_t *)v4l2_buf.m.userptr,
(size_t)v4l2_buf.bytesused,
capture_type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
"RGB" : "JPG");
ret = release_camimage(v_fd, &v4l2_buf);
if (ret != OK)
{
goto exit_this_app;
}
capture_num--;
}
ret = stop_stillcapture(v_fd, capture_type);
if (ret != OK)
{
goto exit_this_app;
}
app_state = APP_STATE_AFTER_CAPTURE;
wait.tv_sec = KEEP_VIDEO_TIME;
wait.tv_usec = 0;
gettimeofday(&start, NULL);
printf("Finished capturing...\n");
break; /* Finish APP_STATE_UNDER_CAPTURE */
default:
printf("Unknown error is occurred.. state=%d\n", app_state);
goto exit_this_app;
break;
}
}
exit_this_app:
/* Close video device file makes dequeue all buffers */
close(v_fd);
free_buffer(buffers_video, VIDEO_BUFNUM);
free_buffer(buffers_still, STILL_BUFNUM);
exit_without_cleaning_buffer:
capture_uninitialize(CAMERA_DEV_PATH);
exit_without_cleaning_videodriver:
#ifdef CONFIG_EXAMPLES_CAMERA_OUTPUT_LCD
nximage_finalize();
#endif
return ret;
}