视频流传输相关知识

上次整理视频一些知识,这些知识以采集,编码/解码相关的,也引出了H264概念

文章: https://www.cnblogs.com/winafa/p/12768392.html

H264的设计,主要还是网络传输

但网络传输,在我们印象中,基础也就是UDP/TCP之类的,那么视频(数据量又那么大),有没有合适的应用层协议呢?

答案肯定有的,像rtsp rtp都是为了视频传输而来,因为视频数据对实时性要求比较高,因此这些协议本身也是针对实时性进行部分优化

什么是RTP?

RTP,即real-time transport protocol(实时传输协议),为实时传输交互的音频和视频提供了端到端传输服务。其中包括载荷的类型确认,序列编码,时间戳和传输监控功能。

特点:序列(也就是保证前后顺序)、时间戳(便于抉择,检测网络丢包/数据同步之类)、传输监控(意思是断开,重连之类可以恢复)

一般来说RTP协议,都是基于UDP,因此对每包数据都带有报文头信息:

 

视频流传输相关知识_第1张图片

 除了X=1以外,其他情况下,报文头占12字节。

关于RTP格式,这篇文章整理的很详细,具体可以参考下

https://blog.csdn.net/chen495810242/article/details/39207305

1)        V:RTP协议的版本号,占2位,当前协议版本号为2

2)        P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。

3)        X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头

4)        CC:CSRC计数器,占4位,指示CSRC 标识符的个数

5)        M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。

6)        PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等,在流媒体中大部分是用来区分音频流和视频流的,这样便于客户端进行解析。

7)        序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别记数的。

8)        时戳(Timestamp):占32位,必须使用90 kHz 时钟频率。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。

9)        同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。

10)    特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。

这里要补充点知识,我们知道视频数据本身是很大的,比如I帧数据,往往都比较大,

而UDP是本身单包长度有限制的(在无线传输的事情,肯定),大概一包可以传输1500字节

因此要对大视频(单帧超过1400字节)流数据进行拆包处理

从RTP的Header来看,还不足以知道该包是什么?包含哪些信息

话说回来,前面也介绍了H264主要分成 I帧/P帧/B帧,这些可以称为裸流,拿来编解码是够了,但传输是不够的。

因此,H264又做了进一步努力,以便更好的传输

什么是NALU?

 NALU(Network Abstract Layer Unit) 可以通俗点理解,就是网络传输单元,也就是H264定义报文格式

NALU结构非常简单,NALU结构:NALU header(1byte) + NALU payload

 header就一个字节,最高位b7=0,如果=1表示错误

             然后b6~b5 表示数据优先级,11 最高,也就是不能丢弃

             最后5位,b4~b0表示数据类型,像PPS/SPS/都这里定义(加粗为重点)

 0:未定义

                                         1:非IDR图像不采用数据划分片段   大名鼎鼎的P帧

                                         2:非IDR图像采用数据划分片段A部分

                                         3:非IDR图像采用数据划分片段B部分

                                         4:非IDR图像采用数据划分片段C部分

                                         5:IDR图像片段。   大名鼎鼎的I帧

                                         6:补充增强信息 SEI

                                         7:序列参数集 SPS,一般跟PPS一起,里面会填视频本身一些基础信息,比如分辨率,解码的时候必须要用到

                                         8:图像参数集 PPS

                                         9:分隔符

                                         10:序列结束符

                                         11:流结束符

                                         12:填充数据

                                         13:序列参数集扩展

                                         14:带前缀的NALU

                                         15:子序列参数集

                                         16-18:保留

                                         19:不采用数据划分的辅助编码图像片段

                                         20:编码片段扩展

                                         21-23:保留

                                         24-31:未定义

另外,需要强调的是,H264的开头还有固定 00 00 00 01 四个字节内容(这个基本上是给人看)

实际VCL流应该是这样的

 

NALU这么利用RTP进行传输?

因此,像PPS SPS很小的帧,也不会超过1400字节,也就是一包就够了

