# 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+** 的相关要求。