live555学习笔记7-RTP打包与发送

七 RTP打包与发送


rtp传送开始于函数:MediaSink::startPlaying()。想想也有道理,应是sink跟source要数据,所以从sink上调用startplaying(嘿嘿,相当于directshow的拉模式)。


看一下这个函数:
Boolean MediaSink::startPlaying(MediaSource& source, afterPlayingFunc* afterFunc, void* afterClientData) { //参数afterFunc是在播放结束时才被调用。 // Make sure we're not already being played: if (fSource != NULL) { envir().setResultMsg("This sink is already being played"); return False; } // Make sure our source is compatible: if (!sourceIsCompatibleWithUs(source)) { envir().setResultMsg( "MediaSink::startPlaying(): source is not compatible!"); return False; } //记下一些要使用的对象 fSource = (FramedSource*) &source; fAfterFunc = afterFunc; fAfterClientData = afterClientData; return continuePlaying(); }

为了进一步封装(让继承类少写一些代码),搞出了一个虚函数continuePlaying()。让我们来看一下:

Boolean MultiFramedRTPSink::continuePlaying() { // Send the first packet. // (This will also schedule any future sends.) buildAndSendPacket(True); return True; }MultiFramedRTPSink是与帧有关的类,其实它要求每次必须从source获得一个帧的数据,所以才叫这个name。可以看到continuePlaying()完全被buildAndSendPacket()代替。看一下buildAndSendPacket():
void MultiFramedRTPSink::buildAndSendPacket(Boolean isFirstPacket) { //此函数中主要是准备rtp包的头,为一些需要跟据实际数据改变的字段留出位置。 fIsFirstPacket = isFirstPacket; // Set up the RTP header: unsigned rtpHdr = 0x80000000; // RTP version 2; marker ('M') bit not set (by default; it can be set later) rtpHdr |= (fRTPPayloadType << 16); rtpHdr |= fSeqNo; // sequence number fOutBuf->enqueueWord(rtpHdr);//向包中加入一个字 // Note where the RTP timestamp will go. // (We can't fill this in until we start packing payload frames.) fTimestampPosition = fOutBuf->curPacketSize(); fOutBuf->skipBytes(4); // leave a hole for the timestamp 在缓冲中空出时间戳的位置 fOutBuf->enqueueWord(SSRC()); // Allow for a special, payload-format-specific header following the // RTP header: fSpecialHeaderPosition = fOutBuf->curPacketSize(); fSpecialHeaderSize = specialHeaderSize(); fOutBuf->skipBytes(fSpecialHeaderSize); // Begin packing as many (complete) frames into the packet as we can: fTotalFrameSpecificHeaderSizes = 0; fNoFramesLeft = False; fNumFramesUsedSoFar = 0; // 一个包中已打入的帧数。 //头准备好了,再打包帧数据 packFrame(); }继续看packFrame():
void MultiFramedRTPSink::packFrame() { // First, see if we have an overflow frame that was too big for the last pkt if (fOutBuf->haveOverflowData()) { //如果有帧数据,则使用之。OverflowData是指上次打包时剩下的帧数据,因为一个包可能容纳不了一个帧。 // Use this frame before reading a new one from the source unsigned frameSize = fOutBuf->overflowDataSize(); struct timeval presentationTime = fOutBuf->overflowPresentationTime(); unsigned durationInMicroseconds =fOutBuf->overflowDurationInMicroseconds(); fOutBuf->useOverflowData(); afterGettingFrame1(frameSize, 0, presentationTime,durationInMicroseconds); } else { //一点帧数据都没有,跟source要吧。 // Normal case: we need to read a new frame from the source if (fSource == NULL) return; //更新缓冲中的一些位置 fCurFrameSpecificHeaderPosition = fOutBuf->curPacketSize(); fCurFrameSpecificHeaderSize = frameSpecificHeaderSize(); fOutBuf->skipBytes(fCurFrameSpecificHeaderSize); fTotalFrameSpecificHeaderSizes += fCurFrameSpecificHeaderSize; //从source获取下一帧 fSource->getNextFrame(fOutBuf->curPtr(),//新数据存放开始的位置 fOutBuf->totalBytesAvailable(),//缓冲中空余的空间大小 afterGettingFrame, //因为可能source中的读数据函数会被放在任务调度中,所以把获取帧后应调用的函数传给source this, ourHandleClosure, //这个是source结束时(比如文件读完了)要调用的函数。 this); } }可以想像下面就是source从文件(或某个设备)中读取一帧数据,读完后返回给sink,当然不是从函数返回了,而是以调用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); }没什么可看的,只是过度为调用成员函数,所以afterGettingFrame1()才是重点:
void MultiFramedRTPSink::afterGettingFrame1( unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds) { if (fIsFirstPacket) { // Record the fact that we're starting to play now: gettimeofday(&fNextSendTime, NULL); } //如果给予一帧的缓冲不够大,就会发生截断一帧数据的现象。但也只能提示一下用户 if (numTruncatedBytes > 0) { unsigned const bufferSize = fOutBuf->totalBytesAvailable(); envir() << "MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size (" << bufferSize << "). " << numTruncatedBytes << " bytes of trailing data was dropped! Correct this by increasing \"OutPacketBuffer::maxSize\" to at least " << OutPacketBuffer::maxSize + numTruncatedBytes << ", *before* creating this 'RTPSink'. (Current value is " << OutPacketBuffer::maxSize << ".)\n"; } unsigned curFragmentationOffset = fCurFragmentationOffset; unsigned numFrameBytesToUse = frameSize; unsigned overflowBytes = 0; //如果包只已经打入帧数据了,并且不能再向这个包中加数据了,则把新获得的帧数据保存下来。 // If we have already packed one or more frames into this packet, // check whether this new frame is eligible to be packed after them. // (This is independent of whether the packet has enough room for this // new frame; that check comes later.) if (fNumFramesUsedSoFar > 0) { //如果包中已有了一个帧,并且不允许再打入新的帧了,则只记录下新的帧。 if ((fPreviousFrameEndedFragmentation && !allowOtherFramesAfterLastFragment()) || !frameCanAppearAfterPacketStart(fOutBuf->curPtr(), frameSize)) { // Save away this frame for next time: numFrameBytesToUse = 0; fOutBuf->setOverflowData(fOutBuf->curPacketSize(), frameSize, presentationTime, durationInMicroseconds); } } //表示当前打入的是否是上一个帧的最后一块数据。 fPreviousFrameEndedFragmentation = False; //下面是计算获取的帧中有多少数据可以打到当前包中,剩下的数据就作为overflow数据保存下来。 if (numFrameBytesToUse > 0) { // Check whether this frame overflows the packet if (fOutBuf->wouldOverflow(frameSize)) { // Don't use this frame now; instead, save it as overflow data, and // send it in the next packet instead. However, if the frame is too // big to fit in a packet by itself, then we need to fragment it (and // use some of it in this packet, if the payload format permits this.) if (isTooBigForAPacket(frameSize) && (fNumFramesUsedSoFar == 0 || allowFragmentationAfterStart())) { // We need to fragment this frame, and use some of it now: overflowBytes = computeOverflowForNewFrame(frameSize); numFrameBytesToUse -= overflowBytes; fCurFragmentationOffset += numFrameBytesToUse; } else { // We don't use any of this frame now: overflowBytes = frameSize; numFrameBytesToUse = 0; } fOutBuf->setOverflowData(fOutBuf->curPacketSize() + numFrameBytesToUse, overflowBytes, presentationTime, durationInMicroseconds); } else if (fCurFragmentationOffset > 0) { // This is the last fragment of a frame that was fragmented over // more than one packet. Do any special handling for this case: fCurFragmentationOffset = 0; fPreviousFrameEndedFragmentation = True; } } if (numFrameBytesToUse == 0 && frameSize > 0) { //如果包中有数据并且没有新数据了,则发送之。(这种情况好像很难发生啊!) // Send our packet now, because we have filled it up: sendPacketIfNecessary(); } else { //需要向包中打入数据。 // Use this frame in our outgoing packet: unsigned char* frameStart = fOutBuf->curPtr(); fOutBuf->increment(numFrameBytesToUse); // do this now, in case "doSpecialFrameHandling()" calls "setFramePadding()" to append padding bytes // Here's where any payload format specific processing gets done: doSpecialFrameHandling(curFragmentationOffset, frameStart, numFrameBytesToUse, presentationTime, overflowBytes); ++fNumFramesUsedSoFar; // Update the time at which the next packet should be sent, based // on the duration of the frame that we just packed into it. // However, if this frame has overflow data remaining, then don't // count its duration yet. if (overflowBytes == 0) { fNextSendTime.tv_usec += durationInMicroseconds; fNextSendTime.tv_sec += fNextSendTime.tv_usec / 1000000; fNextSendTime.tv_usec %= 1000000; } //如果需要,就发出包,否则继续打入数据。 // Send our packet now if (i) it's already at our preferred size, or // (ii) (heuristic) another frame of the same size as the one we just // read would overflow the packet, or // (iii) it contains the last fragment of a fragmented frame, and we // don't allow anything else to follow this or // (iv) one frame per packet is allowed: if (fOutBuf->isPreferredSize() || fOutBuf->wouldOverflow(numFrameBytesToUse) || (fPreviousFrameEndedFragmentation && !allowOtherFramesAfterLastFragment()) || !frameCanAppearAfterPacketStart( fOutBuf->curPtr() - frameSize, frameSize)) { // The packet is ready to be sent now sendPacketIfNecessary(); } else { // There's room for more frames; try getting another: packFrame(); } } }
看一下发送数据的函数:
void MultiFramedRTPSink::sendPacketIfNecessary() { //发送包 if (fNumFramesUsedSoFar > 0) { // Send the packet: #ifdef TEST_LOSS if ((our_random()%10) != 0) // simulate 10% packet loss ##### #endif if (!fRTPInterface.sendPacket(fOutBuf->packet(),fOutBuf->curPacketSize())) { // if failure handler has been specified, call it if (fOnSendErrorFunc != NULL) (*fOnSendErrorFunc)(fOnSendErrorData); } ++fPacketCount; fTotalOctetCount += fOutBuf->curPacketSize(); fOctetCount += fOutBuf->curPacketSize() - rtpHeaderSize - fSpecialHeaderSize - fTotalFrameSpecificHeaderSizes; ++fSeqNo; // for next time } //如果还有剩余数据,则调整缓冲区 if (fOutBuf->haveOverflowData() && fOutBuf->totalBytesAvailable() > fOutBuf->totalBufferSize() / 2) { // Efficiency hack: Reset the packet start pointer to just in front of // the overflow data (allowing for the RTP header and special headers), // so that we probably don't have to "memmove()" the overflow data // into place when building the next packet: unsigned newPacketStart = fOutBuf->curPacketSize()- (rtpHeaderSize + fSpecialHeaderSize + frameSpecificHeaderSize()); fOutBuf->adjustPacketStart(newPacketStart); } else { // Normal case: Reset the packet start pointer back to the start: fOutBuf->resetPacketStart(); } fOutBuf->resetOffset(); fNumFramesUsedSoFar = 0; if (fNoFramesLeft) { //如果再没有数据了,则结束之 // We're done: onSourceClosure(this); } else { //如果还有数据,则在下一次需要发送的时间再次打包发送。 // We have more frames left to send. Figure out when the next frame // is due to start playing, then make sure that we wait this long before // sending the next packet. struct timeval timeNow; gettimeofday(&timeNow, NULL); int secsDiff = fNextSendTime.tv_sec - timeNow.tv_sec; int64_t uSecondsToGo = secsDiff * 1000000 + (fNextSendTime.tv_usec - timeNow.tv_usec); if (uSecondsToGo < 0 || secsDiff < 0) { // sanity check: Make sure that the time-to-delay is non-negative: uSecondsToGo = 0; } // Delay this amount of time: nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*) sendNext, this); } }

可以看到为了延迟包的发送,使用了delay task来执行下次打包发送任务。

sendNext()中又调用了buildAndSendPacket()函数,呵呵,又是一个圈圈。

总结一下调用过程:



最后,再说明一下包缓冲区的使用:

MultiFramedRTPSink中的帧数据和包缓冲区共用一个,只是用一些额外的变量指明缓冲区中属于包的部分以及属于帧数据的部分(包以外的数据叫做overflow data)。它有时会把overflow data以mem move的方式移到包开始的位置,有时把包的开始位置直接设置到overflow data开始的地方。那么这个缓冲的大小是怎样确定的呢?是跟据调用者指定的的一个最大的包的大小+60000算出的。这个地方把我搞胡涂了:如果一次从source获取一个帧的话,那这个缓冲应设为不小于最大的一个帧的大小才是,为何是按包的大小设置呢?可以看到,当缓冲不够时只是提示一下:

if (numTruncatedBytes > 0) { unsigned const bufferSize = fOutBuf->totalBytesAvailable(); envir() << "MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size (" << bufferSize << "). " << numTruncatedBytes << " bytes of trailing data was dropped! Correct this by increasing \"OutPacketBuffer::maxSize\" to at least " << OutPacketBuffer::maxSize + numTruncatedBytes << ", *before* creating this 'RTPSink'. (Current value is " << OutPacketBuffer::maxSize << ".)\n"; }当然此时不会出错,但有可能导致时间戳计算不准,或增加时间戳计算与source端处理的复杂性(因为一次取一帧时间戳是很好计算的)。

转载于:https://www.cnblogs.com/android-html5/archive/2011/10/31/2533627.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/297898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASCII 说明

Bin Dec Hex 缩写/字符 解释 0000 0000 0 00 NUL(null) 空字符 0000 0001 1 01 SOH(start of handing) 标题开始 0000 0010 2 02 STX (start of text) 正文开始 0000 0011 3 03 ETX(end of text) 正文结束 0000 0100 4 04 EOT(end of transm…

开源Math.NET基础数学类库使用(04)C#解析Matrix Marke数据格式

原文:【原创】开源Math.NET基础数学类库使用(04)C#解析Matrix Marke数据格式开源Math.NET基础数学类库使用系列文章总目录&#xff1a; 1.开源.NET基础数学计算组件Math.NET(一)综合介绍 2.开源.NET基础数学计算组件Math.NET(二)矩阵向量计算 3.开源.NET基础数学计算组件Ma…

元胞自动机模型_【ABM仿真模拟】第三章 元胞自动机 B

是新朋友吗&#xff1f;记得先点蓝字关注我哦&#xff5e;第三章 元胞自动机 B2020/01/233.3投票模型(Voting) 课程导读无论是国内国外&#xff0c;投票结果的预测都异常受到关注。在众多唐斯模型、中间选民模型 、以及Sznajd模型中&#xff0c;投票模型作为典型的元胞自动机&a…

喜报!985大学首次登上Nature封面,这所学校可太不容易了!

全世界只有3.14 % 的人关注了爆炸吧知识建校64年来&#xff0c;第一次登上期刊封面又一所985院校出息了&#xff01;伦敦时间6月4号&#xff0c;《Nature》刊发了电子科技大学邓旭教授团队的最新研究成果&#xff0c;并被选为当期封面。《设计坚固的超疏水表面》《Nature》作为…

getbean方法找不到bean_iphone手机静音找不到怎么办 iphone静音找不到解决方法【图文】...

一个网友给小编留言&#xff0c;询问&#xff1a;“我的iphone调静音了现在找不到&#xff0c;有什么工具能找到”这一个问题&#xff0c;因此&#xff0c;在今天的iPhone使用教程&#xff0c;小编就给大家讲解一下具体的解决方法&#xff0c;那么&#xff0c;iphone手机静音找…

Web使用热敏打印小票(IE环境)

概述在html页下使用Epson P60II 热敏纸下打印小票&#xff0c;使用的打印方案为调用window.print()。代码实现1、定义窗体&#xff0c;设置宽度和高度<body onload"window.external.Print(0,0);" style"margin-top:0px;" ><form id"form1&qu…

linux配置ip地址 routes,CentOS 7 设置网络IP地址(示例代码)

CentOS 7 设置网络IP地址自动获得IP地址1&#xff0c;CentOS 7自动获得一个IP地址[[email protected] ~]# dhclient2&#xff0c;查看网卡信息&#xff0c;会发现有一个ens33的网卡[[email protected] ~]# ip addr1: lo: mtu 65536 qdisc noqueue state UNKNOWN qlen 1link/loo…

一些鲜为人知的编程事实(省身)

David Veksler曾发表过一篇博文《Some lesser-known truths about programming》&#xff0c;列出了一些鲜为人知的编程事实&#xff0c;这些事实是什么呢&#xff1f;酷壳个人网站楼主陈皓对此文进行了翻译&#xff0c;全文如下&#xff1a; 我的程序员经历让我明白了一些关于…

Android推送通知指南(转)

在开发Android和iPhone应用程序时&#xff0c;我们往往需要从服务器不定的向手机客户端即时推送各种通知消息&#xff0c;iPhone上已经有了比较简单的和完美的推送通知解决方案&#xff0c;可是Android平台上实现起来却相对比较麻烦&#xff0c;最近利用几天的时间对Android的推…

成长 | 《大厂晋升指南》学习总结(上)

【学习总结】| Edison Zhou温馨提示&#xff1a;文中的贴图均来自极客时间《大厂晋升指南》课程。0写在开头今年加入了一家产业互联网平台企业&#xff0c;公司刚好也开始借鉴阿里的职级体系。对于从来没有在互联网企业呆过的我&#xff0c;对于职级体系还比较陌生&#xff0c;…

vue路由上的#/怎么去掉_如何去掉vue路由中的#

通过脚手架vue-cli构建的项目&#xff0c;在项目启动后&#xff0c;URL地址上都会带有#&#xff0c;如&#xff1a;http://localhost:8080/#/father原因&#xff1a;这是因为vue-router 默认hash模式&#xff0c; 使用 URL 的 hash 来模拟一个完整的 URL&#xff0c;于是当 URL…

python3读取文件夹-python3获取文件及文件夹大小

Help on function walk inmodule os: walk(top, topdownTrue, οnerrοrNone, followlinksFalse) Directory tree generator. For each directoryinthe directory tree rooted at top (including top itself, but excluding"." and ".."), yields a 3-tupl…

String,StringBuffer与StringBuilder的区别

2019独角兽企业重金招聘Python工程师标准>>> String 字符串常量StringBuffer 字符串变量&#xff08;线程安全&#xff09;StringBuilder 字符串变量&#xff08;非线程安全&#xff09; 简要的说&#xff0c; String 类型和 StringBuffer 类型的主要性能区别其实在…

68张机械原理动图,够你看一晚上了!

全世界只有3.14 % 的人关注了爆炸吧知识机械动态图有的可以洞察工作原理&#xff0c;有的可以洞察结构&#xff0c;有的可以表达工作过程&#xff0c;不学机械的也能看得懂&#xff01;今天的68幅动态图总有一些你没有见过&#xff0c;相当棒&#xff01;一、制造篇一张图告诉你…

STB 上Linux软件系统解决方案

近年来&#xff0c;国内巨大的STB潜在市场&#xff0c;吸引了包括芯片厂商、CA厂商、中间件供应商、机顶盒生产厂商等厂家的热情&#xff0c;围绕STB的竞争全面展开。 机顶盒是一种专用设备&#xff0c;包括软件模块和硬件模块。软件模块包括系统引导程序、嵌入式操作系统和应用…

coreldraw带圈字符_coreldraw中如何使字体围绕一个圆形排列?

展开全部方法一2113&#xff1a;使用“使文5261本适合路径”命令步骤1&#xff1a;打4102好所需文1653字&#xff0c;并版选择工具箱中“权椭圆工具”按住Ctrl键&#xff0c;拖动鼠标绘制出一个正圆形。步骤2&#xff1a;选中文字&#xff0c;执行菜单栏“文本>使文本适合路…

Android 说说亮屏锁和键盘锁

Android中的两把锁 – WalkLock and KeyguardLock 详细分析WalkLock – 顾名思义 唤醒锁 点亮屏幕用的KeyguardLock – 顾名思义 键盘锁 解锁键盘用的详细介绍&#xff1a;1&#xff1a; WalkLock 唤醒锁WalkLock真的能点亮屏幕吗&#xff1f;答案是肯定的。 可是有时候为什么不…

频谱分析幅值单位_案例分享丨某水泥厂入窑斗提减速机不对中故障分析及处理...

斗式提升机是利用均匀固接于无端奉引构件上的一系列料斗,竖向提升物料的连续输送机械。分为环链、板链和皮带三种。今天因大师将给大家分享一个水泥厂的入窑斗提减速机不对中故障的诊断案例。1 设备概况设备基本信息&#xff1a;设备位置&#xff1a;1#产线入窑斗提&#xff08…

中国高校鄙视链指南

全世界只有3.14 % 的人关注了爆炸吧知识某一天&#xff0c;中国各大高校齐聚一堂&#xff0c;开了一场“拒绝高校鄙视链座谈会”&#xff0c;誓要消除高校之间的不平等&#xff0c;不让莘莘学子因为母校问题而被人低看一眼&#xff0c;遗憾终生。首先主持人介绍清华北大发言&am…

.NET 6 ThreadPool 实现概述

前言在即将发布的 .NET 6 runtime 中&#xff0c;默认的线程池实现从 C 代码改为了 C#&#xff0c;更方便我们学习线程池的设计了。https://github.com/dotnet/runtime/tree/release/6.0/src/libraries/System.Threading.ThreadPool新的线程池实现位于 PortableThreadPool 中&a…