视频流传输相关知识

22 篇文章 0 订阅

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

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

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

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

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

什么是RTP?

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

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

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

 

 除了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还是预留的,可以拿来用

 

  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这样开源背书了,但还是比较困难的

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

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebRTC 是一个用于实时通信的开放性项目,它可以在浏览器之间传输音频、视频和数据流。要将视频流传输到后端,你可以通过以下步骤进行操作: 1. 在前端使用 WebRTC API 获取视频流:使用 getUserMedia() 方法从用户的摄像头获取视频流。这将返回一个 MediaStream 对象,其中包含视频轨道。 2. 将视频流发送到后端:可以使用 WebRTC 的 RTCPeerConnection API 创建一个连接对象,并通过该连接将视频流发送到后端。使用 addTrack() 方法将视频轨道添加到连接对象中,并通过 createOffer() 方法生成一个 SDP(会话描述协议)。 3. 将 SDP 发送到后端:将生成的 SDP 发送到后端服务器。可以使用 WebSocket、XHR 或其他适合的通信方式将 SDP 发送给后端。 4. 在后端接收和处理视频流:后端服务器接收到 SDP 后,可以使用 WebRTC 的 RTCPeerConnection API 创建一个连接对象,并通过 setRemoteDescription() 方法设置接收到的 SDP。然后,可以使用 createAnswer() 方法生成一个应答 SDP。 5. 将应答 SDP 发送回前端:将生成的应答 SDP 发送回前端,以便建立双向通信。后端可以使用适当的通信方式将应答 SDP 发送给前端。 6. 在前端接收和处理应答 SDP:前端接收到应答 SDP 后,使用 setRemoteDescription() 方法设置接收到的应答 SDP。 7. 建立视频通信:在前端和后端都设置好 SDP 后,使用 RTCPeerConnection 的 onicecandidate 事件监听 ICE 候选者,并通过 addIceCandidate() 方法将候选者发送到对方。最终建立起视频通信。 这是一个基本的步骤示例,具体实现可能会根据你使用的技术栈和框架有所差异。使用 WebRTC 进行实时通信需要一定的技术知识和经验,建议参考相关的文档和示例代码来了解更多细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值