首页 > 系统相关 >Linux V4l2简单使用

Linux V4l2简单使用

时间:2024-03-26 17:24:01浏览次数:30  
标签:int V4L2 Linux unsigned char width 简单 V4l2 out

V4L2:Video for Linux two,缩写 Video4Linux2,是 Linux 内核中的一个框架,提供了一套用于视频设备驱动程序开发的 API。

它是一个开放的、通用的、模块化的视频设备驱动程序框架,允许 Linux 操作系统和应用程序与各种视频设备(如摄像头、视频采集卡等)进行交互。

V4L2 提供了通用的 API,使应用程序能够访问和控制视频设备,包括获取设备信息、设置设备参数、采集视频数据、控制设备状态等。V4L2 还提供了一个统一的视频数据格式,允许应用程序在处理视频数据时无需考虑设备的具体格式。

V4L2 是 V4L 的改进版。V4L2 支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用多;直接读取的方式相对速度慢一些,常用于静态图片数据的采集;用户指针使用较少。

 

V4L2 的主要特性

  1. 模块化的架构:V4L2 是一个模块化的架构,允许多个设备驱动程序同时存在并共享同一个 API。每个设备驱动程序都是一个独立的内核模块,可以在运行时加载和卸载。这种架构可以使开发人员更容易地开发新的视频设备驱动程序,并允许多个驱动程序同时使用相同的 API。

  2. 统一的设备节点:V4L2 提供了统一的设备节点,使应用程序可以使用相同的方式访问不同类型的视频设备。这种节点通常是 /dev/videoX,其中 X 是一个数字,表示设备的编号。应用程序可以通过打开这个节点来访问设备,并使用 V4L2 API 进行数据采集和控制。

  3. 统一的视频数据格式:V4L2 提供了一个统一的视频数据格式,称为 V4L2_PIX_FMT,允许应用程序在处理视频数据时无需考虑设备的具体格式。V4L2_PIX_FMT 包括了许多常见的视频格式,如 RGB、YUV 等。应用程序可以使用 V4L2 API 来查询设备支持的数据格式,并选择适当的格式进行数据采集和处理。

  4. 支持多种视频设备:V4L2 支持许多不同类型的视频设备,包括摄像头、视频采集卡、TV 卡等。每个设备都有自己的驱动程序,提供了相应的 V4L2 API。这些驱动程序可以根据设备的不同特性,提供不同的采集模式、数据格式、控制参数等。

  5. 支持流式 I/O:V4L2 支持流式 I/O,即通过内存映射的方式将视频数据从设备直接传输到应用程序中。这种方式可以减少数据复制的次数,提高数据传输的效率。

  6. 支持控制参数:V4L2 允许应用程序通过 API 来控制视频设备的参数,包括亮度、对比度、色彩饱和度、曝光时间等。应用程序可以使用 V4L2 API 来查询设备支持的参数,并设置适当的值。

  7. 支持事件通知:V4L2 支持事件通知,当视频设备状态发生变化时,如视频信号丢失、帧率变化等,V4L2 驱动程序可以向应用程序发送通知,以便应用程序做出相应的处理。

从上面的特征可以看出,V4L2 提供了一套通用、灵活、可扩展的视频设备驱动程序框架,使得 Linux 操作系统和应用程序可以方便地与各种视频设备进行交互,并且不需要关心设备的具体实现细节。从而让开发人员能够更加专注于应用程序的开发。

 

V4L2 视频采集步骤

 

简单例子 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <linux/videodev2.h>
#include <linux/fb.h>

#define   FILE_NAME     "./my.yuyv"
#define   FILE_YUV420   "test.yuv420"
#define   FILE_YUV422   "test.yuv422"
#define   FILE_RGB     "test.rgb32"

#define   WIDTH         640                      // 图片的宽度
#define   HEIGHT        480                     // 图片的高度
#define   COUNT         5                         // 缓冲区个数
#define   FMT           V4L2_PIX_FMT_YUYV         // 图片格式
#define   LEN_YUV422    WIDTH * HEIGHT * 2       //YUV422长度

#define   VIDEO_FILE    "/dev/video1"
#define   FB_FILE       "/dev/fb0"

//文件句柄
int fd = 0;
int m_fb = 0;       //frameBuffer  句柄

FILE *fl = nullptr;
FILE *f420 = nullptr;
FILE *f422 = nullptr;
FILE *frgb32 = nullptr;

