当前位置:首页 > 显示光电 > 显示光电
[导读]摄像头预览不是open设备→read帧→写屏幕。V4L2标准路径是:驱动分配缓冲→mmap映射用户空间→QBUF入队→STREAMON启动→DQBUF取帧→处理→再QBUF。 OSD叠加也别每帧用OpenCV重画全图,简单场景写帧缓冲,量产走DRM plane硬件合成。


摄像头预览不是open设备→read帧→写屏幕。V4L2标准路径是:驱动分配缓冲→mmap映射用户空间→QBUF入队→STREAMON启动→DQBUF取帧→处理→再QBUF。 OSD叠加也别每帧用OpenCV重画全图,简单场景写帧缓冲,量产走DRM plane硬件合成。

一、V4L2采集初始化

标准流程五步不能省:

int fd = open("/dev/video0", O_RDWR);

struct v4l2_capability cap;

ioctl(fd, VIDIOC_QUERYCAP, &cap);          // 确认VIDEO_CAPTURE


struct v4l2_format fmt = {0};

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width = 640;

fmt.fmt.pix.height = 480;

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  // 或MJPEG

ioctl(fd, VIDIOC_S_FMT, &fmt);

/* 以驱动返回的width/height为准,别硬信自己设的值 */


struct v4l2_requestbuffers req = {0};

req.count = 4; req.type = fmt.type;

req.memory = V4L2_MEMORY_MMAP;

ioctl(fd, VIDIOC_REQBUFS, &req);

格式优先级:MIPI CAM支持NV12/YUYV就别强制MJPEG;USB UVC高分辨率YUYV会撑爆带宽再换MJPEG。

二、mmap缓冲区

零拷贝关键:不让内核态数据copy到用户态。

struct buf { void*start; unsigned len; } bufs[4];

for(int i=0;i<req.count;i++){

   struct v4l2_buffer b={0};

   b.type=fmt.type; b.memory=V4L2_MEMORY_MMAP; b.index=i;

   ioctl(fd, VIDIOC_QUERYBUF, &b);

   bufs[i].start = mmap(NULL,b.length,PROT_READ|PROT_WRITE,

                        MAP_SHARED,fd,b.m.offset);

   bufs[i].len = b.length;

   ioctl(fd, VIDIOC_QBUF, &b);          // 先全入队

}

enum v4l2_buf_type t = fmt.type;

ioctl(fd, VIDIOC_STREAMON, &t);

缓冲不回队,驱动几下就没空buffer,采集循环卡死。

三、预览到Framebuffer

小屏/fb0方案最直接:把YUYV转RGB565写显存。

int fb = open("/dev/fb0", O_RDWR);

struct fb_var_screeninfo v;

ioctl(fb, FBIOGET_VSCREENINFO, &v);

unsigned stride = v.xres * 2;          // RGB565

uint16_t *fbmem = mmap(NULL, v.yres*stride, PROT_WRITE,

                       MAP_SHARED, fb, 0);


void yuyv_to_rgb565(const uint8_t*y, uint16_t*d, int w,int h){

   for(int i=0;i<w*h;i++){

       int y0=y[i*2], u=y[i*2+1], v0=y[i*2+3];

       int r = y0+1.4075*(v0-128);

       int g = y0-0.3455*(u-128)-0.7169*(v0-128);

       int b = y0+1.779*(u-128);

       d[i]=((r>>3)<<11)|((g>>2)<<5)|(b>>3);

   }

}


while(run){

   fd_set fds; FD_ZERO(&fds); FD_SET(fd,&fds);

   select(fd+1,&fds,NULL,NULL,NULL);

   struct v4l2_buffer b={0};

   b.type=fmt.type; b.memory=V4L2_MEMORY_MMAP;

   ioctl(fd, VIDIOC_DQBUF, &b);             // 取帧

   yuyv_to_rgb565(bufs[b.index].start, fbmem, 640,480);

   draw_osd(fbmem);                         // 叠加OSD

   ioctl(fd, VIDIOC_QBUF, &b);              // 归还

}

