Linux C如何获取摄像头视频流?

Linux系统下通过C语言访问摄像头通常依赖Video for Linux Two(V4L2)框架,这是Linux内核提供的视频设备接口标准,广泛应用于USB摄像头、内置摄像头等视频输入设备,V4L2通过设备文件(如/dev/video0)与用户程序交互,支持视频捕获、格式设置、帧缓冲管理等功能,本文将详细介绍使用C语言结合V4L2获取摄像头视频流的完整流程,包括环境准备、核心概念、代码实现及常见问题处理。

linuxc如何获取摄像头

环境准备

在开始开发前,需确保系统已安装必要的工具和库,以Ubuntu/Debian系统为例,通过以下命令安装V4L2开发工具和依赖库:

sudo apt update
sudo apt install v4l-utils libv4l-dev build-essential

安装后,使用ls /dev/video*查看摄像头设备节点,通常为/dev/video0(若有多个摄像头,可能依次编号为video1、video2等),若提示权限不足,需将当前用户加入video组(重启后生效):

sudo usermod -a -G video $USER

V4L2核心概念

V4L2通过一系列ioctl系统调用控制摄像头,核心概念包括:

  1. 设备文件:摄像头以字符设备形式存在,通过open()函数以读写模式(O_RDWR)打开。
  2. 视频格式:包括像素格式(如YUYV、MJPG、RGB24)、分辨率(如640×480)、帧率(如30fps)等,需通过struct v4l2_format结构体设置。
  3. 帧缓冲:存储视频帧的内存区域,V4L2支持两种内存映射方式:
    • mmap:将内核空间缓冲区映射到用户空间,减少数据拷贝,适合实时视频流;
    • 用户指针:用户自行分配内存,需手动拷贝数据,灵活性较低但可控性强。

具体实现步骤

打开设备

使用open()函数打开摄像头设备文件,需检查返回值是否有效:

int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
    perror("Failed to open device");
    exit(EXIT_FAILURE);
}

查询设备能力

通过VIDIOC_QUERYCAP ioctl获取设备基本信息(如设备名称、版本号、支持的功能):

struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
    perror("VIDIOC_QUERYCAP failed");
    close(fd);
    exit(EXIT_FAILURE);
}
printf("Device: %sn", cap.card);

设置视频格式

根据摄像头支持的格式设置分辨率、像素格式等参数,首先填写struct v4l2_format结构体,然后通过VIDIOC_S_FMT ioctl应用设置:

linuxc如何获取摄像头

struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 捕获类型
fmt.fmt.pix.width = 640;               // 分辨率宽度
fmt.fmt.pix.height = 480;              // 分辨率高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 像素格式(YUV422)
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; // 隔行/逐行扫描
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
    perror("VIDIOC_S_FMT failed");
    close(fd);
    exit(EXIT_FAILURE);
}

申请缓冲区

通过VIDIOC_REQBUFS ioctl申请内核缓冲区,指定缓冲区数量和内存类型:

struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4;                        // 缓冲区数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;        // 使用mmap映射
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
    perror("VIDIOC_REQBUFS failed");
    close(fd);
    exit(EXIT_FAILURE);
}

映射缓冲区

遍历缓冲区,通过VIDIOC_QUERYBUF ioctl获取每个缓冲区的物理地址,并使用mmap映射到用户空间:

struct buffer {
    void *start;
    size_t length;
} *buffers = calloc(req.count, sizeof(*buffers));
for (int i = 0; i < req.count; i++) {
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
        perror("VIDIOC_QUERYBUF failed");
        free(buffers);
        close(fd);
        exit(EXIT_FAILURE);
    }
    buffers[i].length = buf.length;
    buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (buffers[i].start == MAP_FAILED) {
        perror("mmap failed");
        free(buffers);
        close(fd);
        exit(EXIT_FAILURE);
    }
}

开始捕获

将缓冲区入队(VIDIOC_QBUF),然后调用VIDIOC_STREAMON启动视频流:

for (int i = 0; i < req.count; i++) {
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
        perror("VIDIOC_QBUF failed");
        free(buffers);
        close(fd);
        exit(EXIT_FAILURE);
    }
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
    perror("VIDIOC_STREAMON failed");
    free(buffers);
    close(fd);
    exit(EXIT_FAILURE);
}

循环捕获帧

使用select()或poll()等待缓冲区就绪,然后通过VIDIOC_DQBUF出队获取帧数据,处理完成后重新入队:

fd_set fds;
struct timeval tv;
int r;
while (1) {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    tv.tv_sec = 2;  // 超时时间(秒)
    tv.tv_usec = 0;
    r = select(fd + 1, &fds, NULL, NULL, &tv);
    if (r < 0) {
        perror("select failed");
        break;
    } else if (r == 0) {
        printf("Timeoutn");
        continue;
    }
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
        perror("VIDIOC_DQBUF failed");
        break;
    }
    // 处理帧数据(buffers[buf.index].start指向帧数据)
    printf("Captured frame: %d bytesn", buf.bytesused);
    if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
        perror("VIDIOC_QBUF failed");
        break;
    }
}

释放资源

