Linux摄像头(v4l2应用)采集JPEG

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdint.h>

int fd = -1;
struct v4l2_capability cap;
enum v4l2_buf_type capture_type;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_frmsizeenum frmsize;
struct v4l2_frmivalenum fival;
struct v4l2_format fmt;
struct v4l2_requestbuffers req_buffers;

void saveJPEGFrame(uint8_t* frameData, size_t frameSize, int frameIndex) {
    char filename[20];
    snprintf(filename, sizeof(filename), "./frame_%d.jpg", frameIndex);

    // 创建输出文件
    FILE* outFile = fopen(filename, "wb");
    if (outFile == NULL) {
        perror("无法创建输出文件");
        return;
    }

    // 将 JPEG 数据写入文件
    fwrite(frameData, 1, frameSize, outFile);

    // 关闭文件
    fclose(outFile);
}

void saveMJPEG(struct v4l2_buffer *buf, uint8_t* buffer, int frameIndex) {
    uint8_t* frameData = buffer + buf->m.offset;
    size_t remainingData = buf->bytesused;

    while (remainingData > 0) {
        // 找到 JPEG 图像的起始位置
        uint8_t* startMarker = frameData;
        size_t frameSize = 0;

        // 在数据中查找 JPEG 图像的结束标记
        while (frameSize < remainingData) {
            if (startMarker[0] == 0xFF && startMarker[1] == 0xD9) {
                // 找到了 JPEG 图像的结束标记
                frameSize += 2; // 包括结束标记本身
                break;
            }
            startMarker++;
            frameSize++;
        }

        // 保存 JPEG 图像
        saveJPEGFrame(frameData, frameSize, frameIndex);

        // 更新下一个 JPEG 图像的起始位置和剩余数据大小
        frameData += frameSize;
        remainingData -= frameSize;
    }
}

volatile int is_exec = 1;

void handler(int signum) 
{
    is_exec = 0;
}

