RtspServer:轻量级RTSP服务器和推流器

文章目录

项目概述

RtspServer 是由 PHZ76 开发的高效、可定制的实时流媒体服务器解决方案。它基于作者编写的网络基础库 xop ,允许开发者轻松处理和分发实时音视频流。项目提供了一个名为 DesktopSharing 的示例应用,可以捕获桌面和麦克风声音,并在编码后通过 RTSP 协议进行转发和推流。该项目支持 Windows 和 Linux 平台,代码量少,相比于 live555 等经典的流媒体库,RtspServer 更加轻量级,易于集成和二次开发,已在公司的项目中应用。

技术分析

支持的编码格式

RtspServer 支持多种音视频编码格式,涵盖了广泛的应用场景:

  • 视频编码:H.264、H.265
  • 音频编码:G.711A、AAC

传输方式

  • 单播 (Unicast)

    • RTP_OVER_UDP:通过 UDP 协议传输 RTP 数据,延迟低,但在网络不稳定时可能会丢包。
    • RTP_OVER_RTSP (TCP):通过 RTSP 协议在 TCP 连接上传输 RTP 数据,可靠性高,适用于防火墙或 NAT 环境。
  • 组播 (Multicast)

    • 适用于需要将同一流媒体数据发送给多个客户端的场景,节省网络带宽。

心跳检测机制

针对单播传输,RtspServer 内置了心跳检测机制,可以及时发现和处理连接异常,确保数据传输的稳定性。

RTSP 推流

RtspServer 支持 RTSP 推流功能,使用 TCP 协议进行数据传输,保证了数据的可靠传输,适用于对传输可靠性要求较高的应用场景。

安全性

RtspServer 内置了摘要认证(Digest Authentication),为服务提供了安全保障,防止未经授权的访问。

架构分析

RtspServer 整体架构

RtspServer 的架构主要包括以下组件:

  • RTSP Server:处理客户端的 RTSP 请求,包括 SETUP、PLAY、PAUSE、TEARDOWN 等指令,管理会话和媒体流。
  • Media Session:表示一个媒体会话,包含媒体流的相关信息,如流名称、媒体类型、编码格式等。
  • Media Source:媒体源,负责提供音视频帧数据,可以来自文件、摄像头、麦克风或其他实时数据源。
  • RTP Connection:负责通过 RTP 协议发送音视频数据,支持单播和组播传输。

流程分析

1. 客户端连接和会话建立
  • 客户端通过 RTSP 协议发送连接请求到服务器。
  • RTSP Server 接收到请求后,解析并创建一个新的 Media Session。
  • 为每个媒体流(音频或视频)创建对应的 Media Source。
2. 媒体数据传输
  • Media Source 获取音视频数据帧(可能来自编码器或实时采集设备)。
  • RTP Connection 负责将媒体数据打包成 RTP 包,通过网络传输给客户端。
  • 支持的传输方式包括 RTP_OVER_UDP、RTP_OVER_RTSP 和组播。
3. 心跳检测和连接维护
  • 为了保持连接的稳定性,服务器会定期发送心跳检测,确认客户端的在线状态。
  • 如果检测到连接异常,服务器会及时释放资源,防止资源泄漏。

xop 基础库

项目介绍

xop 是 RtspServer 的基础网络库,参考了 muduolive555 的设计,封装了一个简单高效的网络框架,提供了构建高性能网络应用的基础组件。

功能特性

  • 跨平台支持 :兼容 Windows 和 Linux 操作系统。
    • Windows 下 :使用 select 实现事件循环。
    • Linux 下 :使用 epoll 实现高效的事件通知机制。
  • 事件驱动模型:基于 Reactor 模式,实现非阻塞 IO 和事件驱动。
  • 定时器:提供高精度的定时任务调度。
  • 内存管理:实现了环形缓冲区和内存池,提升内存分配和释放的效率。
  • 日志系统:内置简洁的日志功能,方便调试和运行监控。

