【Linux应用】V4L2的摄像头配置、获取等操作,并进行视频录制
【Linux应用】V4L2的摄像头配置、获取等操作,并进行视频录制
文章目录
- 查找摄像头驱动
- V4L2查询指令
- 查找视频格式
- 设置摄像头信息
- 申请帧缓冲、内存映射
- 入队、出队操作
- 开启、停止视频采集
- 缓存初始化
- 数据读取
- 视频帧保存到本地
- 附录:开发板快速上手:镜像烧录、串口shell、外设挂载、WiFi配置、SSH连接、文件交互(RADXA ZERO 3为例)
- ZERO 3烧录
- ZERO 3串口shell
- 外设挂载
- 连接WiFi
- SSH连接
- SSH的文件交互
- 换源
查找摄像头驱动
可通过V4L2来查找是否有/dev/videoX
的支持
v4l2-ctl --list-devices
V4L2查询指令
操作摄像头时 可用V4L2的指令宏进行摄像头操作
如:
struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, &cap);
每个指令都有对应不同的结构体传参
结构体定义如下:
struct v4l2_capability {__u8 driver[16]; /* 驱动的名字 */__u8 card[32]; /* 设备的名字 */__u8 bus_info[32]; /* 总线的名字 */__u32 version; /* 版本信息 */__u32 capabilities; /* 设备拥有的能力 */__u32 device_caps;__u32 reserved[3]; /* 保留字段 */
};
其中capabilities
定义了设备能力
可以使用以下函数来获取打印:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>int main(void)
{int fd = -1;/* 打开摄像头 */fd = open("/dev/video1", O_RDWR);if (0 > fd) {perror("open");return -1;}printf("fd = %d\n", fd);struct v4l2_capability cap; ioctl(fd, VIDIOC_QUERYCAP, &cap);printf("cap.driver = %s\n", cap.driver);printf("cap.card = %s\n", cap.card);printf("cap.bus_info = %s\n", cap.bus_info);printf("cap.version = %d\n", cap.version);printf("cap.capabilities = 0x%x\n", cap.capabilities);/* 判断是否是视频采集设备 */if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities)) {fprintf(stderr, "Error: No capture video device!\n");return -1;}/* 关闭摄像头 */close(fd);return 0;
}
查找视频格式
使用 VIDIOC_ENUM_FMT
可以枚举出设备所支持的所有像素格式,调用 ioctl()需要传入一个 struct v4l2_fmtdesc *
指针
ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *fmtdesc);struct v4l2_fmtdesc {__u32 index; /* Format number */__u32 type; /* enum v4l2_buf_type */__u32 flags;__u8 description[32]; /* Description string */__u32 pixelformat; /* Format fourcc */__u32 reserved[4];
};
应用:
struct v4l2_fmtdesc fmtdesc;/* 枚举出摄像头所支持的所有像素格式以及描述信息 */fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) {printf("fmt: %s <0x%x>\n", fmtdesc.description, fmtdesc.pixelformat);fmtdesc.index++;}
同时还有: VIDIOC_ENUM_FRAMESIZES
、VIDIOC_ENUM_FRAMEINTERVALS
、VIDIOC_G_FMT
、VIDIOC_S_FMT
、VIDIOC_G_PARM
、VIDIOC_S_PARM
等定义
用于获取相关信息
应用如下:
static void v4l2_enum_formats(void)
{struct v4l2_fmtdesc fmtdesc = {0};/* 枚举摄像头所支持的所有像素格式以及描述信息 */fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {// 将枚举出来的格式以及描述信息存放在数组中cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);fmtdesc.index++;}
}static void v4l2_print_formats(void)
{struct v4l2_frmsizeenum frmsize = {0};struct v4l2_frmivalenum frmival = {0};int i;frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for (i = 0; cam_fmts[i].pixelformat; i++) {printf("format<0x%x>, description<%s>\n", cam_fmts[i].pixelformat,cam_fmts[i].description);/* 枚举出摄像头所支持的所有视频采集分辨率 */frmsize.index = 0;frmsize.pixel_format = cam_fmts[i].pixelformat;frmival.pixel_format = cam_fmts[i].pixelformat;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) {printf("size<%d*%d> ",frmsize.discrete.width,frmsize.discrete.height);frmsize.index++;/* 获取摄像头视频采集帧率 */frmival.index = 0;frmival.width = frmsize.discrete.width;frmival.height = frmsize.discrete.height;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) {printf("<%dfps>", frmival.discrete.denominator /frmival.discrete.numerator);frmival.index++;}printf("\n");}printf("\n");}
}
设置摄像头信息
通过定义struct v4l2_format
、struct v4l2_streamparm streamparm
来进行设置
这里之前查看过摄像头信息 只能选择配置为MJPEG格式
同样 可以通过命令来查看支持的格式:
v4l2-ctl --list-formats-ext
并通过以下来进行设置:
ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt);
ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);
如:
static int v4l2_set_format(void)
{struct v4l2_format fmt = {0};struct v4l2_streamparm streamparm = {0};/* 设置帧格式 */fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type类型fmt.fmt.pix.width = 640; //视频帧宽度fmt.fmt.pix.height = 480;//视频帧高度fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;// 改为MJPEG格式if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt)) {fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));return -1;}// 验证MJPEG格式是否设置成功if (V4L2_PIX_FMT_MJPEG != fmt.fmt.pix.pixelformat) {fprintf(stderr, "Error: 设备不支持MJPEG格式!\n");return -1;}
/* fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; //像素格式if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt)) {fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));return -1;}
*//*** 判断是否已经设置为我们要求的RGB565像素格式如果没有设置成功表示该设备不支持RGB565像素格式 */
/* if (V4L2_PIX_FMT_RGB565 != fmt.fmt.pix.pixelformat) {fprintf(stderr, "Error: the device does not support RGB565 format!\n");return -1;}
*/frm_width = fmt.fmt.pix.width; //获取实际的帧宽度frm_height = fmt.fmt.pix.height;//获取实际的帧高度printf("视频帧大小<%d * %d>\n", frm_width, frm_height);/* 获取streamparm */streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);/** 判断是否支持帧率设置 **/if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability) {streamparm.parm.capture.timeperframe.numerator = 1;streamparm.parm.capture.timeperframe.denominator = 30;//30fpsif (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm)) {fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));return -1;}}return 0;
}
申请帧缓冲、内存映射
读取摄像头数据的方式有两种,一种是 read 方式,也就是直接通过 read()系统调用读取摄像头采集到的数据;另一种则是 streaming 方式
大部分设备都支持 streaming I/O 方式读取数据,使用
streaming I/O 方式,我们需要向设备申请帧缓冲,并将帧缓冲映射到应用程序进程地址空间中
ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);struct v4l2_requestbuffers {__u32 count; //申请帧缓冲的数量__u32 type; /* enum v4l2_buf_type */__u32 memory; /* enum v4l2_memory */__u32 reserved[2];
};enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
V4L2_MEMORY_DMABUF = 4,
};
通常 使用mmap 设置为 V4L2_MEMORY_MMAP
即可
struct v4l2_requestbuffers reqbuf;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = 3; // 申请 3 个帧缓冲
reqbuf.memory = V4L2_MEMORY_MMAP;
if (0 > ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) {
fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));
return -1;
}
在映射之前,需要查询帧缓冲的信息,譬如帧缓冲的长度、偏移量等信息,使用 VIDIOC_QUERYBUF
指令查询,使用方式如下所示:
ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);
可以使用三级缓冲示例:
struct v4l2_requestbuffers reqbuf;
struct v4l2_buffer buf;void *frm_base[3];
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = 3; // 申请 3 个帧缓冲
reqbuf.memory = V4L2_MEMORY_MMAP;
/* 申请 3 个帧缓冲 */
if (0 > ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) {
fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));
return -1;
}
/* 建立内存映射 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (buf.index = 0; buf.index < 3; buf.index++) {
ioctl(fd, VIDIOC_QUERYBUF, &buf);
frm_base[buf.index] = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.offset);
if (MAP_FAILED == frm_base[buf.index]) {
perror("mmap error");
return -1;
}
}
入队、出队操作
使用 VIDIOC_QBUF
指令将帧缓冲放入到内核的帧缓冲队列中,使用方式如下:
ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);
出队VIDIOC_DQBUF
ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);
开启、停止视频采集
使用 VIDIOC_DQBUF
指令开启视频采集,使用方式如下所示:
ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集
其中 type为enum v4l2_buf_type
类型
应用:
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(fd, VIDIOC_STREAMON, &type)) {
perror("ioctl error");
return -1;
}
如果要结束视频采集,使用 VIDIOC_STREAMOFF
指令,用法前面已经介绍了。使用示例如下所示:
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(fd, VIDIOC_STREAMOFF, &type)) {
perror("ioctl error");
return -1;
}
应用:
static int v4l2_stream_on(void)
{/* 打开摄像头、摄像头开始采集数据 */enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (0 > ioctl(v4l2_fd, VIDIOC_STREAMON, &type)) {fprintf(stderr, "ioctl error: VIDIOC_STREAMON: %s\n", strerror(errno));return -1;}return 0;
}
缓存初始化
static int v4l2_init_buffer(void)
{struct v4l2_requestbuffers reqbuf = {0};struct v4l2_buffer buf = {0};/* 申请帧缓冲 */reqbuf.count = FRAMEBUFFER_COUNT; //帧缓冲的数量reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;if (0 > ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqbuf)) {fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));return -1;}/* 建立内存映射 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf);buf_infos[buf.index].length = buf.length;buf_infos[buf.index].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE, MAP_SHARED,v4l2_fd, buf.m.offset);if (MAP_FAILED == buf_infos[buf.index].start) {perror("mmap error");return -1;}}/* 入队 */for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {if (0 > ioctl(v4l2_fd, VIDIOC_QBUF, &buf)) {fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));return -1;}}return 0;
}
数据读取
配置好缓存后 通过出队、入队操作循环读取
其中 buf_infos是在缓存配置中就已经配置好了的
while (1) {struct v4l2_buffer buf = {0};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;// 出队缓冲区if (ioctl(v4l2_fd, VIDIOC_DQBUF, &buf) < 0) {perror("VIDIOC_DQBUF");break;}printf("帧索引[%d] 数据大小: %d 字节\n", buf.index, buf.bytesused);// 可选:打印前16字节数据(调试用)// printf("前16字节: ");// for(int i=0; i<16 && i<buf.bytesused; i++)// printf("%02X ", ((u_int8_t*)buf_infos[buf.index].start)[i]);// printf("\n");v4l2_save_data(buf_infos[buf.index].start, buf.bytesused);// 重新入队缓冲区if (ioctl(v4l2_fd, VIDIOC_QBUF, &buf) < 0) {perror("VIDIOC_QBUF");break;}}
视频帧保存到本地
通过配置缓存区 获取到视频内容 并写入文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/fb.h>#define FB_DEV "/dev/fb0" //LCD设备节点
#define FRAMEBUFFER_COUNT 3 //帧缓冲数量/*** 摄像头像素格式及其描述信息 ***/
typedef struct camera_format {unsigned char description[32]; //字符串描述信息unsigned int pixelformat; //像素格式
} cam_fmt;/*** 描述一个帧缓冲的信息 ***/
typedef struct cam_buf_info {unsigned short *start; //帧缓冲起始地址unsigned int length; //帧缓冲长度
} cam_buf_info;static int width; //LCD宽度
static int height; //LCD高度
static int line_length;
static unsigned int *screen_base = NULL;//LCD显存基地址
static int fb_fd = -1; //LCD设备文件描述符
static int v4l2_fd = -1; //摄像头设备文件描述符
static cam_buf_info buf_infos[FRAMEBUFFER_COUNT];
static cam_fmt cam_fmts[10];
static int frm_width, frm_height; //视频帧宽度和高度static int v4l2_dev_init(const char *device)
{struct v4l2_capability cap = {0};/* 打开摄像头 */v4l2_fd = open(device, O_RDWR);if (0 > v4l2_fd) {fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));return -1;}/* 查询设备功能 */ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap);/* 判断是否是视频采集设备 */if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities)) {fprintf(stderr, "Error: %s: No capture video device!\n", device);close(v4l2_fd);return -1;}return 0;
}static void v4l2_enum_formats(void)
{struct v4l2_fmtdesc fmtdesc = {0};/* 枚举摄像头所支持的所有像素格式以及描述信息 */fmtdesc.index = 0;fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {// 将枚举出来的格式以及描述信息存放在数组中cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);fmtdesc.index++;}
}static void v4l2_print_formats(void)
{struct v4l2_frmsizeenum frmsize = {0};struct v4l2_frmivalenum frmival = {0};int i;frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for (i = 0; cam_fmts[i].pixelformat; i++) {printf("format<0x%x>, description<%s>\n", cam_fmts[i].pixelformat,cam_fmts[i].description);/* 枚举出摄像头所支持的所有视频采集分辨率 */frmsize.index = 0;frmsize.pixel_format = cam_fmts[i].pixelformat;frmival.pixel_format = cam_fmts[i].pixelformat;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) {printf("size<%d*%d> ",frmsize.discrete.width,frmsize.discrete.height);frmsize.index++;/* 获取摄像头视频采集帧率 */frmival.index = 0;frmival.width = frmsize.discrete.width;frmival.height = frmsize.discrete.height;while (0 == ioctl(v4l2_fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) {printf("<%dfps>", frmival.discrete.denominator /frmival.discrete.numerator);frmival.index++;}printf("\n");}printf("\n");}
}static int v4l2_set_format(void)
{struct v4l2_format fmt = {0};struct v4l2_streamparm streamparm = {0};/* 设置帧格式 */fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type类型fmt.fmt.pix.width = 1280; //视频帧宽度fmt.fmt.pix.height = 720;//视频帧高度fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;// 改为MJPEG格式if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt)) {fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));return -1;}// 验证MJPEG格式是否设置成功if (V4L2_PIX_FMT_MJPEG != fmt.fmt.pix.pixelformat) {fprintf(stderr, "Error: 设备不支持MJPEG格式!\n");return -1;}/* fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; //像素格式if (0 > ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt)) {fprintf(stderr, "ioctl error: VIDIOC_S_FMT: %s\n", strerror(errno));return -1;}
*//*** 判断是否已经设置为我们要求的RGB565像素格式如果没有设置成功表示该设备不支持RGB565像素格式 */
/* if (V4L2_PIX_FMT_RGB565 != fmt.fmt.pix.pixelformat) {fprintf(stderr, "Error: the device does not support RGB565 format!\n");return -1;}
*/frm_width = fmt.fmt.pix.width; //获取实际的帧宽度frm_height = fmt.fmt.pix.height;//获取实际的帧高度printf("视频帧大小<%d * %d>\n", frm_width, frm_height);/* 获取streamparm */streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);/** 判断是否支持帧率设置 **/if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability) {streamparm.parm.capture.timeperframe.numerator = 1;streamparm.parm.capture.timeperframe.denominator = 30;//30fpsif (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm)) {fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));return -1;}}return 0;
}static int v4l2_init_buffer(void)
{struct v4l2_requestbuffers reqbuf = {0};struct v4l2_buffer buf = {0};/* 申请帧缓冲 */reqbuf.count = FRAMEBUFFER_COUNT; //帧缓冲的数量reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuf.memory = V4L2_MEMORY_MMAP;if (0 > ioctl(v4l2_fd, VIDIOC_REQBUFS, &reqbuf)) {fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));return -1;}/* 建立内存映射 */buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf);buf_infos[buf.index].length = buf.length;buf_infos[buf.index].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE, MAP_SHARED,v4l2_fd, buf.m.offset);if (MAP_FAILED == buf_infos[buf.index].start) {perror("mmap error");return -1;}}/* 入队 */for (buf.index = 0; buf.index < FRAMEBUFFER_COUNT; buf.index++) {if (0 > ioctl(v4l2_fd, VIDIOC_QBUF, &buf)) {fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));return -1;}}return 0;
}static int v4l2_stream_on(void)
{/* 打开摄像头、摄像头开始采集数据 */enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (0 > ioctl(v4l2_fd, VIDIOC_STREAMON, &type)) {fprintf(stderr, "ioctl error: VIDIOC_STREAMON: %s\n", strerror(errno));return -1;}return 0;
}static FILE *video_fp = NULL;static void v4l2_save_data(const void *p, int size) {size_t written = fwrite(p, 1, size, video_fp);if (written != size) {perror("fwrite错误");}fflush(video_fp); // 强制刷新缓冲区printf("成功写入 %zu/%d 字节\n", written, size);
}/* 程序退出时添加 */
void v4l2_cleanup(void)
{if (video_fp) {fclose(video_fp);video_fp = NULL;}
}static void v4l2_read_data(void) {video_fp = fopen("./test.mjpeg", "ab");if (!video_fp) {perror("fopen失败");return;}while (1) {struct v4l2_buffer buf = {0};buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;// 出队缓冲区if (ioctl(v4l2_fd, VIDIOC_DQBUF, &buf) < 0) {perror("VIDIOC_DQBUF");break;}printf("帧索引[%d] 数据大小: %d 字节\n", buf.index, buf.bytesused);// 可选:打印前16字节数据(调试用)// printf("前16字节: ");// for(int i=0; i<16 && i<buf.bytesused; i++)// printf("%02X ", ((u_int8_t*)buf_infos[buf.index].start)[i]);// printf("\n");v4l2_save_data(buf_infos[buf.index].start, buf.bytesused);// 重新入队缓冲区if (ioctl(v4l2_fd, VIDIOC_QBUF, &buf) < 0) {perror("VIDIOC_QBUF");break;}}return;
}int main(int argc, char *argv[])
{atexit(v4l2_cleanup);video_fp = fopen("./test.mjpeg", "wb");if (video_fp) {fclose(video_fp);printf("文件已初始化\n");} else {perror("fopen");exit(EXIT_FAILURE);}FILE *init_fp = fopen("./test.mjpeg", "ab"); // 改用追加模式初始化if (init_fp) {fclose(init_fp);printf("文件准备就绪\n");}/* 初始化摄像头 */if (v4l2_dev_init("/dev/video0"))exit(EXIT_FAILURE);/* 枚举所有格式并打印摄像头支持的分辨率及帧率 */v4l2_enum_formats();v4l2_print_formats();/* 设置格式 */if (v4l2_set_format())exit(EXIT_FAILURE);/* 初始化帧缓冲:申请、内存映射、入队 */if (v4l2_init_buffer())exit(EXIT_FAILURE);/* 开启视频采集 */if (v4l2_stream_on())exit(EXIT_FAILURE);/* 读取数据:出队 */v4l2_read_data(); //在函数内循环采集数据、将其显示到LCD屏exit(EXIT_SUCCESS);
}
播放使用命令:
ffplay test.mjpeg
附录:开发板快速上手:镜像烧录、串口shell、外设挂载、WiFi配置、SSH连接、文件交互(RADXA ZERO 3为例)
开发板快速上手:镜像烧录、串口shell、外设挂载、WiFi配置、SSH连接、文件交互(RADXA ZERO 3为例)
ZERO 3烧录
ZERO 3有两种 最本质的就是一个带WiFi一个不带WiFi
ZERO 3作为一个Linux板 其存储支持从sd卡EFI启动
系统安装则直接通过Balena Etcher来进行
可以使用其Windows版本即可
官方文档:
官方文档快速上手描述
镜像下载则也可以使用官方镜像:
Radxa ZERO 3 Debian Build 6
该镜像是最初的镜像 很多依赖都没有
如果开发的话 可以直接下载第三方镜像 或者自己在原有官方镜像上去安装镜像包等等
烧录很简单 直接用工具选择U盘 选择镜像即可
下图为烧录好了的sd卡
除了系统分区外 就是一个config分区 其挂载根根目录名称即为config
ZERO 3串口shell
串口与ZERO 3链接如下图:
链接后 配置串口为
baudrate: 1500000
data bit: 8
stop bit: 1
parity : none
flow control: none
启动后 用户名和密码都为radxa
外设挂载
根据df
命令查询系统挂载点
其做好了的U盘外部存储空间为/config
连接WiFi
使用如下指令链接WiFi:
nmcli device wifi #扫描WiFi
sudo nmcli device wifi connect <ssid> password <passwd> #连接WiFi
连上后 使用ip a
命令即可查看连接状态和ip
SSH连接
使用命令sudo systemctl status ssh
查看ssh状态
前提是要安装ssh
如果没有安装 则通过shell安装ssh:
sudo apt-get update
sudo apt-get install openssh-server openssh-sftp-server
根据Active
判断是否需要重启ssh服务:
重启ssh服务:
sudo systemctl restart ssh
启动后如下:
Active
状态就被设置为了running
开机自启动ssh则使用命令:
sudo systemctl enable --now ssh
在PC上 支持ssh的设备中 使用以下指令链接到板子:
ssh [username]@[IP address] # or ssh [username]@[hostname]
如下图:
SSH的文件交互
若是在PC上作为主机去访问设备
那么就是在PC的cmd中运行shell
有的终端软件配备了ssh的文件管理传输功能
连上以后就可以直接搜素到当前目录下的各类文件 以便于实现文件管理
并且可以直接download
# 复制 Windows 文件到 Linux
scp D:\data\1.txt root@192.168.88.161:/root/data
# 复制 Windows 目录到 Linux(记得加 -r)
scp -r D:\data root@192.168.88.161:/root/data# 复制 Linux 文件到 Windows
scp root@192.168.88.161:/root/data/1.txt D:\data
# 复制 Linux 目录到 Windows(记得加 -r)
scp -r root@192.168.88.161:/root/data D:\data
前提是设备开启了ssh可以被链接
发过去后便能在板子上看到:
回传文件夹:
如果是Linux设备去连接Windows设备 则需要在Windows里面设置对应的服务 相关教程很多 这里不过多赘述
如果是Linux设备访问Linux设备 则主设备也要指定IP
scp root@192.168.88.161:/root/1.txt root@192.168.88.162:/root# 如果设置了Linux之间的免密登录,可这样写:
scp 192.168.88.161:///root/1.txt 192.168.88.162:///root
换源
Linux系统的源都在/etc/apt/
下 一般有两个文件
一个是sources.list
另外一个是sources.list.d
目录下的品牌list文件
如树莓派需要备份原本的源:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo cp /etc/apt/sources.list.d/raspi.list /etc/apt/sources.list.d/raspi.list.bak
而radxa的则是:
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo cp /etc/apt/sources.list.d/radxa.list /etc/apt/sources.list.d/radxa.list.bak
将原本的文件进行编辑:
sudo nano /etc/apt/sources.list
注释掉最初的源 并添加新的源:
deb https://mirrors.tuna.tsinghua.edu.cn/debian bookworm main contrib non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian bookworm-updates main contrib non-free-firmware
如图:
然后再换子目录下的源
sudo nano /etc/apt/sources.list.d/raspi.list
替换成:
deb https://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ bullseye main
不过很可惜 清华源没有radxa 但是可以换debian的源(/etc/apt/sources.list
):
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
换源后 执行
sudo apt-get upgrade
sudo apt-get update