int main(int argc, char **argv)
{
    if(2 != argc)
    {
        printf("param error, must like this : @exec_file @devname\n");
        goto exit;
    }

    // 注册信号响应函数,在收到CTL+C信号的时候退出主循环,释放内存关闭设备
    signal(SIGINT, handler);

    // 01以阻塞形式打开video设备
    if(-1 == (fd = open(argv[1], O_RDWR)))
    {
        printf("open %s failed\n", argv[1]);
        goto exit;
    }

    // 02通过ioctl向已经打开的设备发送命令VIDIOC_QUERYCAP,要求驱动程序返回设备信息到v4l2_capability结构体里面
    memset(&cap, 0, sizeof(cap));
    if(-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
    {
        printf("get video dev info failed\n");
        goto exit;
    }

    // 03通过设备返回到v4l2_capability结构体里面的capabilities成员来判断这个驱动文件下面挂载的设备有没有捕获图片的能力
    // 没有捕获图片的能力的文件我们就无法从他那里获取摄像头图片,就要退出程序
    if(0 == (V4L2_CAP_VIDEO_CAPTURE & cap.capabilities))
    {
        printf("is no V4L2_CAP_VIDEO_CAPTURE dev\n");
        goto exit;
    }

    // 04根据cap.device_caps成员识别设备支持的视频输入类型,见上面的3.2.1补充知识点
    if (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE_MPLANE) 
    {
        capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    } 
    else if (cap.device_caps & V4L2_BUF_TYPE_VIDEO_CAPTURE) 
    {
        capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    } 
    else 
    {
        printf("is no V4L2_CAP_VIDEO\n");
        goto exit;
    }

    printf("==============================================================\n");
    printf("version : %x\n",        cap.version);
    printf("card name : %s\n",      cap.card);
    printf("device_caps : %x\n",    cap.device_caps);
    printf("driver name : %s\n",    cap.driver);
    printf("capabilities : %x\n",   cap.capabilities);
    printf("bus_info name : %s\n",  cap.bus_info);
    printf("==============================================================\n");

    // 05通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FMT,要求驱动程序返回像素格式到v4l2_fmtdesc结构体里面
    memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = capture_type; //支持的设备视频输入类型
    fmtdesc.index = 0;
    if(-1 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
    {
        printf("get no VIDIOC_ENUM_FMT\n");
        goto exit;
    }

    printf("==============================================================\n");
    printf("description : %s\n",    fmtdesc.description);
    printf("type : %x\n",           fmtdesc.type);
    printf("index : %x\n",          fmtdesc.index);
    printf("flags : %x\n",          fmtdesc.flags);
//    printf("mbus_code : %x\n",      fmtdesc.mbus_code);
    printf("pixelformat : %x\n",    fmtdesc.pixelformat);
    printf("==============================================================\n");

    // 06通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FRAMESIZES,要求驱动程序返回分辨率格式到v4l2_frmsizeenum结构体里面
    memset(&frmsize, 0, sizeof(frmsize));
    frmsize.pixel_format = fmtdesc.pixelformat;
    frmsize.index = 0;
    if(-1 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
    {
        printf("get no VIDIOC_ENUM_FRAMESIZES\n");
        goto exit;
    }

    // 07判断设备是否为离散设备。一般是离散设备就设置默认帧率,连续设备的的话考虑最大可接受的帧率,这里按照离散设备做处理
    if(V4L2_FRMSIZE_TYPE_DISCRETE != frmsize.type)
    {
        printf("get no V4L2_FRMSIZE_TYPE_DISCRETE\n");
        goto exit;
    }

    printf("==============================================================\n");
    printf("width : %d\n",          frmsize.discrete.width);
    printf("height : %d\n",         frmsize.discrete.height);
    printf("type : %d\n",           frmsize.type);
    printf("index : %d\n",          frmsize.index);
    printf("pixel_format : %d\n",   frmsize.pixel_format);
    printf("==============================================================\n");

    // 08通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FRAMEINTERVALS,要求驱动程序返回帧率格式到v4l2_frmivalenum结构体里面
    memset(&fival, 0, sizeof(fival));
    fival.pixel_format = frmsize.pixel_format;
    fival.width = frmsize.discrete.width;
    fival.height = frmsize.discrete.height;
    if(-1 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival))
    {
        printf("get no VIDIOC_ENUM_FRAMEINTERVALS\n");
        goto exit;
    }       

    // 09判断设备是否为离散设备。一般是离散设备就设置默认帧率,连续设备的的话考虑最大可接受的帧率,这里按照离散设备做处理
    if (V4L2_FRMIVAL_TYPE_DISCRETE != fival.type) 
    {
        printf("get no V4L2_FRMIVAL_TYPE_DISCRETE\n");
        goto exit;
    }

    int frameRate = fival.discrete.denominator / fival.discrete.numerator; // 计算帧率
    printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-frameRate : %d\n", frameRate);

    // 10::05~09步已经查询到了像素格式、分辨率、帧率,我们已经知道了这个摄像头设备支持的格式,这一步就把之前获取到的图像格式设置进驱动程序,以后驱动程序就会按照我们设置好的图像格式向我们应用程序通过图片
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = capture_type;
    fmt.fmt.pix.width = fival.width;
    fmt.fmt.pix.height = fival.height;
    fmt.fmt.pix.pixelformat = fmtdesc.pixelformat;
    if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt))
    {
        printf("get no VIDIOC_S_FMT\n");
        goto exit;
    }

    printf("set cam get picture fmt success\n");

    // 11通过ioctl向已经打开的设备发送命令VIDIOC_REQBUFS,申请创建n个buffer内存存放图片数据
    memset(&req_buffers, 0, sizeof(req_buffers));
    req_buffers.type = capture_type; // 摄像头捕获类型
    req_buffers.memory = V4L2_MEMORY_MMAP;
    req_buffers.count = 4; // 申请buffer数量为4
    if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req_buffers)) 
    {
        printf("get no VIDIOC_REQBUFS\n");
        goto exit;
    }

    // 定义结构体,含有指针以及指针内容的长度,保存驱动程序映射上来的驱动空间指针
    struct MyVideoBuffer
    {
        unsigned char *data;
        int len;
    };
    struct MyVideoBuffer mVideoBuffer[4];

    // 12申请4个buffer,需要循环4次对每个buffer进行初始化参数设置和mmap映射到应用层指针
    for (int i = 0; i < req_buffers.count; i++) 
    {
        // 定义v4l2_buffer结构,每个buffer需要初始化为什么格式?在buffer结构的成员指定好
        struct v4l2_buffer buffer;
        memset(&buffer, 0, sizeof(buffer));
        buffer.index = i; // 初始化第0~3个buffer中的第i个
        buffer.memory = V4L2_MEMORY_MMAP;
        buffer.type = capture_type; //支持的设备视频输入类型

        // 通过ioctl向已经打开的设备发送命令VIDIOC_QUERYBUF,将上面v4l2_buffer结构制定好的参数作为初始值设置到第i个buffer
        if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buffer)) 
        {
            printf("get %d VIDIOC_QUERYBUF error\n", i);
            goto exit;
        }

        // 映射buffer的驱动空间地址到应用层的指针并且保存指针。
        mVideoBuffer[i].data= (unsigned char *)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer.m.offset);
        mVideoBuffer[i].len = buffer.length;

        // 通过ioctl向已经打开的设备发送命令VIDIOC_QBUF: 将buffer送到驱动程序的队列排队,准备接收数据。
        if (-1 == ioctl(fd, VIDIOC_QBUF, &buffer)) 
        {
            printf("set %d VIDIOC_QUERYBUF to line error\n", i);
            goto exit;
        }
    }

    // 13前面主要完成两件事情,一是设置了捕获图片的格式,二是申请映射好了存放图片数据的内存;那到了这里就可以打开摄像头的开关,摄像头就会像脱缰的野狗开始不断捕捉图片,通过ioctl向已经打开的设备发送命令VIDIOC_STREAMON,摄像头设备就开始拍摄每一张图片了
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) 
    {
        printf("set VIDIOC_STREAMON error\n");
        goto exit;
    }

    // 下面就是while(1)开始不断读取数据了
    // 因为第01步里面我们是以阻塞的方式打开设备,阻塞方式就是驱动程序采集到图片完成后才会把这张采集到的图片刷新到我们第12步里面申请好的4个buffer里面的某一个,“通过ioctl向已经打开的设备发送命令VIDIOC_QBUF: 将buffer送到驱动程序的队列排队,准备接收数据”在驱动程序采集到图片完成后才会把图片刷新到我们通过命令VIDIOC_QBUF排队到队列里面的buffer,如果你以非阻塞的形式打开设备,就是无论驱动程序有没有采集到图片,哪怕没有图片,也会去刷新我们排队到队列里面的buffer。具体使用阻塞还是非阻塞,看场景,这里为了学习方便用阻塞
    // 既然是阻塞,也就是只有在驱动程序采集到图片完成后才会把图片刷新到buffer,那再没有完成采集的时候那段时间里如果还在while1的循环里面等待,不是太浪费CPU了吗?应该没有完成采集的这段时间把CPU让出来,一完成采集我马上把buffer里面的数据读走。那我们必须借助select函数来实现这种功能
    //通过select设置超时监听
    fd_set fds;
    struct timeval tv;
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    int frameId = 0;
    while(is_exec)
    {
        FD_ZERO(&fds);
        FD_SET(fd, &fds);
        tv.tv_sec = 0;
        tv.tv_usec = 50000;

        // 通过select监听fd这个设备,因为是阻塞的,如果设备采集完成select会触发类似“中断”的东西,返回大于0
        // 如果select监听fd这个设备返回-1失败说明设备还没有采集完数据,就把CPU让出去
        if(0 >= select(fd + 1, &fds, NULL, NULL, &tv)) 
            continue;

        // 走到这里说明设备采集完成,没有采集完成的在上面一步就continue;出去了
        // 通过ioctl向已经打开的设备发送命令VIDIOC_DQBUF,从队列获取一个buffer也就是一帧图像的数据
        struct v4l2_buffer t_buffer;
        memset(&t_buffer, 0, sizeof(t_buffer));
        t_buffer.type = capture_type; //支持的设备视频输入类型
        t_buffer.memory = V4L2_MEMORY_MMAP;
        if(-1 == ioctl(fd, VIDIOC_DQBUF, &t_buffer)) 
            continue;

        // 判断获取的状态有没有出错
        if (t_buffer.flags & V4L2_BUF_FLAG_ERROR) 
        {
            printf("v4l2 buf error! buf flag 0x%x, index=%d", t_buffer.flags, t_buffer.index);
            continue;
        }

        // 保存 MJPEG 图像,从之前映射好的应用层指针那里读取一帧图像的数据,并转化为JPG格式的图片保存在文件里
        saveMJPEG(&t_buffer, mVideoBuffer[t_buffer.index].data, frameId);

        // 通过ioctl向已经打开的设备发送命令VIDIOC_QBUF,将用完的buffer送回队列重新等待填充数据
        if (-1 == ioctl(fd, VIDIOC_QBUF, &t_buffer)) 
            continue;

        frameId++; // 更新一次图片的编号,保存为每张图片的id
        printf("KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK-1 len::%d index::%d\n", t_buffer.length,t_buffer.index);
    }

    // 14通过ioctl向已经打开的设备发送命令VIDIOC_STREAMOFF,关闭图像采集
    if(-1 == ioctl(fd, VIDIOC_STREAMOFF, &type)) 
    {
        printf("set VIDIOC_STREAMOFF error\n");
        goto exit;
    }

    // 15释放buffer映射
    for(int i=0; i<4; i++) 
        munmap(mVideoBuffer[i].data, mVideoBuffer[i].len);

    // 16通过ioctl向已经打开的设备发送命令VIDIOC_REQBUFS: 清除buffer
    struct v4l2_requestbuffers req_buffers_clear;
    memset(&req_buffers_clear, 0, sizeof(req_buffers_clear));
    req_buffers_clear.type = capture_type; //支持的设备视频输入类型
    req_buffers_clear.memory = V4L2_MEMORY_MMAP;
    req_buffers_clear.count = 0;
    if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req_buffers_clear)) 
    {
        printf("set VIDIOC_REQBUFS error\n");
        goto exit;
    }

    // 17关闭设备
    if(-1 != fd)
        close(fd);
    return 0;