xop 整体架构

  • EventLoop:事件循环,核心组件,负责监听和分发 IO 事件、定时器事件等。
  • Channel:通道,封装了文件描述符及其感兴趣的事件类型,如可读、可写等。
  • TimerQueue:定时器队列,管理所有的定时任务。
  • Buffer:缓冲区,提供高效的数据读写接口。
  • Acceptor:监听器,负责接受新的客户端连接。

应用场景

  • 在线教育平台:提供高清、流畅的远程教学体验,实现教师与学生的实时互动。
  • 视频监控系统:实时传输监控摄像头的视频流,实现远程监控和安全管理。
  • 远程会议系统:确保音频和视频的同步传输,提高远程会议的质量和效率。
  • 桌面共享与远程协助 :通过 DesktopSharing 示例应用,实现桌面实时共享和远程协助功能。

社区问题收集与解答

问题一:刚开始播放时有些花屏

问题描述

在播放开始时,视频出现花屏现象。怀疑是与 GOP(Group of Pictures)有关,没有收到完整的一组 GOP,所以会花屏。是否应该等待下一个 I 帧?

解答

是的,花屏问题通常是由于解码器没有接收到完整的关键帧(I 帧)导致的。解码器需要从 I 帧开始才能正确解码后续的 P 帧和 B 帧。

解决方案

  • 确保首帧为关键帧:在客户端连接后,服务器应确保发送的第一帧是关键帧。可以在编码器中设置,让其在新的会话开始时立即生成一个 I 帧。
  • 检查帧类型 :在编码过程中,可以通过 AVFrame 或其他编码库的接口,获取帧类型,根据编码后的类型(I 帧、P 帧、B 帧)进行处理。
  • 缓冲策略:在客户端实现一定的缓冲策略,等待接收到第一个 I 帧后再开始解码和播放。

问题二:推送 H.265 流时播放器无法播放

问题描述

在使用 H.265 推流时,播放器无法正常播放视频。怀疑是在 H265SourcehandleFrame 函数中处理有误。

解答

问题可能出在对 NALU(Network Abstraction Layer Unit)的处理上。在 H.265 码流中,每个 NALU 前通常有一个起始码(如 0x00 00 00 01)。在计算 NALU 类型时,需要正确跳过起始码。

解决方案

  • 去除起始码:在处理帧数据时,需要跳过起始码,以正确解析 NALU 类型。

    cpp 复制代码
    uint8_t *frameBuf = frame.buffer.get();
    uint32_t frameSize = frame.size;
    frameBuf += 4;      // 跳过起始码
    frameSize -= 4;     // 调整帧大小
  • 统一处理方式 :建议在构建 VideoFrame 时就去掉起始码,这样后续的处理会更加统一。

  • 区别转发和推流 :对于转发和推流的处理,可以有所区别。直接获取 H.265 帧数据后调用 pushFrame,确保计算时是带有起始码的头部信息。

注意

  • NALU 解析:正确解析 NALU 类型对于视频解码非常重要。确保在处理时准确跳过起始码,才能正确识别帧类型。

  • 播放器兼容性:不同的播放器对码流格式的要求可能不同,确保码流符合标准,有助于提高兼容性。

Bug 修复与代码分析

问题描述

在使用 RtspServer 时,发现使用 VLC 和 ffplay 播放时出现错误:

复制代码
Invalid NAL unit 0, skipping

导致播放器无法正常播放视频流。需要对代码进行修复,解决该问题。

分析与解决方案

经过分析,问题出在 RTP 包的组装和 NALU 的处理上。以下是具体的修复步骤和代码修改。

1. 修改 RTP 包的最大负载大小

文件rtp.h

问题分析

  • 原始定义的 MAX_RTP_PAYLOAD_SIZE 为 1420,可能导致 RTP 包过小,增加了网络负载。
  • RTP 头部的大小为 12 字节,实际可用的负载大小应该更大。

