live555中ts流详细解析

                   live555中ts流详细解析

该文档主要是对live555源码下testProgs中testMPEG2TransportStreamer服务器端的详细分析。主要分析ts流实现的总体调用流程。(重新整理下,当时有些 代码、图片复制到CSDN出了点问题)

testMPEG2TransportStreamer中主要涉及的类视图如下,其中这些类主要实现都在liveMedia库下,最原始基类为Medium,下面这些类都是从这个类继承而来。

1.主要是对于ts流文件信息的获取,可以理解为获取数据的对象。


2.主要是对于ts流文件信息的传输,可以理解为消费数据的对象。

 

testMPEG2TransportStreamer服务器端main函数主要流程为:

1)创建我们需要使用的环境(UsageEnvironment* env该类主要是对于一些出错信息,输入、输出信息等的封装)。

2)创建组播地址、端口号(其中端口号和组播地址被传入到一个叫Groupsock类中,该类主要是封装了socket创建udp通信,对于端口信息的封装)。

3)创建sink类用于数据的发送,创建RTSP服务器、媒体会话等(被一个宏定义IMPLEMENT_RTSP_SERVER所控制了,源码中,这个宏是被注释掉的),其中对sink类入口函数如下:

// Create an appropriate 'RTP sink' from the RTP 'groupsock'://创建一个RTPsink   Sink就是消费数据的对象,比如把接收到的数据存储到文件,这个文件就是一个Sink。RTPSink* videoSink
videoSink = SimpleRTPSink::createNew(*env,&rtpGroupsock,33,90000,"video","MP2T",1,True,False/*no 'M' bit*/);//33:RTP有效负荷格式    90000:时间戳频率   "video":媒体类型字符串   "MP2T":RTP有效负载格式名字   1:数字通道?   True:允许每个数据包的多个帧  False:做正常的兆位规则

最后是最重要的一个传输数据入口函数play()。

play()该函数的封装如下:

void play() {
  unsignedconstinputDataChunkSize   = TRANSPORT_PACKETS_PER_NETWORK_PACKET*TRANSPORT_PACKET_SIZE;
  //Open the input file as a 'byte-stream file source':
  //将输入文件作为“字节流文件源”打开
/*主要工作的内容是打开流文件,获取文件的大小信息,获取文件描述符,文件所处的状态以及调整文件指针位置*/
  ByteStreamFileSource* fileSource
    = ByteStreamFileSource::createNew(*env,inputFileName,inputDataChunkSize);  //创建一个流文件源
  if (fileSource==NULL){
    *env<< "Unable to open file\"" << inputFileName
     << "\"as a byte-stream file source\n";
    exit(1);
  }
  // Create a 'framer' for the input source(to give us proper inter-packet gaps): /*为输入文件创建一个成帧器*/
  videoSource= MPEG2TransportStreamFramer::createNew(*env,fileSource); //Finally, start playing://最后开始播放
  *env<< "Beginning to read fromfile...\n";
  videoSink->startPlaying(*videoSource,afterPlaying,videoSink);//获取数据源videoSource,FramedSource* videoSource,RTPSink* videoSink;//消费数据的对象
}

其中TRANSPORT_PACKET_SIZE为传输的ts包,大小为188字节,TRANSPORT_PACKETS_PER_NETWORK_PACKET大小为7,所以每次传输的最大ts包为188x7。前面所说的source类中关于获取数据的信息,是由下面函数所创建,同时传入我们的ts流文件(inputFileName即ts流文件,env为使用环境,inputDataChunSize为传输的ts包大小)。

   ByteStreamFileSource* fileSource

  = ByteStreamFileSource::createNew(*env,inputFileName,inputDataChunkSize);

另外还有一个函数videoSource = MPEG2TransportStreamFramer::createNew(*env,fileSource);可以理解为一个成帧器,意义不是很大。

接下来是play()函数中最重要的一个函数startPlaying(),该函数是一个发送数据的入口函数。