//缓冲区地址
uint8_t *pYUV420Buff = NULL;         //yuv420数据缓冲区(输出)
unsigned char *pYUV422 = NULL;       //yuv422数据缓冲区(输出)
unsigned char *pRGBBuff = NULL;      //rgb数据缓冲区(输出)
unsigned char *datas[COUNT];         // 缓冲区数据地址
unsigned char yuv422Buf[LEN_YUV422] = {0};


/**  YUV422  长度大小:  width * height * 2              2 byte
 *   YUV444  长度大小:  width * height * 3              3 byte
 *   YUV420  长度大小:  3 * width * height / 2          1.5 byte
 *   RGB24   长度大小:  3 * width * height              3 byte
*/

//YUYV422转YUV420
int yuyv_to_yuv420p(const unsigned char *in, unsigned char *out, unsigned int width, unsigned int height)
{
    unsigned char *y = out;
    unsigned char *u = out + width*height;
    unsigned char *v = out + width*height + width*height/4;

    unsigned int i,j;
    unsigned int base_h;
    unsigned int is_y = 1, is_u = 1;
    unsigned int y_index = 0, u_index = 0, v_index = 0;

    unsigned long yuv422_length = 2 * width * height;

    //序列为YU YV YU YV,一个yuv422帧的长度 width * height * 2 个字节
    //丢弃偶数行 u v
    for(i=0; i<yuv422_length; i+=2)
    {
        *(y+y_index) = *(in+i);
        y_index++;
    }

    for(i=0; i<height; i+=2)
    {
        base_h = i*width*2;
        for(j=base_h+1; j<base_h+width*2; j+=2)
        {

            if(is_u)
            {
                *(u+u_index) = *(in+j);
                u_index++;
                is_u = 0;
            }
            else
            {
                *(v+v_index) = *(in+j);
                v_index++;
                is_u = 1;
            }
        }
    }

    return 1;
}


//yuyv422转rgb24
void yuyv422_to_rgb24(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
{
    int x;
    int z=0;
    unsigned char *ptr = rgb_buffer;
    unsigned char *yuyv= yuv_buffer;
    for (x = 0; x < iWidth*iHeight; x++)
    {
        int r, g, b;
        int y, u, v;

        if (!z)
        y = yuyv[0] << 8;
        else
        y = yuyv[2] << 8;
        u = yuyv[1] - 128;
        v = yuyv[3] - 128;

        r = (y + (359 * v)) >> 8;
        g = (y - (88 * u) - (183 * v)) >> 8;
        b = (y + (454 * u)) >> 8;

        *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
        *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
        *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);

        if(z++)
        {
            z = 0;
            yuyv += 4;
        }
    }
}

void yuv422sp_to_yuv422p( unsigned  char* yuv422sp,  unsigned  char* yuv422p,  int width,  int height)
{
     int i, j;
     int y_size;
     int uv_size;
     unsigned  char* p_y1;
     unsigned  char* p_uv;

     unsigned  char* p_y2;
     unsigned  char* p_u;
     unsigned  char* p_v;

    y_size = uv_size = width * height;

    p_y1 = yuv422sp;
    p_uv = yuv422sp + y_size;

    p_y2 = yuv422p;
    p_u  = yuv422p + y_size;
    p_v  = p_u + width * height /  2;

    memcpy(p_y2, p_y1, y_size);

     for (j =  0, i =  0; j < uv_size; j+= 2, i++)
    {
        p_u[i] = p_uv[j];
        p_v[i] = p_uv[j+ 1];
    }
}


//YUV420转RGB24
void YUV420P_TO_RGB24(unsigned char *yuv420p, unsigned char *rgb24, int width, int height) {
    int index = 0;
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int indexY = y * width + x;
            int indexU = width * height + y / 2 * width / 2 + x / 2;
            int indexV = width * height + width * height / 4 + y / 2 * width / 2 + x / 2;

            unsigned char Y = yuv420p[indexY];
            unsigned char U = yuv420p[indexU];
            unsigned char V = yuv420p[indexV];

            rgb24[index++] = Y + 1.402 * (V - 128); //R
            rgb24[index++] = Y - 0.34413 * (U - 128) - 0.71414 * (V - 128); //G
            rgb24[index++] = Y + 1.772 * (U - 128); //B
        }
    }
}

