基本思想:因为手中有一块RK3399 PRO的开发板,外接了AHD的摄像头,为了实现实时获取视频帧,所以需要学习一下v4l2编程。。
第一步:先拷贝一张图,整个v4l2的取帧流程
具体讲解参考这个大佬的bilibili和有道笔记 9118c374edb&type=note&_time=1652171642765">https://note.youdao.com/ynoteshare/index.html?id=7c4c0e28888d03ec70d339118c374edb&type=note&_time=1652171642765
第一步
查看支持的显示 分辨率
ubutnu@ubuntu~$: v4l2-ctl --list-formats-ext -d /dev/video0
代码,完成摄像头的帧获取和写入本地显示
#include <iostream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include <zconf.h>
#include<linux/videodev2.h>
#include<sys/mman.h>
int main() {
//打开设备
int fd=open("/dev/video0",O_RDWR);
if(fd<0){
perror("open device fail\n");
return -1;
}
// 获取摄像头支持的格式
//获取摄像头支持的格式
struct v4l2_fmtdesc v4fmt;
v4fmt.index=0;
v4fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret=ioctl(fd,VIDIOC_ENUM_FMT,&v4fmt );
if(ret<0){
perror("acvquire fail\n");
}
printf("%s\n",v4fmt.description);
unsigned char *p=(unsigned char*)&v4fmt.pixelformat;
printf("%c %c %c %c\n",p[0],p[1],p[2],p[3]);
// 设置摄像头支持的格式
//设置摄像头支持的格式
struct v4l2_format vFormat;
vFormat.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
vFormat.fmt.pix.width=640;
vFormat.fmt.pix.height=480;
vFormat.fmt.pix.pixelformat=V4L2_PIX_FMT_MJPEG;
ret=ioctl(fd,VIDIOC_S_FMT,&vFormat);
if(ret<0){
perror("set fail\n");
}
//申请内核空间
struct v4l2_requestbuffers vqbuff;
vqbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
vqbuff.count=4;
vqbuff.memory=V4L2_MEMORY_MMAP;
ret =ioctl(fd,VIDIOC_REQBUFS,&vqbuff);
if(ret<0){
perror("buff fail\n");
}
//申请内存空间
struct v4l2_buffer vbuff;
vbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
unsigned char * mptr[4];
for(int i=0;i<4;i++){
vbuff.index=i;
ret=ioctl(fd,VIDIOC_QUERYBUF,&vbuff);
if(ret<0)
{
perror("requeire buff fail\n");
}
mptr[i]= (unsigned char *s)mmap(NULL,vbuff.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,vbuff.m.offset);
//通知完毕
ret=ioctl(fd,VIDIOC_QBUF,&vbuff);
if(ret<0){
perror("put fail");
}
}
// 开始采集
int type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(fd,VIDIOC_STREAMON,&type);
if(ret<0){
perror("open fail");
}
//从队列中取数据
struct v4l2_buffer readbuff;
readbuff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret=ioctl(fd,VIDIOC_DQBUF,&readbuff);
if(ret<0){
perror("read fail");
}
FILE* file=fopen("example.jpg","w+");
fwrite(mptr[readbuff.index],readbuff.length,1,file);
fclose(file);
//通知内核 已经使用完
ret=ioctl(fd,VIDIOC_QBUF,&readbuff);
if(ret<0){
perror("put quee fail\n");
}
//停止采集
ret=ioctl(fd,VIDIOC_STREAMOFF,&type);
if(ret<0){
perror("stop eque@e fail\n");
}
//关闭设备
close(fd);
printf("close device successfully\n");
return 0;
}
写到本地的图片
cmakelist.txt
cmake_minimum_required(VERSION 3.16)
project(untitled3)
set(CMAKE_CXX_STANDARD 14)
#sudo apt install libjpeg8-dev
add_executable(untitled3 main.cpp)
target_link_libraries(
untitled3
-ljpeg
)
源代码,编译ctrl+alt+f1在命令行执行
#include <iostream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include <zconf.h>
#include<linux/videodev2.h>
#include<sys/mman.h>
#include <jpeglib.h>
#include <cstring>
#include <linux/fb.h>
int read_JPEG_file(unsigned char *jpegData, unsigned char *rgbdata, int size) {
struct jpeg_error_mgr jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr);
//1创建解码对象并且初始化
jpeg_create_decompress(&cinfo);
//2.装备解码的数据
//jpeg_stdio_src(&cinfo, infile);
jpeg_mem_src(&cinfo, (unsigned char *) jpegData, size);
//3.获取jpeg图片文件的参数
(void) jpeg_read_header(&cinfo, TRUE);
/* Step 4: set parameters for decompression */
//5.开始解码
(void) jpeg_start_decompress(&cinfo);
//6.申请存储一行数据的内存空间
int row_stride = cinfo.output_width * cinfo.output_components;
unsigned char *buffer = (unsigned char *) (malloc(row_stride));
int i = 0;
while (cinfo.output_scanline < cinfo.output_height) {
//printf("****%d\n",i);
(void) jpeg_read_scanlines(&cinfo, &buffer, 1);
memcpy(rgbdata + i * 640 * 3, buffer, row_stride);
i++;
}
//7.解码完成
(void) jpeg_finish_decompress(&cinfo);
//8.释放解码对象
jpeg_destroy_decompress(&cinfo);
return 1;
}
void lcd_show_rgb(unsigned int *lcdptr, int lcd_w, int lcd_h, unsigned char *rgb_data, int width, int height) {
unsigned int *ptr = lcdptr;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
memcpy(ptr + j, rgb_data + j * 3, 3);
}
ptr += lcd_w;
rgb_data += width * 3;
}
}
int main() {
//打开lcd
int lcdfd = open("/dev/fb0", O_RDWR);
if (lcdfd < 0) {
perror("open lcdfd fail");
return -1;
}
struct fb_var_screeninfo info;
ioctl(lcdfd, FBIOGET_VSCREENINFO, &info);
printf("xres = %u, yres = %u.\n", info.xres, info.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", info.xres_virtual, info.yres_virtual);
printf("bpp = %u.\n", info.bits_per_pixel);
//xu ni ji ubuntu
// int lcd_w = info.xres_virtual;
//int lcd_h = info.yres_virtual;
// arm
int lcd_w = info.xres;
int lcd_h = info.yres;
unsigned int *lcdptr = (unsigned int *) mmap(NULL, lcd_w * lcd_h * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0);
if (lcdptr == NULL) {
perror("create lcdptr fail");
return -1;
}
//打开设备
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("open device fail\n");
return -1;
}
// 获取摄像头支持的格式
//获取摄像头支持的格式
struct v4l2_fmtdesc v4fmt;
v4fmt.index = 0;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
if (ret < 0) {
perror("acquire fail\n");
return -1;
}
printf("%s\n", v4fmt.description);
unsigned char *p = (unsigned char *) &v4fmt.pixelformat;
printf("%c %c %c %c\n", p[0], p[1], p[2], p[3]);
// 设置摄像头支持的格式
//设置摄像头支持的格式
struct v4l2_format vFormat;
vFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vFormat.fmt.pix.width = 640;
vFormat.fmt.pix.height = 480;
vFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
ret = ioctl(fd, VIDIOC_S_FMT, &vFormat);
if (ret < 0) {
perror("set fail\n");
return -1;
}
//申请内核空间
struct v4l2_requestbuffers vqbuff;
vqbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vqbuff.count = 4;
vqbuff.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &vqbuff);
if (ret < 0) {
perror("buff fail\n");
return -1;
}
//申请内存空间
struct v4l2_buffer mptrbuff;
mptrbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
unsigned char *mptr[4];
unsigned int mptr_len[4];
for (int i = 0; i < 4; i++) {
mptrbuff.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mptrbuff);
if (ret < 0) {
perror("require buff fail\n");
return -1;
}
mptr[i] = (unsigned char *) mmap(NULL, mptrbuff.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
mptrbuff.m.offset);
mptr_len[i] = mptrbuff.length;
//通知完毕
ret = ioctl(fd, VIDIOC_QBUF, &mptrbuff);
if (ret < 0) {
perror("put fail");
return -1;
}
}
// 开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < 0) {
perror("open fail");
return -1;
}
unsigned char rgb_data[640 * 480 * 3];
while (true) {
//从队列中取数据
struct v4l2_buffer readbuff;
readbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuff);
if (ret < 0) {
perror("read fail");
}
read_JPEG_file(mptr[readbuff.index], rgb_data, readbuff.length);
lcd_show_rgb(lcdptr,lcd_w,lcd_h, rgb_data, 640, 480);
// FILE* file=fopen("example.jpg","w+");
// fwrite(mptr[readbuff.index],readbuff.length,1,file);
//fclose(file);
//通知内核 已经使用完
ret = ioctl(fd, VIDIOC_QBUF, &readbuff);
if (ret < 0) {
perror("put equee fail\n");
}
}
//停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if (ret < 0) {
perror("stop eque@e fail\n");
}
//释放资源、
for (int i = 0; i < 4; i++) {
munmap(mptr[i], mptr_len[i]);
}
//关闭设备
close(fd);
printf("close device successfully\n");
return 0;
}
测是的MJPEG的格式的图片显示
yuyv转bgr显示 640*480
#include <iostream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include <zconf.h>
#include<linux/videodev2.h>
#include<sys/mman.h>
#include <jpeglib.h>
#include <cstring>
#include <linux/fb.h>
int read_JPEG_file(unsigned char *jpegData, unsigned char *rgbdata, int size) {
struct jpeg_error_mgr jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr);
//1创建解码对象并且初始化
jpeg_create_decompress(&cinfo);
//2.装备解码的数据
//jpeg_stdio_src(&cinfo, infile);
jpeg_mem_src(&cinfo, (unsigned char *) jpegData, size);
//3.获取jpeg图片文件的参数
(void) jpeg_read_header(&cinfo, TRUE);
/* Step 4: set parameters for decompression */
//5.开始解码
(void) jpeg_start_decompress(&cinfo);
//6.申请存储一行数据的内存空间
int row_stride = cinfo.output_width * cinfo.output_components;
unsigned char *buffer = (unsigned char *) (malloc(row_stride));
int i = 0;
while (cinfo.output_scanline < cinfo.output_height) {
//printf("****%d\n",i);
(void) jpeg_read_scanlines(&cinfo, &buffer, 1);
memcpy(rgbdata + i * 640 * 3, buffer, row_stride);
i++;
}
//7.解码完成
(void) jpeg_finish_decompress(&cinfo);
//8.释放解码对象
jpeg_destroy_decompress(&cinfo);
return 1;
}
void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char *rgbdata, int w, int h)
{
//码流Y0 U0 Y1 V1 Y2 U2 Y3 V3 --》YUYV像素[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]--》RGB像素
int r1, g1, b1;
int r2, g2, b2;
for(int i=0; i<w*h/2; i++)
{
char data[4];
memcpy(data, yuyvdata+i*4, 4);
unsigned char Y0=data[0];
unsigned char U0=data[1];
unsigned char Y1=data[2];
unsigned char V1=data[3];
//Y0U0Y1V1 -->[Y0 U0 V1] [Y1 U0 V1]
r1 = Y0+1.4075*(V1-128); if(r1>255)r1=255; if(r1<0)r1=0;
g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0;
b1 = Y0 + 1.779 * (U0-128); if(b1>255)b1=255; if(b1<0)b1=0;
r2 = Y1+1.4075*(V1-128);if(r2>255)r2=255; if(r2<0)r2=0;
g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0;
b2 = Y1 + 1.779 * (U0-128); if(b2>255)b2=255; if(b2<0)b2=0;
// rgb的数据
// rgbdata[i*6+0]=r1;
// rgbdata[i*6+1]=g1;
// rgbdata[i*6+2]=b1;
// rgbdata[i*6+3]=r2;
// rgbdata[i*6+4]=g2;
// rgbdata[i*6+5]=b2;
//bgr的数据
rgbdata[i*6+0]=b1;
rgbdata[i*6+1]=g1;
rgbdata[i*6+2]=r1;
rgbdata[i*6+3]=b2;
rgbdata[i*6+4]=g2;
rgbdata[i*6+5]=r2;
}
}
void lcd_show_rgb(unsigned int *lcdptr, int lcd_w, int lcd_h, unsigned char *rgb_data, int width, int height) {
unsigned int *ptr = lcdptr;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
memcpy(ptr + j, rgb_data + j * 3, 3);
}
ptr += lcd_w;
rgb_data += width * 3;
}
}
int main() {
//打开lcd
int lcdfd = open("/dev/fb0", O_RDWR);
if (lcdfd < 0) {
perror("open lcdfd fail");
return -1;
}
struct fb_var_screeninfo info;
ioctl(lcdfd, FBIOGET_VSCREENINFO, &info);
printf("xres = %u, yres = %u.\n", info.xres, info.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", info.xres_virtual, info.yres_virtual);
printf("bpp = %u.\n", info.bits_per_pixel);
//xu ni ji ubuntu
// int lcd_w = info.xres_virtual;
//int lcd_h = info.yres_virtual;
// arm
int lcd_w = info.xres;
int lcd_h = info.yres;
unsigned int *lcdptr = (unsigned int *) mmap(NULL, lcd_w * lcd_h * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0);
if (lcdptr == NULL) {
perror("create lcdptr fail");
return -1;
}
//打开设备
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("open device fail\n");
return -1;
}
// 获取摄像头支持的格式
//获取摄像头支持的格式
struct v4l2_fmtdesc v4fmt;
v4fmt.index = 0;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
if (ret < 0) {
perror("acquire fail\n");
return -1;
}
printf("%s\n", v4fmt.description);
unsigned char *p = (unsigned char *) &v4fmt.pixelformat;
printf("%c %c %c %c\n", p[0], p[1], p[2], p[3]);
// 设置摄像头支持的格式
//设置摄像头支持的格式
struct v4l2_format vFormat;
vFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vFormat.fmt.pix.width = 640;
vFormat.fmt.pix.height = 480;
vFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
ret = ioctl(fd, VIDIOC_S_FMT, &vFormat);
if (ret < 0) {
perror("set fail\n");
return -1;
}
//申请内核空间
struct v4l2_requestbuffers vqbuff;
vqbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vqbuff.count = 4;
vqbuff.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &vqbuff);
if (ret < 0) {
perror("buff fail\n");
return -1;
}
//申请内存空间
struct v4l2_buffer mptrbuff;
mptrbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
unsigned char *mptr[4];
unsigned int mptr_len[4];
for (int i = 0; i < 4; i++) {
mptrbuff.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mptrbuff);
if (ret < 0) {
perror("require buff fail\n");
return -1;
}
mptr[i] = (unsigned char *) mmap(NULL, mptrbuff.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
mptrbuff.m.offset);
mptr_len[i] = mptrbuff.length;
//通知完毕
ret = ioctl(fd, VIDIOC_QBUF, &mptrbuff);
if (ret < 0) {
perror("put fail");
return -1;
}
}
// 开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < 0) {
perror("open fail");
return -1;
}
unsigned char rgb_data[640 * 480 * 3];
while (true) {
//从队列中取数据
struct v4l2_buffer readbuff;
readbuff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuff);
if (ret < 0) {
perror("read fail");
}
yuyv_to_rgb(mptr[readbuff.index], rgb_data, 640, 480);
//read_JPEG_file(mptr[readbuff.index], rgb_data, readbuff.length);
lcd_show_rgb(lcdptr,lcd_w,lcd_h, rgb_data, 640, 480);
// FILE* file=fopen("example.jpg","w+");
// fwrite(mptr[readbuff.index],readbuff.length,1,file);
//fclose(file);
//通知内核 已经使用完
ret = ioctl(fd, VIDIOC_QBUF, &readbuff);
if (ret < 0) {
perror("put equee fail\n");
}
}
//停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if (ret < 0) {
perror("stop eque@e fail\n");
}
//释放资源、
for (int i = 0; i < 4; i++) {
munmap(mptr[i], mptr_len[i]);
}
//关闭设备
close(fd);
printf("close device successfully\n");
return 0;
}
测试图片
参考
有道云笔记
Linux应用开发【第七章】摄像头V4L2编程应用开发 - 知乎