修改内容

cpp 复制代码
// 原始定义
#define MAX_RTP_PAYLOAD_SIZE   1420

// 修改为
#define MAX_RTP_PAYLOAD_SIZE   1440

解释

  • MAX_RTP_PAYLOAD_SIZE 增加到 1440,可以减少 RTP 包的数量,提高传输效率。
  • 参考了 Live555 中的 MultiFramedRTPSink.cpp,调整了最大负载大小。
2. 调整 RTP TCP 头部大小

文件rtp.h

问题分析

  • 原代码中 RTP_TCP_HEAD_SIZE 定义为 4,当传输模式为 RTP_OVER_UDP 时,这个值应该为 0。
  • 在 UDP 传输中,不需要额外的 TCP 头部。

修改内容

cpp 复制代码
// 原始定义
//#define RTP_TCP_HEAD_SIZE	   4 // when transport_mode_ = RTP_OVER_TCP

// 修改为
#define RTP_TCP_HEAD_SIZE      0 // when transport_mode_ = RTP_OVER_UDP

解释

  • RTP_TCP_HEAD_SIZE 设置为 0,确保在 UDP 传输时,不会错误地加入额外的头部。
3. 修正 H.264 视频源的帧处理

文件H264Source.cpp

问题分析

  • 在处理 H.264 帧时,需要正确跳过起始码(0x00 00 00 01),否则会导致 NALU 解析错误。
  • 当帧大小小于等于最大 RTP 负载时,直接发送整个帧;否则,需要进行分片(FU-A)。

修改内容

cpp 复制代码
if (frame_size <= MAX_RTP_PAYLOAD_SIZE)
{
    frame_buf  += 4; // 跳过起始码
    frame_size -= 4;
    // 构建 RTP 包
    RtpPacket rtp_pkt;
    rtp_pkt.type = frame.type;
    rtp_pkt.timestamp = frame.timestamp;
    rtp_pkt.size = frame_size + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;
    rtp_pkt.last = 1;
    ...
}
else
{
    frame_buf  += 4; // 跳过起始码
    frame_size -= 4;
    char FU_A[2] = {0};
    FU_A[0] = (frame_buf[0] & 0xE0) | 28;            // NALU 头部
    FU_A[1] = 0x80 | (frame_buf[0] & 0x1F);          // FU-A 起始标志
    // 进行分片处理
}

解释

  • 对于小于等于最大负载的帧,直接跳过起始码,发送整个 NALU。
  • 对于大于最大负载的帧,跳过起始码,进行 FU-A 分片,需要正确构建 FU-A 头部。
4. 修正 H.265 视频源的帧处理

文件H265Source.cpp

问题分析

  • H.265 的 NALU 解析与 H.264 类似,但头部格式不同,需要相应调整。
  • 同样需要正确处理起始码和分片。

修改内容

cpp 复制代码
if (frame_size <= MAX_RTP_PAYLOAD_SIZE)
{
    frame_buf  += 4; // 跳过起始码
    frame_size -= 4;
    // 构建 RTP 包
    RtpPacket rtp_pkt;
    rtp_pkt.type = frame.type;
    rtp_pkt.timestamp = frame.timestamp;
    rtp_pkt.size = frame_size + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;
    rtp_pkt.last = 1;
    ...
}
else
{
    frame_buf  += 4; // 跳过起始码
    frame_size -= 4;
    uint8_t PL_FU[3] = {0};
    uint8_t nalUnitType = (frame_buf[0] & 0x7E) >> 1;
    PL_FU[0] = (frame_buf[0] & 0x81) | (49 << 1);    // NALU 头部
    PL_FU[1] = frame_buf[1];                         // NALU 头部
    PL_FU[2] = 0x80 | nalUnitType;                   // FU 起始标志
    // 进行分片处理
}

解释

  • 同样跳过起始码,正确调整 frame_bufframe_size
  • 构建 H.265 的分片头部,确保 NALU 类型和标志位正确。

