live555开发笔记(三):live555创建RTSP服务器源码剖析,创建h264文件rtsp服务器源码深度剖析

前言

对于live555的rtsp服务器有了而基本的了解之后,进一步对示例源码进行剖析,熟悉整个h264文件流媒体的开发步骤。

Demo


  
  播放本地文件,多路播放的时候,总是以第一个文件进度为准,所以当前这个Demo是同步播放的。

这对于摄像头采集视频实时播放来说,这个是满足这个功能的。

基本概念

Source、Sink、Filter

  • Source:作为数据流的起点,负责生成或获取原始数据。例如:ByteStreamFileSource:从文件读取原始字节流6,H264VideoStreamFramer:解析H.264视频流并生成帧数据
  • Filter:在数据流从Souce流到Sink的过程中能够设置Filter,用于过滤或做进一步加工。例如:H264or5Fragmenter:将视频帧分片以适应RTP包大小;解码器Filter:将编码数据解码为原始帧。
  • Sink:作为数据流的终点,负责消费或转发数据。
      整个LiveMedia中,数据都是从Souce,经过一个或多个Filter。终于流向Sink。在server中数据流是从文件或设备流向网络,而在client数据流是从网络流向文件或屏幕。
      MediaSouce是全部Souce的基类,MediaSink是全部Sink的基类。
      从类数量和代码规模能够看到。LiveMedia类是整个LIVE555的核心,其内部包括数十个操作详细编码和封装格式的类。LiveMedia定义的各种Souce均是从文件读取,假设想实现从设备获得实时流的传输,能够定义自己的Souce。

ClientSession、ClientConnection

  • ClientSession:对于每一个连接到server的client。server会为其创建一个ClientSession对象,保存该client的socket、ip地址等。同一时候在该client中定义了各种响应函数用以处理和回应client的各种请求。
  • ClientConnection:用于处理一些与正常播放无关的命令。如命令未找到、命令不支持或媒体文件未找到等。在ClientConnection处理DESCRIBE命令时会创建ClientSession对象。其它命令在ClientSession中处理。

MediaSession、MediaSubsession、Track

LIVE555使用MediaSession管理一个包括音视频的媒体文件。每一个MediaSession使用文件名称唯一标识。使用SubSession管理MediaSession中的一个音频流或视频流,音频或视频均为一个媒体文件里的媒体流,因此一个MediaSession能够有多个MediaSubsession,可单独管理音频流、视频流,并为每一个媒体流分配一个TrackID,如视频流分配为Track1,音频流分配为Track2,此后client必须在URL指定要为那个Track发送SETUP命令,因此我们能够觉得MediaSubsession代表Server端媒体文件的一个Track,也即相应一个媒体流。MediaSession代表Server端一个媒体文件。

对于既包括音频又包括视频的媒体文件,MediaSession内包括两个MediaSubsession。但MediaSession和MediaSubsession仅代表静态信息。若多个client请求同一个文件,server仅会创建一个MediaSession。各个client公用。为了区分各个MediaSession的状态又定义了StreamState类,用来管理每一个媒体流的状态。在MediaSubsession中完毕了Souce和Sink连接。Souce对指针象会被设置进sink。在Sink须要数据时,能够通过调用Souce的GetNextFrame来获得。

LIVE555中大量使用简单工厂模式,每一个子类均有一个CreateNew静态成员。该子类的构造函数被设置为Protected,因此在外部不能直接通过new来构造。同一时候。每一个类的构造函数的參数中均有一个指向UsageEnvironment的指针,从而能够输出错误信息和将自己增加调度。

HashTable

IVE555内部实现了一个简单哈希表类BasicHashTable。在LIVE555中。有非常多地方须要用到该哈希表类。如:媒体文件名称与MediaSession的映射,SessionID与ClientSession的映射,UserName和Password的映射等。

SDP

SDP是Session Description Protocol的缩写。是一个用来描写叙述多媒体会话的应用层协议。它是基于文本的,用于会话建立过程中的媒体类型和编码方案的协商等。

rtp与rtcp与rtsp服务器

RTP(实时传输协议)

负责传输实时音视频数据流,基于UDP/IP协议实现低延迟传输。支持时间戳、序列号等机制,确保数据包的时序性和同步性。不保证传输可靠性,需结合RTCP进行质量监控。

RTCP(实时传输控制协议)

作为RTP的辅助协议,用于监控传输质量、反馈统计信息(如丢包率、延迟、抖动)。功能实现:通过发送SR(发送者报告)和RR(接收者报告)反馈网络状态。支持带宽动态调整(如TMMBR/TMMBN)和流同步。

RTSP(实时流协议)服务器

