live555源码分析----mpg文件的处理(续)

    前一篇文章对mpg文件处理的分析中,有个一个比较严重的错误,因为有些重要的细节没有注意到。mpg文件是音视频交错排列的,如果需要读取的是视频数据,但当前文件位置却是音频数据该怎么办?前面分析时说,将把遇到的音频数据保存到缓存中,直到读取到视频数据,live555中并非如此处理的。


先简单的说明一下mpg处理过程涉及的几个类的作用
MPEG1or2Demux, 对应一个session,完成文件的解复用操作。其读取文件的过程是通过ByteStreamFileSource实现的
MPEG1or2DemuxedElementaryStream,作为source,对应一个流,一个或多个实例对应一个MPEG1or2Demux
MPEG1or2DemuxedServerMediaSubsession,作为subsession
MPEG1or2FileServerDemux, 一个服务类,整个程序只有一个实例,MPEG1or2Demux、MPEG1or2DemuxedElementaryStream、MPEG1or2DemuxedServerMediaSubsession三个类将通过本类进行联系。完成的工作如下:
1)创建subsession实例(MPEG1or2DemuxedServerMediaSubsession);
2)为每一个session创建MPEG1or2Demux实例,用于解复用。
3)创建source实例(MPEG1or2DemuxedElementaryStream), subsession中


再来看一个重要的结构体OutputDescriptor,它被定义在MPEG1or2Demux类中,文件中的每一个流对应一个OutputDescriptor实例

  // A descriptor for each possible stream id tag:
  typedef struct OutputDescriptor {
    // input parameters
    unsigned char* to; unsigned maxSize;
    FramedSource::afterGettingFunc* fAfterGettingFunc;
    void* afterGettingClientData;
    FramedSource::onCloseFunc* fOnCloseFunc;
    void* onCloseClientData;


    // output parameters
    unsigned frameSize; struct timeval presentationTime;
    class SavedData; // forward
    SavedData* savedDataHead;
    SavedData* savedDataTail;
    unsigned savedDataTotalSize;


    // status parameters
    Boolean isPotentiallyReadable;
    Boolean isCurrentlyActive;
    Boolean isCurrentlyAwaitingData;
  } OutputDescriptor_t;
  OutputDescriptor_t fOutput[256];


  
需要注意以下3个成员:
isPotentiallyReadable,对应的流存在,即存在对应流的MPEG1or2DemuxedElementaryStream实例
isCurrentlyActive,对应的流是活动的,当从第一次从这个流中读取数据时就置为True, 停止读取后置为False
isCurrentlyAwaitingData,当前正需要从对应的流中读取数据。从文件中读取数据并分析后,通过此标志判断读取到的数据是否是当前需要的流数据,如果不是则需要数据存在到缓存中


我们来看MPEG1or2Demux::getNextFrame函数

void MPEG1or2Demux::getNextFrame(u_int8_t streamIdTag,
				 unsigned char* to, unsigned maxSize,
				 FramedSource::afterGettingFunc* afterGettingFunc,
				 void* afterGettingClientData,
				 FramedSource::onCloseFunc* onCloseFunc,
				 void* onCloseClientData) {
  // First, check whether we have saved data for this stream id:
  //从缓存中读取数据
  if (useSavedData(streamIdTag, to, maxSize,
		   afterGettingFunc, afterGettingClientData)) {
    return;
  }


  // Then save the parameters of the specified stream id:
  // 将信息保存到流对应的OutputDescriptor_t实例中
  registerReadInterest(streamIdTag, to, maxSize,
		       afterGettingFunc, afterGettingClientData,
		       onCloseFunc, onCloseClientData);


  // Next, if we're the only currently pending read, continue looking for data:
  // 进一步处理,将从文件中读取数据
  if (fNumPendingReads == 1 || fHaveUndeliveredData) {
    fHaveUndeliveredData = 0;
    continueReadProcessing();
  } // otherwise the continued read processing has already been taken care of
}



useSavedData函数从缓存中读取数据,没什么好说的。简单看一下registerReadInterest函数