exit:
    if(-1 != fd)
        close(fd);
    return -1;
}

xxx.Cpp

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdint.h>
#include <string>
#include <cstdio>
#include <vector>
#include <iostream>

// 定义结构体,含有指针以及指针内容的长度,保存驱动程序映射上来的驱动空间指针
struct MyVideoBuffer
{
	uint8_t *data;
	int len;
};

class GetFrame
{
public:
	GetFrame(std::string &t_devname, int t_frameBufNums);
	GetFrame(std::string &&t_devname, int t_frameBufNums);
	~GetFrame();

public:
	int InitDev(void);
	int GetFrameBuffer(void);
	int StreamOn(void);
	int StreamOff(void);
	int UnInitDev(void);
	int GetFd(void);
	void CloseFd(void);
	void SavePictureToFile(struct v4l2_buffer &buf, int t_frameId);
	enum v4l2_buf_type GetCaptureType(void);
	
private:
	void saveJPEGFrame(uint8_t* frameData, size_t frameSize, int frameIndex);
	void saveMJPEG(struct v4l2_buffer &buf, uint8_t* buffer, int frameIndex);
	void SetFrameBufNums(int t_frameBufNums);

private:
	std::string devname;
	int fd{-1};
	int frameRate{0};
	int frameBufNums{5};
	enum v4l2_buf_type capture_type;
	std::vector<MyVideoBuffer> mVideoBuffer;
};

