关于v4l2 接口介绍的可以参考这篇博文:
https://blog.csdn.net/zx3517288/article/details/51682530
测试程序的v4l2的主要参考代码
摄像头的设置
static int start_capture(int fd)
{
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//开始捕捉图像数据
return ioctl(fd, VIDIOC_STREAMON, &type);
}
struct v4l2_capability cap;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_streamparm streamparm;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
//setup camera
//查看设备功能
ret = ioctl(fd_video, VIDIOC_QUERYCAP, &cap);
if (ret < 0) {
APP_PRINT("requre VIDIOC_QUERYCAP fialed! \n");
return -1;
} else {
//APP_PRINT("requre VIDIOC_QUERYCAP ok! \n");
}
printf("\nCameravCapabilityInfo:\n");
printf("\tdriver:%s\n",cap.driver);
printf("\tcard:%s\n",cap.card);
printf("\tbus_info:%s\n",cap.bus_info);
printf("\tversion:%d\n",cap.version);
printf("\tcapabilities:%x\n",cap.capabilities);
if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) {
printf("\tCamera supports capture.\n");
} else {
printf("\tCamera didn't support capture.\n");
}
if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) {
printf("\tCamera supports streaming.\n");
} else {
printf("\tCamera didn't support streaming.\n");
}
// 得到摄像头所支持的所有格式
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("\nCamera Support video formats:\n");
while (ioctl(fd_video, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
printf("\t%d.%s\n", fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
//set video format
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = VIDEO_WIDTH;
fmt.fmt.pix.height = VIDEO_HEIGHT;
fmt.fmt.pix.pixelformat = VIDEO_PIXEL_FORMAT;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if(ioctl(fd_video, VIDIOC_S_FMT, &fmt) < 0){//设置图片格式
APP_PRINT("Ioctl VIDIOC_S_FMT error!!!\n");
return -1;
} else {
//APP_PRINT("Ioctl VIDIOC_S_FMT ok!!!\n");
}
if(ioctl(fd_video, VIDIOC_G_FMT, &fmt) < 0){//得到图片格式
APP_PRINT("Ioctl VIDIOC_G_FMT error!!!\n");
return -1;
} else {
//APP_PRINT("Ioctl VIDIOC_G_FMT ok!!!\n");
}
printf("\nV4l2FmtInfo:\n");
printf("\t fmt.type:\t\t%d\n",fmt.type);
printf("\t pix.pixelformat:\t%c%c%c%c\n", \
fmt.fmt.pix.pixelformat & 0xFF,\
(fmt.fmt.pix.pixelformat >> 8) & 0xFF, \
(fmt.fmt.pix.pixelformat >> 16) & 0xFF,\
(fmt.fmt.pix.pixelformat >> 24) & 0xFF);
printf("\t pix.width:\t\t%d\n",fmt.fmt.pix.width);
printf("\t pix.height:\t\t%d\n",fmt.fmt.pix.height);
printf("\t pix.field:\t\t%d\n",fmt.fmt.pix.field);
printf("\n");
//设置帧格式
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
streamparm.parm.capture.timeperframe.numerator = 1;
streamparm.parm.capture.timeperframe.denominator = 30;
streamparm.parm.capture.capturemode = 0;
if (ioctl(fd_video, VIDIOC_S_PARM, &streamparm) < 0) {
APP_PRINT("Ioctl VIDIOC_S_PARM error!!!\n");
return -1;
} else {
//APP_PRINT("Ioctl VIDIOC_S_PARM ok!!!\n");
}
//申请v4l2 buf
req.count = n_map_buffers;//申请缓冲数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd_video, VIDIOC_REQBUFS, &req);//申请缓冲
if (req.count < 2) {
APP_PRINT("Ioctl VIDIOC_REQBUFS error!!!\n");
return -1;
} else {
n_map_buffers = req.count;
//APP_PRINT("Ioctl VIDIOC_REQBUFS ok, req.count:%d !!!\n", req.count);
}
//映射v4l2 buf
memset(img_bufs, 0, (sizeof(img_bufs)/sizeof(img_bufs[9]))*sizeof(struct app_v4l2_buf));
for (buf_index = 0; buf_index < req.count; ++buf_index){
struct v4l2_buffer buf;//驱动中的一帧
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = buf_index;
if (ioctl(fd_video, VIDIOC_QUERYBUF, &buf) < 0){//映射用户空间
APP_PRINT("Ioctl VIDIOC_QUERYBUF error, index:%d!!!\n", buf_index);
return -1;
} else {
//APP_PRINT("Ioctl VIDIOC_QUERYBUF ok, index:%d!!!\n", buf_index);
}
img_bufs[buf_index].length = buf.length;
img_bufs[buf_index].start =(unsigned char*) mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_video, buf.m.offset);
if (MAP_FAILED == img_bufs[buf_index].start){
APP_PRINT("mmap error, index:%d!!!\n", buf_index);
return -1;
} else {
//APP_PRINT("mmap ok, index:%d!!!\n", buf_index);
}
//APP_PRINT("v4l2 buffer %d: address = 0x%x, length = %d \n",req.count, (unsigned int)img_bufs[buf_index].start, img_bufs[buf_index].length);
}
printf("V4l2 buf info:\n");
printf("\tBuf count:%d\n", req.count);
for (buf_index=0; buf_index<req.count; ++buf_index) {
printf("\tindex:%d map_address:0x%x length:%d \n", buf_index, (unsigned int)img_bufs[buf_index].start, img_bufs[buf_index].length);
}
printf("\n");
//申请到的缓冲进入队列
for (buf_index=0; buf_index<req.count; ++buf_index) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = buf_index;
//申请到的缓冲进入队列
if (ioctl(fd_video, VIDIOC_QBUF, &buf) < 0) {
APP_PRINT("Ioctl VIDIOC_QBUF error, index:%d!!!\n", buf_index);
return -1;
} else {
//APP_PRINT("Ioctl VIDIOC_QBUF ok, index:%d!!!\n", buf_index);
}
}
//开始捕捉图像数据
ret = start_capture(fd_video);
初始化成功会有如下信息输出:
CameravCapabilityInfo:
driver:uvcvideo
card:PC Camera
bus_info:usb-ci_hdrc.1-1.3
version:264536
capabilities:84200001
Camera supports capture.
Camera supports streaming.
Camera Support video formats:
1.Motion-JPEG
2.YUYV 4:2:2
V4l2FmtInfo:
fmt.type: 1
pix.pixelformat: MJPG
pix.width: 320
pix.height: 240
pix.field: 1
V4l2 buf info:
Buf count:16
index:0 map_address:0x76e2f000 length:230400
index:1 map_address:0x76df6000 length:230400
index:2 map_address:0x76dbd000 length:230400
index:3 map_address:0x76d84000 length:230400
index:4 map_address:0x76d4b000 length:230400
index:5 map_address:0x76d12000 length:230400
index:6 map_address:0x76cd9000 length:230400
index:7 map_address:0x76ca0000 length:230400
index:8 map_address:0x76c67000 length:230400
index:9 map_address:0x76c2e000 length:230400
index:10 map_address:0x76bf5000 length:230400
index:11 map_address:0x76bbc000 length:230400
index:12 map_address:0x76b83000 length:230400
index:13 map_address:0x76b4a000 length:230400
index:14 map_address:0x76b11000 length:230400
index:15 map_address:0x76ad8000 length:230400
这里进行摄像头数据格式设置之前首先要明确摄像头的参数:
- 支持数据格式
- 支持分辨率
- 支持帧率
摄像头数据获取
测试代码里面创建了一个线程专门获取摄像头数据主要代码如下:
void *process_data_thread_fn(void* arg)
{
struct v4l2_buffer buf;
int ret = 0;
FILE *fp_save_file = NULL;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(fd_video, &fds);
/* Timeout. */
tv.tv_sec = 10;
tv.tv_usec = 0;
unsigned char *ptr_write_fb = NULL;
char rgb888_file_name[128];
char local_sys_cmd[256];
unsigned int filesize;
struct stat statbuff;
while (1) {
usleep(1000 * 5);
if (!quit_flag) {
*/
//读取摄像头数据
//memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd_video, VIDIOC_DQBUF, &buf);//出列采集的帧缓冲,成功返回0
if (!ret) {
//要保存图像数据我们就保存
if (save_img_flag) {
if (index_save_image_file <= cnt_save_images) {
//将摄像头采集得到的数据写入文件中
sprintf(save_file_name, "save_%dx%d_%d.%s", VIDEO_WIDTH, VIDEO_HEIGHT, index_save_image_file, SUFFIX_SAVE_IMAGE(VIDEO_PIXEL_FORMAT));
fp_save_file = fopen(save_file_name, "w+");
if (NULL == fp_save_file) {
APP_PRINT("Open %s error!!!\n", save_file_name);
continue;
}
fwrite(img_bufs[buf.index].start, buf.bytesused , 1, fp_save_file); //buf.index 为获取到的v4l2 buf 队列中有数据buf的索引,我们可以通过这个索引获取到图像数据
fclose(fp_save_file);
APP_PRINT("Save %s ok!!!\n", save_file_name);
} else {
APP_PRINT("Alread save %d pics quit!!!\n", cnt_save_images);
break;
}
index_save_image_file++;
} else {
if (VIDEO_PIXEL_FORMAT == V4L2_PIX_FMT_JPEG) {
//将摄像头采集得到的数据写入文件中
sprintf(save_file_name, "save_%dx%d_%d.%s", VIDEO_WIDTH, VIDEO_HEIGHT, index_save_image_file, SUFFIX_SAVE_IMAGE(VIDEO_PIXEL_FORMAT));
fp_save_file = fopen(save_file_name, "w+");
if (NULL == fp_save_file) {
APP_PRINT("Open %s error!!!\n", save_file_name);
continue;
}
fwrite(img_bufs[buf.index].start, buf.bytesused , 1, fp_save_file);
fclose(fp_save_file);
//调用djpeg 解码 240x240 jpeg->rgb888 并且缩放至 240x180 保存到一个临时文件中
sprintf(rgb888_file_name, "save_%dx%d_%d.rgb888", 240, 180);
//./djpeg -rgb -scale 3/4 -outfile save_240x180_1.rgb888 save_320x240_1.jpeg
sprintf(local_sys_cmd, "./djpeg -rgb -scale 3/4 -outfile %s %s", rgb888_file_name, save_file_name);
system(local_sys_cmd);
sprintf(local_sys_cmd, "rm %s", save_file_name);
system(local_sys_cmd);
//将缩放后的 rgb888 读到buf里面来
fp_save_file = fopen(rgb888_file_name, "r");
if (NULL == fp_save_file) {
APP_PRINT("Open %s error!!!\n", save_file_name);
continue;
}
stat(rgb888_file_name, &statbuff);
filesize = statbuff.st_size;
fread(rgb888_buf_full, filesize, 1, fp_save_file);
//APP_PRINT("Index:%d file size:%u\n", index_save_image_file, filesize);
fclose(fp_save_file);
sprintf(local_sys_cmd, "rm %s", rgb888_file_name);
system(local_sys_cmd);
index_save_image_file++;
//fb 显示目前只支持 240x180
ptr_write_fb = (unsigned char *)(fbp + ((VIDEO_HEIGHT-180)/2)*VIDEO_WIDTH*2);
cvt_rgb888_2_rgb555_lcd_order(rgb888_buf_full, rgb565_buf_full, 240, 180);
memcpy(ptr_write_fb, rgb565_buf_full, 240 * 180 *2);
ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &vinfo);
if (ret < 0) {
APP_PRINT("FBIOPAN_DISPLAY frame_num:%d error \n", index_save_image_file);
}
}
}
} else {
APP_PRINT("VIDIOC_DQBUF error!!!\n");
}
ret = ioctl(fd_video,VIDIOC_QBUF, &buf);
if (ret < 0) {
APP_PRINT("VIDIOC_QBUF error, buf_index:%d, errno:%d !!!\n", buf.index, errno);
}
} else {
APP_PRINT("Recevie quit flag,quit!!!\n");
break;
}
}
return 0;
}
通过上面的设置,我们可以连续的从摄像头获取到320x240格式的jpeg格式的视频帧数据。
摄像头jpeg数据的显示
有个问题就是我们lcd分辨率为240x240且数据格式rgb555,摄像头最小分辨率为320x240,为了让摄像头图像能在lcd上显示,我们对320x240 jpeg摄像头数据进行如下处理:
320x240-jpeg-buf---->保存为320x240 jpeg图像--借助libjpeg-turbo-cjpeg进行处理->240x180 rgb888格式图像->读出240x180 rgb888格式图片到内存->240x180 rgb888buf------>240x180-rgb565 buf->更新framebuf->数据通过spi发送到lcd
代码中用到的libjpeg-turbo在git地址:
https://gitee.com/wllw7176/self_100ask_imx6ull/tree/master/self_dir/third_part/libjpeg-turbo-1.5.3
编译这个库只要执行里面的mybuild.sh就可以了,上面测的代码用到的cjpeg和库就在编译后产生的文件中,将这些文件复制到test_spi_lcd_st7789_jpeg所在目录就可以了。
test_spi_lcd_st7789_jpeg 为测试使用的执行程序,编译后直接产生。