live555 rtsp server数据流通路

深入探讨Live555库中H264编码视频数据的读取与传输流程,从文件读取到网络发送,详细分析各组件功能与交互,包括StreamParser、MPEGVideoStreamFramer、H264or5Fragmenter等关键类的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

live555 数据是怎么读取传输的,下面一起来看看。

live 的发送过程

以 H264 编码格式发送为例,主要操作流程如下:

  1. H264or5VideoRTPSink::continuePlaying() 在该函数中将开始数据的传输。
    先创建 H264or5Fragmenter 对象,它将读取的数据按照rtsp协议分段发送出去,而后将 H264or5Fragmenter 赋值到 fSource。
  2. MultiFramedRTPSink::continuePlaying(),在该函数中,调用 buildAndSendPacket(True),最终调用到 MultiFramedRTPSink::packFrame(),而后通过 fSource->getNextFrame() 调用到 FramedSource::getNextFrame() 函数,从而调用到虚函数 doGetNextFrame()。
  3. 由于 fSource 赋值为 H264or5Fragmenter,所以将会通过 FramedSource::getNextFrame() 调用到 H264or5Fragmenter::doGetNextFrame() 函数。而是第一次调用该函数,fNumValidDataBytes变量在构造函数中通过reset() 函数复位置 1,所以将调用 fInputSource->getNextFrame()。
  4. 上面的 fInputSource 赋值为什么呢?
    在运行RTSP server前,将会通过 addServerMediaSession() 添加流信息到server,而一般流信息中都将从 FramedFilter 类继承下来的。H264 的是 H264VideoStreamFramer 类,所以上面的 fInputSource 赋值为 H264VideoStreamFramer 对象,通过 FramedSource::getNextFrame() 在fInputSource->getNextFrame()这里,将会调用到 H264VideoStreamFramer 的父类 (H264or5VideoStreamFramer —> MPEGVideoStreamFramer —> FramedFilter) MPEGVideoStreamFramer 中的 doGetNextFrame() 函数。
  5. MPEGVideoStreamFramer::doGetNextFrame()函数,该函数主要是调用 MPEGVideoStreamFramer::continueReadProcessing() 函数完成一些操作。
    在 MPEGVideoStreamFramer::continueReadProcessing() 函数中,将会通过 fParser->parse() 读取数据,由于 parse() 是虚函数,H264格式将会调用到 H264or5VideoStreamParser::parse() 函数。在该函数中,将会通过以下类似的接口读取数据(test4Bytes() —> ensureValidBytes() —> ensureValidBytes1() —> fInputSource->getNextFrame())。这里的 fInputSource->getNextFrame()。这里的 fInputSource 是在创建 H264or5VideoStreamFramer 时,根据传递下来 inputSource 创建了 H264or5VideoStreamParser 实例对象保存在 fParser。所以这里将会调用到创建 H264VideoStreamFramer 对象时传递的 inputsource 读取数据。(H264VideoStreamFramer 对象在注册rtsp server相关的 ServerMediaSession 时的 addSubsession 对象的 createNewStreamSource() 函数创建)
  6. 按照上述说的,最终将会在 ensureValidBytes1() 函数中读取数据,该函数并不是按照上面传递的参数大小读取数据,而是会尽可能多的读取数据,在 StreamParser::ensureValidBytes1() 中,fTotNumValidBytes 代表bank中已经保存了流数据的字节数,fCurParserIndex 代表当前解析流的位置,数据先读取再解析。读取完成之后,将会调用 StreamParser::afterGettingBytes() 函数(该函数主要是 使用了 source->fFrameSize 和 source->fPresentationTime),最后将调用 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 函数。
  7. (H264or5VideoStreamParser —> MPEGVideoStreamParser —> StreamParser) StreamParser 的衍生如上,可以了解到 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 最终会调用 MPEGVideoStreamFramer::continueReadProcessing()。会发现,再次回到了这个函数,由于第一次调用parse() 时,我们没有缓冲数据,所以最终执行 ensureValidBytes1() 函数读取,当前已经获取到数据了,我们将直接解析该数据。
  8. 在 H264or5VideoStreamParser::parse() 函数按照 H264 格式解析数据之后,将返回解析成功的数据大小,MPEGVideoStreamParser.fTo 为记录当前的发送写入数据指针,MPEGVideoStreamParser.fLimit 为缓冲队列总大小。然后返回到 MPEGVideoStreamFramer::continueReadProcessing() 函数。在该函数中,将根据解析的情况,设置 fFrameSize、fNumTruncatedBytes 和 fDurationInMicroseconds,最后调用 afterGetting(this)。所以没什么有时候在其他位置的 afterGetting() 函数确认 fFrameSize 等值变化了,就是在这里变化的,它们需要从流解析之后再调用 afterGetting()。
  9. 前一步的 afterGetting() 经过 FramedSource::afterGetting() 函数之后,将调用到 H264or5Fragmenter::afterGettingFrame(),在该函数中,frameSize 等变量都已经发生改变,最后调用到 H264or5Fragmenter::doGetNextFrame()。只是因为这一次 fNumValidDataBytes = frameSize,所以将会和第一次调用该函数时不一致,不再调用 fInputSource->getNextFrame() 而是进行相应的数据解析。
  10. 继续以发送H264编码格式为例进行介绍,在经过 H264or5VideoStreamParser::parse() 解析之后,第一次将会返回 10 个字节的数据,这些是H264的SPS信息,所以在 H264or5Fragmenter::doGetNextFrame()中,只是将 fInputBuffer数据拷贝到 fTo, 然后调用 FramedSource::afterGetting(this) 函数。这里的 afterGetting(),由于是在 MultiFramedRTPSink::packFrame() 函数中通过 fSource->getNextFrame() 调用的,所以 afterGetting() 将调用到 MultiFramedRTPSink::afterGettingFrame() 。
  11. 在 MultiFramedRTPSink::afterGettingFrame() 中,由于是数据流的最开始部分,没有溢出,也没有之前的帧数据,所以最后只是// Use this frame in our outgoing packet:,fNumFramesUsedSoFar自加。由于 H264 的 H264or5VideoRTPSink::frameCanAppearAfterPacketStart() 返回的是 false,所以将会调用 sendPacketIfNecessary() 发送数据。此时,数据将发送出去,最开始的 H264or5VideoRTPSink::continuePlaying() 执行完毕。
  12. 那么,上述只是发送了一次数据而已,后续又是怎样进行相应的数据读取呢,我们先来看看 MultiFramedRTPSink::sendPacketIfNecessary() 的具体实现。在该函数中通过调用 fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()) 完成数据的发送,最后又会调用 nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this)。延时调用 sendNext() 函数,而 sendNext() 函数就是简单的调用了 sink->buildAndSendPacket(False)。从这里就可以看到,又通过 buildAndSendPacket() 函数打包封装数据进行下一次发送。