enum v4l2_buf_type GetFrame::GetCaptureType(void)
{
	return capture_type;
}

GetFrame::GetFrame(std::string &t_devname, int t_frameBufNums)
{
	devname.clear();
	devname.swap(t_devname);

	mVideoBuffer.clear();
	SetFrameBufNums(t_frameBufNums);
}

GetFrame::GetFrame(std::string &&t_devname, int t_frameBufNums) : devname(std::move(t_devname))
{
	mVideoBuffer.clear();
	SetFrameBufNums(t_frameBufNums);
}

GetFrame::~GetFrame()
{
	devname.clear();
	StreamOff();
  UnInitDev();

	if(-1 != fd)
		close(fd);
}

int GetFrame::StreamOn(void)
{
	// 13前面主要完成两件事情,一是设置了捕获图片的格式,二是申请映射好了存放图片数据的内存;
	// 那到了这里就可以打开摄像头的开关,摄像头就会像脱缰的野狗开始不断捕捉图片,通过ioctl向已经打开的设备发送命令VIDIOC_STREAMON,摄像头设备就开始拍摄每一张图片了
  int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	return ioctl(fd, VIDIOC_STREAMON, &type);
}

int GetFrame::StreamOff(void)
{
  // 14通过ioctl向已经打开的设备发送命令VIDIOC_STREAMOFF,关闭图像采集
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  return ioctl(fd, VIDIOC_STREAMOFF, &type);
}