// Finally, start playing://最后开始播放
*env << "Beginningto read from file...\n";
videoSink->startPlaying(*videoSource,afterPlaying,videoSink);//获取数据源videoSource,FramedSource* videoSource,RTPSink* videoSink;//消费数据的对象
该函数的实现是在MediaSink类中,所以和前面所理解的sink为发送数据的对象是一致的。该函数如下:
Boolean MediaSink::startPlaying(MediaSource&source,     /* source = videoSource*/ afterPlayingFunc*afterFunc,             /*afterFunc= afterPlaying*/void*afterClientData) {/*afterClientData = videosink*/
// Make sure we're not already being played:  确保我们还没有播放
  if (fSource!=NULL){
    envir().setResultMsg("Thissink is already being played");
    returnFalse;
  }// Makesure our source is compatible:   确保我们的源是兼容的
  if (!sourceIsCompatibleWithUs(source)) {    
 envir().setResultMsg("MediaSink::startPlaying():source is not compatible!");
    returnFalse;
  } //记下一些要使用的对象,保存数据
  fSource= (FramedSource*)&source;
  fAfterFunc= afterFunc;
  fAfterClientData=afterClientData;
  returncontinuePlaying();   //在继承类MultiFrameRTPSink中
}

从上面代码中可以看到,其实还没有进行真正的数据发送,只是在为数据发送做准备,比如有没有源,是否兼容等,接着调用continuePlaying()函数,该函数是在MultiFramedRTPSink类中实现,如下:

Boolean MultiFramedRTPSink::continuePlaying() {
  // Send the first packet.  发送第一个包  // (This will also schedule any futuresends.) 这样将安排未来的任何发送
  buildAndSendPacket(True);
  returnTrue;
}
从上面函数中看到该函数并没有做什么事情,而是buildAndSendPacket(True)
函数的一个封装,buildAndSendPacket(True)函数仍然在该类中实现,如下:
void MultiFramedRTPSink::buildAndSendPacket(BooleanisFirstPacket) {
  fIsFirstPacket=isFirstPacket; //isFirstPacket = true;
  //Set up the RTP header:   设置RTP包头
  unsignedrtpHdr=0x80000000;// RTP version 2; marker ('M') bit not set (by default; it can be setlater)  RTP版本2,标记M位而不设置(默认情况下,它可以之后设置)
  rtpHdr |=(fRTPPayloadType<<16);       //有效负荷
  rtpHdr |=fSeqNo;// sequence number    序列号
  fOutBuf->enqueueWord(rtpHdr);         //向包头加入一个字,此函数在MediaSink中
  // Note where the RTP timestamp willgo.     RTP时间戳将去哪里。(我们可以不填写直到我们开始包装负载帧。)
  // (We can't fill this inuntil 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:
  //允许一个特殊的、具体的RTP报头报头负载格式如下:
  fSpecialHeaderPosition=fOutBuf->curPacketSize();    //这些函数在MediaSink里面
  fSpecialHeaderSize= specialHeaderSize();
  fOutBuf->skipBytes(fSpecialHeaderSize);
  // Begin packing as many (complete) framesinto the packet as we can: 因为我们可以开始打包尽可能多的数据包
  fTotalFrameSpecificHeaderSizes=0;
  fNoFramesLeft= False;
  fNumFramesUsedSoFar=0; //一个包中已打入的帧数。
  //头准备好了,再打包帧数据
  packFrame();   
}

从上面的函数可以看出该函数主要是对ts的一个封包,将其封包为rtp包,其中主要是对rtp包头的设置,当其准备好包头以后,就准备在打包帧数据了,接着调用packFrame()函数,该函数同样在MultiFramedRTPSink类中实现,如下:

void MultiFramedRTPSink::packFrame(){
  // Get the next frame.  得到下一个帧 // First, skip over thespace we'll use for any frame-specific header:首先,跳过我们将使用的任何特定帧的空间:
  fCurFrameSpecificHeaderPosition=fOutBuf->curPacketSize();
  fCurFrameSpecificHeaderSize=frameSpecificHeaderSize();
  fOutBuf->skipBytes(fCurFrameSpecificHeaderSize);
  fTotalFrameSpecificHeaderSizes+=fCurFrameSpecificHeaderSize;
  // See if we have an overflow frame that wastoo big for the last pkt  看看我们是否有溢出的帧,由于最后的包太大
  if (fOutBuf->haveOverflowData()){
    // Use this frame before reading a new onefrom the source   
      //如果有帧数据,在使用之。OverflowData是指上次打包时,剩下的帧数据,因为一个包可能容纳不了一个帧。
    unsignedframeSize=fOutBuf->overflowDataSize();
    structtimeval presentationTime = fOutBuf->overflowPresentationTime();
    unsigneddurationInMicroseconds=fOutBuf->overflowDurationInMicroseconds();
    fOutBuf->useOverflowData();
    afterGettingFrame1(frameSize,0,presentationTime,durationInMicroseconds);
  } else{
    // Normal case: we need to read a new framefrom the source 
     //当发送的帧数据没有时,我们需要从源中读取一个新的帧
    if (fSource== NULL) return;
    /*fOutBuf->curPtr():新数据存放开始的位置;
    *fOutBuf->totalBytesAvailable():缓冲中空余的空间大小;
     *afterGettingFrame:因为可能source中的读数据函数会被放在任务调度中,所以把获取帧后应调用的函数传授给source;
     *ourHandleClosure:这个是source结束时(比如文件读完了)要调用的函数;   */
    fSource->getNextFrame(fOutBuf->curPtr(),fOutBuf->totalBytesAvailable(),   //fOutBuf输出包缓冲   getNextFrame在FrameSource类里面afterGettingFrame,this,ourHandleClosure,this);
  }
}

该函数主要实现的功能是:对上一个包帧数据的一个判断,如果还有数据,那么调用afterGettingFrame1()函数进行数据发送,该函数是一个重点,实现如下:

void MultiFramedRTPSink
::afterGettingFrame1(unsignedframeSize,unsignednumTruncatedBytes, //数据截断numTruncatedBytes= 0,没有发生数据截断;structtimevalpresentationTime,unsigneddurationInMicroseconds) {
  if (fIsFirstPacket) {
    // Record the fact that we're starting toplay now: 记录事件,我们准备开始播放
    gettimeofday(&fNextSendTime,NULL);
  }
  fMostRecentPresentationTime = presentationTime;
  if (fInitialPresentationTime.tv_sec==0&& fInitialPresentationTime.tv_usec==0) {
    fInitialPresentationTime = presentationTime;
  }   
  if (numTruncatedBytes>0) {//如果给予一帧的缓冲不够大,就会发生截断一帧数据的现象。但也只能提示一下用户 
    unsignedconstbufferSize=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 atleast "
        << OutPacketBuffer::maxSize+numTruncatedBytes<<", *before* creating this'RTPSink'.  (Current value is "
        << OutPacketBuffer::maxSize<<".)\n";
  }
  unsignedcurFragmentationOffset=fCurFragmentationOffset; //当前段偏移
  unsignednumFrameBytesToUse=frameSize;//帧字节大小
  unsignedoverflowBytes=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) {  //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, saveit as overflow data, and
      // send it in the next packetinstead.  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, ifthe payload format permits this.)    
      if(isTooBigForAPacket(frameSize)
          && (fNumFramesUsedSoFar==0 || allowFragmentationAfterStart())){
        // We need to fragment this frame, and usesome 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);
    } elseif (fCurFragmentationOffset> 0) {
      //This is the last fragment of a frame that was fragmented over
      //more than one packet.  Do any specialhandling for this case:
        //这是一个帧的最后一个片段,在一个以上的数据包被碎片化。对这种情况做任何特殊处理:
      fCurFragmentationOffset=0;
      fPreviousFrameEndedFragmentation=True;
    }
  }
 
  if (numFrameBytesToUse==0 && frameSize> 0) {
    // Send our packet now, because we havefilled it up:
      //如果包中有数据并且没有新数据了,则发送之。(这种情况好像很难发生啊!) 
      //现在/发送我们的数据包,因为我们已经填补了它:
    sendPacketIfNecessary();
  } else{
      //需要向包中打入数据。 
    // Use this frame in our outgoing packet:
    unsignedchar*frameStart=fOutBuf->curPtr(); //获取新数据存放开始的位置
    fOutBuf->increment(numFrameBytesToUse);
        // do this now, in case"doSpecialFrameHandling()" calls "setFramePadding()" toappend 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 packetshould 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 thisor
    //(iv) one frame per packet is allowed:
    //如果(我)它已经在我们的首选大小,或发送我们的数据包
     //(二)(启发式)另一个帧相同大小的一个我们只是读会溢出数据包,或
      //(III)它包含一个支离破碎的框架的最后一个片段,我们
      //不允许任何其他东西遵循这个或
      //每包一个帧是允许的:
    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 gettinganother: 有更多的帧的空间,尝试得到另一个:
      packFrame();
    }
  }
}