作为流媒体会话的控制中心,负责客户端与服务器的交互(如播放、暂停、停止等指令)。

  • 会话管理:通过SETUP、PLAY、TEARDOWN等命令控制媒体流传输的生命周期。
  • 协议协调:与RTP/RTCP协同工作,RTSP定义控制逻辑,RTP传输数据,RTCP监控质量。
  • 多流支持:可同时管理音视频等多路流,并通过SDP协议协商编解码参数。

关键类介绍

H264VideoRTPSink:H264视频数据核心组件

H264VideoRTPSink是H.264视频流通过RTP/RTSP协议实现实时传输的关键模块,负责协议封装、数据分片和网络适配,确保视频流在实时场景下的高效性和兼容性。


  H264VideoRTPSink是Live555 流媒体框架中用于封装和传输H.264视频数据的核心组件,其作用主要包含以下方面:

  • RTP数据封装:H264VideoRTPSink 将 H.264 视频数据按 RTP 协议规范封装成网络传输包。具体包括:添加 RTP 包头信息(如时间戳、序列号、负载类型等);根据 H.264 的 NALU(网络抽象层单元)结构对视频数据进行分片或重组,确保数据适应网络传输的最大传输单元(MTU)限制。
  • 分片处理(Fragmentation):当单个 H.264 帧超过 MTU 限制时,H264VideoRTPSink 会将其拆分为多个 RTP 包(例如使用 FU-A 分片模式),并在包头中标记分片的起始、中间和结束位置。
  • 与 RTSP 协议协同工作:在RTSP会话中,H264VideoRTPSink作为数据传输的终点(Sink),与MediaSource(数据源)配合,完成从数据读取到RTP封装的完整流程。在客户端发送PLAY请求后,服务器通过H264VideoRTPSink将封装后的RTP流推送至网络。缓冲区管理处理大数据量H.264视频时,H264VideoRTPSink需依赖动态调整的缓冲区(如OutPacketBuffer::maxSize),防止因数据包过大导致的传输失败。

RTCPInstance:RTCP通讯类

在Live555框架中,RTCPInstance与RTPSink、RTPInterface等类协作,共同实现完整的RTP/RTCP流媒体传输功能。其设计独立于其他高层协议模块,仅依赖基础网络组件,具备较好的封装性。


  RTCPInstance是Live555框架中封装RTCP协议通信的核心类:

  • RTCP协议通信的封装与实现:负责RTCP数据包的收发处理,支持通过RTPInterface实现基于UDP或TCP的传输。接收到的RTCP报文(如SR、RR、BYE等)在incomingReportHandler等回调函数中处理,实现网络状态反馈和会话控制。
  • 网络状态监测与统计:统计RTP包的收发情况,收集丢包率、延迟、抖动等指标,为流量控制提供数据支持。依赖RTPSink类获取发送端统计信息,实现与RTP流的关联。
  • 反馈与动态调整机制:通过发送SR(发送者报告)和RR(接收者报告)实时反馈网络质量,触发发送速率调整或丢包重传。支持带宽控制机制(如TMMBR/TMMBN),根据网络状况动态调整媒体流传输参数。
  • 会话管理与流同步:处理BYE报文实现参与者退出通知,维护会话成员状态。通过SDES报文传递参与者描述信息,辅助多流同步(如音视频同步)。

ByteStreamFileSource:基础数据源组件

ByteStreamFileSource是Live555中处理文件型H.264流的核心入口组件,承担数据读取与基础分块功能,并为上层解析、封装模块提供标准化输入接口。


  ByteStreamFileSource在Live555 流媒体框架中作为基础数据源组件,核心作用如下:

  • 原始字节流读取:负责从本地文件(如 H.264 裸流文件)中读取未封装的原始字节数据,并以分块(Framed)形式输出,供后续解析模块处理。
  • 数据链的起点:在典型的 H.264 传输链路(如 H264VideoFileServerMediaSubsession中)中,其作为初始节点启动数据流,后续连接解析器(如H264VideoStreamParser)和分帧器(H264VideoStreamFramer),形成完整处理链路:ByteStreamFileSource → H264VideoStreamParser → H264VideoStreamFramer → ... → RTP 封装。
  • 可扩展性支持:虽然默认实现为文件读取,但其继承自FramedSource基类,用户可通过自定义派生类(如实时采集或编码的数据源)替代该组件,实现灵活的数据输入适配。
  • 关键参数配置:支持通过fileSize属性获取文件大小信息,便于预估传输带宽需求。在动态服务器场景中,需注意其与缓冲区大小(如OutPacketBuffer::maxSize)的协同配置,避免大帧溢出问题。