//yuyv422转yuv422
void YUYV422ToYUV422(char* in, unsigned char* out, unsigned int width, unsigned int height)
{
    unsigned int total(width * height);

    char* in_y(in);
    char* in_u(in + 1);
    char* in_v(in + 3);

    char* out_y((char*)out);
    char* out_u((char*)out + width * height);
    char* out_v((char*)out + width * height + 1);

    for (unsigned int i(0); i < total; i += 10)
    {
        *out_y = *in_y; out_y++; in_y += 2;
        *out_y = *in_y; out_y++; in_y += 2;
        *out_u = *in_u; out_u += 2; in_u += 4;
        *out_v = *in_v; out_v += 2; in_v += 4;

        *out_y = *in_y; out_y++; in_y += 2;
        *out_y = *in_y; out_y++; in_y += 2;
        *out_u = *in_u; out_u += 2; in_u += 4;
        *out_v = *in_v; out_v += 2; in_v += 4;

        *out_y = *in_y; out_y++; in_y += 2;
        *out_y = *in_y; out_y++; in_y += 2;
        *out_u = *in_u; out_u += 2; in_u += 4;
        *out_v = *in_v; out_v += 2; in_v += 4;

        *out_y = *in_y; out_y++; in_y += 2;
        *out_y = *in_y; out_y++; in_y += 2;
        *out_u = *in_u; out_u += 2; in_u += 4;
        *out_v = *in_v; out_v += 2; in_v += 4;

        *out_y = *in_y; out_y++; in_y += 2;
        *out_y = *in_y; out_y++; in_y += 2;
        *out_u = *in_u; out_u += 2; in_u += 4;
        *out_v = *in_v; out_v += 2; in_v += 4;
    }
}

//写文件
int writeFile(FILE *fp, void *data, int len){
    if ( fp == nullptr || !data )
        return -1;

    return fwrite(data, len, 1, fp);
}

//读取一帧
int readFrame(int count)
{
    // printf("readFrame - %d\n", count);
    struct v4l2_buffer buff;
    memset(&buff, 0, sizeof (buff));
    buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buff.memory = V4L2_MEMORY_MMAP;

    //第三个参数存放地址, 否则没办法拿到帧数据
    if ( 0 == ioctl(fd, VIDIOC_DQBUF, &buff) )      //从缓冲区取出一个缓冲帧
    {
        memset(pYUV420Buff, 0, 3 * WIDTH * HEIGHT / 2);
        printf("%p - frame Len: %d\n", datas[buff.index], buff.length);
        //yuyv转yuv420
        yuyv_to_yuv420p(datas[buff.index], pYUV420Buff, WIDTH, HEIGHT);
        // int wLen = writeFile(f420, pYUV420Buff, 3 * WIDTH * HEIGHT / 2);
        // printf("yuv420 writelen: %d\n", wLen);
        //显示一帧到fb输出
    }else{
        perror("get frame fail.\n");
    }

    //将应用层的将帧的缓冲区重新放入视频采集队列
    if ( -1 == ioctl(fd, VIDIOC_QBUF, &buff) )
        return 0;

    return 1;
}

//循环读取
void mainLoop()
{
    //先获取100帧数据, 并写文件
    // int count = 50;
    // while ( count-- > 0 )
    while ( 1 )
    {
        for(;;){
            //等待获取帧
            fd_set fds;
            struct timeval tv;
            //超时时间设置
            tv.tv_sec = 10;
            tv.tv_usec = 0;

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

            //监听设备套接字
            int res = select(fd + 1, &fds, NULL, NULL, &tv);
            //失败
            if (-1 == res) {
                if (EINTR == errno)
                    continue;
                printf("error.\n");
                exit(0);
            }

            //超时
            if (0 == res) {
                printf("TimeOut.\n");
                exit(0);
            }

            //正常读取一帧
            // if ( readFrame(count) )
            if ( readFrame(0) )
            {
                // printf("count - %d\n", count);
                break;
            }
        }
    }
}

void stop_capturing(void) {

    enum v4l2_buf_type type;
}



void fun_sig(int sig)
{
    printf("\nsig = %d\n",sig);

    //停止数据流
    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if ( -1 == ioctl(fd, VIDIOC_STREAMOFF &type) ){
        perror("stop Capture fail.\n");
    }

    //关闭句柄
    fclose(fl);
    fclose(f420);
    fclose(frgb32);
    fclose(f422);
    close(fd);
    exit(0);
}