void MPEG1or2Demux::registerReadInterest(u_int8_t streamIdTag,
				     unsigned char* to, unsigned maxSize,
				     FramedSource::afterGettingFunc* afterGettingFunc,
				     void* afterGettingClientData,
				     FramedSource::onCloseFunc* onCloseFunc,
				     void* onCloseClientData) {
  struct OutputDescriptor& out = fOutput[streamIdTag];


  // Make sure this stream is not already being read:
  if (out.isCurrentlyAwaitingData) {
    envir() << "MPEG1or2Demux::registerReadInterest(): attempt to read stream id "
	    << (void*)streamIdTag << " more than once!\n";
    envir().internalError();
  }


  out.to = to; out.maxSize = maxSize;
  out.fAfterGettingFunc = afterGettingFunc;
  out.afterGettingClientData = afterGettingClientData;
  out.fOnCloseFunc = onCloseFunc;
  out.onCloseClientData = onCloseClientData;
  out.isCurrentlyActive = True;  //当前流处理活动中
  out.isCurrentlyAwaitingData = True;  //当前流需要读取数据
  // out.frameSize and out.presentationTime will be set when a frame's read


  ++fNumPendingReads;  //等待读取数据的流计数
}




registerReadInterest函数中,最后三条语句应注意。isCurrentlyAwaitingData为True表示对应流需要读取数据,fNumPendingReads表示等读取数据的流计数。


现在再来看MPEG1or2Demux::getNextFrame函数中后面的if条件语句
  if (fNumPendingReads == 1 || fHaveUndeliveredData) {
    fHaveUndeliveredData = 0;
    continueReadProcessing();
  }


   条件一fNumPendingReads == 1,fNumPendingReads表示未读取到数据的流的个数。为什么同时读取数据的流会超过一个呢?对于mpg文件来说,需要读取音频时当前文件位置可能正好是一个视频包,这时无法读取到所需要的数据,就会递增fNumPendingReads变量。条件二fHaveUndeliveredData,表示前面的处理过程中,某一个流未能如愿读取到需要的数据。


   上面的两个条件,我觉得有点问题,fNumPendingReads = 0是不可能的, fNumPendingReads>1时,表示前面的处理过程中,某一个流未能如愿读取到需要的数据,这时fHaveUndeliveredData就应该为True,这样的话if语句是不是永远为真呢?这个疑问有待确定。


继续来看MPEG1or2Demux::continueReadProcessing函数的实现

void MPEG1or2Demux::continueReadProcessing() {
  while (fNumPendingReads > 0) {
    unsigned char acquiredStreamIdTag = fParser->parse();


    if (acquiredStreamIdTag != 0) {
      // We were able to acquire a frame from the input.
      struct OutputDescriptor& newOut = fOutput[acquiredStreamIdTag];
      newOut.isCurrentlyAwaitingData = False;
        // indicates that we can be read again
        // (This needs to be set before the 'after getting' call below,
        //  in case it tries to read another frame)


      // Call our own 'after getting' function.  Because we're not a 'leaf'
      // source, we can call this directly, without risking infinite recursion.
      if (newOut.fAfterGettingFunc != NULL) {
	(*newOut.fAfterGettingFunc)(newOut.afterGettingClientData,
				    newOut.frameSize, 0 /* numTruncatedBytes */,
				    newOut.presentationTime,
				    0 /* durationInMicroseconds ?????#####*/);
      --fNumPendingReads;
      }
    } else {
      // We were unable to parse a complete frame from the input, because:
      // - we had to read more data from the source stream, or
      // - we found a frame for a stream that was being read, but whose
      //   reader is not ready to get the frame right now, or
      // - the source stream has ended.
      break;
    }
  }
}




    这个函数中有一个while循环,fParser->parse()返回当前读取到的数据的流标号,第一次循环过程的返回值必定是当前所需要的流数据,第二次循环返回值则是先前未读取到的流数据,当所有需要读取数据的流均已经读取数据后,fNumPendingReads值递减为0,循环结束。


再来看MPEGProgramStreamParser::parse函数
unsigned char MPEGProgramStreamParser::parse() {
  unsigned char acquiredStreamTagId = 0;


  try {
    do {
      switch (fCurrentParseState) {
      case PARSING_PACK_HEADER: {
	parsePackHeader();
	break;
      }
      case PARSING_SYSTEM_HEADER: {
	parseSystemHeader();
	break;
      }
      case PARSING_PES_PACKET: {
	acquiredStreamTagId = parsePESPacket();
	break;
      }
      }
    } while(acquiredStreamTagId == 0);


    return acquiredStreamTagId;
  } catch (int /*e*/) {
#ifdef DEBUG
    fprintf(stderr, "MPEGProgramStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n");
    fflush(stderr);
#endif
    return 0;  // the parsing got interrupted
  }
}