int GetFrame::InitDev(void)
{
	do
	{
		if(devname.empty())
		{
			std::cout << "has no devname" << std::endl;
			break;
		}

		if(-1 == (fd = open(devname.c_str(), O_RDWR)))
		{
			std::cout << "open " << devname <<" failed" << std::endl;
			break;
		}

		// 02通过ioctl向已经打开的设备发送命令VIDIOC_QUERYCAP
		// 要求驱动程序返回设备信息到v4l2_capability结构体里面
    struct v4l2_capability cap;
		memset(&cap, 0, sizeof(cap));
		if(-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
		{
			std::cout << "get video dev info failed" << std::endl;
			break;
		}

		// 03通过设备返回到v4l2_capability结构体里面的capabilities成员
		// 来判断这个驱动文件下面挂载的设备有没有捕获图片的能力
    // 没有捕获图片的能力的文件我们就无法从他那里获取摄像头图片,就要退出程序
		if(0 == (V4L2_CAP_VIDEO_CAPTURE & cap.capabilities))
		{
			std::cout << "is no cap dev" << std::endl;
			break;
		}
    
    // 04根据cap.device_caps成员识别设备支持的视频输入类型
    if (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE_MPLANE) 
		{
			capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
		} 
		else if (cap.device_caps & V4L2_BUF_TYPE_VIDEO_CAPTURE) 
		{
			capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		} 
		else 
		{
			std::cout << "unknow cap_type dev" << std::endl;
			break;
		}

		// 05通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FMT
		// 要求驱动程序返回像素格式到v4l2_fmtdesc结构体里面
		struct v4l2_fmtdesc fmtdesc;
		memset(&fmtdesc, 0, sizeof(fmtdesc));
		fmtdesc.type = capture_type; //支持的设备视频输入类型
		fmtdesc.index = 0;
		if(-1 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
		{
			std::cout << "get enum fmt failed" << std::endl;
			break;
		}

		// 06通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FRAMESIZES
		// 要求驱动程序返回分辨率格式到v4l2_frmsizeenum结构体里面
		struct v4l2_frmsizeenum frmsize;
    memset(&frmsize, 0, sizeof(frmsize));
		frmsize.pixel_format = fmtdesc.pixelformat;
		frmsize.index = 0;
		if(-1 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
		{
			std::cout << "get enum frame size failed" << std::endl;
			break;
		}
    
    // 07判断设备是否为离散设备。一般是离散设备就设置默认帧率
		// 连续设备的的话考虑最大可接受的帧率,这里按照离散设备做处理
		if(V4L2_FRMSIZE_TYPE_DISCRETE != frmsize.type)
		{
			std::cout << "frame type is no discrete" << std::endl;
			break;
		}

		// 08通过ioctl向已经打开的设备发送命令VIDIOC_ENUM_FRAMEINTERVALS
		// 要求驱动程序返回帧率格式到v4l2_frmivalenum结构体里面
		struct v4l2_frmivalenum fival;
    memset(&fival, 0, sizeof(fival));
		fival.pixel_format = frmsize.pixel_format;
		fival.width = frmsize.discrete.width;
		fival.height = frmsize.discrete.height;
		if(-1 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival))
		{
			std::cout << "get enum frame intervals size failed" << std::endl;
			break;
		}		
	
    // 09判断设备是否为离散设备。一般是离散设备就设置默认帧率
		// 连续设备的的话考虑最大可接受的帧率,这里按照离散设备做处理
		if (V4L2_FRMIVAL_TYPE_DISCRETE != fival.type) 
		{
			std::cout << "frame ival type is no discrete" << std::endl;
			break;
		}
	
		frameRate = fival.discrete.denominator / fival.discrete.numerator; // 计算帧率

    // 10::05~09步已经查询到了像素格式、分辨率、帧率,我们已经知道了这个摄像头设备支持的格式
		// 这一步就把之前获取到的图像格式设置进驱动程序,以后驱动程序就会按照我们设置好的图像格式向我们应用程序通过图片
		struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = capture_type;
    fmt.fmt.pix.width = fival.width;
    fmt.fmt.pix.height = fival.height;
    fmt.fmt.pix.pixelformat = fmtdesc.pixelformat;
		if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt))
		{
			std::cout << "set s fmt failed" << std::endl;
			break;
		}

		return 0;
	} while(0);

	return -1;
}