int main(int argc, char **argv)
{
    //捕捉系统Ctrl+C信号
    signal(SIGINT, fun_sig);
    //捕捉段错误信号
    signal(SIGSEGV, fun_sig);

    /* 第一步:打开摄像头设备文件 */
    int ret, i;
    fd = open(VIDEO_FILE, O_RDWR);
    if (-1 == fd){
        perror("open /dev/video0 fail.\n");
        return -1;
    }

    //获取设备属性, 包括图像的格式
    struct v4l2_fmtdesc fmtdesc;
    memset(&fmtdesc, 0, sizeof (fmtdesc));
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;         //操作类型为获取图片
    while( ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0 )
    {
        printf("index: %d, description: %s\n", fmtdesc.index+1, fmtdesc.description);
        fmtdesc.index++;
    }
    printf("VIDIOC_ENUM_FMT Finish...\n");


    /* 第二步:设置捕捉图片帧格式 */
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 操作类型为获取图片
    format.fmt.pix.width = WIDTH;               // 图片的宽度
    format.fmt.pix.height = HEIGHT;             // 图片的高度
    format.fmt.pix.pixelformat = FMT;           // 图片格式
    ret = ioctl(fd, VIDIOC_S_FMT, &format);     // 进行设置(Set)
    if (-1 == ret)
    {
        perror("ioctl VIDIOC_S_FMT");
        close(fd);
        return -2;
    }
    printf("VIDIOC_S_FMT Finish...\n");


    /* 第三步:检查是否设置成功 */
    ret = ioctl(fd, VIDIOC_G_FMT, &format);
    if (-1 == ret)
    {
        perror("ioctl VIDIOC_G_FMT");
        close(fd);
        return -3;
    }
    if (format.fmt.pix.pixelformat == FMT)
    {
        printf("ioctl VIDIOC_S_FMT sucessful\n");
    }
    else{
        printf("ioctl VIDIOC_S_FMT failed\n");
    }
    printf("VIDIOC_G_FMT Finish...\n");

    
    /* 第四步:让摄像头驱动申请存放图像数据的缓冲区 */
    struct v4l2_requestbuffers reqbuf;
    reqbuf.count = COUNT;                       // 缓冲区个数
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 缓冲区类型
    reqbuf.memory = V4L2_MEMORY_MMAP;           // 缓冲区的用途:用于内存映射
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
    if (-1 == ret)
    {
        perror("ioctl VIDIOC_REQBUFS");
        close(fd);
        return -4;
    }
    printf("VIDIOC_REQBUFS Finish...\n");


    /* 第五步:查询每个缓冲区的信息,同时进行内存映射 */
    struct v4l2_buffer buff;
    buff.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buff.memory = V4L2_MEMORY_MMAP;
    for (i = 0; i < COUNT; i++)
    {
        buff.index = i;
        ret = ioctl(fd, VIDIOC_QUERYBUF, &buff);
        if (-1 == ret)
            break;

        /* 打印缓冲区的长度和偏移量 */
        printf("buf[%d]: len = %d offset: %d\n", i, buff.length, buff.m.offset);

        /* 把每块缓冲区映射到当前进程来 */
        datas[i] = (unsigned char*)mmap(NULL, buff.length, PROT_READ, MAP_SHARED, fd, buff.m.offset);
        if (MAP_FAILED == datas[i])             // 映射失败
        {
            perror("mmap failed");
            return -5;
        }

        /* 把映射成功的缓冲区加入到摄像头驱动的图像数据采集队列里 */
        ret = ioctl(fd, VIDIOC_QBUF, &buff);    // Queue
        if (-1 == ret)
        {
            perror("VIDIOC_QBUF");
            return -6;
        }
    }
    printf("VIDIOC_QUERYBUF Finish...\n");

    
    /* 第六步:启动采集 */
    int on = V4L2_BUF_TYPE_VIDEO_CAPTURE;       // 设置启动标志位
    ret = ioctl(fd, VIDIOC_STREAMON, &on);      // 开启摄像头流
    if (-1 == ret)
    {
        perror("ioctl VIDIOC_STREAMON");
        close(fd);
        return -7;
    }

    printf("VIDIOC_STREAMON Finish...\n");


    //申请YUV420图像内存(空间大小为 3 * width * height / 2 )
    //申请RGB32图像内存(空间大小为3 * width * height )
    pYUV420Buff = new uint8_t[3 * WIDTH * HEIGHT / 2];
    pRGBBuff = new unsigned char [3 * WIDTH * HEIGHT];
    pYUV422 = new unsigned char [2 * LEN_YUV422];

#if 0 

    fl = fopen(FILE_NAME, "w");
    if (NULL == fl)
    {
        fprintf(stderr, "open %s failed.\n", FILE_NAME);
        close(fd);
        return -1;
    }

    f420 = fopen(FILE_YUV420, "w");
    if ( NULL == f420 )
    {
        fprintf(stderr, "open %s failed.\n", FILE_YUV420);
        close(fd);
        return -1;
    }

    f422 = fopen(FILE_YUV422, "w");
    if (  f422 == nullptr )
    {
        fprintf(stderr, "open %s failed.\n", FILE_YUV422);
        close(fd);
        return -1;
    }

    frgb32 = fopen(FILE_RGB, "w");
    if ( NULL == frgb32 )
    {
        fprintf(stderr, "open %s failed.\n", FILE_RGB);
        close(fd);
        return -1;
    }

#endif

    /* 第七步: 循环获取视频帧数据 */
    mainLoop();

    /* 第八步: 关闭文件句柄 */

#if 0     
    fclose(fl);
    fclose(f420);
    fclose(frgb32);
    fclose(f422);
#endif

    close(fd);

    return 0;
}

