如我们在前文 live555 源码分析:ServerMediaSession 中看到的,H264VideoFileServerMediaSubsession
的继承层次体系如下图:
在这个继承层次体系中,ServerMediaSubsession
定义了可以对流媒体的单个子会话执行的操作,它有着如下这样的生命周期:
对于这些操作的实现则是由 OnDemandServerMediaSubsession
完成的,本文分析这个类的定义和实现。
我们再来看一下产生 SDP 消息行的 sdpLines()
,它在处理 RTSP DESCRIBE
消息时会用到:
这个函数执行的步骤如下:
第 1 步,调用 createNewStreamSource()
函数,创建 FramedSource
。createNewStreamSource()
函数在 H264VideoFileServerMediaSubsession
中定义:
返回的 FramedSource
为 ByteStreamFileSource
。FramedSource
用于获取视频流中的单个帧,它有着如下这样的继承层次结构:
第 2 步,调用 createGroupsock()
函数创建 Groupsock
。
第 3 步,计算 RTP 载荷类型。
第 4 步,调用 createNewRTPSink()
创建 RTPSink
,createNewRTPSink()
同样在 H264VideoFileServerMediaSubsession
中实现:
传进来的 FramedSource
并没有被用到。返回的 RTPSink
为 H264VideoRTPSink
。H264VideoRTPSink
的继承层次结构如下图所示:
第 5 步,调用 setSDPLinesFromRTPSink()
生成 SDP 行。前面的几步都是在为这一步的执行做准备。这个函数实现如下:
在这个函数中,获取生成 SDP 消息所需的数据,然后生成 SDP 消息。mediaType
来自于 VideoRTPSink
:
这也是 VideoRTPSink
提供的仅有的功能了。
rtpPayloadType
来自于 RTPSink
:
前面在 sdpLines()
中计算得到 RTP 载荷类型,在创建 RTPSink
时计算的值会被传递给它。这里返回的正是前面计算得到的值。对于 H.264 视频流,这个值为 96。
map 行来自于 RTPSink
:
对于 RFC 3551 分配了载荷类型的媒体类型,无需 map 行。对于动态的载荷类型,则需要该行,其中包含了载荷类型、载荷格式名称,时间戳频率等,当通道个数大于 1 时,还包含通道个数。
通道个数来自于 MultiFramedRTPSink
:
载荷格式名称和时间戳频率来自于 H264or5VideoRTPSink
,对于 H.264 的视频流而言,载荷格式名称为 “H264”,时间戳频率为 90000:
回到 setSDPLinesFromRTPSink()
。然后是 range SDP 行,它来自于 ServerMediaSubsession
:
所需获取的最后的 SDP 信息是 aux SDP 行,这些信息需要从视频流数据中解析获得,通过 H264VideoFileServerMediaSubsession
的 getAuxSDPLine()
函数获得:
这个函数在首次调用时,会通过 RTPSink
启动对流媒体文件帧数据的读取,即调用 RTPSink
的 startPlaying()
函数。
startPlaying()
函数执行一个长长的调用链,最终调用 ByteStreamFileSource
的 doGetNextFrame()
函数,这个调用栈如下:
ByteStreamFileSource
的 doGetNextFrame()
启动对流媒体数据的读取,这个函数的实现如下:
对于异步读取来说,是注册一个文件读取处理程序 fileReadableHandler
,对于同步读取,则是直接通过 doReadFromFile()
读取文件数据。
回到 H264VideoFileServerMediaSubsession
的 getAuxSDPLine()
。执行了 RTPSink
的 startPlaying()
之后,就会通过 checkForAuxSDPLine(void* clientData)
检查一下视频流元数据的读取是否结束:
这个函数在执行的时候,若发现 aux SDP 行还没有获取到,就会注册一个定时器任务过一段时间继续检查。
aux SDP 行通过 RTPSink
的 auxSDPLine()
获取,该函数实际的实现位于 H264VideoRTPSink
中:
要产生 SDP 行,需要用到视频流数据的 SPS 帧和 PPS 帧,这两帧数据有两种来源,一种是在对象构造时,由调用者传入:
另外一种则是从数据源中获取:
对于从数据源获取 SPS 帧和 PPS 帧的情况,在从文件中获取数据之后,这些数据会被推给 framerSource
,推 SPS 的过程如下:
推 PPS 的过程如下:
SPS 和 PPS 帧数据要都拿到才能产生
SDP 行。H264VideoRTPSink::auxSDPLine()
在拿不到 SDP 行数据时,会返回 null 给调用者。
对于 H264VideoFileServerMediaSubsession
的 checkForAuxSDPLine()
而言,在拿不到 SDP 的情况下,则是再次调度一个定时器任务执行,等待下一次检查的到来。
在 H264VideoRTPSink::auxSDPLine()
中,如果已经有了 SPS 和 PPS 数据的话则会先移除 SPS 中的 Emulation 字节,然后计算 SPS 帧数据和 PPS 帧数据的 Base64 编码,并最终产生 SDP 行。
移除 SPS 中的 Emulation 字节的过程就像下面这样:
所谓的 Emulation 字节,是由于 H.264 流数据中,通过 0x00000001 和 0x000001 字节序列作为帧数据的分割序列,为了避免解码器将编码的帧数据中的这样的序列误认为是帧之间的分割序列,而强制在出现两个 0x00 字节的帧数据时,插入一个 0x03 字节。而在计算 SPS 的 Base64 时需要先移除这些字节。
H264VideoFileServerMediaSubsession
的 getAuxSDPLine()
在拿到 aux SDP 行之后,就把它返回给调用者 OnDemandServerMediaSubsession::setSDPLinesFromRTPSink()
。aux SDP 行通常像下面这样:
OnDemandServerMediaSubsession::setSDPLinesFromRTPSink()
利用所有的信息,生成最终的子会话 SDP 行。最终的子会话 SDP 行通常像下面这样:
|
|
打赏
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 源码分析:播放启动