上面的函数中,最有catch语句,所以前面的函数调用必定会抛出异常。parsePESPacket函数返回流标识作为循环条件,所以这个函数最重要
unsigned char MPEGProgramStreamParser::parsePESPacket() {


    ...


    // Check whether our using source is interested in this stream type.
    // If so, deliver the frame to him:
    MPEG1or2Demux::OutputDescriptor_t& out = fUsingDemux->fOutput[stream_id];
    if (out.isCurrentlyAwaitingData) {      //当前流正在等待数据
      unsigned numBytesToCopy;
      if (PES_packet_length > out.maxSize) {
	fUsingDemux->envir() << "MPEGProgramStreamParser::parsePESPacket() error: PES_packet_length ("
			      << PES_packet_length
			      << ") exceeds max frame size asked for ("
			      << out.maxSize << ")\n";
	numBytesToCopy = out.maxSize;
      } else {
	numBytesToCopy = PES_packet_length;
      }


      getBytes(out.to, numBytesToCopy);
      out.frameSize = numBytesToCopy;


      // set out.presentationTime later #####
      acquiredStreamIdTag = stream_id;
      PES_packet_length -= numBytesToCopy;
    } else if (out.isCurrentlyActive) {     //当前流是活动的
      // Someone has been reading this stream, but isn't right now.
      // We can't deliver this frame until he asks for it, so punt for now.
      // The next time he asks for a frame, he'll get it.


      restoreSavedParserState(); // so we read from the beginning next time
      fUsingDemux->fHaveUndeliveredData = True;     //有未投递的数据
      throw READER_NOT_READY;  //直接抛出异常,
    } else if (out.isPotentiallyReadable &&  //当前流需要读取数据
	       out.savedDataTotalSize + PES_packet_length < 1000000 /*limit*/) {    
      // Someone is interested in this stream, but hasn't begun reading it yet.
      // Save this data, so that the reader will get it when he later asks for it.
      //
      // 从文件中读取的数据,当前并不需要,所以就把它保存到存在中
      //
      unsigned char* buf = new unsigned char[PES_packet_length];
      getBytes(buf, PES_packet_length);
      MPEG1or2Demux::OutputDescriptor::SavedData* savedData
	= new MPEG1or2Demux::OutputDescriptor::SavedData(buf, PES_packet_length);
      if (out.savedDataHead == NULL) {
	out.savedDataHead = out.savedDataTail = savedData;
      } else {
	out.savedDataTail->next = savedData;
	out.savedDataTail = savedData;
      }
      out.savedDataTotalSize += PES_packet_length;
      PES_packet_length = 0;
    }
    skipBytes(PES_packet_length);
  }


  // Check for another PES Packet next:
  setParseState(PARSING_PES_PACKET);


 return acquiredStreamIdTag;
}

我来看上面有中文注释的if结构,
第一个条件out.isCurrentlyAwaitingData为True时,表明读取到的数据正是需要读取的。这应该是正常的情况
第二个条件out.isCurrentlyActive为True时,这个条件是在表明对应的流是活动的,但当前并没有要求读取数据。这个条件只要流已经开始播放,在其停止之前都为True。这个条件处理中,将对应流的fHaveUndeliveredData标记为True,最后抛出了异常,这个异常在MPEGProgramStreamParser::parse中处理的
第三个条件out.isPotentiallyReadable为True时,保存数据到缓存中。isPotentiallyReadable是在MPEG1or2DemuxedElementaryStream类构造函数中就置为True的。


根据上面的分析,当流正在播放时,无论如何都不会将数据保存到缓冲中,只有在流的播放停止的情况下才会进行。


从上面的分析中,我们还可以发现其它一些东西,这里指明一下。当未能读取到所以的流数据时,就不会调用after函数进行处理,但是after函数中完成了一个重要的功能,每一个after调用最后都会将下一次数据处理的任务注册到任务管理器中。那不调用after函数流的处理是不是会中断呢?当然不会。例如,当前处理过程没能读取到需要的视频数据,但在下一次处理音频数据时,会把视频数据也读取了,并调用视频处理的after函数,这就是MPEG1or2Demux::continueReadProcessing() 函数中循环的作用。


一个需要进一步思考的问题:
1).按上述方式,当前需要处理的数据会推迟(直到其它流读取完数据),这样是不是会导致延时的时间比正常情况下要久,从而使得发送变慢呢?
2).out.isCurrentlyActive为True时,保存数据到缓存中,这样是不是更好些呢?





你可能感兴趣的:(live555源码分析----mpg文件的处理(续))