一、核心函数/指令功能总结
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子进程(编码推流),实现数据无拷贝传递,降低延迟。
-
核心逻辑:V4L2采集数据 → 写入管道写端 → FFmpeg从管道读端读取 → 编码后推流。
-
核心优势:数据不落地、不写入磁盘,直接在内存中流转,保证推流实时性。
-
关键注意:管道是单向通信(父进程写、子进程读),不可反向操作。
三、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); }}四、代码关键解析(对应管道通信核心)
-
管道创建(pipefd[2]):pipefd[0]是读端(FFmpeg用),pipefd[1]是写端(V4L2用),实现进程间数据传递。
-
进程隔离与通信:子进程(FFmpeg)和父进程(V4L2)通过管道通信,不直接访问内核内存,仅通过映射和管道传递数据。
-
FFmpeg 命令解析:-f rawvideo指定数据格式,-i -指定从标准输入读取,编码后推流到RTMP服务器。
-
异常处理:判断推流状态、fork失败、管道创建失败等场景,避免程序崩溃。
五、核心总结(必记)
-
mmap:映射摄像头数据到用户空间,无需拷贝,提升效率;
-
管道:连接V4L2和FFmpeg,实现无拷贝传递,降低延迟;
-
V4L2采集数据 → 管道 → FFmpeg编码 → RTMP推流,全程内存操作,无磁盘IO;
-
关键避坑:管道需正确关闭无用端,fork后需管理子进程,避免内存泄漏和程序异常。
Some information may be outdated