从上面函数可以看到该函数主要实现了检查该帧数据是否有资格打包,保存下一个帧,计算有多少数据可以打包等,如果数据包已经准备好了就调用sendPacketIfNecessary()函数进行数据的发送,否则调用packFrame()函数再次进行判断,接下来主要介绍发送数据函数sendPacketIfNecessary(),主要实现如下:

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())) {   //在RTPInterface里
    // 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 序列号加1
  } //如果还有剩余数据,则调整缓冲区
  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:
      //包开始指针在溢出数据前(允许RTP报头和特殊的头),所以,我们可能不需要“memmove()“溢出数据当建立的下一个数据包时:
    unsignednewPacketStart=fOutBuf->curPacketSize()
      - (rtpHeaderSize+ fSpecialHeaderSize + frameSpecificHeaderSize());
    fOutBuf->adjustPacketStart(newPacketStart);
  } else{
    // Normal case: Reset the packet startpointer back to the start:
      //正常情况:重置数据包开始指针回到开始:
    fOutBuf->resetPacketStart();
  }
  fOutBuf->resetOffset();
  fNumFramesUsedSoFar=0;
 
  if (fNoFramesLeft) {    //如果再没有数据了,则结束之 
    //We're done:
    onSourceClosure();
  } else{
      //如果还有数据,则在下一次需要发送的时间再次打包发送。
    //We have more frames left to send.  Figureout when the next frame
    //is due to start playing, then make sure that we wait this long before
    //sending the next packet.
      //我们有更多的帧发送。确定下一个帧是何时开始播放的,然后请确保我们在发送下一个数据包之前先等上一段时间。
    structtimeval 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) {// sanitycheck: 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);
  }
}

sendPacketIfNecessary()该函数主要是发送数据,主要有确定下一个帧数据何时发送,调用延迟程序,重置数据包开始指针等。

以上情况均属于上一个包中有数据剩余,接着回到packFrame()函数,如果没有数据,我们将调用getNextFrame()函数获得帧数据

fSource->getNextFrame(fOutBuf->curPtr(),fOutBuf->totalBytesAvailable(),//fOutBuf输出包缓冲   getNextFrame在FrameSource类里面afterGettingFrame,this, ourHandleClosure,this);
getNextFrame()函数是在FrameSource类里面实现,主要是获取帧数据,该函数如下:
void FramedSource::getNextFrame(unsignedchar*to,unsignedmaxSize,afterGettingFunc*afterGettingFunc,void*afterGettingClientData,onCloseFunc*onCloseFunc,void*onCloseClientData){
  // Make sure we're notalready being read   确保我们没有被读
  if (fIsCurrentlyAwaitingData) {
    envir()<< "FramedSource[" <<this <<"]::getNextFrame(): attempting to read more thanonce at the same time!\n";
    envir().internalError();
  }
  fTo= to;//存放读取到数据的内存指针
  fMaxSize= maxSize;//允许的最大数据量,即fto指针指向的内存区间的大小
  fNumTruncatedBytes=0;// bydefault; could be changed by doGetNextFrame()
  fDurationInMicroseconds=0;// by default; could be changed by doGetNextFrame()
  fAfterGettingFunc=afterGettingFunc;
  fAfterGettingClientData=afterGettingClientData;
  fOnCloseFunc= onCloseFunc;
  fOnCloseClientData=onCloseClientData;
  fIsCurrentlyAwaitingData=True;
  doGetNextFrame();//从文件中获取数据,只是获取数据,在ByteStreamFileSource中
}

从上面可以看到其实该函数并没有做什么很多事,主要是对参数进行了一些设置,接着调用doGetNextFrame()函数,但是该函数却没有在该类中进行实现,而是在继承类ByteStreamFileSource中进行实现,函数如下:

void ByteStreamFileSource::doGetNextFrame(){//判断是否已经到文件尾部,feof(fp)有两个返回值:如果遇到文件结束,函数feof(fp)的值为非零值,否则为0。
  if (feof(fFid)||ferror(fFid) || (fLimitNumBytesToStream&&fNumBytesToStream== 0)) {
    handleClosure();
    return;
  }
#ifdef READ_FROM_FILES_SYNCHRONOUSLY
  doReadFromFile();
#else
  if (!fHaveStartedReading) {
    // Await readable data from the file:
    envir().taskScheduler().turnOnBackgroundReadHandling(fileno(fFid),           (TaskScheduler::BackgroundHandlerProc*)&fileReadableHandler,this);
    fHaveStartedReading= True;
  }
#endif
}

doGetNextFrame()函数可以看到主要是对ts文件流的一个判断,看是否已经读取完,而真正对文件的读取操作是在doReadFromFile()函数中的。同时会调用任务调度进行处理。所以这就是testMPEG2TransportStreamer服务器端传输ts流的总体实现过程。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值