第二次调用 sink->buildAndSendPacket(False) 函数调用流程(下一个rtp包数据):

  1. packFrame() —> 由于第一次发送包时,只是解析SPS信息,所以前一个包并没有溢出,所以可以继续调用 fSource->getNextFrame() (fSource 为 H264or5Fragmenter 对象) —> H264or5Fragmenter::doGetNextFrame();
  2. 又回到 H264or5Fragmenter::doGetNextFrame() 函数,由于第一次是的时候,在 H264or5Fragmenter::doGetNextFrame() 中进行了数据的解析,当时 fCurDataOffset == 1 且 fNumValidDataBytes - 1 <= fMaxSize,所以最后 fNumValidDataBytes = fCurDataOffset = 1,所以这次继续还是 fNumValidDataBytes == 1,将调用 fInputSource->getNextFrame() (fInputSource 为 MPEGVideoStreamFramer 对象) —> MPEGVideoStreamFramer::doGetNextFrame()。
  3. 在 MPEGVideoStreamFramer::doGetNextFrame() 函数中最终又是经过parse后(由于第一次已经读取了很多数据,而第一次解析没有解析完,所以这次没有真正的从文件或者其他方式获取数据而是继续解析之前残留的),将解析完成的数据通过 afterGetting(this) 返回,将调用到 H264or5Fragmenter::afterGettingFrame()。
  4. 在 H264or5Fragmenter::afterGettingFrame() 函数中,还是按照第一次的方式进行解析,然后通过 afterGetting(this) 返回,将调用到 MultiFramedRTPSink::afterGettingFrame(),直到最后又完成了一次 buildAndSendPacket()。

