Linux下使用framebuffer显示摄像头图像成功后,如何用lvgl显示摄像头视频

1.板子是imx6ullpro,屏幕分辨率是1024600,摄像头的分辨率是800600的,摄像头图像并不能占满全部屏幕。
2.在使用framebuffer后想使用lvgl在右侧生成按钮来控制,比如打开摄像头关闭摄像头,截图或者录屏。
3.问题在于打开摄像头后,摄像头实时图像可以正常显示,按钮没有被覆盖但按钮无法按取。
4.就算强制关闭摄像头后,屏幕上还会留下关闭时的图片。
5.论坛里也有人说用lvgl画布可以显示,但跟着一些回复也没有搞定,很是迷惑
6.以下是代码,通过start按钮打开摄像头

lv_obj_t *back_btn;
lv_obj_t *back_btn_text;

lv_obj_t *screenshot;
lv_obj_t *screenshot_text;

lv_obj_t *screenrecord;
lv_obj_t *screenrecord_text;

lv_obj_t *background_video;

lv_obj_t *start_btn;
lv_obj_t *start_btn_text;
int start_or_stop_flag=0;

lv_obj_t *canvas;
static lv_color_t canvasBuf[(32*800)*600/8];

void video_back_event_cb(lv_event_t *e);
void video_start_event_cb(lv_event_t *e);
void  canvas_test(void);



void video_test(void)
{
   //background
   LV_IMG_DECLARE(white);
   background_video=lv_img_create(lv_scr_act());
   lv_img_set_src(background_video,&white);
   lv_obj_center(background_video);

   //back_btn
   back_btn=lv_btn_create(lv_scr_act());
    lv_obj_set_size(back_btn,150,100);
    lv_obj_set_style_bg_color(back_btn,lv_color_hex(0x000000),0);
    lv_obj_set_style_bg_opa(back_btn,30,0);
    lv_obj_set_style_border_color(back_btn,lv_color_hex(0xffffff),0);
    lv_obj_set_pos(back_btn,850,0);
    lv_obj_add_event_cb(back_btn,video_back_event_cb,LV_EVENT_ALL,NULL);

   back_btn_text=lv_label_create(back_btn);
    lv_label_set_text(back_btn_text,LV_SYMBOL_LEFT  "Back");
    lv_obj_set_style_text_font(back_btn_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
    lv_obj_align(back_btn_text,LV_ALIGN_CENTER,0,0);
   // lv_obj_add_event_cb(back_btn_text,video_back_event_cb,LV_EVENT_ALL,NULL);

   //start and stop
   start_btn=lv_btn_create(lv_scr_act());
    lv_obj_set_size(start_btn,150,100);
    lv_obj_set_style_bg_color(start_btn,lv_color_hex(0x000000),0);
    lv_obj_set_style_bg_opa(start_btn,30,0);
    lv_obj_set_style_border_color(start_btn,lv_color_hex(0xffffff),0);
    lv_obj_set_pos(start_btn,850,150);
    

    start_btn_text=lv_label_create(start_btn);
    lv_label_set_text(start_btn_text,LV_SYMBOL_PLAY  "START");
    lv_obj_set_style_text_font(start_btn_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
    lv_obj_align(start_btn_text,LV_ALIGN_CENTER,0,0);   
    lv_obj_add_event_cb(start_btn,video_start_event_cb,LV_EVENT_ALL,NULL);

    //screenshot
    screenshot=lv_btn_create(lv_scr_act());
    lv_obj_set_size(screenshot,150,100);
    lv_obj_set_style_bg_color(screenshot,lv_color_hex(0x000000),0);
    lv_obj_set_style_bg_opa(screenshot,30,0);
    lv_obj_set_style_border_color(screenshot,lv_color_hex(0xffffff),0);
    lv_obj_set_pos(screenshot,850,300);

    screenshot_text=lv_label_create(screenshot);
    lv_label_set_text(screenshot_text,LV_SYMBOL_IMAGE  "IMAGE");
    lv_obj_set_style_text_font(screenshot_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
    lv_obj_align(screenshot_text,LV_ALIGN_CENTER,0,0);

    //screenrecord
    screenrecord=lv_btn_create(lv_scr_act());
    lv_obj_set_size(screenrecord,150,100);
    lv_obj_set_style_bg_color(screenrecord,lv_color_hex(0x000000),0);
    lv_obj_set_style_bg_opa(screenrecord,30,0);
    lv_obj_set_style_border_color(screenrecord,lv_color_hex(0xffffff),0);
    lv_obj_set_pos(screenrecord,850,450);

    screenrecord_text=lv_label_create(screenrecord);
    lv_label_set_text(screenrecord_text,LV_SYMBOL_VIDEO  "VIDEO");
    lv_obj_set_style_text_font(screenrecord_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
    lv_obj_align(screenrecord_text,LV_ALIGN_CENTER,0,0);
   

}
void video_back_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);

     if(code==LV_EVENT_CLICKED)
    {
         lv_obj_del(background_video);
         //lv_obj_add_flag(file_explorer,LV_OBJ_FLAG_HIDDEN);
         lv_obj_del(back_btn_text);
         lv_obj_del(back_btn);
         lv_obj_del(start_btn);
         lv_obj_del(screenrecord);
         lv_obj_del(screenshot);
         
     
         
         
         

    }

}

void video_start_event_cb(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_target(e);

     if(code==LV_EVENT_CLICKED)
    {
         if(start_or_stop_flag==0)
         {
            lv_label_set_text(start_btn_text,LV_SYMBOL_PAUSE  "STOP");
            lv_obj_set_style_text_font(start_btn_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
            lv_obj_align(start_btn_text,LV_ALIGN_CENTER,0,0); 
            start_or_stop_flag=1;
         }else if(start_or_stop_flag==1)
         {
            lv_label_set_text(start_btn_text,LV_SYMBOL_PLAY  "START");
            lv_obj_set_style_text_font(start_btn_text,&lv_font_montserrat_30,LV_STATE_DEFAULT);
            lv_obj_align(start_btn_text,LV_ALIGN_CENTER,0,0); 
            start_or_stop_flag=0;
         }

         video_show();
        
        
        

         
         
         

    }

}


#include "video.h"

#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 <linux/fb.h>
#include <stdio.h>


int fd_fb;                                                        /* 文件句柄 */
static struct fb_var_screeninfo var; /* LCD可变参数 */
static unsigned int *fb_base = NULL;                        /* Framebuffer映射基地址 */
static int screen_size;                                      /* 整个Framebuffer大小*/
int lcd_w = 1024 ,lcd_h= 600;


//解码函数
void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char *rgbdata, int w, int h)
{
	 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]
	    b1 = Y0+1.4075*(V1-128); if(b1>255)b1=255; if(b1<0)b1=0;
	    g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0;
	    r1 = Y0 + 1.779 * (U0-128);  if(r1>255)r1=255; if(r1<0)r1=0;
	 
	    b2 = Y1+1.4075*(V1-128);if(b2>255)b2=255; if(b2<0)b2=0;
	    g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0;
	    r2 = Y1 + 1.779 * (U0-128);  if(r2>255)r2=255; if(r2<0)r2=0;


	    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;
	}

}

