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。
整理后的中文注释代码
/*
为了使此应用程序正常工作,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
live555开发笔记(三):live555创建RTSP服务器源码剖析,创建h264文件rtsp服务器源码深度剖析的更多相关文章
- Django开发笔记三
Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.基于类的方式重写登录:views.py: from ...
- javaCV开发详解之5:录制音频(录制麦克风)到本地文件/流媒体服务器(基于javax.sound、javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- RBL开发笔记三
2014-08-26 20:06:24 今天就是在开发这个EPOLL来处理网络事件 封装较为健壮的EPOLL模型来处理基本的网络IO 1) 超时这个主题先没有弄 在开发EPOLL包括select/po ...
- 安卓开发笔记(二十一):Android Studio如何创建assets目录
方法如下: 因为在用WebView控件查看安卓内置网页的时候,必须创建这个资源文件夹,将网页放置在这个目录之下,默认是没有assets这个目录的,这样才可以实现网页代码html.css.javascr ...
- 钉钉开发笔记(三)MySQL的配置
最近在编写web的过程中,经常需要与后台工作人员互动.由于比较麻烦.没有效率. 就果断的请教了,公司的后台大牛,学习下数据库的一些简单操作,现在就把利用MySQL连接服务器, 进行可视化操作的简单步骤 ...
- Vue-cli开发笔记三----------引入外部插件
(一)绝对路径直接引入: (1)主入口页面index.html中头部script标签引入: <script type="text/javascript" src=" ...
- openwrt开发笔记三:uci移植及API调用
1.uci编译安装.移植 安装依赖 libubox #安装cmake sudo apt-get install cmake #下载依赖库libubox git clone http://git.nbd ...
- 网站开发进阶(三十一)js如何将html表格导出为excel文件(后记)
js如何将html表格导出为excel文件(后记) 前言 项目前期做了个导出Excel表格的功能,但是经过测试发现只有在IE上才可以正确实现,在Chrome等浏览器中无法实现导出效果.经过上网搜索,尝 ...
- phpstorm连接服务器,实时编辑上传文件到服务器
教程一:我的老版本,并且是汉化的,找到该位置 打开后:点击Configuration进行配置! 输入服务器的ip.端口.用户名.密码即可 打开编辑: 教程二:下面更新了一个新版本的(2018.2): ...
- 【转】Android开发笔记(序)写在前面的目录
原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经 ...
随机推荐
- 【杂谈】主键ID如何选择——自增数 OR UUID?
1.生成位置如何影响选择? 数据库往返时间 使用自增数时,ID是由数据库在执行INSERT操作时生成的:而UUID则可以在应用层生成. 考虑这样的场景: 一个方法需要插入A和B两个实体.其中B的数据需 ...
- Aspire+扣子智能体实现AI自动CodeReview
一.引言 Code Review在软件开发中扮演着至关重要的角色,它不仅能够提升代码质量,确保代码的可维护性和一致性,还能促进团队成员之间的知识共享和技术提升. 传统的代码审查过程面临着诸多挑战和局限 ...
- MES生产制造管理系统-BI看板 MES大屏看板
可视化看板最主要的目的是为了将生产状况透明化,让大家能够快速了解当前的生产状况以及进度,通过大数据汇总分析,为管理层做决策提供数据支撑,看板数据必须达到以下基本要求: 数据准确--真实反映生产情况 数 ...
- 浅谈李飞飞巴黎演讲:如果 AI 资源被少数公司垄断,整个生态系统都会完蛋
在巴黎人工智能峰会开幕式上,斯坦福大学教授.人工智能专家李飞飞发表了主题演讲,揭示了人工智能如何从"观察者"转变为重塑世界的"行动者".她在致辞中,分析了&qu ...
- LCP 1. 猜数字
地址:https://leetcode-cn.com/problems/guess-numbers/ <?php /** 小A 和 小B 在玩猜数字.小B 每次从 1, 2, 3 中随机选择一个 ...
- Java DecimalFormat四舍五入的坑及正确用法
一.DecimalFormat四舍五入的坑 1.1 有时候我们在处理小数保留几位小数时,想到了DecimalFormat这个类的使用,百度搜一把可能用到以下方式. 1 public static vo ...
- [Software Note ] Fibersim-export-OffsetedMesh
输出Offseted 的Drape data 只在fibersim 导出界面打开Allow offset simulation 选项,输出的网格还是在layup surface 上: 输出的数据并未偏 ...
- postman 如何比较两台电脑的脚本是否一样
- Vim编辑windows格式文件出现的[noeol][dos]的含义、解决方法及方法解释
文章目录 前言 [dos] [noeol] 前言 最近想要将保存再windows的文件传到linux上,传进去保存文件之后,用vim打开发现在文件的底下出现了[dos] [noeol]这两个标志.然后 ...
- 【保姆级教程】windows 安装 docker 全流程
一.背景 许多小伙伴在安装 Dify 或是 RagFlow 这些工具的时候,往往会遇到一个难题,那就是 Docker 的安装. 首先,我们的PC安装的绝大部分是 Windows,但众所周知的原因,Wi ...