参考: https://mp.weixin.qq.com/s/sG_8_tzQUI7hOkBV7s51Zw

 

标签:int,V4L2,Linux,unsigned,char,width,简单,V4l2,out
From: https://www.cnblogs.com/weijian168/p/18097123

相关文章

  • Linux Mint下Qt Creator无法输入中文解决办法
    ubuntu下有对应的fcitx-frontend-qt6软件包,直接安装就能解决问题。但是linuxmint只有基于qt5的,目前使用Qtonlineinstaller安装的QtCreator是基于Qt6.6编译的所以,只能自己编译对应的fcitx-frontend-qt6动态库,然后放到对应目录下首先下载对应的源码gitclonehttps://github......
  • V4L2应用程序开发(1)
    参考资料:韦东山第三期 v4l2应用程序开发分为两个部分,数据采集流程和控制流程两个部分 数据采集流程:分为空闲链表和完成链表 驱动程序周而复始地做如下事情:从硬件采集到数据把"空闲链表"取出buffer,把数据存入buffer把含有数据的buffer放入"完成链表"APP也会周而......
  • linux添加目录到环境变量中
    步骤一、/etc/profile文件进入编辑模式执行如下命令让/etc/profile文件进入编辑模式。sudovim/etc/profile步骤二、添加内容在文件的最后一行添加如下内容:PATH=$PATH:/home/fenglv/software/installed/bin/其中/home/fenglv/software/installed/bin/为要添加的目录,如2-2......
  • dbt return macro 内部实现简单说明
    jinja2默认是没有returnmacro的,dbt在实现的时候比较有意思,通过一个exception触发的,以下是简单说明参考使用一个包含return的macro{%macrodemoapp(name,version)%}{%ifversion=='v1'%}{{return("appdemo")}}{%else%}......
  • Linux调试小技巧总结
    1如果你是release运行时出问题,很难复现请,参考我的这篇博客,在编译之初做好准备  https://www.cnblogs.com/8335IT/p/18079295  linux上编译release并剥离调试信息  配合gdbattachpid/c/breakXXX.cpp:lineNo./s/n/pstack等在线调试(root)2如果你是测试过程中发现crash......
  • PIL的简单使用
    PIL概念:PIL(pythonImagingLibrary),已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。PIL中所涉及的基本概念有如下几个:通道(bands)、模式(mode)、尺寸(size)、坐标系统(coordinatesystem)、调色板(palette)、信息(info)和滤波器(filters)。1、通道通常......
  • Linux常用命令介绍
    Linux常用命令介绍Linux中的命令非常多,但是玩过Linux的人也从来不会因为Linux的命令如此之多而烦恼,因为我们只需要掌握我们最常用的命令就可以了。因为不想在使用时总是东查西找,所以在此总结一下,方便一下以后的查看。下面就说说我最常用的Linux命令。1、cd命令这是一......
  • Linux命令大全
    常用快捷键ctrl+c            停止进程ctrl+l =clear            清屏reset           彻底清屏ctrl+q          退出ctrl+alt               linux和......
  • 保姆级教程:教你UniMRCP对接华为云ASR(Linux版)
    本文分享自华为云社区《unimrcp对接华为云ASR(Linux版)》,作者:ASR-beginer。本篇文章提供了unimrcp对接华为云ASR的保姆级教程,根据第一到四章,可从头逐步编译+集成基于华为云ASR的unimrcp系统(授人以渔)。同时,本文第五章(直接给条鱼)提供了作者修改好的源码,直接一键编译即可。一、安装u......
  • 一个基于.NET Core构建的简单、跨平台、模块化的商城系统
    前言今天大姚给大家分享一个基于.NETCore构建的简单、跨平台、模块化、完全开源免费(MITLicense)的商城系统:ModuleShop。商城后台管理端功能商品:分类、品牌、单位、选项(销售属性)、属性、属性模板、属性组。销售:订单、物流。内容:首页配置、评论、回复。配置:国家、用......