live发送过程,数据是怎么传递的?

  1. 数据是怎么来的?
    从最开始的文件读取开始,在 ByteStreamFileSource::doReadFromFile() 函数中,将会通过 read() 函数从磁盘中读取到数据保存在 fTo 指针指向的内存,而读取的大小为 fFrameSize,最大可读取大小为 fMaxSize,实际上fMaxSize可为 300000 个字节,下一步将会知道该值的大小。同时,将会在这里获取得到一个时间戳。
  2. 保存数据的内存是哪里来的?
    在上面我们已经知道,是在 StreamParser::ensureValidBytes1() 中调用相应输入源的 getNextFrame() 函数,所以,在 ByteStreamFileSource::doReadFromFile() 中的参数都是这里赋值的。
    来看看 StreamParser 类是怎么管理数据的。
class StreamParser {
  /* 在创建StreamParser对象时,将会创建两个 BANK_SIZE 大小的内存空间,
   * 并将地址保存在指针数组fBank,BANK_SIZE 默认大小为300000。
   */
  // Use a pair of 'banks', and swap between them as they fill up:
  unsigned char* fBank[2];
  unsigned char fCurBankNum;
  unsigned char* fCurBank;

  /* fSavedParserIndex 用于保存 fCurParserIndex 的值 */
  // The most recent 'saved' parse position:
  unsigned fSavedParserIndex; // <= fCurParserIndex
  unsigned char fSavedRemainingUnparsedBits;

  /* fCurParserIndex 指向已经读取到的数据被解析的数组内容索引 */
  // The current position of the parser within the current bank:
  unsigned fCurParserIndex; // <= fTotNumValidBytes
  unsigned char fRemainingUnparsedBits; // in previous byte: [0,7]

  /* fTotNumValidBytes 指向fBank已写的数组索引 */
  // The total number of valid bytes stored in the current bank:
  unsigned fTotNumValidBytes; // <= BANK_SIZE

  // Whether we have seen EOF on the input source:
  Boolean fHaveSeenEOF;

  struct timeval fLastSeenPresentationTime; // hack used for EOF handling
};

在 StreamParser::ensureValidBytes1() 函数中,将尽可能多的读取流数据。当fCurBank 指向的内存空间不能保存 numBytesNeeded 大小的内容时,将会将把 fSavedParserIndex —> fTotNumValidBytes 处的数据拷贝到新的bank数组,然后再使用该bank进行内容读取(为什么是 fSavedParserIndex 而不是 fCurParserIndex,因为当前虽然已经解析到 fCurParserIndex,但是真正确认的只是到 fSavedParserIndex,所以是从 fSavedParserIndex 开始,拷贝之后再更新相应变量的值)。读取到数据之后,将会通过 fClientContinueFunc(fClientContinueClientData, ptr, numBytesRead, presentationTime) 返回上层,此时将调用到 MPEGVideoStreamFramer::continueReadProcessing(),注意,这里初始在 fClientContinueFunc() 中传递下来的函数参数并没有使用。

  1. MPEGVideoStreamFramer::continueReadProcessing() 又是怎么处理的?
    在该函数中主要是在 H264or5VideoStreamParser::parse() 函数解析数据流,这些数据流就是 StreamParser::ensureValidBytes1() 读取的。H264or5VideoStreamParser::parse() 主要进行以下操作:
  • 寻找H264码流每帧的开始码(0x00000001),将会一直寻找,直到找到了才会往下走。
  • 寻找H264码流的NALUnit单元,根据不同的 NAL 类型进行相应的拷贝(VPS—>fLastSeenVPS、SPS—>fLastSeenSPS、PPS—>fLastSeenPPS)
  1. StreamParser 对象中的bank数据是怎么传递上去呢?
    首先我们看看 MPEGVideoStreamParser 类。
class MPEGVideoStreamParser: public StreamParser {
  /* 在MPEGVideoStreamFramer::doGetNextFrame()中,通过 registerReadInterest()
   * 把FramedSource->fTo 赋值給MPEGVideoStreamParser的以下变量,需要注意的是,
   * 这里的 FramedSource->fTo 是在 H264or5Fragmenter::doGetNextFrame() 中通过
   * getNextFrame() 函数赋值的,所以 fTo = H264or5Fragmenter->fInputBuffer。
   * 在 MPEGVideoStreamFramer::doGetNextFrame() 中,先 registerReadInterest()
   * 再有相应的parse()
   */
  /* fStartOfFrame 每帧的开始位置,在registerReadInterest() 赋值后就不变化 */
  unsigned char* fStartOfFrame;
  unsigned char* fTo;
  unsigned char* fLimit;
  unsigned fNumTruncatedBytes;
}

