当前位置:   article > 正文

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/767530
推荐阅读
相关标签
  

闽ICP备14008679号