FrameSource:帧源抽象类

FrameSource是Live555中实现按帧输入媒体数据的基础组件,为RTSP/RTP 传输提供标准化的数据源接口,支持多格式媒体流的灵活扩展。


  FrameSource 在 Live555 流媒体框架中作为数据源的核心抽象类,其作用可归纳如下:

  • 基础数据源抽象:FrameSource 是负责按帧(Framed)提供流媒体数据的基类,定义了统一的帧数据读取接口。其子类需实现 doGetNextFrame 方法,用于逐帧获取原始媒体数据(如视频帧或音频块)。
  • RTP数据流起点:在RTSP/RTP传输链路中,FrameSource 作为数据生产者,将媒体数据以帧为单位传递给下游处理模块(如解析器、封装器)。例如,H264VideoStreamFramer 继承自 FrameSource,负责将裸 H.264 码流分帧后输出。
  • 接口标准化:FrameSource 通过纯虚函数强制子类实现关键操作,包括:帧数据异步获取机制(通过 afterGetting 回调触发数据传输);帧数据的分块与缓冲区管理。
  • 与下游组件协同:FrameSource与MediaSink类(如H264VideoRTPSink)形成数据链路:MediaSink通过fSource成员绑定FrameSource,驱动数据拉取与封装流程;在RTSP会话中,FrameSource的数据最终被封装为RTP包并发送至客户端。

H264VideoStreamFramer:H264视频流帧器

H264VideoStreamFramer在RTSP流媒体服务中通常由H264VideoFileServerMediaSubsession创建,是H.264实时流传输的关键解析层,承担了从原始字节流到结构化视频数据的转换任务。


  H264VideoStreamFramer在Live555框架中作为视频流解析的核心组件,主要承担H.264基本流(ES)的解析与重构,其核心功能如下:

  • H.264原始流解析‌:从ByteStreamFileSource等数据源读取原始字节流,通过内置的H264VideoStreamParser解析器识别NALU(网络抽象层单元)边界,将连续字节流分割为独立NALU单元‌25处理H.264 Annex B格式的起始码(如0x00000001),实现NALU的精准定位‌56
  • ‌参数集处理‌:提取并缓存SPS(序列参数集)和PPS(图像参数集),用于后续解码器初始化‌,在流启动时优先发送SPS/PPS,确保解码端正确初始化‌。
  • 分帧逻辑控制‌:区分VCL(视频编码层)和非VCL NALU,根据帧类型(如IDR帧、非IDR帧)组织数据输出‌56;处理分片单元(Slice)的关联性,确保帧完整性‌
  • 时间戳生成机制:基于视频帧率或外部输入时钟计算RTP时间戳,实现与音视频同步;处理B帧/P帧的显示时间戳(PTS)与解码时间戳(DTS)关系。
  • ‌与上下游组件协作‌:作为FramedSource的子类,向上连接ByteStreamFileSource获取原始数据,向下对接H264FUAFragmenter完成RTP分片‌;通过事件驱动模型触发数据读取,形成Source → Parser → Framer → Fragmenter → RTPSink的完整处理链路‌。
      H264VideoStreamFramer把自己的缓冲(其实是sink的)传给H264VideoStreamParser,每当H264VideoStreamFramer要获取一个NALU时,就跟H264VideoStreamParser要,而H264VideoStreamParser就从ByteStreamFileSource读一坨数据,然后进行分析,如果取得了一个NALU,就传给H264VideoStreamFramer。

Live555流媒体服务实现基本流程

步骤一:创建任务调度管理器

步骤二:创建rtp和rtcp

在不同平台使用的socketaddr_storage类型有区别,有些事socketaddr_in,主要是groupsock的头文件构造函数类型的区别,判断是live555各种版本有区别。

步骤三:创建H264VideoRTPSink

步骤四:创建RTCPInstance

步骤五:RTSP服务器

步骤六:创建ServerMediaSession实例

步骤七:创建subsession实例

步骤八:开始播放

步骤九:服务器运行

Demo区别


  没有启动服务器http端口监听,而是直接play。

整理后的中文注释代码

c 复制代码
/*
 为了使此应用程序正常工作,H.264 Elementary Stream视频文件*必须*包含SPS和PPS NAL单元,
 最好在文件开头或附近。这些SPS和PPS NAL单元用于指定在输出流的SDP描述中设置的"配置"信息
 (由此应用程序内置的RTSP服务器设置)。另请注意,与其他一些"*Streamer"演示应用程序不同,
  生成的流只能使用RTSP客户端(如"openRTSP")接收
*/

