RTSP音视频传输软件流程文档
目录
一、系统概述
架构说明
本系统采用客户端-服务器架构,实现执法记录仪的音视频实时传输功能:
- 服务器端(RV1126B): 运行在执法记录仪设备上,负责采集摄像头视频和麦克风音频,编码后通过RTSP协议推流
- 客户端(VM): 运行在监控终端上,通过RTSP协议拉取音视频流,解码后显示和播放
技术栈
| 组件 | 服务器端 | 客户端 |
|---|---|---|
| 编程语言 | C | C++ |
| UI框架 | 无 | Qt 5 |
| 多媒体框架 | GStreamer + RTSP Server | GStreamer |
| 视频编解码 | H.264 (mpph264enc) | H.264 (avdec_h264) |
| 音频编解码 | Vorbis (vorbisenc) | Vorbis (vorbisdec) |
| 传输协议 | RTSP/RTP over TCP | RTSP/RTP over TCP |
| 视频采集 | V4L2 (/dev/video23) | N/A |
| 音频采集 | ALSA (default device) | N/A |
数据流向
[服务器端]
摄像头(V4L2) → H.264编码 → RTP封装 ↘
→ RTSP Server → TCP网络 → [客户端] → 解码 → 显示/播放
麦克风(ALSA) → Vorbis编码 → RTP封装 ↗
二、服务器端详细流程
1. 程序启动与初始化
1.1 主函数入口 (main.c:19)
函数 : int main(int argc, char* argv[])
执行步骤:
-
注册信号处理器 (main.c:27-28)
- 注册
SIGINT(Ctrl+C) 信号处理函数signal_handler - 注册
SIGTERM信号处理函数signal_handler - 目的:优雅地关闭服务器
信号 编号 全称 触发方式 说明 SIGINT 2 Signal Interrupt Ctrl+C中断信号 SIGTERM 15 Signal Terminate kill <pid>终止信号 SIGKILL 9 Signal Kill kill -9 <pid>强制杀死 其他常见信号的全称:
信号 编号 全称 说明 SIGHUP 1 Signal Hangup 终端挂断 SIGQUIT 3 Signal Quit 退出信号( Ctrl+\)SIGABRT 6 Signal Abort 异常终止 SIGALRM 14 Signal Alarm 定时器信号 SIGCHLD 17 Signal Child 子进程状态改变 SIGSTOP 19 Signal Stop 暂停进程(无法捕获) SIGCONT 18 Signal Continue 继续执行 - 注册
-
初始化RTSP服务器结构体 (main.c:31)
- 调用
rtsp_server_init(&server, "8554") - 设置全局服务器指针
g_server = &server(main.c:32)
- 调用
-
设置视频参数 (main.c:35)
- 调用
rtsp_server_set_video_params(&server, 1280, 720, 30) - 配置为 720p@30fps
- 调用
-
设置挂载点 (main.c:40)
- 调用
rtsp_server_set_mount_point(&server, "/live") - RTSP URL路径为
/live
- 调用
-
启动服务器 (main.c:43)
- 调用
rtsp_server_start(&server) - 如果失败则退出程序
- 调用
-
进入主循环 (main.c:52-54)
- 无限循环,每秒休眠1秒
- 等待信号中断
1.2 服务器初始化 (rtsp_server.c:33)
函数 : void rtsp_server_init(RTSPServer* server, const char* port)
执行步骤:
-
初始化结构体成员为NULL (rtsp_server.c:34-36)
cserver->server = NULL; server->mounts = NULL; server->loop = NULL; -
设置端口号 (rtsp_server.c:39-40)
- 复制端口字符串到
server->port - 默认值为 "8554"
- 复制端口字符串到
-
设置默认挂载点 (rtsp_server.c:43-44)
- 设置
server->mount_point为 "/live"
- 设置
-
设置默认视频参数 (rtsp_server.c:47-49)
server->width = 1920server->height = 1080server->framerate = 30- 注:这些值会被 main 函数中的设置函数
rtsp_server_set_video_params覆盖
2. 服务器启动流程
2.1 启动服务器 (rtsp_server.c:74)
函数 : int rtsp_server_start(RTSPServer* server)
执行步骤:
-
初始化GStreamer (rtsp_server.c:79)
- 调用
gst_init(NULL, NULL) - 初始化GStreamer多媒体框架
- 调用
-
设置调试级别 (rtsp_server.c:82)
- 调用
gst_debug_set_default_threshold(GST_LEVEL_WARNING) - 只显示警告及以上级别的日志
- 调用
-
配置音频增益 (rtsp_server.c:86-88)
- 通过
amixer命令设置ALSA音频参数 - 提高麦克风录音音量
- 设置 ADC OSR Volume 为 on
- 设置 ADC OSR 为 100%
- 设置 ADC2DAC Mixer 为 90%
- 通过
-
创建RTSP服务器对象 (rtsp_server.c:91)
- 调用
gst_rtsp_server_new() - 创建 GStreamer RTSP Server 实例
- 存储到
server->server
- 调用
-
设置服务器端口 (rtsp_server.c:98)
- 调用
g_object_set(server->server, "service", server->port, NULL) - 设置RTSP服务监听端口(默认8554)
- 调用
-
获取挂载点管理器 (rtsp_server.c:101)
- 调用
gst_rtsp_server_get_mount_points(server->server) - 获取挂载点管理对象
server->mounts
- 调用
-
创建媒体工厂 (rtsp_server.c:104)
- 调用
gst_rtsp_media_factory_new() - 创建媒体工厂对象
factory
- 调用
-
创建GStreamer管道字符串 (rtsp_server.c:107)
- 调用
create_pipeline_string(server, pipeline, sizeof(pipeline)) - 生成完整的GStreamer管道描述字符串(详见编解码流程章节)
- 调用
-
设置管道到媒体工厂 (rtsp_server.c:109)
- 调用
gst_rtsp_media_factory_set_launch(factory, pipeline) - 将管道字符串设置到工厂
- 调用
-
配置媒体工厂参数 (rtsp_server.c:112-118)
gst_rtsp_media_factory_set_shared(factory, TRUE)- 允许多客户端共享gst_rtsp_media_factory_set_latency(factory, 200)- 设置延迟200msgst_rtsp_media_factory_set_protocols(factory, GST_RTSP_LOWER_TRANS_TCP)- 使用TCP传输
-
添加挂载点 (rtsp_server.c:121)
- 调用
gst_rtsp_mount_points_add_factory(server->mounts, server->mount_point, factory) - 将媒体工厂挂载到
/live路径
- 调用
-
释放挂载点引用 (rtsp_server.c:124)
- 调用
g_object_unref(server->mounts)
- 调用
-
附加服务器到主上下文 (rtsp_server.c:127)
- 调用
gst_rtsp_server_attach(server->server, NULL) - 将RTSP服务器附加到GLib主循环
- 调用
-
创建并启动主循环线程 (rtsp_server.c:134-141)
- 创建 GMainLoop:
server->loop = g_main_loop_new(NULL, FALSE) - 创建线程:
pthread_create(&thread_id, NULL, main_loop_thread, server->loop) - 分离线程:
pthread_detach(thread_id) - 线程函数
main_loop_thread运行g_main_loop_run(loop)(rtsp_server.c:27-30)
- 创建 GMainLoop:
-
返回成功 (rtsp_server.c:143)
- 返回 1 表示启动成功
3. 媒体采集与编码流程
3.1 GStreamer管道构建 (rtsp_server.c:7)
c
static void create_pipeline_string(const RTSPServer* server, char* buffer, size_t buffer_size) {
snprintf(buffer, buffer_size,
"( "
/* 视频流 */
"v4l2src device=/dev/video23 ! "
"video/x-raw,format=NV12,width=%d,height=%d,framerate=%d/1 ! "
"mpph264enc ! "
"rtph264pay name=pay0 pt=96 "
/* 音频流 - 使用 default 设备和 Vorbis 编码 */
"alsasrc device=default ! "
"audio/x-raw,format=S16LE,rate=48000,channels=2 ! "
"audioconvert ! "
"audioresample ! "
"vorbisenc quality=0.7 ! "
"rtpvorbispay name=pay1 pt=97 "
")",
server->width, server->height, server->framerate);
}
管道元素说明:
| 元素 | 功能 | 参数 |
|---|---|---|
| v4l2src | V4L2视频采集 | device=/dev/video23 |
| video/x-raw | 视频格式约束 | format=NV12, width/height/framerate |
| mpph264enc | H.264硬件编码 | 使用RK平台MPP编码器 |
| rtph264pay | H.264 RTP封装 | name=pay0, pt=96 |
| alsasrc | ALSA音频采集 | device=default |
| audio/x-raw | 音频格式约束 | format=S16LE, rate=48000, channels=2 |
| audioconvert | 音频格式转换 | 自动转换 |
| audioresample | 音频重采样 | 自动重采样 |
| vorbisenc | Vorbis音频编码 | quality=0.7 |
| rtpvorbispay | Vorbis RTP封装 | name=pay1, pt=97 |
3.2 视频采集与编码流程
数据流:
- V4L2采集 -
v4l2src从/dev/video23设备读取原始视频帧 - 格式约束 - 输出 NV12 格式,指定分辨率和帧率
- H.264编码 -
mpph264enc使用硬件编码器压缩视频 - RTP封装 -
rtph264pay将H.264 NAL单元封装成RTP包- Payload Type: 96
- 自动处理分片和时间戳
3.3 音频采集与编码流程
数据流:
- ALSA采集 -
alsasrc从默认音频设备采集音频 - 格式约束 - 输出 S16LE 格式,48kHz采样率,双声道
- 格式转换 -
audioconvert确保格式兼容性 - 重采样 -
audioresample调整采样率(如需要) - Vorbis编码 -
vorbisenc压缩音频,质量0.7 - RTP封装 -
rtpvorbispay将Vorbis数据封装成RTP包- Payload Type: 97
4. 服务器运行状态
4.1 主循环运行
线程 : main_loop_thread (rtsp_server.c:27)
- 在独立线程中运行 GLib 主循环
- 处理 GStreamer 事件和 RTSP 请求
- 持续运行直到
g_main_loop_quit()被调用
4.2 RTSP会话处理
由GStreamer RTSP Server自动处理:
- 客户端连接 - 接受TCP连接(端口8554)
- DESCRIBE请求 - 返回SDP描述(包含视频和音频轨道信息)
- SETUP请求 - 建立RTP/RTCP会话
- PLAY请求 - 开始推送RTP数据包
- TEARDOWN请求 - 关闭会话
5. 服务器停止流程
5.1 信号处理 (main.c:11)
函数 : void signal_handler(int signum)
执行步骤:
- 打印信号信息 (main.c:12)
- 停止服务器 (main.c:14)
- 调用
rtsp_server_stop(g_server)
- 调用
- 退出程序 (main.c:16)
5.2 停止服务器 (rtsp_server.c:146)
函数 : void rtsp_server_stop(RTSPServer* server)
执行步骤:
-
退出主循环 (rtsp_server.c:148)
- 调用
g_main_loop_quit(server->loop) - 释放主循环:
g_main_loop_unref(server->loop)
- 调用
-
释放服务器对象 (rtsp_server.c:154)
- 调用
g_object_unref(server->server) - 这会自动清理所有RTSP会话和GStreamer管道
- 调用
-
打印停止信息 (rtsp_server.c:158)
三、客户端详细流程
1. 程序启动与初始化
1.1 主函数入口 (main.cpp:4)
函数 : int main(int argc, char *argv[])
执行步骤:
-
创建Qt应用 (main.cpp:6)
QApplication a(argc, argv)
-
创建主窗口 (main.cpp:7)
MainWindow w
-
显示窗口 (main.cpp:8)
w.show()
-
进入事件循环 (main.cpp:9)
return a.exec()
1.2 主窗口构造 (mainwindow.cpp:6)
函数 : MainWindow::MainWindow(QWidget *parent)
执行步骤:
-
初始化UI (mainwindow.cpp:12)
ui->setupUi(this)- 加载UI界面
-
设置窗口标题 (mainwindow.cpp:15)
setWindowTitle("RTSP Client - 执法记录仪")
-
创建RTSP客户端对象 (mainwindow.cpp:18)
rtspClient_ = new RTSPClient(this)
-
创建视频显示控件 (mainwindow.cpp:21-22)
videoWidget_ = new VideoWidget(this)- 添加到布局:
ui->videoLayout->addWidget(videoWidget_)
-
连接信号和槽 (mainwindow.cpp:25-31)
- 连接按钮点击事件
- 连接RTSP客户端信号到主窗口槽函数
-
设置默认URL (mainwindow.cpp:34)
ui->urlLineEdit->setText("rtsp://10.69.167.90:8554/live")
-
初始化UI状态 (mainwindow.cpp:37)
- 调用
updateUIState()
- 调用
1.3 RTSP客户端构造 (rtsp_client.cpp:5)
函数 : RTSPClient::RTSPClient(QObject *parent)
执行步骤:
-
初始化成员变量 (rtsp_client.cpp:7-10)
cpppipeline_ = nullptr; appsink_ = nullptr; volume_ = nullptr; connected_ = false; -
初始化GStreamer (rtsp_client.cpp:13)
gst_init(nullptr, nullptr)
1.4 视频控件构造 (video_widget.cpp:4)
函数 : VideoWidget::VideoWidget(QWidget *parent)
执行步骤:
-
设置黑色背景 (video_widget.cpp:8-11)
- 设置窗口背景色为黑色
-
设置最小尺寸 (video_widget.cpp:14)
setMinimumSize(640, 480)
2. 连接RTSP服务器流程
2.1 用户点击连接按钮 (mainwindow.cpp:47)
函数 : void MainWindow::onConnectClicked()
执行步骤:
-
获取URL (mainwindow.cpp:48)
- 从输入框获取RTSP URL
-
验证URL (mainwindow.cpp:50-53)
- 检查URL是否为空
-
更新UI状态 (mainwindow.cpp:55-56)
- 显示"正在连接..."
- 禁用连接按钮
-
调用连接函数 (mainwindow.cpp:58)
rtspClient_->connect(url)
2.2 建立RTSP连接 (rtsp_client.cpp:20)
函数 : bool RTSPClient::connect(const QString& url)
执行步骤:
-
检查连接状态 (rtsp_client.cpp:21-24)
- 如果已连接则返回false
-
构建GStreamer管道字符串 (rtsp_client.cpp:27-33)
rtspsrc location=%1 latency=100 protocols=tcp name=src src. ! application/x-rtp,media=video ! rtph264depay ! avdec_h264 ! videoconvert ! video/x-raw,format=RGB ! appsink name=sink emit-signals=true sync=false max-buffers=1 drop=true src. ! application/x-rtp,media=audio ! rtpvorbisdepay ! vorbisdec ! audioconvert ! volume name=volume ! autoaudiosink -
创建管道 (rtsp_client.cpp:39)
- 调用
gst_parse_launch(pipelineStr, &error) - 解析管道字符串并创建
pipeline_对象
- 调用
-
获取appsink元素 (rtsp_client.cpp:55)
- 调用
gst_bin_get_by_name(GST_BIN(pipeline_), "sink") - 用于接收视频帧
- 调用
-
获取volume元素 (rtsp_client.cpp:64)
- 调用
gst_bin_get_by_name(GST_BIN(pipeline_), "volume") - 设置默认音量为50%:
g_object_set(volume_, "volume", 0.5, nullptr)
- 调用
-
设置appsink回调 (rtsp_client.cpp:73)
g_signal_connect(appsink_, "new-sample", G_CALLBACK(onNewSample), this)- 当新帧到达时调用
onNewSample函数
-
添加总线监听 (rtsp_client.cpp:76-78)
- 获取管道总线:
gst_element_get_bus(pipeline_) - 添加消息监听:
gst_bus_add_watch(bus, onBusMessage, this) - 用于处理错误和状态变化
- 获取管道总线:
-
启动管道 (rtsp_client.cpp:81)
- 调用
gst_element_set_state(pipeline_, GST_STATE_PLAYING) - 开始拉流和解码
- 调用
-
更新连接状态 (rtsp_client.cpp:91-92)
connected_ = true- 发送信号:
emit connectionStateChanged(true)
-
返回成功 (rtsp_client.cpp:95)
3. 接收与解码流程
3.1 GStreamer管道结构
客户端管道元素:
| 元素 | 功能 | 参数 |
|---|---|---|
| rtspsrc | RTSP客户端源 | location=URL, latency=100, protocols=tcp |
| rtph264depay | H.264 RTP解封装 | 自动 |
| avdec_h264 | H.264软件解码 | FFmpeg解码器 |
| videoconvert | 视频格式转换 | 自动 |
| video/x-raw | 视频格式约束 | format=RGB |
| appsink | 应用程序接收器 | emit-signals=true, sync=false, max-buffers=1, drop=true |
| rtpvorbisdepay | Vorbis RTP解封装 | 自动 |
| vorbisdec | Vorbis音频解码 | 自动 |
| audioconvert | 音频格式转换 | 自动 |
| volume | 音量控制 | volume=0.5 |
| autoaudiosink | 自动音频输出 | 自动选择音频设备 |
3.2 视频帧接收回调 (rtsp_client.cpp:142)
函数 : static GstFlowReturn RTSPClient::onNewSample(GstElement* sink, gpointer data)
执行步骤:
-
获取样本 (rtsp_client.cpp:146)
- 调用
gst_app_sink_pull_sample(GST_APP_SINK(sink)) - 从appsink拉取新的视频帧样本
- 调用
-
获取缓冲区和caps (rtsp_client.cpp:152-153)
GstBuffer* buffer = gst_sample_get_buffer(sample)GstCaps* caps = gst_sample_get_caps(sample)
-
解析视频信息 (rtsp_client.cpp:161-162)
- 调用
gst_video_info_from_caps(&video_info, caps) - 获取视频宽度、高度、格式等信息
- 调用
-
映射缓冲区 (rtsp_client.cpp:169)
- 调用
gst_buffer_map(buffer, &map, GST_MAP_READ) - 获取视频数据指针
map.data
- 调用
-
创建QImage (rtsp_client.cpp:175-178)
cppint width = GST_VIDEO_INFO_WIDTH(&video_info); int height = GST_VIDEO_INFO_HEIGHT(&video_info); QImage image(map.data, width, height, GST_VIDEO_INFO_PLANE_STRIDE(&video_info, 0), QImage::Format_RGB888); -
深拷贝图像 (rtsp_client.cpp:181)
QImage imageCopy = image.copy()- 避免数据被释放后访问
-
解除映射和释放 (rtsp_client.cpp:184-185)
gst_buffer_unmap(buffer, &map)gst_sample_unref(sample)
-
发送信号 (rtsp_client.cpp:188)
emit client->frameReady(imageCopy)- 通知主窗口有新帧到达
-
返回成功 (rtsp_client.cpp:190)
- 返回
GST_FLOW_OK
- 返回
3.3 音频播放流程
自动处理:
- GStreamer管道中的
autoaudiosink自动将解码后的音频输出到系统音频设备 volume元素控制音量(可通过setVolume()调整)- 无需应用程序手动处理音频数据
4. 视频显示流程
4.1 主窗口接收新帧 (mainwindow.cpp:71)
函数 : void MainWindow::onNewFrame(QImage frame)
执行步骤:
-
更新视频控件 (mainwindow.cpp:72)
- 调用
videoWidget_->updateFrame(frame)
- 调用
-
更新状态栏 (mainwindow.cpp:75-82)
- 每30帧更新一次状态信息
- 显示分辨率信息
4.2 视频控件更新帧 (video_widget.cpp:20)
函数 : void VideoWidget::updateFrame(const QImage& frame)
执行步骤:
-
加锁 (video_widget.cpp:21)
QMutexLocker locker(&mutex_)- 保护
currentFrame_成员变量
-
保存帧 (video_widget.cpp:22)
currentFrame_ = frame
-
触发重绘 (video_widget.cpp:23)
update()- 调用Qt的重绘机制
4.3 视频控件绘制 (video_widget.cpp:32)
函数 : void VideoWidget::paintEvent(QPaintEvent *event)
执行步骤:
-
创建画笔 (video_widget.cpp:35-36)
QPainter painter(this)- 设置平滑变换
-
加锁 (video_widget.cpp:38)
QMutexLocker locker(&mutex_)
-
检查帧是否为空 (video_widget.cpp:40)
- 如果为空,显示 "No Video" 文字
-
计算缩放比例 (video_widget.cpp:46-51)
- 保持宽高比
- 计算适合窗口的缩放比例
-
计算居中位置 (video_widget.cpp:53-60)
- 计算缩放后的尺寸
- 计算居中显示的坐标
-
绘制图像 (video_widget.cpp:61)
painter.drawImage(targetRect, currentFrame_)
5. 断开连接流程
5.1 用户点击断开按钮 (mainwindow.cpp:65)
函数 : void MainWindow::onDisconnectClicked()
执行步骤:
-
断开RTSP连接 (mainwindow.cpp:66)
- 调用
rtspClient_->disconnect()
- 调用
-
清除视频显示 (mainwindow.cpp:67)
- 调用
videoWidget_->clear()
- 调用
-
更新状态 (mainwindow.cpp:68)
- 显示"已断开"
5.2 断开RTSP连接 (rtsp_client.cpp:98)
函数 : void RTSPClient::disconnect()
执行步骤:
-
检查连接状态 (rtsp_client.cpp:99-101)
- 如果未连接则直接返回
-
停止管道 (rtsp_client.cpp:104)
- 调用
gst_element_set_state(pipeline_, GST_STATE_NULL) - 停止所有数据流
- 调用
-
释放管道 (rtsp_client.cpp:105-106)
gst_object_unref(pipeline_)pipeline_ = nullptr
-
释放appsink (rtsp_client.cpp:110-112)
gst_object_unref(appsink_)appsink_ = nullptr
-
释放volume (rtsp_client.cpp:115-117)
gst_object_unref(volume_)volume_ = nullptr
-
更新连接状态 (rtsp_client.cpp:119-120)
connected_ = false- 发送信号:
emit connectionStateChanged(false)
四、协议交互时序
RTSP握手序列
正常连接流程:
-
客户端发起连接
- 客户端调用
rtspClient_->connect(url) - GStreamer创建TCP连接到服务器端口8554
- 客户端调用
-
DESCRIBE请求
-
客户端:
DESCRIBE rtsp://server:8554/live RTSP/1.0 -
服务器: 返回SDP描述,包含视频和音频轨道信息
v=0
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
m=audio 0 RTP/AVP 97
a=rtpmap:97 VORBIS/48000/2
-
-
SETUP请求(视频)
- 客户端:
SETUP rtsp://server:8554/live/stream=0 RTSP/1.0 - 服务器: 分配RTP/RTCP端口,返回Session ID
- 客户端:
-
SETUP请求(音频)
- 客户端:
SETUP rtsp://server:8554/live/stream=1 RTSP/1.0 - 服务器: 分配RTP/RTCP端口,使用相同Session ID
- 客户端:
-
PLAY请求
- 客户端:
PLAY rtsp://server:8554/live RTSP/1.0 - 服务器: 开始发送RTP数据包
- 客户端:
-
数据传输
- 服务器持续发送视频和音频RTP包
- 客户端接收、解封装、解码、显示/播放
-
TEARDOWN请求
- 客户端:
TEARDOWN rtsp://server:8554/live RTSP/1.0 - 服务器: 关闭会话,停止发送数据
- 客户端:
RTP数据包流
视频RTP包:
| 字段 | 值 | 说明 |
|---|---|---|
| Payload Type | 96 | H.264视频 |
| 时间戳 | 90kHz时钟 | 视频时间戳 |
| 序列号 | 递增 | 用于检测丢包 |
| 数据 | H.264 NAL单元 | 可能分片 |
音频RTP包:
| 字段 | 值 | 说明 |
|---|---|---|
| Payload Type | 97 | Vorbis音频 |
| 时间戳 | 48kHz时钟 | 音频时间戳 |
| 序列号 | 递增 | 用于检测丢包 |
| 数据 | Vorbis帧 | 完整或分片 |
五、关键数据结构详解
服务器端数据结构
RTSPServer结构体 (rtsp_server.h:16)
c
typedef struct {
GstRTSPServer* server; // GStreamer RTSP服务器对象
GstRTSPMountPoints* mounts; // 挂载点管理器
GMainLoop* loop; // GLib主循环
char port[16]; // 服务器端口(如"8554")
char mount_point[64]; // 挂载点路径(如"/live")
int width; // 视频宽度
int height; // 视频高度
int framerate; // 视频帧率
} RTSPServer;
生命周期:
- 初始化 :
rtsp_server_init()- 设置默认值 - 配置 :
rtsp_server_set_video_params(),rtsp_server_set_mount_point() - 启动 :
rtsp_server_start()- 创建GStreamer对象 - 运行: 主循环持续运行
- 停止 :
rtsp_server_stop()- 释放所有资源
GStreamer内部数据结构
GstRTSPServer:
- 管理RTSP会话
- 处理RTSP协议请求
- 维护客户端连接列表
GstRTSPMediaFactory:
- 创建媒体管道
- 管理共享模式
- 配置传输参数
GstElement (管道元素):
- v4l2src: 维护V4L2设备句柄和缓冲区队列
- mpph264enc: 维护编码器状态和参数
- rtph264pay: 维护RTP序列号和时间戳
- alsasrc: 维护ALSA设备句柄和音频缓冲区
- vorbisenc: 维护Vorbis编码器状态
- rtpvorbispay: 维护RTP序列号和时间戳
客户端数据结构
RTSPClient类 (rtsp_client.h:14)
cpp
class RTSPClient : public QObject {
private:
GstElement* pipeline_; // GStreamer管道对象
GstElement* appsink_; // 视频接收器元素
GstElement* volume_; // 音量控制元素
bool connected_; // 连接状态标志
};
成员说明:
| 成员 | 类型 | 用途 |
|---|---|---|
| pipeline_ | GstElement* | 整个GStreamer管道,包含所有元素 |
| appsink_ | GstElement* | 视频帧接收器,触发new-sample信号 |
| volume_ | GstElement* | 音量控制,可动态调整音量 |
| connected_ | bool | 连接状态标志,防止重复连接 |
生命周期:
- 构造: 初始化为nullptr和false
- 连接 :
connect()- 创建管道和元素 - 运行: 接收帧回调持续触发
- 断开 :
disconnect()- 释放所有GStreamer对象
VideoWidget类 (video_widget.h:14)
cpp
class VideoWidget : public QWidget {
private:
QImage currentFrame_; // 当前显示的帧
QMutex mutex_; // 互斥锁,保护currentFrame_
};
成员说明:
| 成员 | 类型 | 用途 |
|---|---|---|
| currentFrame_ | QImage | 存储当前要显示的视频帧 |
| mutex_ | QMutex | 保护currentFrame_的线程安全访问 |
线程安全:
updateFrame()在GStreamer回调线程中调用paintEvent()在Qt主线程中调用- 使用
QMutexLocker确保线程安全
MainWindow类 (mainwindow.h:17)
cpp
class MainWindow : public QMainWindow {
private:
Ui::MainWindow *ui; // UI界面
RTSPClient* rtspClient_; // RTSP客户端对象
VideoWidget* videoWidget_; // 视频显示控件
};
对象关系:
MainWindow
├── ui (UI控件)
├── rtspClient_ (RTSP客户端)
│ └── pipeline_ (GStreamer管道)
│ ├── rtspsrc (RTSP源)
│ ├── 解码器链
│ └── appsink (帧接收)
└── videoWidget_ (视频显示)
└── currentFrame_ (当前帧)
六、编解码流程
服务器端编码流程
视频编码流程
H.264编码参数:
- 编码器: mpph264enc (Rockchip MPP硬件编码器)
- 输入格式: NV12 (YUV 4:2:0)
- 输出格式: H.264 Annex-B字节流
- 分辨率: 可配置 (默认1280x720)
- 帧率: 可配置 (默认30fps)
编码流程:
- V4L2采集 - 从摄像头设备读取原始NV12帧
- 格式验证 - 确保符合NV12格式和指定分辨率
- 硬件编码 - MPP编码器将NV12转换为H.264
- NAL单元输出 - 输出H.264 NAL单元(SPS, PPS, IDR, P帧等)
- RTP封装 - rtph264pay将NAL单元封装成RTP包
关键特性:
- 硬件加速编码,低CPU占用
- 自动生成SPS/PPS参数集
- 支持I帧和P帧
- 自动处理帧间预测和运动补偿
音频编码流程
Vorbis编码参数:
- 编码器: vorbisenc (Xiph.Org Vorbis编码器)
- 输入格式: S16LE (16位小端PCM)
- 采样率: 48000 Hz
- 声道数: 2 (立体声)
- 质量: 0.7 (可变比特率)
编码流程:
- ALSA采集 - 从麦克风设备读取PCM音频
- 格式转换 - audioconvert确保格式兼容
- 重采样 - audioresample调整到48kHz
- Vorbis编码 - 压缩音频数据
- RTP封装 - rtpvorbispay封装成RTP包
关键特性:
- 有损压缩,质量0.7约为128kbps
- 支持可变比特率(VBR)
- 低延迟编码
- 开源免费格式
客户端解码流程
视频解码流程
H.264解码参数:
- 解码器: avdec_h264 (FFmpeg H.264解码器)
- 输入格式: H.264 Annex-B字节流
- 输出格式: RGB888 (用于Qt显示)
解码流程:
- RTP接收 - rtspsrc接收RTP包
- RTP解封装 - rtph264depay提取H.264 NAL单元
- NAL单元重组 - 处理分片的NAL单元
- H.264解码 - avdec_h264解码为YUV帧
- 格式转换 - videoconvert转换为RGB888
- 帧提取 - appsink提取帧到应用程序
关键特性:
- 软件解码,兼容性好
- 自动处理SPS/PPS参数集
- 支持多参考帧
- 自动错误隐藏
音频解码流程
Vorbis解码参数:
- 解码器: vorbisdec (Xiph.Org Vorbis解码器)
- 输入格式: Vorbis压缩数据
- 输出格式: PCM音频
解码流程:
- RTP接收 - rtspsrc接收RTP包
- RTP解封装 - rtpvorbisdepay提取Vorbis数据
- Vorbis解码 - 解压缩为PCM
- 格式转换 - audioconvert确保格式兼容
- 音量调整 - volume元素调整音量
- 音频输出 - autoaudiosink播放音频
关键特性:
- 低延迟解码
- 自动处理Vorbis头信息
- 支持动态音量调整
- 自动音频设备选择
编解码性能
服务器端性能:
| 分辨率 | 帧率 | CPU占用 | 编码延迟 |
|---|---|---|---|
| 1280x720 | 30fps | ~5% | <30ms |
| 1920x1080 | 30fps | ~8% | <40ms |
客户端性能:
| 分辨率 | 帧率 | CPU占用 | 解码延迟 |
|---|---|---|---|
| 1280x720 | 30fps | ~15% | <50ms |
| 1920x1080 | 30fps | ~25% | <70ms |
七、网络传输机制
传输协议栈
协议层次:
应用层: RTSP (控制) / RTP (数据)
传输层: TCP (RTSP + RTP)
网络层: IP
链路层: Ethernet / WiFi
RTSP控制通道
端口: 8554 (默认)
传输方式: TCP
功能:
- 建立和管理会话
- 协商媒体参数
- 控制播放状态
- 传输SDP描述
关键配置 (rtsp_server.c:118):
c
gst_rtsp_media_factory_set_protocols(factory, GST_RTSP_LOWER_TRANS_TCP);
- 强制使用TCP传输RTP数据
- 提高稳定性,避免UDP丢包
- 适合不稳定网络环境
RTP数据通道
传输方式: TCP (通过RTSP连接复用)
特点:
- 与RTSP共享TCP连接
- 交织模式 (Interleaved Mode)
- 可靠传输,无丢包
- 延迟略高于UDP
RTP包结构:
[RTP Header (12字节)]
- Version (2 bits): 2
- Padding (1 bit): 0
- Extension (1 bit): 0
- CSRC Count (4 bits): 0
- Marker (1 bit): 帧结束标志
- Payload Type (7 bits): 96(视频) / 97(音频)
- Sequence Number (16 bits): 递增序列号
- Timestamp (32 bits): 媒体时间戳
- SSRC (32 bits): 同步源标识符
[RTP Payload (可变长度)]
- H.264 NAL单元 或 Vorbis数据
网络参数配置
服务器端配置:
| 参数 | 值 | 说明 |
|---|---|---|
| 端口 | 8554 | RTSP监听端口 |
| 传输协议 | TCP | 可靠传输 |
| 共享模式 | TRUE | 允许多客户端 |
| 延迟 | 200ms | 缓冲延迟 |
客户端配置 (rtsp_client.cpp:28):
| 参数 | 值 | 说明 |
|---|---|---|
| latency | 100ms | 接收缓冲延迟 |
| protocols | tcp | 使用TCP传输 |
| sync | false | 不同步到时钟 |
| max-buffers | 1 | 最多缓存1帧 |
| drop | true | 丢弃旧帧 |
网络流量估算
视频流量 (1280x720@30fps, H.264):
- 比特率: ~2-4 Mbps
- 每秒数据: ~250-500 KB
- RTP包大小: ~1400字节
- 每秒包数: ~180-360个
音频流量 (48kHz, Vorbis quality 0.7):
- 比特率: ~128 kbps
- 每秒数据: ~16 KB
- RTP包大小: ~200-400字节
- 每秒包数: ~40-80个
总流量: ~2.1-4.1 Mbps
网络优化策略
服务器端优化:
- 硬件编码 - 使用MPP硬件编码器降低延迟
- TCP传输 - 避免UDP丢包问题
- 共享模式 - 多客户端共享同一编码流
- 适当延迟 - 200ms缓冲平衡延迟和流畅度
客户端优化:
- 低延迟模式 - latency=100ms
- 丢帧策略 - max-buffers=1, drop=true
- 异步显示 - sync=false,不等待时钟
- TCP传输 - 可靠接收
八、错误处理机制
服务器端错误处理
初始化错误
错误场景:
-
GStreamer初始化失败
- 原因: GStreamer库未安装或版本不兼容
- 处理: 程序无法启动
-
RTSP服务器创建失败 (rtsp_server.c:92-95)
cif (!server->server) { fprintf(stderr, "Failed to create RTSP server\n"); return 0; }- 原因: 内存不足或GStreamer库问题
- 处理: 返回错误,程序退出
-
主循环线程创建失败 (rtsp_server.c:135-140)
cif (pthread_create(&thread_id, NULL, main_loop_thread, server->loop) != 0) { fprintf(stderr, "Failed to create main loop thread\n"); g_main_loop_unref(server->loop); server->loop = NULL; return 0; }- 原因: 系统资源不足
- 处理: 清理资源,返回错误
运行时错误
错误场景:
-
V4L2设备打开失败
- 原因: 设备不存在或权限不足
- 处理: GStreamer自动报错,管道启动失败
-
ALSA设备打开失败
- 原因: 音频设备被占用或不存在
- 处理: GStreamer自动报错,管道启动失败
-
编码器初始化失败
- 原因: 硬件编码器不可用
- 处理: GStreamer自动报错,管道启动失败
错误日志:
- GStreamer会输出详细的错误日志到stderr
- 调试级别设置为WARNING (rtsp_server.c:82)
信号处理
信号捕获 (main.c:11-17):
c
void signal_handler(int signum) {
printf("\nInterrupt signal (%d) received.\n", signum);
if (g_server) {
rtsp_server_stop(g_server);
}
exit(signum);
}
支持的信号:
- SIGINT (Ctrl+C): 优雅关闭
- SIGTERM: 终止信号
清理流程:
- 退出GLib主循环
- 释放RTSP服务器对象
- 自动关闭所有客户端连接
- 释放GStreamer管道
客户端错误处理
连接错误
错误场景:
-
URL为空 (mainwindow.cpp:50-53)
cppif (url.isEmpty()) { QMessageBox::warning(this, "警告", "请输入RTSP URL"); return; }- 处理: 显示警告对话框
-
管道创建失败 (rtsp_client.cpp:41-47)
cppif (error) { QString errorMsg = QString("Failed to create pipeline: %1").arg(error->message); qCritical() << errorMsg; emit this->error(errorMsg); g_error_free(error); return false; }- 原因: 管道字符串语法错误或元素不存在
- 处理: 发送错误信号,显示错误对话框
-
管道启动失败 (rtsp_client.cpp:82-89)
cppif (ret == GST_STATE_CHANGE_FAILURE) { emit this->error("Failed to start pipeline"); gst_object_unref(appsink_); gst_object_unref(pipeline_); appsink_ = nullptr; pipeline_ = nullptr; return false; }- 原因: 网络连接失败或服务器不可达
- 处理: 清理资源,发送错误信号
-
重复连接 (rtsp_client.cpp:21-24)
cppif (connected_) { qWarning() << "Already connected"; return false; }- 处理: 返回false,不执行连接
运行时错误
GStreamer总线消息处理 (rtsp_client.cpp:193):
函数 : static gboolean RTSPClient::onBusMessage(GstBus* bus, GstMessage* message, gpointer data)
错误类型:
-
GST_MESSAGE_ERROR (rtsp_client.cpp:198-211)
cppcase GST_MESSAGE_ERROR: { GError* err; gchar* debug_info; gst_message_parse_error(message, &err, &debug_info); QString errorMsg = QString("Error: %1").arg(err->message); qCritical() << errorMsg; if (debug_info) { qDebug() << "Debug info:" << debug_info; } emit client->error(errorMsg); g_error_free(err); g_free(debug_info); break; }- 错误示例:
- 网络连接中断
- 解码器错误
- 格式不兼容
- 处理: 发送错误信号,显示错误对话框
- 错误示例:
-
GST_MESSAGE_EOS (rtsp_client.cpp:212-215)
cppcase GST_MESSAGE_EOS: qDebug() << "End of stream"; emit client->error("Stream ended"); break;- 原因: 服务器关闭流或网络断开
- 处理: 发送错误信号
-
GST_MESSAGE_STATE_CHANGED (rtsp_client.cpp:216-218)
- 状态变化通知
- 处理: 忽略(可扩展)
帧接收错误
错误场景 (rtsp_client.cpp:142-191):
-
样本获取失败 (rtsp_client.cpp:147-149)
cppif (!sample) { return GST_FLOW_ERROR; } -
缓冲区或caps为空 (rtsp_client.cpp:155-158)
cppif (!buffer || !caps) { gst_sample_unref(sample); return GST_FLOW_ERROR; } -
视频信息解析失败 (rtsp_client.cpp:162-165)
cppif (!gst_video_info_from_caps(&video_info, caps)) { gst_sample_unref(sample); return GST_FLOW_ERROR; } -
缓冲区映射失败 (rtsp_client.cpp:169-172)
cppif (!gst_buffer_map(buffer, &map, GST_MAP_READ)) { gst_sample_unref(sample); return GST_FLOW_ERROR; }
错误处理:
- 返回
GST_FLOW_ERROR - GStreamer会记录错误并尝试恢复
- 如果持续失败,会触发ERROR消息
UI错误显示
错误信号处理 (mainwindow.cpp:85-89):
cpp
void MainWindow::onError(QString message) {
qCritical() << "Error:" << message;
QMessageBox::critical(this, "错误", message);
ui->statusLabel->setText("错误: " + message);
}
错误显示方式:
- 控制台日志 - qCritical输出到stderr
- 错误对话框 - QMessageBox显示给用户
- 状态栏 - 更新状态标签
错误恢复策略
服务器端:
- 无自动恢复机制
- 需要手动重启程序
- 建议使用systemd等工具实现自动重启
客户端:
- 用户可以点击"断开"后重新"连接"
- 不支持自动重连
- 错误后需要手动操作
常见错误及解决方案
| 错误 | 原因 | 解决方案 |
|---|---|---|
| Failed to create RTSP server | GStreamer库问题 | 检查GStreamer安装 |
| Failed to open device /dev/video23 | 设备不存在 | 检查V4L2设备路径 |
| Failed to open audio device | ALSA设备被占用 | 关闭其他音频程序 |
| Failed to create pipeline | 管道语法错误 | 检查GStreamer元素是否安装 |
| Failed to start pipeline | 网络连接失败 | 检查服务器IP和端口 |
| Stream ended | 服务器关闭 | 重新启动服务器 |
| Error: Could not connect | 网络不可达 | 检查网络连接 |