但像I帧这样,比较大的数据帧,一般来说都是超过1400的,那么要进行拆包

这里要插入一点知识,前面说到H264前面有 00 00 00 01 4个字节内容,且没任何含义,因此,用RTP传输的时候,

要先去掉 00 00 00 01 四个固定字节内容,接收端收到之后,需要添加上去。这个也很重要,否则H264解码会挂掉的

单包就能够搞定的:

      直接将NALU打包成RTP包进行传输    RTP header(12bytes) + NALU header (1byte) + NALU payload(实际数据)

需要多包:

   RTP header (12bytes)+ FU Indicator (1byte)  +  FU header(1 byte) + NALU payload

会有疑问:怎么区分 NALU header  还是  FU Indicator?

同样1个字节,是这样的:NALU header 只用 0~20,像21~31还是预留的,可以拿来用

 

视频流传输相关知识_第2张图片

  FU header 的格式如下:
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+
   S: 1 bit
   当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。(分包用)
   E: 1 bit
   当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。(分包用)
   R: 1 bit
   保留位必须设置为0,接收者必须忽略该位。
   Type: 5 bits    (H264里的什么PPS SPS I帧,都在这里定义) 从这里可以知道这是什么帧

贴一段,据说来自live555的源码:

Boolean H264VideoRTPSource
::processSpecialHeader(BufferedPacket* packet,
                       unsigned& resultSpecialHeaderSize) {
  unsigned char* headerStart = packet->data();
  unsigned packetSize = packet->dataSize();
  unsigned numBytesToSkip;
  
  // Check the 'nal_unit_type' for special 'aggregation' or 'fragmentation' packets:
  if (packetSize < 1) return False;
  fCurPacketNALUnitType = (headerStart[0]&0x1F);   //FU Indicator后五位即NALU类型 0x1F = 0001 1111
  switch (fCurPacketNALUnitType) {
  case 24: { // STAP-A
    numBytesToSkip = 1; // discard the type byte
    break;
  }
  case 25: case 26: case 27: { // STAP-B, MTAP16, or MTAP24
    numBytesToSkip = 3; // discard the type byte, and the initial DON
    break;
  }
  case 28: case 29: { // // FU-A or FU-B
    // For these NALUs, the first two bytes are the FU indicator and the FU header.
    // If the start bit is set, we reconstruct the original NAL header into byte 1:
    if (packetSize < 2) return False;
    unsigned char startBit = headerStart[1]&0x80;    //FU Header start标记位  0x80= 1000 0000
    unsigned char endBit = headerStart[1]&0x40;      //FU Header End标记位    0x40= 0100 0000
    if (startBit) {
      fCurrentPacketBeginsFrame = True;

      headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);  //还原NALU头
      numBytesToSkip = 1;
    } else {
      // The start bit is not set, so we skip both the FU indicator and header:
      fCurrentPacketBeginsFrame = False;
      numBytesToSkip = 2;
    }
    fCurrentPacketCompletesFrame = (endBit != 0);
    break;
  }
  default: {
    // This packet contains one complete NAL unit:
    fCurrentPacketBeginsFrame = fCurrentPacketCompletesFrame = True;   //默认没有分片,完整的NALU
    numBytesToSkip = 0;
    break;
  }
  }

  resultSpecialHeaderSize = numBytesToSkip;
  return True;
}


   以上内容,部分参考

https://www.cnblogs.com/leehm/p/11009504.html

live555是什么?

 live555是一个开源框架,支持rtp等协议。实际上,你可以理解为rtp协议实现代码。

当然live555也不止rtp协议

实际上要写好传输代码,也不容易,虽然有live555这样开源背书了,但还是比较困难的

以上知识是基础知识,实际经验是无法语言描述出来的

代码还应考虑很多:比如缓冲区,时序控制,断开之后重连,握手,等等很多了,把这些都实现了,也需要很久很久

你可能感兴趣的:(C++,Linux,网络,java,机器学习,人工智能,python)