int GetFrame::GetFrameBuffer(void)
{
	do
	{
    // 11通过ioctl向已经打开的设备发送命令VIDIOC_REQBUFS,申请创建n个buffer内存存放图片数据
		struct v4l2_requestbuffers req_buffers;
    memset(&req_buffers, 0, sizeof(req_buffers));
    req_buffers.type = capture_type; // 摄像头捕获类型
    req_buffers.memory = V4L2_MEMORY_MMAP;
    req_buffers.count = frameBufNums; // 申请buffer数量
    if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req_buffers)) 
		{
      std::cout << "set s fmt failed" << std::endl;
			break;
    }
    
		// ... 
    if(mVideoBuffer.empty())
		{
			for (int i = 0; i < frameBufNums; i++)
			{
				MyVideoBuffer t_oMyVideoBuffer;
				t_oMyVideoBuffer.data = nullptr;
				t_oMyVideoBuffer.len = 0;
				mVideoBuffer.emplace_back(t_oMyVideoBuffer);
			}
		}
    
    // 12申请4个buffer,需要循环4次对每个buffer进行初始化参数设置和mmap映射到应用层指针
    for (int i = 0; i < req_buffers.count; i++) 
		{
			// 定义v4l2_buffer结构,每个buffer需要初始化为什么格式?在buffer结构的成员指定好
			struct v4l2_buffer buffer;
			memset(&buffer, 0, sizeof(buffer));
			buffer.index = i; // 初始化第0~3个buffer中的第i个
			buffer.memory = V4L2_MEMORY_MMAP;
			buffer.type = capture_type; //支持的设备视频输入类型
 
      // 通过ioctl向已经打开的设备发送命令VIDIOC_QUERYBUF,将上面v4l2_buffer结构制定好的参数作为初始值设置到第i个buffer
      if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buffer)) 
			{
				std::cout << "set " << i << " error" << std::endl;
				break;
      }
		
			// 映射buffer的驱动空间地址到应用层的指针并且保存指针。
			mVideoBuffer[i].data= (unsigned char *)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer.m.offset);
			mVideoBuffer[i].len = buffer.length;

			// 通过ioctl向已经打开的设备发送命令VIDIOC_QBUF: 将buffer送到驱动程序的队列排队,准备接收数据。
			if (-1 == ioctl(fd, VIDIOC_QBUF, &buffer)) 
			{
				std::cout << "set " << i << " to line error" << std::endl;
				break;
			}
    }

		return 0;
	} while(0);

	return -1;
}

void GetFrame::CloseFd(void)
{
	if(-1 != fd)
		close(fd);
}

void GetFrame::saveJPEGFrame(uint8_t* frameData, size_t frameSize, int frameIndex) 
{
	std::string filename;
	filename.clear();
	filename.append("./frame_");
	filename.append(std::to_string(frameIndex));
	filename.append(".jpg");

  // 创建输出文件
	FILE* outFile = fopen(filename.c_str(), "wb");
	if(outFile == NULL) 
	{
		perror("无法创建输出文件");
		return;
	}

	// 将 JPEG 数据写入文件
	fwrite(frameData, 1, frameSize, outFile);

	// 关闭文件
	fclose(outFile);
}