#include <liveMedia.hh> #include <BasicUsageEnvironment.hh> #include <GroupsockHelper.hh> UsageEnvironment* env; //char const* inputFileName = "test.264"; char const* inputFileName = "T:/test/front/20250311_123244_0.h264"; H264VideoStreamFramer* videoSource; RTPSink* videoSink; void announceURL(RTSPServer* rtspServer, ServerMediaSession* sms) { if(rtspServer == NULL || sms == NULL) { return; } UsageEnvironment& env = rtspServer->envir(); env << "Play this stream using the URL "; if(weHaveAnIPv4Address(env)) { char* url = rtspServer->ipv4rtspURL(sms); env << "\"" << url << "\""; delete[] url; if (weHaveAnIPv6Address(env)) { env << " or "; } } if(weHaveAnIPv6Address(env)) { char* url = rtspServer->ipv6rtspURL(sms); env << "\"" << url << "\""; delete[] url; } env << "\n"; } void play(); // forward int main(int argc, char** argv) { // 步骤一:创建任务调度器和运行信息环境 TaskScheduler* scheduler = BasicTaskScheduler::createNew(); env = BasicUsageEnvironment::createNew(*scheduler); // 步骤二:创建groupsocks用于RTP和RTCP struct sockaddr_storage destinationAddress; destinationAddress.ss_family = AF_INET; ((struct sockaddr_in&)destinationAddress).sin_addr.s_addr = chooseRandomIPv4SSMAddress(*env); // 这是一个多播地址。如果希望使用单播进行流式传输,那么应该使用"testOnDemand RTSPServer"测试程序(而不是此测试程序)作为模型。 const unsigned short rtpPortNum = 18888; const unsigned short rtcpPortNum = rtpPortNum+1; const unsigned char ttl = 255; const Port rtpPort(rtpPortNum); const Port rtcpPort(rtcpPortNum); Groupsock rtpGroupsock(*env, destinationAddress, rtpPort, ttl); rtpGroupsock.multicastSendOnly(); Groupsock rtcpGroupsock(*env, destinationAddress, rtcpPort, ttl); rtcpGroupsock.multicastSendOnly(); // // 步骤三:从RTP"groupsock"创建"H264视频RTP"接收器 OutPacketBuffer::maxSize = 100000; videoSink = H264VideoRTPSink::createNew(*env, &rtpGroupsock, 96); // 步骤四:为此RTP接收器创建(并启动)一个"RTCP实例" kbps为单位;RTCP b/w份额 const unsigned estimatedSessionBandwidth = 500; const unsigned maxCNAMElen = 100; unsigned char CNAME[maxCNAMElen+1]; gethostname((char*)CNAME, maxCNAMElen); CNAME[maxCNAMElen] = '\0'; // just in case RTCPInstance* rtcp = RTCPInstance::createNew(*env, &rtcpGroupsock, estimatedSessionBandwidth, CNAME, videoSink, NULL, // 代表服务器 True); // 代表SSM源 // 步骤五:这将自动启动RTCP运行 RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554); if (rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } // 步骤六:创建ServerMediaSession ServerMediaSession* sms = ServerMediaSession::createNew(*env, "testStream", inputFileName, "Session streamed by \"testH264VideoStreamer\"", True); // 步骤七:创建subsession sms->addSubsession(PassiveServerMediaSubsession::createNew(*videoSink, rtcp)); rtspServer->addServerMediaSession(sms); announceURL(rtspServer, sms); // 开始流播放: *env << "Beginning streaming...\n"; // 这个开始播放函数调用不调用区别: // 1.不调用时,有客户端输入,无法播放;必须调用play,进入则会开始调用播放,从头开始播放 // 2.后进入的客户端播放进度会主动同步首个连接的客户端播放进度 play(); // 服务器阻塞进入服务循环 env->taskScheduler().doEventLoop(); // does not return return 0; } void afterPlaying(void* clientData) { *env << "...done reading from file\n"; // 停止播放 videoSink->stopPlaying(); // 请注意,这也会关闭此源读取的输入文件。这是静态方法,可以直接关闭 Medium::close(videoSource); // 再次开始播放 play(); } void play() { // 将输入文件作为"字节流文件源"打开 ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(*env, inputFileName); if(fileSource == NULL) { *env << "Unable to open file \"" << inputFileName << "\" as a byte-stream file source\n"; exit(1); } FramedSource* videoES = fileSource; // 为视频基本流创建帧器 videoSource = H264VideoStreamFramer::createNew(*env, videoES); // 最后,开始播放 *env << "Beginning to read from file...\n"; videoSink->startPlaying(*videoSource, afterPlaying, videoSink); } 

工程模板v1.2.0