纸翼 · 加载中
1199 words
6 minutes
智能猫眼项目技术详解(使用管道进行推流)

一、核心函数/指令功能总结#

1. V4L2 相关核心函数(采集与控制)#

  • V4L2 设备控制:配置摄像头采集参数(分辨率、格式),启动/停止采集,核心是获取摄像头原始数据(YUV格式)。

  • ioctl 系统调用:控制V4L2设备(启动/停止采集、配置缓冲区),是操作摄像头硬件的核心接口。

  • mmap() 函数:将摄像头数据对应的内核内存,映射到用户空间,实现无拷贝访问,避免数据冗余拷贝。

2. 管道通信相关函数#

  • pipe() 函数:创建内核缓冲区(管道),作为两个进程(V4L2父进程、FFmpeg子进程)的通信载体。

  • write() 函数:将V4L2采集的YUV数据,写入管道写端,供FFmpeg读取。

  • dup2() 函数:重定向标准输入,让FFmpeg从管道读取数据,无需额外拷贝。

  • fork() 函数:创建子进程运行FFmpeg,避免主进程阻塞,保证推流流畅。

3. 辅助工具函数#

  • usleep() 函数:控制摄像头启动后稳定时间,避免数据采集异常。

  • strerror() 函数:获取错误信息,用于异常处理(如推流失败、管道创建失败)。

二、管道核心原理(关键重点)#

管道是Linux内核提供的进程间通信机制,本质是「内核维护的内存缓冲区」,用于连接V4L2父进程(采集数据)和FFmpeg子进程(编码推流),实现数据无拷贝传递,降低延迟。

  1. 核心逻辑:V4L2采集数据 → 写入管道写端 → FFmpeg从管道读端读取 → 编码后推流。

  2. 核心优势:数据不落地、不写入磁盘,直接在内存中流转,保证推流实时性。

  3. 关键注意:管道是单向通信(父进程写、子进程读),不可反向操作。

三、V4L2 推流完整代码(核心实现)#

以下是完整的推流核心代码,对应上述管道通信和V4L2采集逻辑,可直接对应实际应用场景:

#include <errno.h>
#include <unistd.h>
#include "lvgl/lvgl.h"
// 全局变量(简化,实际需根据项目定义)
typedef struct {
int stream_fd; // 设备文件描述符
int is_streaming; // 是否正在推流
int stream_pid; // 推流子进程PID
int stream_pipe_fd; // 管道写端
// 其他必要参数(分辨率、帧率等)
} UIContext;
UIContext g_ui;
// 启动推流核心函数
void start_streaming(void)
{
/* 检查是否已经在推流 */
if (g_ui.is_streaming) {
LV_LOG_ERROR("Already streaming, cannot start again");
return;
}
/* 如果摄像头没有运行,先启动摄像头 */
if (!g_ui.v4l2_thread_running) {
LV_LOG_INFO("Camera not running, starting first...");
start_camera(); // 启动摄像头采集线程
usleep(500000); // 等待500ms,让摄像头稳定
// 非视频页面,关闭显示节省资源
if (g_ui.current_page != PAGE_VIDEO) {
g_ui.display_enabled = false;
LV_LOG_INFO("Display disabled (not in Video page)");
}
}
/* 获取分辨率和帧率字符串 */
const char *res_str = get_resolution_str(g_ui.resolution);
const char *fps_str = get_framerate_str(g_ui.framerate);
LV_LOG_INFO("Starting RTMP stream to rtmp://<ip>:1935/live/camera");
/* ========== 创建管道 ========== */
int pipefd[2];
if (pipe(pipefd) < 0) {
LV_LOG_ERROR("Failed to create pipe: %s", strerror(errno));
return;
}
/* ========== 创建子进程运行 FFmpeg ========== */
g_ui.stream_pid = fork();
if (g_ui.stream_pid == 0) {
/* 子进程:执行FFmpeg编码推流 */
setpgid(0, 0); // 独立进程组,避免影响主进程
close(pipefd[1]); // 子进程只读,关闭写端
// 重定向标准输入为管道读端
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
// 构建FFmpeg推流命令
char cmd[1024];
snprintf(cmd, sizeof(cmd),
"ffmpeg -y "
"-f rawvideo -pixel_format yuyv422 -video_size %s -framerate %s -i - "
"-c:v libx264 -preset ultrafast -tune zerolatency -b:v 1000k "
"-f flv rtmp://<ip>:1935/live/camera",
res_str, fps_str);
LV_LOG_INFO("Streaming command: %s", cmd);
// 执行FFmpeg命令
execlp("sh", "sh", "-c", cmd, NULL);
// 执行失败处理
perror("Failed to execute FFmpeg");
_exit(1);
} else if (g_ui.stream_pid < 0) {
/* fork失败处理 */
LV_LOG_ERROR("Failed to start streaming");
close(pipefd[0]);
close(pipefd[1]);
g_ui.stream_pid = -1;
} else {
/* 父进程:保存管道写端,用于后续写入摄像头数据 */
close(pipefd[0]); // 父进程只写,关闭读端
g_ui.stream_pipe_fd = pipefd[1];
g_ui.is_streaming = true;
LV_LOG_INFO("Streaming started (PID: %d)", g_ui.stream_pid);
}
}

四、代码关键解析(对应管道通信核心)#

  1. 管道创建(pipefd[2]):pipefd[0]是读端(FFmpeg用),pipefd[1]是写端(V4L2用),实现进程间数据传递。

  2. 进程隔离与通信:子进程(FFmpeg)和父进程(V4L2)通过管道通信,不直接访问内核内存,仅通过映射和管道传递数据。

  3. FFmpeg 命令解析:-f rawvideo指定数据格式,-i -指定从标准输入读取,编码后推流到RTMP服务器。

  4. 异常处理:判断推流状态、fork失败、管道创建失败等场景,避免程序崩溃。

五、核心总结(必记)#

  1. mmap:映射摄像头数据到用户空间,无需拷贝,提升效率;

  2. 管道:连接V4L2和FFmpeg,实现无拷贝传递,降低延迟;

  3. V4L2采集数据 → 管道 → FFmpeg编码 → RTMP推流,全程内存操作,无磁盘IO;

  4. 关键避坑:管道需正确关闭无用端,fork后需管理子进程,避免内存泄漏和程序异常。

智能猫眼项目技术详解(使用管道进行推流)
https://blog.huangzy.xyz/posts/智能猫眼项目技术详解使用管道进行推流/
Author
纸翼
Published at
2026-04-01
License
CC BY-NC-SA 4.0

Some information may be outdated