void GetFrame::saveMJPEG(struct v4l2_buffer &buf, uint8_t* buffer, int frameIndex) 
{
	uint8_t* frameData = buffer + buf.m.offset;
	size_t remainingData = buf.bytesused;

	while (remainingData > 0) 
	{
		// 找到 JPEG 图像的起始位置
		uint8_t* startMarker = frameData;
		size_t frameSize = 0;

		// 在数据中查找 JPEG 图像的结束标记
		while (frameSize < remainingData) 
		{
			if (startMarker[0] == 0xFF && startMarker[1] == 0xD9) 
			{
				// 找到了 JPEG 图像的结束标记
				frameSize += 2; // 包括结束标记本身
				break;
			}
			startMarker++;
			frameSize++;
		}

		// 保存 JPEG 图像
		saveJPEGFrame(frameData, frameSize, frameIndex);

		// 更新下一个 JPEG 图像的起始位置和剩余数据大小
		frameData += frameSize;
		remainingData -= frameSize;
	}
}

void GetFrame::SetFrameBufNums(int t_frameBufNums)
{
	if(0 > t_frameBufNums)
		return ;
	frameBufNums = t_frameBufNums;
}

int GetFrame::UnInitDev(void)
{
	// 15释放buffer映射
  for(int i=0; i<frameBufNums; i++) 
    munmap(mVideoBuffer[i].data, mVideoBuffer[i].len);
    
  // 16通过ioctl向已经打开的设备发送命令VIDIOC_REQBUFS: 清除buffer
  struct v4l2_requestbuffers req_buffers_clear;
	memset(&req_buffers_clear, 0, sizeof(req_buffers_clear));
  req_buffers_clear.type = capture_type; //支持的设备视频输入类型
  req_buffers_clear.memory = V4L2_MEMORY_MMAP;
  req_buffers_clear.count = 0;
  return ioctl(fd, VIDIOC_REQBUFS, &req_buffers_clear);
}

int GetFrame::GetFd(void)
{
	return fd;
}

void GetFrame::SavePictureToFile(struct v4l2_buffer &buf, int t_frameId)
{
	saveMJPEG(buf, mVideoBuffer[buf.index].data, t_frameId);
}

volatile int is_exec = 1;

void handler(int signum) 
{
	is_exec = 0;
}