总结

通过上述修改,修复了播放器无法正常播放的问题。主要原因在于:

  • 起始码处理:需要正确跳过起始码,才能正确解析 NALU。
  • RTP 包组装:调整最大负载大小,确保网络传输的效率和稳定性。
  • 分片处理:在帧过大时,正确地进行 RTP 分片,构建正确的 FU-A(H.264)或 FU(H.265)包。

这些修改使得 RtspServer 在与常见播放器(如 VLC、ffplay)兼容性方面得到了改善。

进一步的技术分析

深入理解 RTP 分片与 NALU 处理

在 RTP 传输中,当单个 NALU 的大小超过了最大 RTP 负载大小时,需要对其进行分片。分片时,需要构建特殊的 RTP 包,称为 Fragmentation Unit(FU)。

H.264 中的 FU-A 分片
  • FU-A 头部格式

    复制代码
    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |S|E|R|  Type   |
    +---------------+
  • S(Start):分片的开始标志位,1 表示这是第一个分片。

  • E(End):分片的结束标志位,1 表示这是最后一个分片。

  • Type:原始 NALU 的类型。

H.265 中的 FU 分片
  • FU 头部格式

    复制代码
    +---------------+
    |0|1|2|3|4|5|6|7|
    +-+-+-+-+-+-+-+-+
    |S|E|  Type     |
    +---------------+
  • S(Start):分片的开始标志位。

  • E(End):分片的结束标志位。

  • Type:原始 NALU 的类型。

起始码的处理

  • 起始码(Start Code) :在原始的 H.264/H.265 码流中,起始码用于分隔 NALU,一般为 0x00 00 00 01
  • RTP 传输中不需要起始码:在 RTP 传输中,起始码不应包含在传输的数据中,需要在发送前去除。
  • 解析 NALU 类型:跳过起始码后,才能正确解析 NALU 的头部,获取 NALU 类型。

播放器兼容性

  • VLC 和 ffplay 等播放器在解析 RTP 流时,严格遵循标准。如果发送的数据不符合 RTP 和 H.264/H.265 的封装规范,就会出现错误。

  • 错误信息

    复制代码
    Invalid NAL unit 0, skipping

    表示接收到的 NALU 类型为 0,这是无效的,可能是因为没有正确跳过起始码导致的。

相关推荐
青岛前景互联信息技术有限公司3 天前
视频AI与智能预警:如何提前发现园区安全隐患?
大数据·人工智能·视频
Mike_6666 天前
推流和推理什么区别
推流·推理·cpu推理·cpu推流·gpu推流·gpu推理
小贺儿开发10 天前
Unity3D 旋钮交互视频控制系统 1.0
unity·人机交互·视频·配置文件·videoplayer·输入系统·角度
骄傲的心别枯萎10 天前
RTSP的原理讲解
音视频·rtsp
橙色阳光五月天11 天前
使用 hyperframes 结合其他技术是否可以做出XX动物园游览动态图
人工智能·ai·ai作画·自动化·视频
程序员正茂12 天前
Unity3d中RawImage显示视频画面偏白的解决方法
unity·视频·rawimage
沉浸式学习ing13 天前
网课视频里的PPT怎么提取?视频转图文讲义的实操教程
笔记·ai·aigc·学习方法·视频·ppt
iNeuOS工业互联网15 天前
iNeuOS工业互联网操作系统集成大模型智库(iNeuOS_AiMind·心智灵慧)
大数据·人工智能·智能制造·视频·工业互联网·ineuos
大学生小郑18 天前
CMOS 传感器堆叠结构
图像处理·学习·音视频·视频
代码小书生20 天前
视频下载工具!支持8K、4K分辨率画质,视频音频文件多线程下载!字幕同步匹配下载保存到本地,自带视频格式转换功能!
音频·视频·视频下载·8k·4k·下载神器·画质