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

相关推荐

  • 测网络带宽有什么用?

    网络带宽直接影响文件传输速度、应用响应时间和用户体验,精确测试可帮助:验证运营商提供的带宽是否符合合同标准排查内网传输瓶颈(如NAS、服务器间速度)评估云服务器或IDC的网络质量优化应用性能(如视频流、CDN节点)五大专业级Linux带宽测试工具iperf3:行业黄金标准定位:跨平台、精准测量TCP/UDP吞吐……

    2025年7月31日
    6700
  • 如何打开HDF文件?

    HDF是一种分层数据格式,核心在于其树状结构组织数据(类似文件夹),能高效存储和管理包含元数据的大型复杂科学数据集,支持多种数据类型且跨平台兼容。

    2025年6月13日
    4600
  • 如何在Linux中快速查看端口状态?

    使用 netstat 命令(经典工具)功能:查看所有活动的网络连接、监听端口及路由表,安装(部分系统需手动安装):sudo apt install net-tools # Debian/Ubuntusudo yum install net-tools # CentOS/RHEL常用参数组合:sudo netst……

    2025年7月19日
    4700
  • Linux系统如何同时打开多个终端窗口?

    在Linux系统中,打开多个终端是提升工作效率的常见需求,无论是同时运行多个命令、监控不同进程,还是进行多任务并行处理,掌握多种打开终端的方法都十分必要,以下将从命令行操作、图形界面交互、终端管理工具及自动化脚本等多个维度,详细介绍Linux下打开多个终端的技巧,通过命令行直接打开多个终端Linux终端模拟器通……

    2025年10月5日
    700
  • 电脑如何装双系统linux系统盘

    备份重要数据,准备 Linux 安装盘,在电脑 BIOS 中设置启动顺序,按

    2025年8月13日
    2900

发表回复

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

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信