本文分析 live555 中,流媒体播放启动,数据开始通过 RTP/RTCP 传输的过程。
如我们在 live555 源码分析:子会话 SETUP 中看到的,一个流媒体子会话的播放启动,由 StreamState::startPlaying
完成:
在这个函数中,首先找到子会话的目标地址,也就是客户端的 IP 地址,和用于接收 RTP/RTCP 的端口号,然后通过 StreamState::startPlaying()
启动播放,最后将 RTP 包的初始序列号和初始时间戳返回给调用者,也就是 RTSPServer
,并由后者返回给客户端,以用于客户端的播放同步。
StreamState::startPlaying()
的实现是这样的:
在这个函数中,首先在 RTCPInstance 还没有创建时去创建它:
忽略 RTP/RTCP 包走 TCP 的情况。随后 StreamState::startPlaying()
对 RTP 和 RTCP 的 groupsock 做一些设置,即为它们添加目标地址,并为 RTCPInstance 做了一些设置:
之后 StreamState::startPlaying()
发出一个 RTCP 包。
fUDPSink
用于流模式为 RAW UDP 的情况,忽略这种流模式的情况。最后执行 MediaSink::startPlaying()
,并设置标记 fAreCurrentlyPlaying
,表示流播放已经启动。
RTP 包的发送
下面具体来看 RTP 包是怎么被发送出去的。MediaSink::startPlaying()
函数的定义如下:
在这个函数中,保存了传入的回调及回调的参数,然后执行 continuePlaying()
,continuePlaying()
是一个纯虚函数,其实现由 MediaSink
的子类 H264or5VideoRTPSink
实现:
在这个类中,主要是为 H264or5Fragmenter
设置了流媒体数据源,并将 fSource
设置为 H264or5Fragmenter
。在这里,MultiFramedRTPSink
持有的流媒体数据源 FramedSource
由最初在 H264VideoFileServerMediaSubsession
中创建的 H264VideoStreamFramer
变为了 H264or5Fragmenter
,而 H264or5Fragmenter
则封装了 H264VideoStreamFramer
。
随后 H264or5VideoRTPSink::continuePlaying()
执行 MultiFramedRTPSink::continuePlaying()
做进一步的处理。
MultiFramedRTPSink::continuePlaying()
执行 MultiFramedRTPSink::buildAndSendPacket()
。而 MultiFramedRTPSink::buildAndSendPacket()
则是在输出缓冲区构造了 RTP 头部,对于其中暂时无法准确获得的头部字段,还预留了空间。随后调用了 MultiFramedRTPSink::packFrame()
。
MultiFramedRTPSink::packFrame()
由 FramedSource
的 getNextFrame()
获得帧数据,并在获得帧数据之后得到通知。
这个函数主要用于为 FramedSource
设置媒体流数据要读到哪里,可以读多少自己,以及回调函数的地址。并最终执行 doGetNextFrame()
读取数据。
最终数据将由 ByteStreamFileSource
的 doGetNextFrame()
执行读取任务的调度,并从文件中读取。
这个调用栈比较深。看起来可能会让人感觉比较费解。实际上 live555 中采用装饰器模式来设计 FramedSource
,一个 FramedSource
可以包装另一个 FramedSource
,并额外提供一些功能,或为了性能优化,或为了数据解析等。
live555 中众多的 FramedSource
类之间的关系大概如下图所示:
上面的调用栈,也主要根据 FramedSource
的包装关系,由虚线分割为几个不同的阶段。
在 ByteStreamFileSource
的 doGetNextFrame()
中,调度读取任务:
ByteStreamFileSource::fileReadableHandler()
读取流媒体内容,并通知调用者:
数据读取完成之后,MultiFramedRTPSink
将得到通知:
我们同样将回调的调用栈,根据 FramedSource
的包装关系,分为几个阶段,不同阶段以虚线分割。
MultiFramedRTPSink::afterGettingFrame()
函数定义如下:
在这个函数中调用 afterGettingFrame1()
, afterGettingFrame1()
则会根据需要调用 sendPacketIfNecessary()
。MultiFramedRTPSink::sendPacketIfNecessary()
定义如下:
在 MultiFramedRTPSink::sendPacketIfNecessary()
中,会发送帧数据。且如果流媒体数据发送没有结束的话,在一帧数据发送完成之后,会调度一个定时器任务 MultiFramedRTPSink::sendNext()
再次发送帧数据。
MultiFramedRTPSink::sendNext()
执行与 MultiFramedRTPSink::continuePlaying()
类似的流程,获取下一帧数据并发送。
当然也并不是每一次发送帧数据的时候,都需要直接从流媒体源中去获得数据。在 StreamParser
中会做判断,当需要帧数据的时候,它会发起对流媒体文件的读取。若无需从文件中读取流媒体数据,则会直接回调:
总结一下 RTP 数据包的发送过程:
OnDemandServerMediaSubsession
中执行startStream()
时,将发起一个对流媒体文件进行读取的任务,读取文件的工作由ByteStreamFileSource
的doReadFromFile()
执行。- 在文件读取了一些数据之后,
MultiFramedRTPSink
得到回调afterGetting()
,在这个回调中,发送帧数据。 MultiFramedRTPSink
的回调中,如果流媒体数据还没有读完的话,则调度一个定时器任务,一段时间之后再次发起获取帧数据的动作。- 重复 2 和 3 两步,直到所有的数据都发送完。
RTCP 包的接收
StreamState::startPlaying()
通过 OnDemandServerMediaSubsession::createRTCP()
创建 RTCPInstance
:
OnDemandServerMediaSubsession::createRTCP()
则通过 RTCPInstance::createNew()
创建:
可以看到,在 RTCPInstance
的构造函数中,调用 RTPInterface::startNetworkReading()
注册了一个回调:
在 RTPInterface::startNetworkReading()
中则会向 TaskScheduler 注册 RTCP 的 socket 及该 socket 上的事件的处理程序。live555 中正是通过这种方式,在有 RTCP 包到来时得到通知,并通过 RTCPInstance::incomingReportHandler()
来处理 RTCP 包的。
RTCP 包的发送
RTCP 包根据需要,由 RTCPInstance::sendReport()
等函数发送:
就像在 StreamState::startPlaying()
中看到的那样。
打赏
Done.
live555 源码分析系列文章
live555 源码分析:简介
live555 源码分析:基础设施
live555 源码分析:MediaSever
Wireshark 抓包分析 RTSP/RTP/RTCP 基本工作过程
live555 源码分析:RTSPServer
live555 源码分析:DESCRIBE 的处理
live555 源码分析:SETUP 的处理
live555 源码分析:PLAY 的处理
live555 源码分析:RTSPServer 组件结构
live555 源码分析:ServerMediaSession
live555 源码分析:子会话 SDP 行生成
live555 源码分析:子会话 SETUP
live555 源码分析:播放启动