在linux虚拟机上显示摄像头视频(V4L2编程)
使用V4L2编程在虚拟机上显示动态图像还不会使用V4L2进行基础操作的同学请参考前面的文章:使用V4L2拍照本次,我们进行进阶版学习,通过将摄像头的mjpg格式照片(摄像头不能直接采集rgb格式的照片)数据流转化成rgb格式并且显示在虚拟机上,以此显示动态视频。rgb格式是大多数lcd液晶屏能显示的格式,对此也为下一章在开发板的lcd上显示动态视频打下基础,运行代码和注释如下:#include &
·
使用V4L2编程在虚拟机上显示动态图像
还不会使用V4L2进行基础操作的同学请参考前面的文章:使用V4L2拍照
本次,我们进行进阶版学习,通过将摄像头的mjpg格式照片(摄像头不能直接采集rgb格式的照片)数据流转化成rgb格式并且显示在虚拟机上,以此显示动态视频。rgb格式是大多数lcd液晶屏能显示的格式,对此也为下一章在开发板的lcd上显示动态视频打下基础,运行代码和注释如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
#include <jpeglib.h>
#include <linux/fb.h>
#include <stdio.h>
//如下函数read_JPEG_file为封装好的函数,需要转换格式的时候直接调用即可,不用深入理解
int read_JPEG_file (const char *jpegData, 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,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 = 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;
}
int fd_fb;
static struct fb_var_screeninfo var; /* LCD可变参数 */
static unsigned int *fb_base = NULL; /* Framebuffer映射基地址 */
int lcd_w = 800 ,lcd_h= 480; //定义显示器分辨率
//将数据流以3字节为单位拷贝到rgb显存中
void lcd_show_rgb(unsigned char *rgbdata, int w ,int h)
{
unsigned int *ptr = fb_base;
for(int i = 0; i <h; i++) {
for(int j = 0; j < w; j++) {
memcpy(ptr+j,rgbdata+j*3,3);//
}
ptr += lcd_w;
rgbdata += w*3;
}
}
int main(void)
{
fd_fb = open("/dev/fb0", O_RDWR); //打开LCD文件
if(fd_fb < 0)
{
perror("/dev/fb0");
exit(-1);
}
if (ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))
{
printf("can't get fb_var_screeninfo \n");
goto err1;
}
//虚拟机-ubuntu
lcd_w = var.xres_virtual; //xres_virtual参数可以自动获取当前虚拟机显示器分辨率
lcd_h = var.yres_virtual;
//建立显示器fb内存映射 方便控制
fb_base = (unsigned int*)mmap(NULL,lcd_w*lcd_h*4,PROT_READ|PROT_WRITE,MAP_SHARED, fd_fb,0);
if(fb_base == NULL)
{
printf("can't mmap Framebuffer\n");
goto err1;
}
int fd = open("/dev/video0",O_RDWR); //打开摄像头设备
if (fd < 0)
{
perror("打开设备失败");
return -1;
}
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集
vfmt.fmt.pix.width = 640; //设置摄像头采集参数,不可以任意设置
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //设置视频采集格式为mjpg格式
int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
if (ret < 0)
{
perror("设置格式失败1");
}
//申请内核空间
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式
ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
if (ret < 0)
{
perror("申请空间失败");
}
//映射
unsigned char *mptr[4];//保存映射后用户空间的首地址
unsigned int size[4];
struct v4l2_buffer mapbuffer;
//初始化type和index
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(int i = 0; i <4;i++) {
mapbuffer.index = i;
ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer); //从内核空间中查询一个空间作映射
if (ret < 0)
{
perror("查询内核空间失败");
}
//映射到用户空间
mptr[i] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
size[i] = mapbuffer.length; //保存映射长度用于后期释放
//查询后通知内核已经放回
ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
if (ret < 0)
{
perror("放回失败");
}
}
//开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_STREAMON,&type);
if (ret < 0)
{
perror("开启失败");
}
//定义一个空间存储解码后的rgb
unsigned char rgbdata[640*480*3];
while(1)
{
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //每个结构体都需要设置type为这个参赛要记住
ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);
if (ret < 0)
{
perror("读取数据失败");
}
//显示在lcd上
{
read_JPEG_file(mptr[readbuffer.index],rgbdata,readbuffer.length);//把jpeg数据解码为rgb数据
lcd_show_rgb(rgbdata,640,480);
}
//通知内核使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}
}
//停止采集
ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
//释放映射
for(int i=0; i<4; i)munmap(mptr[i], size[i]);
close(fd); //关闭文件
return 0;
err1:
close(fd_fb);
return -1;
}
编译文件
首先需要安装jpeg解码—libjpeg然后编译:
sudo apt install libjpeg8-dev
gcc -o show video_show_jpg.c -ljpeg
编译出可执行文件之后,不能直接在图形化界面的虚拟机执行,需要跳转到虚拟机的字符界面,每一个linux系统都有7个虚拟终端,其中第1-6为命令行(即你所说的字符界面),第七个为GUI(就是你看到的图形界面)通过按ctrl + alt + F1进入第一个虚拟终端,同理ctrl + alt + F2为第二个,以此类推。每一个都试一下直到进入字符界面
使用sudo ./show执行自己生成的可执行文件即可。
显示效果
肉眼可见,图片是偏蓝色的,这是因为我们每次以3字节的数据搬运没有对颜色进行处理造成的
接下来就可以进行最后一步了,在lcd上显示摄像头采集的视频数据流:嵌入式linux在lcd上显示摄像头图像
更多推荐
已为社区贡献3条内容
所有评论(0)