停止视频流(VIDIOC_STREAMOFF),取消内存映射,关闭设备文件:

ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < req.count; i++) {
    munmap(buffers[i].start, buffers[i].length);
}
free(buffers);
close(fd);

常见视频格式对比

像素格式 说明 适用场景
V4L2_PIX_FMT_YUYV 紧缩YUV422格式,每2字节存储4像素的YUV数据,兼容性高 通用视频捕获、实时传输
V4L2_PIX_FMT_MJPG JPEG压缩格式,数据量小,需CPU解压 网络传输、低带宽场景
V4L2_PIX_FMT_RGB24 每像素3字节(R/G/B),处理方便但数据量大,需转换格式 图像处理、GUI显示
V4L2_PIX_FMT_GREY 灰度格式,每像素1字节,节省内存 人脸检测、边缘检测等算法处理

常见问题处理

  1. 权限问题:若提示“Permission denied”,可临时通过sudo chmod 666 /dev/video0开放权限,或确保用户属于video组。
  2. 设备未识别:检查摄像头是否连接,使用lsusb查看USB设备,若未显示摄像头驱动,尝试加载驱动(如UVC摄像头:sudo modprobe uvcvideo)。
  3. 格式不支持:通过v4l2-ctl --list-formats命令查看摄像头支持的所有格式,修改代码中的fmt.fmt.pix.pixelformat为支持的值。
  4. 帧率异常:降低分辨率或尝试其他帧率设置,部分摄像头可能不支持高帧率,需在格式设置中调整fmt.fmt.pix.field参数。

FAQs

Q1: 运行程序提示“VIDIOC_S_FMT: Invalid argument”怎么办?
A: 通常是因为设置的分辨率或像素格式不被摄像头支持,可先用v4l2-ctl --list-formats命令查看设备支持的所有格式和分辨率组合,

linuxc如何获取摄像头

v4l2-ctl --list-formats -d /dev/video0

然后修改代码中的fmt.fmt.pix.widthfmt.fmt.pix.heightfmt.fmt.pix.pixelformat为设备支持的值。

Q2: 如何实现实时预览摄像头画面?
A: 在循环捕获帧后,可将帧数据通过GUI库(如SDL、GTK)显示,以SDL为例,需安装SDL2库(sudo apt install libsdl2-dev),并将YUYV格式转换为RGB格式后渲染到窗口,关键代码片段如下:

#include <SDL2/SDL.h>
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("Camera Preview", 0, 0, 640, 480, SDL_WINDOW_SHOWN);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, 640, 480);
// 在循环捕获帧后,转换YUYV到RGB并更新纹理
SDL_UpdateTexture(texture, NULL, rgb_buffer, 640 * 3);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);

注意:YUYV到RGB的转换需自行实现算法,或使用libv4l2提供的转换函数。

原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/34488.html

(0)
酷番叔酷番叔
上一篇 2025年10月2日 15:01
下一篇 2025年10月2日 15:12

相关推荐

  • 如何在Ubuntu 22.04添加Deepin仓库?

    官方原生QQ(推荐优先尝试)腾讯为部分Linux发行版提供官方版本,但更新较慢(截至2024年最新版为3.2.2):下载安装包访问腾讯官方下载页(需确保链接安全):wget https://dldir1.qq.com/qqfile/qq/QQNT/linuxqq_3.2.2-22023_x86_64.rpm……

    2025年7月21日
    8900
  • Linux安装Geneious Prime的正确方法?

    安装前准备系统要求操作系统:Ubuntu 20.04+/Debian 10+ 或兼容的发行版(CentOS/RHEL需额外依赖)内存:≥8 GB(推荐16 GB以上)存储空间:≥1 GB可用空间Java环境:OpenJDK 11 或 Oracle JDK 11(必须) sudo apt update &amp……

    2025年7月13日
    9200
  • 如何在Linux环境下用C语言编写程序代码?

    在Linux环境下进行C语言程序开发,是许多开发者和学习者的首选,这得益于Linux系统对开源工具链的完美支持以及强大的命令行环境,整个过程从环境搭建到代码编写、编译、调试,形成了一套完整的流程,下面将详细介绍每个环节的具体操作和注意事项,开发环境准备在Linux下编写C语言程序,首先需要确保系统安装了必要的工……

    2025年9月21日
    8300
  • 如何修改Linux系统的待机休眠时间与唤醒设置?

    在Linux系统中,待机时间的设置涉及屏幕关闭、硬盘休眠、系统挂起等多个方面,合理调整既能节省能源,又能避免频繁唤醒硬件带来的损耗,以下是修改Linux待机时间的详细方法,涵盖图形界面、命令行工具及系统级配置,适用于不同使用场景和用户需求,通过桌面环境图形界面修改(适合普通用户)主流Linux桌面环境(如GNO……

    2025年10月3日
    6100
  • Linux网卡半双工设置指南

    核心概念与注意事项半双工(Half-Duplex):数据只能单向传输(同一时间仅能发送或接收),常见于早期网络设备,适用场景:连接仅支持半双工的设备(如老式Hub),解决全双工模式下的冲突问题(如collisions错误激增),风险提示:现代网络通常使用全双工,强制半双工可能降低带宽利用率,操作需root权限……

    2025年7月28日
    10500

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信