坑点:

摄像头分辨率≠LCD分辨率,取min_w/min_h拷贝,换行按各自stride走

YUYV不能直接memcpy进RGB565显存,必花屏

stride不对→斜条纹

转换放主循环吃CPU,NEON加速或改DRM零拷贝

四、OSD叠加:软件方案

在写fb前直接改像素,最适合时间水印、准星、报警框。

void osd_put_text(uint16_t*fb,int stride,int x,int y,const char*s){

   /* 取5x7字模,逐像素写 */

   for(int row=0;row<7;row++){

       uint8_t line = font5x7[(int)s[0]*7+row];

       for(int col=0;col<5;col++){

           if(line&(1<<(4-col)))

               fb[(y+row)*stride/2 + x+col] = 0xFFFF;

       }

   }

}


void draw_osd(uint16_t*fb){

   static int cnt=0; char buf[16];

   snprintf(buf,sizeof(buf),"CAM %d",cnt++);

   osd_put_text(fb, 640 * 2, 10,10, buf);

   /* 画报警框 */

   for(int x=100;x<300;x++)

       fb[100 * 640 + x]=0xF800, fb[200 * 640 + x]=0xF800;

}

工业场景别每帧重绘全屏OSD,只改变化区(时间字符串、框位置),其余保留视频层。

五、DRM/KMS硬件叠加(量产方案)

fb0软合成在i.MX8/RK平台已经淘汰,正确做法是视频走V4L2 buffer→DRM plane,OSD走另一plane,显示控制器硬件混合,CPU零拷贝。

V4L2 DQBUF拿到dma-buf fd

drmModeAddFB2把视频帧挂到video plane

UI层用ARGB plane画文字/按钮

atomic commit一次合成

伪流程:

drm_handle_event(drm_fd, &ev);

drmModeSetPlane(drm_fd, video_plane, crtc_id,

              drm_fb_video, 0,

              0,0,W,H, 0,0,W<<16,H<<16);

/* OSD plane单独更新 */

优点:预览不占CPU、无撕裂、支持缩放到全屏;缺点是要驱支DRM plane和dma-buf。

六、V4L2 Video Overlay接口

部分芯片(如全志VIN)支持V4L2_BUF_TYPE_VIDEO_OVERLAY,直接把OSD bitmap交给驱动合成:

fmt.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;

fmt.fmt.win.w.left = 10; fmt.fmt.win.w.top = 10;

fmt.fmt.win.w.width = 128; fmt.fmt.win.w.height = 16;

fmt.fmt.win.global_alpha = 200;

ioctl(fd, VIDIOC_S_FMT, &fmt);

ioctl(fd, VIDIOC_OVERLAY, 1);     // 使能

前提是驱动实现overlay能力,不是所有USB cam都有。

七、落坑清单

S_FMT后不回读参数→实际分辨率错

YUYV当RGB565直接拷→花屏

stride按width算→宽高不对齐时斜移

缓冲DQ后忘了QBUF→几帧后卡死

OSD全屏重画→预览掉帧

fb0方案上1080p→CPU转格式爆掉

不select直接DQBUF→非阻塞返回EAGAIN没处理

DRM平台还用fb0→多层UI叠不出

时间字符串每帧sprintf→无关开销

八、落地顺序

v4l2-ctl确认格式/分辨率支持

REQBUFS+mmap+QBUF+STREAMON跑通取帧

fb0先出图(YUYV转RGB565)

OSD在fb上画文字/框

只更新变化区降CPU

量产平台改DRM plane:视频层+OSD层

dma-buf共享,去掉memcpy

嵌入式Linux摄像头预览的本质:V4L2管采、显示子系统管出、OSD管叠加层。调试口诀——黑屏看DRM connector,花屏看stride和格式,卡顿看buffer数量和拷贝次数,OSD抖就看是不是每帧重画全屏。 小项目写fb够用,车载/工业HMI上量必须走硬件plane,不然CPU全耗在YUV转换上了。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