live555源码分析(七)播放过程
live555源码分析系列
live555源码分析(一)live555初体验
live555源码分析(二)基本组件上
live555源码分析(三)基本组件下
live555源码分析(四)RTSPServer分析
live555源码分析(五)DESCRIBE请求的处理
live555源码分析(六)SETUP和PLAY请求的处理
live555源码分析(七)播放过程
live555源码分析(八)多播
live555源码分析(七)播放过程
本文衔接上文,分析播放过程,涉及音视频数据如何获取、如何处理以及如何发送
上一篇文章我们分析到StreamState::startPlaying
函数,本文从这里开始分析,其定义如下
void StreamState
::startPlaying(Destinations* dests, unsigned clientSessionId,TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,void* serverRequestAlternativeByteHandlerClientData) {/* 将目的添加到消费者中 */if (dests->isTCP) { //TCPfRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);} else { //UDPfRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId);fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId);}/* 开始播放 */fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
}
从上面可以看到,会将目的添加到RTPSink中,然后调用RTPSink
开始播放,并指定好它的源为fMediaSource
在我们live555源码分析(四)RTSPServer分析中实现的这个例子中
RTPSink
是这样产生的
RTPSink* H264VideoFileServerMediaSubsession
::createNewRTPSink(Groupsock* rtpGroupsock,unsigned char rtpPayloadTypeIfDynamic,FramedSource* /*inputSource*/) {return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}
H264VideoRTPSink
的作用是将H264的NALU进行RTP打包发送
fMediaSource
是这样产生的
FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {estBitrate = 500; // kbps, estimateByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);if (fileSource == NULL) return NULL;fFileSize = fileSource->fileSize();return H264VideoStreamFramer::createNew(envir(), fileSource);
}
live555对数据源的类很多采用了装饰者模式,如上述中,ByteStreamFileSource
用于读取文件字节流,H264VideoStreamFramer
用于读取h264文件的nalu
搞清楚生产者和消费者对应谁之后,我们继续分析fRTPSink->startPlaying()
,它的定义如下
Boolean MediaSink::startPlaying(MediaSource& source,afterPlayingFunc* afterFunc,void* afterClientData) {return continuePlaying();
}
continuePlaying
在本例中对应如下
Boolean H264or5VideoRTPSink::continuePlaying() {fOurFragmenter = new H264or5Fragmenter(fHNumber, envir(),fSource, OutPacketBuffer::maxSize,ourMaxPacketSize() - 12/*RTP hdr size*/);fSource = fOurFragmenter;return MultiFramedRTPSink::continuePlaying();
}
可以看到,这里又会将我们定义的生产者H264VideoStreamFramer
对象作为参数传递给H264or5Fragmenter
创建新的生产者,然后修改fSource
这里使用的也是装饰者模式,H264or5Fragmenter
的作用是将NALU进行特定的RTP打包
从头到尾的生产者关系如下
ByteStreamFileSource //文件字节流|
H264VideoStreamFramer //提取NALU|
H264or5Fragmenter //对NALU进行RTP打包
到这里,相信你不会觉得定义这么多的生产者很乱,反而会觉得设计得挺巧妙
接着往下看
Boolean MultiFramedRTPSink::continuePlaying() {buildAndSendPacket(True);
}
void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) {unsigned rtpHdr = 0x80000000;rtpHdr |= (fRTPPayloadType<<16);rtpHdr |= fSeqNo;fOutBuf->enqueueWord(rtpHdr);...packFrame();
}
buildAndSendPacket
函数对设置了RTP得头部,其中跳过了时间戳,这个将在稍后设置
RTP头部得定义如下,感兴趣的话可以仔细分析代码
接下来分析packFrame
函数,其定义如下
void MultiFramedRTPSink::packFrame() {fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),afterGettingFrame, this, ourHandleClosure, this);
}
可以看到,上述调用了生产者,获取一帧数据,获取完数据之后会调用afterGettingFrame
函数
afterGettingFrame
定义如下,这里请记住,我们稍后分析
void MultiFramedRTPSink::afterGettingFrame(void* clientData, unsigned numBytesRead,unsigned numTruncatedBytes,struct timeval presentationTime,unsigned durationInMicroseconds) {MultiFramedRTPSink* sink = (MultiFramedRTPSink*)clientData;sink->afterGettingFrame1(numBytesRead, numTruncatedBytes,presentationTime, durationInMicroseconds);
}
在此例中fSource
对应H264or5Fragmenter
,看一看它的getNextFrame
函数定义
void H264or5Fragmenter::doGetNextFrame() {if (fNumValidDataBytes == 1) { //缓存没有数据fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,afterGettingFrame, this,FramedSource::handleClosure, this);} else { //缓存有数据/** 打包方式* 1.单NALU模式* 2.分片模式* 3.聚合模式*/FramedSource::afterGetting(this);}
}
首先,如果缓存里没有数据,那么就会调用fInputSource
来获取一帧数据,此例中fInputSource
对应的是H264VideoStreamFramer
,调用H264VideoStreamFramer
的getNextFrame
函数将获取一个NALU
如果缓存里有数据,那么就进行RTP打包,这里的数据是位于RTP的载荷位置,这里实现了三种NALU的RTP打包模式,单NALU打包、分片打包、聚合打包
我们首先来分析getNextFrame
函数,此例中,其定义如下
void MPEGVideoStreamFramer::doGetNextFrame() {fParser->registerReadInterest(fTo, fMaxSize);continueReadProcessing();
}void MPEGVideoStreamFramer::continueReadProcessing() {unsigned acquiredFrameSize = fParser->parse();afterGetting(this);
}
最终会通过fParser->parse
来解析获取数据,然后调用afterGetting
fParser
在H264or5VideoStreamFramer
的构造函数中构造,其作用是解析H264或者H265文件
H264or5VideoStreamFramer
::H264or5VideoStreamFramer(int hNumber, UsageEnvironment& env, FramedSource* inputSource,Boolean createParser, Boolean includeStartCodeInOutput)
{fParser = createParser? new H264or5VideoStreamParser(hNumber, this, inputSource, includeStartCodeInOutput)
}
好的,在获取完一帧后,会调用afterGetting
函数,其对应
void H264or5Fragmenter::afterGettingFrame(void* clientData, unsigned frameSize,unsigned numTruncatedBytes,struct timeval presentationTime,unsigned durationInMicroseconds) {H264or5Fragmenter* fragmenter = (H264or5Fragmenter*)clientData;fragmenter->afterGettingFrame1(frameSize, numTruncatedBytes, presentationTime,durationInMicroseconds);
}
void H264or5Fragmenter::afterGettingFrame1(unsigned frameSize,unsigned numTruncatedBytes,struct timeval presentationTime,unsigned durationInMicroseconds) {fNumValidDataBytes += frameSize;fSaveNumTruncatedBytes = numTruncatedBytes;fPresentationTime = presentationTime; //本帧数据的时间戳fDurationInMicroseconds = durationInMicroseconds; //下一次获取数据的间隔时间doGetNextFrame();
}
可以看到在获取到一帧数据后,又调用了doGetNextFrame
函数
void H264or5Fragmenter::doGetNextFrame() {if (fNumValidDataBytes == 1) { //缓存没数据fInputSource->getNextFrame(&fInputBuffer[1], fInputBufferSize - 1,afterGettingFrame, this,FramedSource::handleClosure, this);} else { //缓存有数据/** 打包方式* 1.单NALU模式* 2.分片模式* 3.聚合模式*/FramedSource::afterGetting(this);}
}
第二次调用这个函数,现在会进入else分支,对数据进行RTP封装,然后调用FramedSource::afterGetting
如果对于RTP封装感兴趣的话,可以看从零开始写一个RTSP服务器(三)RTP传输H.264
我们接着查看FramedSource::afterGetting
,其定义在下面函数调用中指定
void MultiFramedRTPSink::packFrame() {fSource->getNextFrame(fOutBuf->curPtr(), fOutBuf->totalBytesAvailable(),afterGettingFrame, this, ourHandleClosure, this);
}
定义如下
void MultiFramedRTPSink::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes,struct timeval presentationTime,unsigned durationInMicroseconds) {...doSpecialFrameHandling(curFragmentationOffset, frameStart,numFrameBytesToUse, presentationTime,overflowBytes);sendPacketIfNecessary();...
}
这里忽略了许多东西,只保留了其中最主要的部分
首先调用doSpecialFrameHandling
对RTP包进行处理,本例中,其定义如下
void H264or5VideoRTPSink::doSpecialFrameHandling(unsigned /*fragmentationOffset*/,unsigned char* /*frameStart*/,unsigned /*numBytesInFrame*/,struct timeval framePresentationTime,unsigned /*numRemainingBytes*/) {...if()setMarkerBit();setTimestamp(framePresentationTime);
}
可以看到此函数会设置默写标志位和时间戳
到了这里,一个RTP包就完成了,接下来看sendPacketIfNecessary
,其定义如下
void MultiFramedRTPSink::sendPacketIfNecessary() {...fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()...envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);
}
首先将数据传送给所有的客户端,然后添加一个定时任务,准备下一次发送
整个过程就是这样,确实有点绕,我将其画为流程图,如下
本文到这里就结束了
live555源码分析(七)播放过程相关推荐
- live555 源码分析:播放启动
本文分析 live555 中,流媒体播放启动,数据开始通过 RTP/RTCP 传输的过程. 如我们在 live555 源码分析:子会话 SETUP 中看到的,一个流媒体子会话的播放启动,由 Strea ...
- live555 源码分析: PLAY 的处理
在 SETUP 请求之后,客户端会发起 PLAY 请求,以请求服务器开始传输音视频数据.在 PLAY 请求执行时,一定是已经执行过 SETUP 请求,建立好了客户端会话,因而会与其它要求客户端会话已经 ...
- live555源码分析(四)RTSPServer分析
live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...
- live555源码分析(一)live555初体验
live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...
- live555 源码分析:ServerMediaSession
在 live555 中,用一个 ServerMediaSession 表示流媒体会话,它连接了 RTSPServer 和下层流媒体传输逻辑.ServerMediaSession 和 ServerMed ...
- live555 源码分析: SETUP 的处理
SETUP 请求在 RTSP 的整个工作流程中,用于建立流媒体会话.本文分析 live555 对 SETUP 请求的处理. 在 RTSPServer::RTSPClientConnection::ha ...
- live555 源码分析:RTSPServer
live555 使用 RTSP/RTP/RTCP 协议来实现流媒体的传输,其中使用 RTSP 来建立流媒体会话,并对流媒体会话进行控制.在 live555 中,通过类 RTSPServerSuppor ...
- live555 源码分析:子会话 SDP 行生成
如我们在前文 live555 源码分析:ServerMediaSession 中看到的,H264VideoFileServerMediaSubsession 的继承层次体系如下图: 在这个继承层次体系 ...
- live555 源码分析:RTSPServer 组件结构
前面几篇文章分析了 live555 中 RTSP 的处理逻辑,RTSP 处理有关组件的处理逻辑有点复杂,本文就再来梳理一下它们之间的关系. live555 中 RTSP 处理有关组件关系如下图: 事件 ...
- live555 源码分析: DESCRIBE 的处理
前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONS,GET_PARAMETE ...
最新文章
- Android 高清加载巨图方案 拒绝压缩图片
- android html文字垂直居中,Android 浏览器文本垂直居中问题
- 【原创】分享一些机器学习和深度学习的学习资料
- 员工任务管理系统c语言,C语言职工信息管理系统课程设计任务书.docx
- quill鼠标悬浮 出现提示_jQuery实现鼠标悬停显示提示信息窗口的方法
- 【软件工程第三次作业】
- mongodb-java-driver基本用法
- 孩子哭的时候大人应该怎么办?
- 20191019:(leetcode习题)第K个语法符号
- 年终述职--常见问题分析解答
- JavaScript事件与处理程序绑定(1)
- 网站安全之为Web项目添加验证码功能(一)
- JAVA并发容器之CopyOnWrite容器
- 表达式引擎Aviator基本介绍及使用以及基于Aviator的规则引擎(附代码详细介绍)
- python创建xlsx文件_教程1:创建一个简单的XLSX文件
- HTML常用标签总结 [建议收藏]
- 下载 保存 sina 微博视频
- 生成划掉的字_哪种备忘录划删除线,能划掉文字在字中间划线的便签
- django3.x+DRF+simpleui+uniapp打造自己的任务推广(兼职、悬赏)平台
- android声音编辑器,音频视频编辑器app下载-Audio Video Editorv1.1.0 安卓版-腾牛安卓网...
热门文章
- 腾达ap设置说明_腾达(Tenda)F3无线信号放大模式(Client+AP)设置 | 192路由网
- 如何设置记事本文件.txt文件的默认打开方式为editplus
- MySQL基本架构示意图
- 最新2021计算机排名中国大学排名,2020-2021年计算机类专业排名_中国大学本科教育按专业类排行榜_中国科教评价网...
- rust ffi理解
- 阿里P7需要精通哪些技术?看完Github上星标98K的对标阿里P7学习路线我彻底惊了
- 尝试Ajax数据爬取微博
- 鸿蒙生死印作用,逆天邪神:南溟神帝要抢鸿蒙生死印已成事实,但他还有更大作用...
- uniapp密码输入框
- bash shell学习-实践 (自己实现一些小工具)