void lcd_show_rgb(unsigned char *rgbdata, int w ,int h)
{
    unsigned int *ptr = fb_base;  //不要直接对lcd基地址操作以免卡住
    for(int i = 0; i <h; i++) {
        for(int j = 0; j < w; j++) {
                memcpy(ptr+j,rgbdata+j*3,4);//rgb用3个字节表示一个像素点
        }
        ptr += lcd_w;
        rgbdata += w*3;
    }
}

int video_show(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))  //读取lcd参数
   {
      printf("can't get fb_var_screeninfo \n");
      goto err1;
   }

   printf("X:%d  Y:%d  bbp:%d\n",var.xres,var.yres,var.bits_per_pixel);

   screen_size = var.xres *var.yres *var.bits_per_pixel /8;  //整个Framebuffer大小,bits_per_pixel 表示色深,除以8表示转换单位字节
   //screen_size = 1024 *600 *var.bits_per_pixel /8;
   //建立内存映射 方便控制
   fb_base = (unsigned int*)mmap(NULL,screen_size,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/video1",O_RDWR); //打开摄像头节点,请根据自己的摄像头所在节点修改
    if (fd < 0)
    {
        perror("打开设备失败");
        return -1;
    }

    //获取摄像头支持格式 ioctl(文件描述符,命令,与命令对应的结构体)
    struct v4l2_format vfmt;

    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集
    vfmt.fmt.pix.width = 800; //设置摄像头采集参数,不可以任意设置
    vfmt.fmt.pix.height = 600;
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式 ,根据上一步测得
    
    int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt); //写入摄像头参数
    if (ret < 0)
    {
        perror("设置格式失败1");
    }

    struct v4l2_streamparm Stream_Parm; //定义结构体设置摄像头帧率
    memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));
    Stream_Parm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

    Stream_Parm.parm.capture.timeperframe.denominator = 15 ;  //分母
    Stream_Parm.parm.capture.timeperframe.numerator = 1 ;  //分子
    

    ret = ioctl(fd,VIDIOC_S_PARM,&Stream_Parm); //写入摄像头帧率
    if (ret < 0)
    {
        perror("设置帧率失败");
    }

//以下函数是读取目前摄像头帧率
/*    Stream_Parm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd,VIDIOC_G_PARM,&Stream_Parm);
     if (ret < 0)
    {
        perror("获取帧率失败");
    }

    printf("Frame rate: %u/%u\n",Stream_Parm.parm.capture.timeperframe.numerator,Stream_Parm.parm.capture.timeperframe.denominator);
*/
    //申请内核空间
    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("开启失败");
        }
        lv_obj_t *video_img;
        lv_draw_rect_dsc_t my_img_draw_dsc;
      //定义一个空间存储解码后的rgb
      unsigned char rgbdata[800*600*3];
    lv_draw_rect_dsc_init(&my_img_draw_dsc);
    static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_INDEXED_1BIT(800,600)];
    video_img=lv_canvas_create(lv_scr_act());
    

    
    
    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上      
        yuyv_to_rgb(mptr[readbuffer.index],rgbdata,800,600);//把jpeg数据解码为rgb数据
        lcd_show_rgb(rgbdata,800,600);
    
        //通知内核使用完毕
    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;
}

7.这是ui图片

你这是fb节点冲突没处理好, video_show 中一直在占用fb节点,而lvgl也是使用fb节点。建议全部交由lvgl完成,也就是使用画布,这里有个参考,构造好画布所需的信息之后即可交给画布显示:

https://github.com/lvgl/lvgl/blob/e19410f8f8a256609da72cff549598e0df6fa4cf/examples/widgets/canvas/lv_example_canvas_1.c#L43

你还可以参考我们最近推出的参考示例: 基于Linux+LVGL的嵌入式相机 - LVGL / LVGL8.x - 嵌入式开发问答社区 (100ask.net)