项目:网络摄像头

用 V4L2 采集摄像头数据流,通过 HTTP / RTSP / MJPEG 推流。

一、目标

二、硬件

三、V4L2 编程框架

1. 打开设备

int fd = open("/dev/video0", O_RDWR);

2. 查询能力

struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    return -1;

3. 设置格式

struct v4l2_format fmt = {
    .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .fmt.pix = {
        .width       = 640,
        .height      = 480,
        .pixelformat = V4L2_PIX_FMT_YUYV,    // 或 MJPEG
        .field       = V4L2_FIELD_NONE,
    },
};
ioctl(fd, VIDIOC_S_FMT, &fmt);

4. 申请缓冲区

struct v4l2_requestbuffers req = {
    .count  = 4,
    .type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .memory = V4L2_MEMORY_MMAP,
};
ioctl(fd, VIDIOC_REQBUFS, &req);

struct v4l2_buffer buf;
for (int i = 0; i < req.count; i++) {
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    ioctl(fd, VIDIOC_QUERYBUF, &buf);
    buffers[i].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED,
                            fd, buf.m.offset);
    buffers[i].length = buf.length;
}

5. 入队 + 开始采集

for (int i = 0; i < req.count; i++) {
    struct v4l2_buffer buf = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
                                .memory = V4L2_MEMORY_MMAP,
                                .index = i };
    ioctl(fd, VIDIOC_QBUF, &buf);
}

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);

6. 取帧

struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf);

// process(buffers[buf.index].start, buf.bytesused);
process(buffers[buf.index].start, buf.bytesused);

// 重新入队
ioctl(fd, VIDIOC_QBUF, &buf);

7. 关闭

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

四、MJPEG HTTP 服务

用 libjpeg + mongoose 推 MJPEG 流:

// 启动 MJPEG 流
struct v4l2_format fmt = {
    .fmt.pix = { .width = 640, .height = 480,
                 .pixelformat = V4L2_PIX_FMT_MJPEG },
};
ioctl(fd, VIDIOC_S_FMT, &fmt);
// HTTP handler
void stream_handler(struct mg_connection *c, int ev, void *ev_data)
{
    if (ev == MG_EV_HTTP_MSG) {
        struct mg_http_message *hm = ev_data;
        if (mg_match(hm->uri, mg_str("/stream"), NULL)) {
            mg_printf(c, "HTTP/1.0 200 OK\r\n"
                       "Content-Type: multipart/x-mixed-replace;boundary=myboundary\r\n"
                       "\r\n");
            // 添加到 MJPEG 客户端列表
        }
    }
}

// 抓帧 + 推流
void stream_loop(void)
{
    while (running) {
        // V4L2 取一帧 MJPEG
        v4l2_dequeue(buf);
        // 推给所有客户端
        broadcast_jpeg(buffers[buf.index].start, buf.bytesused);
        v4l2_queue(buf);
    }
}

浏览器访问 http://<ip>:8080/stream 即可看到实时画面。

五、H.264 / H.265 编码

GStreamer 简化版

# 安装
apt install gstreamer1.0-tools

# USB 摄像头本地显示
gst-launch-1.0 v4l2src ! videoconvert ! xvimagesink

# 推 H.264 RTSP
gst-launch-1.0 v4l2src ! videoconvert ! x264enc ! rtph264pay ! gdppay ! tcpserversink port=8554

# 客户端
vlc rtsp://<ip>:8554/test

六、rtsp server(live555)

#include <liveMedia/liveMedia.hh>

// 创建 server
RTSPServer *server = RTSPServer::createNew(8554);
ServerMediaSession *sms = ServerMediaSession::createNew("test");
sms->addSubsession(MP4VideoFileServerMediaSubsession::createNew(...));
server->addServerMediaSession(sms);
server->start();

七、设备树

&usb {
    status = "okay";
};

// USB 摄像头即插即用,无需 DTS

八、性能与延迟

九、典型应用

十、嵌入式硬件选型

SoC 编码能力
i.MX 6ULL 无 VPU,软编码
i.MX 8M Plus H.264/H.265 硬件
RK3568 H.264/H.265 硬件
RK3588 H.265 8K
STM32MP1
全志 V3S 无(软编码)
ESP32-S3 MJPEG only