数据拷贝到哪里我们已经知道了,然后又是在哪里拷贝的呢,我们看看parse函数。

unsigned H264or5VideoStreamParser::parse() {
  /* 寻找H264码流的开始码0x00000001 */
  ...

  /* 获取NAL类型 */
      u_int32_t next4Bytes = test4Bytes();
      if (!fHaveSeenFirstByteOfNALUnit) {
	fFirstByteOfNALUnit = next4Bytes>>24;
	fHaveSeenFirstByteOfNALUnit = True;
      }
    /* 这里就从 bank 数据通过 test4Bytes() 函数读取出来,然后再判断,
     * 是否是开始码(0x00000001)或者NAL码(0x000001),如果不是,那么就
     * 是编码数据,将保存到fTo
     */
      while (next4Bytes != 0x00000001 && (next4Bytes&0xFFFFFF00) != 0x00000100) {
	// We save at least some of "next4Bytes".
    /* 这里做一个判断,如果这四个字节中,最低字节是大于1的,那么,这4个字节都是编码数据,直接保存 */
	if ((unsigned)(next4Bytes&0xFF) > 1) {
	  // Common case: 0x00000001 or 0x000001 definitely doesn't begin anywhere in "next4Bytes", so we save all of it:
	  save4Bytes(next4Bytes);
	  skipBytes(4);
	} else { /* 否则的话,只是保存高字节:因为最低字节是小于或这等于1的,数据可能是这种类型0x80000001,
              * 也就是可能保存NAL码,但是可以确保的是,高字节一定是编码数据,所以这里就只保存高字节
              */
	  // Save the first byte, and continue testing the rest:
	  saveByte(next4Bytes>>24);
	  skipBytes(1);
	}
	setParseState(); // ensures forward progress
	next4Bytes = test4Bytes();
      }
}

在上述的那个while循环时需要注意,在通过 test4Bytes() 读取数据时,很有可能又会读取磁盘数据保存在bank,读完之后,又将是重新进入parse() 函数。

在调用完成parse()之后,数据已经从 StreamParser 的 bank 数组拷贝到 H264or5Fragmenter->fInputBuffer 中,其中,最多可以拷贝 OutPacketBuffer::maxSize 个字节,拷贝了一个NAL单元的数据后,返回到 MPEGVideoStreamFramer::continueReadProcessing() 函数,接着又是 afterGetting(this) 函数的调用,这个时候,将通过 fAfterGettingFunc() 函数指针调用到 H264or5Fragmenter::afterGettingFrame() 函数。

  1. 在 H264or5Fragmenter::afterGettingFrame() 函数中,最终又将调用到 H264or5Fragmenter::doGetNextFrame() 函数。在 H264or5Fragmenter::doGetNextFrame() 函数中,将会根据将要发送的该帧数据之前是否有发送过,以及一个包是否可能填充满等进行相应的数据拷贝,数据将从 H264or5Fragmenter->fInputBuffer 拷贝到 FramedSource->fTo,然后调用 FramedSource::afterGetting(this) 函数。

  2. 在这里,将通过 FramedSource::afterGetting(this) 调用到 MultiFramedRTPSink::afterGettingFrame() 函数,同时,在 H264or5Fragmenter::doGetNextFrame() 中的 FramedSource->fTo 是在 MultiFramedRTPSink::packFrame() 中填充的 fOutBuf->curPtr(),所以,当前数据拷贝到了 fOutBuf->curPtr(),此处最多可以拷贝 RTP_PAYLOAD_MAX_SIZE (1456) 个字节。我们继续分析 MultiFramedRTPSink::afterGettingFrame() 函数。在该函数中,将进行一些解析,判断当前帧是否会溢出等,接着调用 sendPacketIfNecessary() 函数,在这里调用 fRTPInterface.sendPacket(fOutBuf->packet(), fOutBuf->curPacketSize()) 通过socket发送数据,最后延时调用 sendNext() 函数,继续下一次的发送流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chengwei_peng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值