项目:网络摄像头
用 V4L2 采集摄像头数据流,通过 HTTP / RTSP / MJPEG 推流。
一、目标
- V4L2 编程采集视频
- 图像处理(可选)
- HTTP/MJPEG 推流
- Web 端查看
二、硬件
- USB 摄像头(UVC):便宜、免驱
- 或 MIPI-CSI 摄像头(需驱动)
- 推荐:Logitech C270/C310
三、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 编码
- 硬件编码:i.MX 的 VPU、RK 的 rkvdec2 / rkvenc2
- 软件编码:x264 / libx265 / ffmpeg libav
- 推 RTSP:live555 / GStreamer
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
八、性能与延迟
- 编码延迟:硬件 < 50ms,软件 x264 < 200ms
- 网络延迟:局域网 < 50ms
- 端到端:局域网可达 200ms
九、典型应用
- 视频监控 / 安防
- 视频会议
- 直播
- 机器视觉(+ AI)
- 智能门铃 / 猫眼
十、嵌入式硬件选型
| 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 |