首先ls -l /dev/video* 查看视频节点
然后选择对应的节点 在代码里修改即可下面我附上的是完整的代码 编译后直接运行
总体过程就是 先需要使用VIDIOC_QUERYCAP查询一下能力
看一下是能力是单面还是多面
然后 列出支持的格式 以及格式对应的分辨率
列出之后,逐个尝试设置,看哪个可以 设置完成后
再get一下看对不对
然后申请缓存区 映射缓存区 将缓存区放到队列
完成之后就是开始捕获视频
使用select 抓fd 抓一帧 (这里是判断数据来没来)
来了之后,把队列拿下来 然后取数据
取完数据 存数据 验证数据 然后把buff放回队列 停止捕获 释放缓存区(申请的)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/videodev2.h>
#define DEVICE_PATH "/dev/video1"
#define NUM_BUFFERS 4
#define MAX_FORMATS 16
// 列出设备支持的格式
void list_supported_formats(int fd) {
struct v4l2_fmtdesc fmtdesc = {0};
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
printf("设备支持的格式:\n");
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
printf(" %c%c%c%c: %s\n",
fmtdesc.pixelformat & 0xFF,
(fmtdesc.pixelformat >> 8) & 0xFF,
(fmtdesc.pixelformat >> 16) & 0xFF,
(fmtdesc.pixelformat >> 24) & 0xFF,
fmtdesc.description);
// 列出此格式支持的分辨率
struct v4l2_frmsizeenum frmsize = {0};
frmsize.pixel_format = fmtdesc.pixelformat;
frmsize.index = 0;
printf(" 支持的分辨率:\n");
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
printf(" %dx%d\n",
frmsize.discrete.width,
frmsize.discrete.height);
} else if (frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
printf(" 连续范围: %dx%d - %dx%d\n",
frmsize.stepwise.min_width,
frmsize.stepwise.min_height,
frmsize.stepwise.max_width,
frmsize.stepwise.max_height);
} else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
printf(" 步进范围: %dx%d - %dx%d (步长: %dx%d)\n",
frmsize.stepwise.min_width,
frmsize.stepwise.min_height,
frmsize.stepwise.max_width,
frmsize.stepwise.max_height,
frmsize.stepwise.step_width,
frmsize.stepwise.step_height);
}
frmsize.index++;
}
fmtdesc.index++;
}
}
int main(int argc, char *argv[]) {
int fd;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
struct v4l2_plane planes[NUM_BUFFERS];
unsigned char *buffers[NUM_BUFFERS] = {0};
enum v4l2_buf_type type;
int buffer_count;
fd_set fds;
struct timeval timeout;
// 优先尝试的像素格式列表
unsigned int pixel_formats[MAX_FORMATS] = {
V4L2_PIX_FMT_MJPEG, // 优先使用MJPEG (JPEG压缩)
V4L2_PIX_FMT_NV12, // 其次NV12 (YUV 4:2:0)
V4L2_PIX_FMT_UYVY, // 然后UYVY (YUV 4:2:2)
V4L2_PIX_FMT_YUYV // 最后YUYV (YUV 4:2:2)
};
// 尝试的分辨率列表
struct {
int width;
int height;
} resolutions[] = {
{640, 480},
{800, 600},
{1280, 720},
{1920, 1080}
};
int resolution_count = sizeof(resolutions) / sizeof(resolutions[0]);
// 打开设备
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("打开设备失败");
return -1;
}
// 查询设备能力
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("查询设备能力失败");
close(fd);
return -1;
}
printf("设备: %s, 驱动: %s\n", cap.card, cap.driver);
printf("视频捕获支持: %s\n",
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ? "是" : "否");
printf("多平面支持: %s\n",
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) ? "是" : "否");
// 列出支持的格式
list_supported_formats(fd);
// 设置格式 (尝试多种格式和分辨率)
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
int format_found = 0;
int resolution_found = 0;
// 尝试所有格式和分辨率组合
for (int i = 0; i < MAX_FORMATS; i++) {
if (pixel_formats[i] == 0) break;
for (int r = 0; r < resolution_count; r++) {
fmt.fmt.pix_mp.width = resolutions[r].width;
fmt.fmt.pix_mp.height = resolutions[r].height;
fmt.fmt.pix_mp.pixelformat = pixel_formats[i];
fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
fmt.fmt.pix_mp.num_planes = 1;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == 0) {
printf("成功设置格式: %c%c%c%c, %dx%d\n",
fmt.fmt.pix_mp.pixelformat & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 16) & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 24) & 0xFF,
fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height);
format_found = 1;
resolution_found = 1;
break;
} else {
perror("设置格式失败,尝试下一种");
}
}
if (format_found) break;
}
if (!format_found) {
fprintf(stderr, "错误: 无法设置任何支持的格式\n");
close(fd);
return -1;
}
// 验证实际设置的格式
if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) {
perror("获取格式失败");
close(fd);
return -1;
}
printf("实际格式: %c%c%c%c, %dx%d, %u 平面\n",
fmt.fmt.pix_mp.pixelformat & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 16) & 0xFF,
(fmt.fmt.pix_mp.pixelformat >> 24) & 0xFF,
fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height,
fmt.fmt.pix_mp.num_planes);
// 申请缓冲区
memset(&req, 0, sizeof(req));
req.count = NUM_BUFFERS;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("申请缓冲区失败");
close(fd);
return -1;
}
buffer_count = req.count;
printf("分配了 %d 个缓冲区\n", buffer_count);
// 映射缓冲区
for (int i = 0; i < buffer_count; i++) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.planes = planes;
buf.length = 1;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("查询缓冲区失败");
goto cleanup;
}
buffers[i] = mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, planes[0].m.mem_offset);
if (buffers[i] == MAP_FAILED) {
perror("映射缓冲区失败");
goto cleanup;
}
printf("缓冲区 %d: 大小=%zu 字节\n", i, planes[0].length);
// 将缓冲区放入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("缓冲区入队失败");
munmap(buffers[i], planes[0].length);
goto cleanup;
}
}
// 开始捕获
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("启动捕获失败");
goto cleanup;
}
printf("等待视频数据...\n");
// 使用select()等待数据可用
FD_ZERO(&fds);
FD_SET(fd, &fds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int r = select(fd + 1, &fds, NULL, NULL, &timeout);
if (r < 0) {
perror("select失败");
goto cleanup;
} else if (r == 0) {
printf("select超时\n");
goto cleanup;
}
// 捕获一帧
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_MMAP;
buf.m.planes = planes;
buf.length = 1;
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("获取缓冲区失败");
goto cleanup;
}
printf("捕获到一帧: 缓冲区索引=%d, 大小=%zu 字节\n",
buf.index, planes[0].bytesused);
// 根据格式保存数据
const char *extension = "yuv";
if (fmt.fmt.pix_mp.pixelformat == V4L2_PIX_FMT_MJPEG) {
extension = "jpg";
}
char filename[32];
sprintf(filename, "frame.%s", extension);
FILE *fp = fopen(filename, "wb");
if (!fp) {
perror("打开文件失败");
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)
perror("缓冲区回队失败");
goto cleanup;
}
size_t written = fwrite(buffers[buf.index], 1, planes[0].bytesused, fp);
fclose(fp);
printf("已保存 %zu 字节到 %s\n", written, filename);
// 验证数据有效性
if (fmt.fmt.pix_mp.pixelformat == V4L2_PIX_FMT_MJPEG) {
fp = fopen(filename, "rb");
if (fp) {
unsigned char signature[2];
if (fread(signature, 1, 2, fp) == 2) {
if (signature[0] == 0xFF && signature[1] == 0xD8) {
printf("验证: 确认为JPEG格式文件\n");
} else {
printf("警告: 文件不是有效的JPEG格式\n");
}
}
fclose(fp);
}
}
// 将缓冲区重新放入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("缓冲区回队失败");
}
// 停止捕获
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
perror("停止捕获失败");
}
// 释放缓冲区
for (int i = 0; i < buffer_count; i++) {
if (buffers[i]) {
munmap(buffers[i], planes[0].length);
}
}
close(fd);
return 0;
cleanup:
// 清理资源
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < buffer_count; i++) {
if (buffers[i]) {
munmap(buffers[i], planes[0].length);
}
}
close(fd);
return -1;
}
8540

被折叠的 条评论
为什么被折叠?



