通过 V4L2 读取摄像头
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h> // libv4l-dev
#define VIDEO_DEV "/dev/video0"
int main()
{
// 打开设备
int videoFd = open(VIDEO_DEV, O_RDWR);
if(videoFd < 0)
{
fprintf(stderr, "open %s failed: %s\n", VIDEO_DEV, strerror(errno));
return EXIT_FAILURE;
}
// 读取设备属性
struct v4l2_capability videoCap;
if(ioctl(videoFd, VIDIOC_QUERYCAP, &videoCap) < 0)
{
close(videoFd);
fprintf(stderr, "ioctl VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("%s\n", videoCap.card);
// 判断是否是摄像头
if(videoCap.capabilities & V4L2_CAP_VIDEO_CAPTURE != V4L2_CAP_VIDEO_CAPTURE)
{
fprintf(stderr, "%s doesn't support video recording\n", VIDEO_DEV);
close(videoFd);
return EXIT_FAILURE;
}
// 读取支持的格式
bool supportYUYV = false;
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index=0;
fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while(ioctl(videoFd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
if(fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV)
{
supportYUYV = true;
}
}
if(supportYUYV == false)
{
fprintf(stderr, "YUYV 4:2:2 not supported\n");
close(videoFd);
return EXIT_FAILURE;
}
// 设置帧格式
struct v4l2_format videoFormat;
videoFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
videoFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if(ioctl(videoFd, VIDIOC_S_FMT, &videoFormat) < 0)
{
fprintf(stderr, "ioctl VIDIOC_S_FMT failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 申请缓冲
struct v4l2_requestbuffers request;
request.count = 3; //三帧缓冲
request.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
request.memory = V4L2_MEMORY_MMAP;
if(ioctl(videoFd, VIDIOC_REQBUFS, &request) < 0)
{
fprintf(stderr, "ioctl VIDIOC_REQBUFS failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 获取缓冲
struct v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = 0;
if(ioctl(videoFd, VIDIOC_QUERYBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_QUERYBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
void* bufPtr = mmap(NULL, buffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, videoFd, buffer.m.offset);
if(bufPtr == NULL)
{
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 创建一个帧缓冲
if(ioctl(videoFd, VIDIOC_QBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_QBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 开始采集
enum v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(videoFd, VIDIOC_STREAMON, &bufType) < 0)
{
fprintf(stderr, "ioctl VIDIOC_STREAMON failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 等待采集完成
fd_set fds;
FD_ZERO(&fds);
FD_SET(videoFd, &fds);
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
select(1, &fds, NULL, NULL, &tv);
// 读取一帧图像并删除帧缓冲
if(ioctl(videoFd, VIDIOC_DQBUF, &buffer) < 0)
{
fprintf(stderr, "ioctl VIDIOC_DQBUF failed: %s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 停止采集
ioctl(videoFd, VIDIOC_STREAMOFF, &bufType);
// 创建一个PPM文件
FILE* ppmFptr = fopen("output.ppm", "wb");
if(ppmFptr == NULL)
{
fprintf(stderr, "%s\n", strerror(errno));
close(videoFd);
return EXIT_FAILURE;
}
// 写入PPM header
fprintf(ppmFptr, "P3\n%d %d\n255\n", videoFormat.fmt.pix.width, videoFormat.fmt.pix.height);
// 读取像素,写入PPM文件
for(size_t i = 0; i+3 < buffer.length; i+=4)
{
uint8_t Y1 = ((uint8_t*)bufPtr)[i];
uint8_t U = ((uint8_t*)bufPtr)[i+1];
uint8_t Y2 = ((uint8_t*)bufPtr)[i+2];
uint8_t V = ((uint8_t*)bufPtr)[i+3];
uint8_t B = 1.164 * (Y1 - 16) + 2.018 * (U - 128);
uint8_t G = 1.164 * (Y1 - 16) - 0.391 * (U - 128) - 0.813 * (V - 128);
uint8_t R = 1.164 * (Y1 - 16) + 1.596 * (V - 128);
fprintf(ppmFptr, "%u %u %u ", R, G, B);
B = 1.164 * (Y2 - 16) + 2.018 * (U - 128);
G = 1.164 * (Y2 - 16) - 0.391 * (U - 128) - 0.813 * (V - 128);
R = 1.164 * (Y2 - 16) + 1.596 * (V - 128);
fprintf(ppmFptr, "%u %u %u ", R, G, B);
}
close(videoFd);
fclose(ppmFptr);
return EXIT_SUCCESS;
}