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,调用H264VideoStreamFramergetNextFrame函数将获取一个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

fParserH264or5VideoStreamFramer的构造函数中构造,其作用是解析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源码分析(七)播放过程相关推荐

  1. live555 源码分析:播放启动

    本文分析 live555 中,流媒体播放启动,数据开始通过 RTP/RTCP 传输的过程. 如我们在 live555 源码分析:子会话 SETUP 中看到的,一个流媒体子会话的播放启动,由 Strea ...

  2. live555 源码分析: PLAY 的处理

    在 SETUP 请求之后,客户端会发起 PLAY 请求,以请求服务器开始传输音视频数据.在 PLAY 请求执行时,一定是已经执行过 SETUP 请求,建立好了客户端会话,因而会与其它要求客户端会话已经 ...

  3. live555源码分析(四)RTSPServer分析

    live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...

  4. live555源码分析(一)live555初体验

    live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...

  5. live555 源码分析:ServerMediaSession

    在 live555 中,用一个 ServerMediaSession 表示流媒体会话,它连接了 RTSPServer 和下层流媒体传输逻辑.ServerMediaSession 和 ServerMed ...

  6. live555 源码分析: SETUP 的处理

    SETUP 请求在 RTSP 的整个工作流程中,用于建立流媒体会话.本文分析 live555 对 SETUP 请求的处理. 在 RTSPServer::RTSPClientConnection::ha ...

  7. live555 源码分析:RTSPServer

    live555 使用 RTSP/RTP/RTCP 协议来实现流媒体的传输,其中使用 RTSP 来建立流媒体会话,并对流媒体会话进行控制.在 live555 中,通过类 RTSPServerSuppor ...

  8. live555 源码分析:子会话 SDP 行生成

    如我们在前文 live555 源码分析:ServerMediaSession 中看到的,H264VideoFileServerMediaSubsession 的继承层次体系如下图: 在这个继承层次体系 ...

  9. live555 源码分析:RTSPServer 组件结构

    前面几篇文章分析了 live555 中 RTSP 的处理逻辑,RTSP 处理有关组件的处理逻辑有点复杂,本文就再来梳理一下它们之间的关系. live555 中 RTSP 处理有关组件关系如下图: 事件 ...

  10. live555 源码分析: DESCRIBE 的处理

    前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONS,GET_PARAMETE ...

最新文章

  1. Android 高清加载巨图方案 拒绝压缩图片
  2. android html文字垂直居中,Android 浏览器文本垂直居中问题
  3. 【原创】分享一些机器学习和深度学习的学习资料
  4. 员工任务管理系统c语言,C语言职工信息管理系统课程设计任务书.docx
  5. quill鼠标悬浮 出现提示_jQuery实现鼠标悬停显示提示信息窗口的方法
  6. 【软件工程第三次作业】
  7. mongodb-java-driver基本用法
  8. 孩子哭的时候大人应该怎么办?
  9. 20191019:(leetcode习题)第K个语法符号
  10. 年终述职--常见问题分析解答
  11. JavaScript事件与处理程序绑定(1)
  12. 网站安全之为Web项目添加验证码功能(一)
  13. JAVA并发容器之CopyOnWrite容器
  14. 表达式引擎Aviator基本介绍及使用以及基于Aviator的规则引擎(附代码详细介绍)
  15. python创建xlsx文件_教程1:创建一个简单的XLSX文件
  16. HTML常用标签总结 [建议收藏]
  17. 下载 保存 sina 微博视频
  18. 生成划掉的字_哪种备忘录划删除线,能划掉文字在字中间划线的便签
  19. django3.x+DRF+simpleui+uniapp打造自己的任务推广(兼职、悬赏)平台
  20. android声音编辑器,音频视频编辑器app下载-Audio Video Editorv1.1.0 安卓版-腾牛安卓网...

热门文章

  1. 腾达ap设置说明_腾达(Tenda)F3无线信号放大模式(Client+AP)设置 | 192路由网
  2. 如何设置记事本文件.txt文件的默认打开方式为editplus
  3. MySQL基本架构示意图
  4. 最新2021计算机排名中国大学排名,2020-2021年计算机类专业排名_中国大学本科教育按专业类排行榜_中国科教评价网...
  5. rust ffi理解
  6. 阿里P7需要精通哪些技术?看完Github上星标98K的对标阿里P7学习路线我彻底惊了
  7. 尝试Ajax数据爬取微博
  8. 鸿蒙生死印作用,逆天邪神:南溟神帝要抢鸿蒙生死印已成事实,但他还有更大作用...
  9. uniapp密码输入框
  10. bash shell学习-实践 (自己实现一些小工具)