int main(void)
{
	// 注册信号响应函数,在收到CTL+C信号的时候退出主循环,释放内存关闭设备
	signal(SIGINT, handler);

	GetFrame m_GetFrame("/dev/video1", 5);
	m_GetFrame.InitDev();
	m_GetFrame.GetFrameBuffer();
	m_GetFrame.StreamOn();
	int fd = m_GetFrame.GetFd();
	
	fd_set fds;
	struct timeval tv;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
  int frameId = 0;
  while(is_exec)
  {
    FD_ZERO(&fds);
		FD_SET(fd, &fds);
		tv.tv_sec = 0;
		tv.tv_usec = 50000;
		
		if(0 >= select(fd + 1, &fds, NULL, NULL, &tv)) 
			continue;
    
		struct v4l2_buffer t_buffer;
		memset(&t_buffer, 0, sizeof(t_buffer));
		t_buffer.type = m_GetFrame.GetCaptureType(); //支持的设备视频输入类型
		t_buffer.memory = V4L2_MEMORY_MMAP;
		if(-1 == ioctl(fd, VIDIOC_DQBUF, &t_buffer)) 
			continue;
		
    // 判断获取的状态有没有出错
		if (t_buffer.flags & V4L2_BUF_FLAG_ERROR) 
			continue;
		
		// 保存 MJPEG 图像,从之前映射好的应用层指针那里读取一帧图像的数据,并转化为JPG格式的图片保存在文件里
		// 或者做处理,发送给http进程
		m_GetFrame.SavePictureToFile(t_buffer, frameId);

		// 通过ioctl向已经打开的设备发送命令VIDIOC_QBUF,将用完的buffer送回队列重新等待填充数据
		if (-1 == ioctl(fd, VIDIOC_QBUF, &t_buffer)) 
			continue;
    
  	frameId++; // 更新一次图片的编号,保存为每张图片的id
    printf("KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK-1 len::%d index::%d\n", t_buffer.length,t_buffer.index);
  }
  
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux摄像头V4L2编程是一种在Linux系统上使用V4L2(Video for Linux 2)接口进行摄像头编程的技术。它可以让开发者通过编写C/C++程序来控制摄像头的各种参数,如分辨率、帧率、曝光时间等,并实现视频流的采集、处理和显示等功能。这种技术在嵌入式系统、机器视觉、视频监控等领域得到广泛应用。 ### 回答2: Linux摄像头v4l2编程是一种基于Linux操作系统的摄像头驱动及其应用程序的编程技术。V4L2(Video4Linux2)是Linux下的一个视频采集标准,在Linux 装有摄像设备时,通过v4l2可以访问硬件设备,并对其进行控制及获取图像流数据。摄像头v4l2编程可以通过Linux系统下的应用程序对摄像头进行访问和控制,同时可以获取到相应的视频数据进行处理和分析。 在摄像头v4l2编程中,开发者可以使用v4l2库来进行编程,该库提供了一系列的API接口供开发者调用,通过这些接口可以实现对摄像头硬件设备的控制和调节,例如设置摄像头分辨率、亮度、对比度、曝光等参数。同时,开发者也可以通过v4l2库进行相关图像采集操作,获得摄像头的图像流数据,进行图像处理和算法分析。 在摄像头v4l2编程中,开发者需要了解一些基本的概念和技术,例如Linux系统的文件系统、驱动程序的编写、设备文件的读写操作等。此外,开发者还需要了解v4l2库的使用方式、相关API接口的调用和使用方法、摄像头图像采集的技术细节等方面的知识。 总之,Linux摄像头v4l2编程是一种基于Linux系统的应用程序开发技术,通过对v4l2库的学习和使用,开发者可以实现对摄像头硬件设备的控制和图像采集操作,为Linux系统下的视频应用程序提供了很好的支持和帮助。 ### 回答3: v4l2(Video for Linux 2)是在Linux内核中用于视频设备管理的API。它提供了摄像头、调节参数、读取视频流等功能,并且其文档资料非常充足,是Linux上最流行的视频编程接口。 v4l2编程分为锁定缓冲区(mmap)方式和非锁定缓冲区(read/write)方式。在v4l2编程中,摄像头设备会以文件方式进行管理,可以使用open()进行文件打开,并使用ioctl()控制摄像头参数。 摄像头采集和显示需要经过如下步骤: 1. 打开设备文件:使用open()函数打开摄像头文件(/dev/video0等)。 2. 设置设备参数:可以使用ioctl()函数设置摄像头的相关参数,如视频格式、分辨率、帧率等。 3. 预览或采集:使用mmap()或read()/write()等函数进行视频流的采集或预览。 4. 销毁缓冲区:使用munmap()函数销毁缓冲区。 在v4l2编程中,使用带有ioctl()命令的结构体来设置和获取摄像头的参数。这些结构体是非常重要的,并且必须正确地填写和使用。常见的结构体包括: - v4l2_capability:获取摄像头驱动的属性信息。 - v4l2_format:设置图像的格式,如图像的大小、颜色格式等。 - v4l2_requestbuffers:请求缓冲区(用于存放采集到的图像数据)。 - v4l2_buffer:描述一个缓冲区,包括缓冲区地址和长度。 - v4l2_ioctl:控制设备驱动程序。 在v4l2编程中,一些常见的命令包括: - VIDIOC_QUERYCAP:查询设备的能力。 - VIDIOC_S_FMT:设置视频格式。 - VIDIOC_REQBUFS:请求缓冲区。 - VIDIOC_QUERYBUF:查询缓冲区的尺寸和位置。 - VIDIOC_QBUF:压入缓冲区到队列。 - VIDIOC_DQBUF:从队列取出一个缓冲区。 - VIDIOC_STREAMON:启动视频流。 - VIDIOC_STREAMOFF:停止视频流。 总的来说,v4l2编程确实有一定的复杂程度,但使用它可以非常方便地控制和操作摄像头,实现视频采集、预览和处理等功能。当然,在进行v4l2编程时,合理地理解和应用各种结构体和命令非常重要,需要多加练习和深入理解。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值