# RtspPublisherConsole **Repository Path**: AndroidCoderPeng/RtspPublisherConsole ## Basic Information - **Project Name**: RtspPublisherConsole - **Description**: 基于 Qt5 + OpenCV + FFmpeg/x264 + live555 的 Windows 控制台 RTSP 推流程序: 从 USB 摄像头采集画面 → H.264 硬压(x264)→ live555 单播 RTP/RTSP 对外发布 - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-04 - **Last Updated**: 2026-09-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RtspPublisherConsole 基于 **Qt5 + OpenCV + FFmpeg/x264 + live555** 的 Windows 控制台 RTSP 推流程序: 从 USB 摄像头采集画面 → H.264 硬压(x264)→ live555 单播 RTP/RTSP 对外发布。 - 语言标准:C++14 - 目标平台:Windows(MinGW / MSVCRT 工具链),Linux 仅采集后端有分支,链接部分尚未适配 - 默认流地址:`rtsp://<本机IP>:8554/live` --- ## 目录结构 ``` RtspPublisherConsole/ ├── CMakeLists.txt # 构建配置(含运行期 DLL 拷贝) ├── main.cpp # Windows 控制台 UTF-8 / ANSI 虚拟终端初始化 ├── PublisherApplication.hpp / .cpp # QCoreApplication,装配并管理各线程 │ ├── video/ # 采集与编码 │ ├── FrameCapture.hpp / .cpp # OpenCV 摄像头采集(采集线程) │ ├── FrameEncoder.hpp / .cpp # FFmpeg + libx264 编码(编码线程) │ └── H264Splitter.hpp / .cpp # Annex-B / AVCC 解析、SPS/PPS 提取、NALU 拆分 │ ├── rtsp/ # 推流控制 │ ├── RtspManager.hpp / .cpp # 门面:参数管理、格式归一化、时间戳换算 │ └── LiveRtspServer.hpp / .cpp # live555 RTSPServer 生命周期 + 事件循环线程 │ ├── server/ # live555 定制实现 │ ├── LiveServerMediaSession.hpp/.cpp # OnDemandServerMediaSubsession 子类 │ └── LiveFramedSource.hpp / .cpp # FramedSource 子类,从 FrameQueue 取帧 │ ├── utils/ │ ├── FrameQueue.hpp / .cpp # 线程安全帧缓冲(全局单例,多 Reader) │ ├── Logger.hpp / .cpp # 带边框/颜色的分级日志 │ └── GlobalDefinition.hpp # 分辨率/帧率/码率等全局常量 │ ├── script/ # 第三方库构建脚本 │ ├── x264/build_x264.sh │ ├── ffmpeg/build_ffmpeg.sh │ ├── live555/build_live555_mingw.bat │ ├── live555/config.qt.mingw64 │ ├── live555/patch_live555.ps1 │ └── opencv/build_opencv_mingw.bat │ └── 3rdparty/ # 预编译第三方库 ├── x264/ include/ lib/libx264.a ├── ffmpeg/ include/ lib/libav*.a libsw*.a ... ├── live555/ include/ lib/libliveMedia.a libgroupsock.a │ libBasicUsageEnvironment.a libUsageEnvironment.a └── opencv/ x64/mingw/bin/libopencv_world455.dll ``` --- ## 架构与数据流 ```mermaid flowchart TD subgraph CAP["① 采集线程 · QThread"] direction LR CAM["USB 摄像头
OpenCV VideoCapture
MJPEG · 1920x1080@25"] FC["FrameCapture
QTimer 40ms 轮询"] CAM -->|" cv::Mat "| FC end FC -->|" signal frameCaptured
shared_ptr<cv::Mat>
Qt::QueuedConnection "| FB subgraph ENC["② 编码线程 · QThread"] direction LR FB["FrameBuffer
环形缓冲 capacity=3
满则覆盖最旧帧"] FE["FrameEncoder
sws_scale
BGR24 → YUV420P"] X264["libx264
ultrafast + zerolatency
GOP=25 · B帧=0 · 4.5Mbps"] FB --> FE --> X264 end X264 -->|" signal onFrameEncode
vector<uint8_t> + isKeyFrame
Qt::QueuedConnection "| RM subgraph MAIN["③ 主线程 · PublisherApplication"] direction LR RM["RtspManager
门面 · 单例"] R1["① 格式归一化
AVCC → Annex-B"] R2["② 提取
SPS / PPS"] R3["③ PTS 归一化
换算 90kHz
强制单调递增"] RM --> R1 --> R2 --> R3 end R3 -->|" push(H264Frame) "| FQ subgraph BUF["④ 共享缓冲 · 全局单例"] FQ["FrameQueue 容量 30 帧 · 满则丢弃最旧 · per-client Reader · 落后时对齐关键帧"] end FQ -->|" pop(Reader, frame) "| LFS subgraph LIVE["⑤ live555 线程 · std::thread"] direction LR SRV["LiveRtspServer
RTSPServer 监听 :8554
doEventLoop()"] LFS["LiveFramedSource
pop(Reader, frame)
空则 5ms 重试"] FRAMER["H264VideoStream
DiscreteFramer
拆分 NALU 去起始码"] SINK["H264VideoRTPSink"] SRV -->|" createNewStreamSource "| LFS LFS --> FRAMER --> SINK end SINK -->|" RTP / RTSP
rtsp://<ip>:8554/live "| CLIENT CLIENT(["RTSP 客户端
ffplay / VLC"]) SPLIT["H264Splitter(静态工具类)
detectFormat · avccToAnnexB
extractSPS/PPS · splitAnnexBToNALUs"] RM -.->|" 首帧 IDR 携带 SPS/PPS 后
start_rtsp_server() "| SRV RM -.->|" 调用 "| SPLIT LFS -.->|" 调用 "| SPLIT classDef queue fill: #fff4e6, stroke: #d9822b, stroke-width: 2px classDef util fill: #eef6ff, stroke: #4a90d9, stroke-dasharray: 4 3 classDef client fill: #eafaf1, stroke: #27ae60, stroke-width: 2px class FQ queue class SPLIT util class CLIENT client ``` > 图中实线为数据流(帧数据传递),虚线为控制流与工具调用。 > 三个 `QThread`/`std::thread` 之间通过 `FrameQueue` 解耦, > 采集与编码之间额外用 Qt 信号槽的 `QueuedConnection` 跨线程投递。 ### 线程模型 | 线程 | 承载对象 | 驱动方式 | |--------------|---------------------------|--------------------------------------------------------------| | 主线程 | `PublisherApplication` | `QCoreApplication::exec()` | | 采集线程 | `FrameCapture` + `QTimer` | `QThread::started` → `FrameCapture::start()`,40 ms 定时轮询 | | 编码线程 | `FrameEncoder` | `FrameCapture::frameCaptured` 跨线程 `Qt::QueuedConnection` | | live555 线程 | `LiveRtspServer` | `std::thread` + `BasicTaskScheduler::doEventLoop()` | 退出时 `PublisherApplication::cleanup()` 用 `Qt::BlockingQueuedConnection` 阻塞停止采集线程,再 `quit()/wait()` 两个 `QThread`,保证资源有序释放。 --- ## 关键参数 全局常量定义在 `utils/GlobalDefinition.hpp`: | 宏 | 默认值 | 说明 | |------------------|-----------|-------------------------| | `VIDEO_WIDTH` | `1920` | 采集/编码宽度 | | `VIDEO_HEIGHT` | `1080` | 采集/编码高度 | | `VIDEO_FPS` | `25` | 帧率,同时作为 GOP 大小 | | `VIDEO_BIT_RATE` | `4500000` | 编码码率(4.5 Mbps) | | `TIMESTAMP_BASE` | `90000` | RTP 时间基(90 kHz) | 编码器参数(`video/FrameEncoder.cpp`): | 项 | 值 | 原因 | |----------------|------------------------------|-----------------------------------------------------------| | 编码器 | libx264 (`AV_CODEC_ID_H264`) | — | | `pix_fmt` | `YUV420P` | RTSP 通用格式(OpenCV 采集为 BGR24,经 `sws_scale` 转换) | | `time_base` | `1 / VIDEO_FPS` | — | | `gop_size` | `25`(= 1 秒) | 每秒一个 IDR,兼顾首帧速度与压缩率 | | `max_b_frames` | `0` | 实时流禁用 B 帧,避免重排序延迟 | | `preset` | `ultrafast` | 低延迟优先 | | `tune` | `zerolatency` | 低延迟优先 | 采集参数(`video/FrameCapture.cpp`): - 四字符码强制为 **MJPEG** —— 高分辨率下 YUV 会占满 USB 带宽导致掉帧; OpenCV 内部用 libjpeg 解回 BGR 存入 `cv::Mat` ,对编码无影响。 - 设置后回读 `CAP_PROP_FRAME_WIDTH/HEIGHT/FPS` 并打印实际生效值,便于排查驱动不支持的分辨率。 - Linux 走 `cv::CAP_V4L2` 后端,Windows 走默认后端(构建 OpenCV 时需开 `WITH_DSHOW=ON`)。 RTSP 服务参数: - 监听端口 `8554`(`RtspManager::kDefaultRtspPort`,避开了需管理员权限的 554) - 流名 `live`,发布 URL 形如 `rtsp://192.168.1.10:8554/live` - 服务在 **收到第一帧含 SPS/PPS 的关键帧后**才真正启动(`start_rtsp_server()`) - live555 关闭 OpenSSL(`NO_OPENSSL=1`),仅支持无鉴权的 `DESCRIBE/SETUP/PLAY` --- ## 核心模块说明 ### `RtspManager`(门面,单例) - `initialize(stream_name, width, height, fps, bitrate)`:参数非法时回落到默认值 (`720x1280@30, 3 Mbps`);重复调用会先停止服务并清空队列。 - `dispatch_frame(buffer, pts_us, is_key_frame)`: 自动识别 Annex-B / AVCC,AVCC 会先转成 Annex-B,再进入处理流程。 - 时间戳:`pts_us` 归一化(减去首帧基准)后换算为 90 kHz, 并强制单调递增(`pts <= last` 时取 `last + 1`),防止画面花屏。 - SPS/PPS 变更时会等待下一个 IDR 才重置队列并应用新配置,避免中途花屏。 ### `FrameQueue`(帧缓冲,单例) - 容量 `kMaxQueueFrames = 30`(约 1 秒@30fps), **满则丢弃最老帧**,保证实时性优先。 - 每个 `FramedSource`(即每个播放客户端)持有一个 `Reader`,记录各自的消费进度,互不干扰。 - 新客户端接入 `on_client_attach()` 时从 **缓存中最近的关键帧**开始读; 消费落后到缓存范围外时同样重新对齐到关键帧 —— 秒开且不花屏。 ### `LiveFramedSource`(live555 数据源) - `doGetNextFrame()` 取不到数据时挂 `kRetryIntervalUs`(5 ms)延时任务重试,不阻塞事件循环。 - 一帧(一个完整 Annex-B 访问单元)拆成多个 NALU 逐个投递,同一帧内所有 NALU 共用同一 PTS。 - `maxFrameSize()` = 2 MB。 ### `H264Splitter`(纯静态工具) `detectFormat` / `avccToAnnexB` / `extractSPS` / `extractPPS` / `containsSlice` / `containsIDR` / `splitAnnexBToNALUs`,输入输出均不含起始码。 ### `Logger` 分级日志(`d/i/w/e`)+ 格式化变体(`dFmt/iFmt/wFmt/eFmt`)+ 多行边框流式 API: ```cpp Logger::tag("MyModule").i("hello"); Logger::tag("MyModule").iFmt("camera %dx%d", w, h); Logger::tag("MyModule").dBox() .add("SPS").addFmt("%s", hex.c_str()) .print(); ``` Windows 下 `main.cpp` 会设置 `CP_UTF8` 并开启 `ENABLE_VIRTUAL_TERMINAL_PROCESSING`,保证制表符与颜色正常显示。 --- ## 编译与运行 ### 1. 环境准备 | 依赖 | 版本 | 说明 | |---------|-------------------------------------------|--------------------------| | CMake | ≥ 3.10 | — | | Qt | 5.15.2 `mingw81_64` | 仅用 `Qt5::Core` | | MinGW | Qt 自带的 8.1.0(MSVCRT) | **必须**,见下方注意事项 | | OpenCV | 4.5.5(`opencv_world` 单 DLL) | — | | FFmpeg | 静态库,仅启用 H.264 编码 + RTSP 相关组件 | — | | x264 | 静态库 | — | | live555 | 静态库(4 个 `.a`) | — | > ⚠️ **工具链陷阱**:所有第三方库必须与 Qt 的 MinGW 8.1( **MSVCRT**)工具链一致。 > 若误用 MSYS2 的 `ucrt64` GCC,会引入 `clock_gettime64` / `nanosleep64` / `ftime64` > 等符号导致链接失败。`script/` 下的构建脚本已内置该检查。 ### 2. 构建第三方库(可选) `3rdparty/` 已带预编译产物,跳过此步也能直接编译主工程。需要重建时: ```bash # MSYS2 bash 中执行(顺序不可颠倒) ./script/x264/build_x264.sh # 产出 libx264.a ./script/ffmpeg/build_ffmpeg.sh # 依赖 x264,产出 libav*.a 等 ``` ```bat :: Qt MinGW 终端中执行 script\live555\build_live555_mingw.bat :: 内部调用 config.qt.mingw64 + patch_live555.ps1 script\opencv\build_opencv_mingw.bat :: 产出 opencv_world455.dll ``` 脚本中的路径变量(`MINGW_DIR`、`QT_ROOT`、`OPENCV_SRC`、`X264_DIR`)需按本机环境修改。 ### 3. 编译主工程 修改 `CMakeLists.txt` 中的 Qt 路径,使其指向本机安装位置: ```cmake set(CMAKE_PREFIX_PATH "D:\\3rdparty\\Qt5.15\\5.15.2\\mingw81_64") ``` 然后: ```bash cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release cmake --build build -j %NUMBER_OF_PROCESSORS% ``` 构建后 `POST_BUILD` 会自动把 `Qt5Core.dll`、 `plugins/platforms/qwindows.dll`、`libopencv_world455.dll` 拷贝到可执行文件目录,无需手动配置 `PATH`。 ### 4. 运行与拉流 确保摄像头已连接,直接运行 `RtspPublisherConsole.exe`。 日志中出现 `RTSP server started: rtsp://...` 即表示发布成功。 ```bash ffplay -fflags nobuffer -flags low_delay -rtsp_transport tcp rtsp://192.168.1.10:8554/live ``` 也可用 VLC 打开同一地址。 --- ## 调参建议 | 目标 | 修改位置 | |-----------------|---------------------------------------------------------------------------------------------------------| | 换分辨率 / 帧率 | `utils/GlobalDefinition.hpp` 的 `VIDEO_WIDTH/HEIGHT/FPS`(采集与编码共用) | | 换码率 | `VIDEO_BIT_RATE`(`FrameEncoder` 实际使用此宏,而非 `RtspManager::initialize` 的入参) | | 换采集间隔 | `FrameCapture::start()` 内的 `_timerPtr->start(40)`,需与 `VIDEO_FPS` 保持对应 | | 换 RTSP 端口 | `RtspManager.hpp` 的 `kDefaultRtspPort` | | 换流名 | `PublisherApplication.cpp` 中 `RtspManager::get()->initialize("live", ...)` 的第一个参数 | | 降低延迟 | `preset` 改 `ultrafast`、`tune` 保持 `zerolatency`、减小 `gop_size`、减小 `FrameQueue::kMaxQueueFrames` | | 提高画质 | `preset` 改 `veryfast`/`faster`、提高 `VIDEO_BIT_RATE`(会相应增加延迟) | --- ## 已知问题 1. **摄像头索引无效**:`FrameCapture` 构造函数接收 `index` 参数,但 `start()` 中 `_cap.open(0)` 硬编码为 0,无法切换摄像头。 2. **码率参数不一致**:`PublisherApplication` 调用 `initialize(..., 3000000)`, 而编码器实际使用 `VIDEO_BIT_RATE` (4500000);`RtspManager` 的 `bitrate` 目前未参与编码配置。 3. **状态回调为空实现**:`RtspManager::notify_status()` 是空函数, `RtspStatus` 各状态码(`InitSuccess` / `StreamStarted` 等)暂未对外通知。 4. **`FrameEncoder` 异常路径**:`handleFrame()` 在 `av_frame_make_writable` 或 `avcodec_send_frame` 失败时直接 `return`,未复位 `_isEncodingFrame`, 会导致后续帧不再触发编码;缓冲区采用 `cv::Mat` 浅拷贝,存在数据竞争风险。 5. **采集定时器硬编码**:`_timerPtr->start(40)` 与 `VIDEO_FPS` 未联动, 修改帧率后需同步修改此处。 6. **仅适配 Windows**:`CMakeLists.txt` 的头文件路径与链接库全部包在 `if (WIN32)` 内, Linux 下需自行补充。 7. **无鉴权与加密**:live555 以 `NO_OPENSSL=1` 构建,RTSP 无认证、无 TLS。 --- ## 许可证 本项目链接了以 **GPL** 授权的 x264 与 FFmpeg(`--enable-gpl`), 因此整体分发时需遵循 **GPL v2+** 的相关要求。