在linux虚拟机上显示摄像头视频(V4L2编程)

使用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上显示摄像头图像

  • 8
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
根据提供的引用内容,您提供的代码片段似乎是C语言的代码。根据您的问题,您遇到了编译错误。要解决这个问题,您可以尝试以下几个步骤: 1. 检查代码中是否存在拼写错误或语法错误。编译错误通常是由于代码中的错误导致的。请确保您的代码中没有任何拼写错误,并且所有的语法都是正确的。 2. 检查是否缺少头文件。在C语言中,您需要包含所需的头文件来使用特定的函数和库。请确保您的代码中包含了所需的头文件,并且这些头文件的路径是正确的。 3. 检查是否缺少库文件。有时候,编译错误可能是由于缺少所需的库文件而引起的。请确保您的代码中包含了所需的库文件,并且这些库文件的路径是正确的。 4. 检查编译命令是否正确。编译C语言代码时,您需要使用正确的编译命令来编译代码。请确保您使用的编译命令是正确的,并且所有的参数都是正确的。 以下是一个示例的C语言代码,用于创建和读取共享内存: 创建共享内存: ```c #include <stdio.h> #include <stdlib.h> #include <sys/shm.h> int main(int argc, char* argv[]) { key_t key = ftok(argv[1], 1); if (-1 == key) { perror("ftok err"); return 1; } int shmid = shmget(key, atoi(argv[2]), IPC_CREAT | 0644); if (-1 == shmid) { perror("shmget err"); return 1; } printf("shmid:%d\n", shmid); return 0; } ``` 读取共享内存: ```c #include <stdio.h> #include <stdlib.h> #include <sys/shm.h> int main(int argc, char* argv[]) { int shmid = atoi(argv[1]); if (-1 == shmctl(shmid, IPC_RMID, NULL)) { perror("shmctl err"); return 1; } return 0; } ``` 请注意,这只是一个示例代码,您需要根据您的具体需求进行修改和适应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值