webrtc源码阅读之h264 RTP打包

这篇具有很好参考价值的文章主要介绍了webrtc源码阅读之h264 RTP打包。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文来分析webrtc打包h264 rtp包的代码,版本m98

一、RTP协议

1.1 RTP协议概述

  • 实时传输协议(RTP)是一个网络协议,它允许在网络上进行实时的音频和视频数据传输。RTP协议主要用于解决多媒体数据的实时传输问题,特别是对延迟和数据丢失敏感的应用。
  • RTP协议包括两个紧密相关的部分:RTP数据协议和RTP控制协议(RTCP)。RTP数据协议负责数据的传输,RTCP负责监控服务质量并提供同步和标识信息。
  • RTP协议并不保证数据的可靠传输,因为在实时应用中,比起保证数据的完整性,降低延迟和抖动更为重要。因此,RTP通常运行在不可靠的传输协议(如UDP)之上。

1.2 RTP协议的主要特点

  • 实时性:RTP协议能够处理实时数据的传输,包括对延迟敏感的通信,如视频会议和语音通话。
  • 多播传输:RTP支持多播,可以将数据同时发送给多个接收者。
  • 提供序列号:RTP为每个数据包提供序列号,以便接收者可以根据序列号重新组合数据。
  • 提供时间戳:RTP为每个数据包提供时间戳,以便接收者可以同步播放数据。
  • 提供有效载荷标识:RTP可以识别不同类型的有效载荷(如音频、视频等)。

1.3 RTP协议头结构

webrtc源码阅读之h264 RTP打包,webrtc,webrtc
其中:

  • V(版本号):这是一个2位的字段,用于标识RTP的版本。当前的版本是2。
  • P(填充):如果该位被设置,那么RTP头部的末尾将包含一些填充字节,这些字节不包含在包的长度中。
  • X(扩展):如果该位被设置,那么RTP头部将包含一个扩展头部。头部扩展可以用于存储信息,比如视频旋转角度。
  • CC(CSRC计数):这是一个4位的字段,表示CSRC标识符的数量。
  • M(标记):这是一个1位的字段,由具体的有效载荷格式定义其含义。例如,在音频应用中,如果M位被设置,那么这个包通常包含了一个时间段的开始。
  • PT(有效载荷类型):这是一个7位的字段,用于标识RTP包中的有效载荷数据的类型。
  • 序列号:这是一个16位的字段,用于标识发送者发送的RTP包的序列号。
  • 时间戳:这是一个32位的字段,用于标识数据的采样实例。
  • SSRC(同步源):这是一个32位的字段,用于标识RTP数据包的源。
  • CSRC(贡献源):这是一个可选的字段,用于标识对于复合包或混合音频数据的贡献源,主要用于MCU 混流。
  • Extension header(扩展头): 如果X被设置,RTP头的尾部会包含扩展头信息。

二、RTP打包H.264

2.1 H.264 NALU

H.264将视频数据划分为一系列的网络抽象层单元(NALU)。每个NALU都包含了一部分视频数据,并且具有自己的头部结构。每个NALU由一个NALU Header和RBSP组成。下图为NALU Header。
webrtc源码阅读之h264 RTP打包,webrtc,webrtc

  • F(禁止位):占用1位,用于指示NALU是否被禁止。
  • NRI(重要性指示):占用2位,用于示指NAL的U重要性。较高的重要性值表示NALU包含了重要的视频数据。
  • Type(类型):占用5位,用于指示NALU的类型。不同的类型对应不同的视频数据,如关键帧非、关键帧、SPS(序列参数集)等。常见的h.264naul 类型有:
    0:未使用
    1:非关键帧(P帧)
    2:关键帧(I帧)
    3:SPS(序列参数集)
    4:PPS(图像参数集)
    5:分片层(Slice Layer)
    6:扩展NALU
    7:扩展NALU
    8:扩展NALU
    9-23:保留
    24-31:未使用

2.2 RTP载荷结构

RTP载荷分为三种不同的载荷结构:

  • 单一NALU单元结构(Single NAL Unit Packet):包含一个单一的NALU,此时Type与H.264NALU保持一致。
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    在单一NALU打包的情况下,整个H.264 NALU被打包在一个RTP包中。这种方式适用于NALU的大小不超过RTP包的最大大小限制的情况。

  • 组合封包结构(Aggregation Packet):包含多个NALU。
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    此时Type值应该按下表设置
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    webrtc中组包只支持STAP-A(Single-Time Aggregation Packet)结构。顾名思义,STAP-A是指组合成RTP包的所有NALU共享相同的时间戳。
    下图为两个NALU组合成一个RTP包的完整RTP包结构示例:
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc

  • 分片结构(Fragmentation Unit):当一个H.264 NALU比较大时,为了网络传输,可以将一个NALU拆分到多个RTP中进行传输。webrtc支持FU-A的分片结构,如果所示:
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    FU Indicator结构与H.264 NALU头结构相同,如果所示。其中F、NRI与H.264 NALU一致,Type的值为28\29,分别对应FU-A和FU-B。
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    FU header结构如图所示:
    webrtc源码阅读之h264 RTP打包,webrtc,webrtc
    其中:
    S代表Start bit,如果设置为1,代表该RTP包为FU的第一个包;
    E代表End bit,如果设置为1,代表该RTP包为FU的第最后一个包;
    R代表Reserverd bit,保留位,必须设置为0;
    Type与H.264 NALU头保持一致。

2.3 打包模式

RTP规定的打包模式有三种,分别为单一NALU模式(Single NAL unit mode)、非交替模式(Non-interleaved mode)和交替模式(Interleaved mode)。webrtc支持Single NAL unit mode和Non-interleaved mode。三种打包模式与RTP载荷结构的对应关系如图所示:
webrtc源码阅读之h264 RTP打包,webrtc,webrtc

三、webrtc 打包h264流程

在webrtc源码阅读之视频采集、编码、发送中,我们分析到了RTPSenderVideo::SendVideo,在这个函数中,会调用RtpPacketizer::Create对视频数据进行RTP打包,并通过RtpPacketizerH264::NextPacket将数据转换为真正的RTP包。

  std::unique_ptr<RtpPacketizer> packetizer =
      RtpPacketizer::Create(codec_type, payload, limits, video_header);
  .......
    if (!packetizer->NextPacket(packet.get()))
      return false;

我们以视频编码格式为H.264进行分析。

RtpPacketizerH264::RtpPacketizerH264(rtc::ArrayView<const uint8_t> payload,
                                     PayloadSizeLimits limits,
                                     H264PacketizationMode packetization_mode)
    : limits_(limits), num_packets_left_(0) {
  // Guard against uninitialized memory in packetization_mode.
  RTC_CHECK(packetization_mode == H264PacketizationMode::NonInterleaved ||
            packetization_mode == H264PacketizationMode::SingleNalUnit);

//对H264打包时,需要去除H264码流中的StartCode,并以NALU为单位进行打包
//根据H264码流格式,通过StartCode,区分不同NALU,每个input_fragments_元素为一个H264 NALU
  for (const auto& nalu :
       H264::FindNaluIndices(payload.data(), payload.size())) {
    input_fragments_.push_back(
        payload.subview(nalu.payload_start_offset, nalu.payload_size));
  }

  if (!GeneratePackets(packetization_mode)) { //打包为RTP包
    // If failed to generate all the packets, discard already generated
    // packets in case the caller would ignore return value and still try to
    // call NextPacket().
    num_packets_left_ = 0;
    while (!packets_.empty()) {
      packets_.pop();
    }
  }
}

3.1 解析NALU

std::vector<NaluIndex> FindNaluIndices(const uint8_t* buffer,
                                       size_t buffer_size) {
  //H264的StratCode有两种:0x00 0x00 0x01或 0x00 0x00 0x00 0x01,跟据StartCode来区分不同NALU
  std::vector<NaluIndex> sequences;
  if (buffer_size < kNaluShortStartSequenceSize)
    return sequences;

  static_assert(kNaluShortStartSequenceSize >= 2,
                "kNaluShortStartSequenceSize must be larger or equals to 2");
  const size_t end = buffer_size - kNaluShortStartSequenceSize;
  for (size_t i = 0; i < end;) {
    if (buffer[i + 2] > 1) {
      i += 3;
    } else if (buffer[i + 2] == 1) {
      if (buffer[i + 1] == 0 && buffer[i] == 0) {
        // We found a start sequence, now check if it was a 3 of 4 byte one.
        NaluIndex index = {i, i + 3, 0};
        if (index.start_offset > 0 && buffer[index.start_offset - 1] == 0)
          --index.start_offset;

        // Update length of previous entry.
        auto it = sequences.rbegin();
        if (it != sequences.rend())
          it->payload_size = index.start_offset - it->payload_start_offset;

        sequences.push_back(index);
      }

      i += 3;
    } else {
      ++i;
    }
  }

  // Update length of last entry, if any.
  auto it = sequences.rbegin();
  if (it != sequences.rend())
    it->payload_size = buffer_size - it->payload_start_offset;

  return sequences;
}

H.264原始码流的每个NALU前都会有一个StartCode(起始码)。起始码有两种格式0x00 0x00 0x01或 0x00 0x00 0x00 0x01,按这个准则,找到数据中的起始码,并以起始码的位置分割,就找到了NALU的分界。

3.2 打包单一NALU

bool RtpPacketizerH264::PacketizeSingleNalu(size_t fragment_index) {
  // Add a single NALU to the queue, no aggregation.
  size_t payload_size_left = limits_.max_payload_len;
  if (input_fragments_.size() == 1)
    payload_size_left -= limits_.single_packet_reduction_len;
  else if (fragment_index == 0)
    payload_size_left -= limits_.first_packet_reduction_len;
  else if (fragment_index + 1 == input_fragments_.size())
    payload_size_left -= limits_.last_packet_reduction_len;
  rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index];
  //比较可承载负载大小与NALU大小,若NALU太大则SingleNalu模式不能打包整个NALU
  if (payload_size_left < fragment.size()) {
    RTC_LOG(LS_ERROR) << "Failed to fit a fragment to packet in SingleNalu "
                         "packetization mode. Payload size left "
                      << payload_size_left << ", fragment length "
                      << fragment.size() << ", packet capacity "
                      << limits_.max_payload_len;
    return false;
  }
  RTC_CHECK_GT(fragment.size(), 0u);
  packets_.push(PacketUnit(fragment, true /* first */, true /* last */,
                           false /* aggregated */, fragment[0]));
  ++num_packets_left_;
  return true;
}

只有在强制选择SingleNalUnit模式时才会选择这种打包方式,webrtc一般选择的是Non-interleaved mode。

3.3 打包FU-A

bool RtpPacketizerH264::PacketizeFuA(size_t fragment_index) {
  // Fragment payload into packets (FU-A).
  rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index];

  PayloadSizeLimits limits = limits_;
  // Leave room for the FU-A header.
  limits.max_payload_len -= kFuAHeaderSize;
  // Update single/first/last packet reductions unless it is single/first/last
  // fragment.
  if (input_fragments_.size() != 1) {
    // if this fragment is put into a single packet, it might still be the
    // first or the last packet in the whole sequence of packets.
    if (fragment_index == input_fragments_.size() - 1) { //最后一包
      limits.single_packet_reduction_len = limits_.last_packet_reduction_len;
    } else if (fragment_index == 0) { //第一包
      limits.single_packet_reduction_len = limits_.first_packet_reduction_len;
    } else {
      limits.single_packet_reduction_len = 0;
    }
  }
  if (fragment_index != 0) //非第一包,设置第一包标志为0
    limits.first_packet_reduction_len = 0;
  if (fragment_index != input_fragments_.size() - 1) //非最后一包,设置最后一包标志位0
    limits.last_packet_reduction_len = 0;

  // Strip out the original header.
  size_t payload_left = fragment.size() - kNalHeaderSize;
  int offset = kNalHeaderSize;

  //按FU-A分包的大小,计算每个FU的载荷大小
  std::vector<int> payload_sizes = SplitAboutEqually(payload_left, limits);
  if (payload_sizes.empty())
    return false;

  for (size_t i = 0; i < payload_sizes.size(); ++i) { //根据计算出来的FU包的载荷大小进行分包
    int packet_length = payload_sizes[i];
    RTC_CHECK_GT(packet_length, 0);
    packets_.push(PacketUnit(fragment.subview(offset, packet_length),
                             /*first_fragment=*/i == 0,
                             /*last_fragment=*/i == payload_sizes.size() - 1,
                             false, fragment[0]));
    offset += packet_length;
    payload_left -= packet_length;
  }
  num_packets_left_ += payload_sizes.size();
  RTC_CHECK_EQ(0, payload_left);
  return true;
}

这是webrtc中h264最常见的RTP打包方式。根据FU-A的打包规则进行打包。

3.4 打包STAP-A

size_t RtpPacketizerH264::PacketizeStapA(size_t fragment_index) {
  // Aggregate fragments into one packet (STAP-A).
  size_t payload_size_left = limits_.max_payload_len;
  if (input_fragments_.size() == 1)
    payload_size_left -= limits_.single_packet_reduction_len;
  else if (fragment_index == 0)
    payload_size_left -= limits_.first_packet_reduction_len;
  int aggregated_fragments = 0;
  size_t fragment_headers_length = 0;
  rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index];
  RTC_CHECK_GE(payload_size_left, fragment.size());
  ++num_packets_left_;

  //计算出stap-a中每个nalu所需要的空间
  auto payload_size_needed = [&] {
    size_t fragment_size = fragment.size() + fragment_headers_length;
    if (input_fragments_.size() == 1) {
      // Single fragment, single packet, payload_size_left already adjusted
      // with limits_.single_packet_reduction_len.
      return fragment_size;
    }
    if (fragment_index == input_fragments_.size() - 1) {
      // Last fragment, so STAP-A might be the last packet.
      return fragment_size + limits_.last_packet_reduction_len;
    }
    return fragment_size;
  };

  while (payload_size_left >= payload_size_needed()) {
    RTC_CHECK_GT(fragment.size(), 0);
    packets_.push(PacketUnit(fragment, aggregated_fragments == 0, false, true,
                             fragment[0]));
    payload_size_left -= fragment.size();
    payload_size_left -= fragment_headers_length;

    fragment_headers_length = kLengthFieldSize;
    // If we are going to try to aggregate more fragments into this packet
    // we need to add the STAP-A NALU header and a length field for the first
    // NALU of this packet.
    if (aggregated_fragments == 0)
      fragment_headers_length += kNalHeaderSize + kLengthFieldSize;
    ++aggregated_fragments;

    // Next fragment.
    ++fragment_index;
    if (fragment_index == input_fragments_.size())
      break;
    fragment = input_fragments_[fragment_index];
  }
  RTC_CHECK_GT(aggregated_fragments, 0);
  packets_.back().last_fragment = true;
  return fragment_index;
}

3.5 NextPacket

bool RtpPacketizerH264::NextPacket(RtpPacketToSend* rtp_packet) {
  RTC_DCHECK(rtp_packet);
  if (packets_.empty()) {
    return false;
  }

  PacketUnit packet = packets_.front();
  if (packet.first_fragment && packet.last_fragment) {   //如果一个包既是第一包又是最后一包,则说明这是Single NAL unit包
    // Single NAL unit packet.
    size_t bytes_to_send = packet.source_fragment.size();
    uint8_t* buffer = rtp_packet->AllocatePayload(bytes_to_send);
    memcpy(buffer, packet.source_fragment.data(), bytes_to_send);
    packets_.pop();
    input_fragments_.pop_front();
  } else if (packet.aggregated) {  //组合包 stap-a
    NextAggregatePacket(rtp_packet);
  } else { //分片模式 fu-a
    NextFragmentPacket(rtp_packet);
  }
  rtp_packet->SetMarker(packets_.empty());  //如果是最后一包,则置marker位
  --num_packets_left_;
  return true;
}

根据打包类型,调用不同函数。

  • 如果是Single NAL unit packet,则按Single NALU规则直接打进RTP包里。
  • 如果是组合包,则调用NextAggregatePacket进行封装,封装规则与2.2中组合封包结构(Aggregation Packet)一致。
  • 如果是组合包,则调用NextFragmentPacket进行封装,封装规则与2.2中分片结构(Fragmentation Unit)一致。
  • 如果是最后一包,会把RTP头的marker位置1,用来代表帧结束标志。

至此,webrtc将H.264打包位RTP包的过程已经分析完毕。

参考资料

RTP: A Transport Protocol for Real-Time Applications(RFC3550)
RTP Payload Format for H.264 Video(RFC6184)文章来源地址https://www.toymoban.com/news/detail-532832.html

到了这里,关于webrtc源码阅读之h264 RTP打包的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FFmpeg命令行进行UDP、RTP推流(H264、TS),使用ffplay\VLC接收验证

            使用行FFmpeg命令进行UDP、RTP推流(H264、TS),ffplay接收我们在开发网络程序时经常用到UDP或RTP来发送和接收流媒体,而开发程序完毕需要搭建一个环境测试,这时候可能你需要一个推流端或接收端。对于推流端,我们可以借助FFmpeg工具轻松完成该功能,只需要敲一

    2024年02月05日
    浏览(45)
  • webrtc-m79-msvc编译H264

    本文主要参考:webrtc 4577版本vs编译_tusong86的博客-CSDN博客 4577也就是m93,由于源码版本的不同,可能存在一定的出入,可根据实际情况进行修改; 感谢作者的付出; powershell运行: // debug 需要编译参数 enable_iterator_debugging=true gn gen h264-debug-m79 --args=\\\'is_debug=true use_lld=false is_cla

    2024年02月10日
    浏览(41)
  • 微信小程序支持h5实现webrtc h264 h265低延迟传输渲染

    微信小程序自成体系,自身也带了很强的rtc音视频能力,但是他捆绑了他自己的服务,开发也相对受限于他的api。基于以前的了解可以采webview的方式内嵌h5网址来实现自定义的webrtc.但实践起来并不轻松,主要是小程序的严格限制,各平台的兼容,不得不使出吃奶的力气来实现

    2024年02月04日
    浏览(37)
  • H264的打包,nal,es,pes,pts,dts,ps,ts

    视频编码层 :预测、变换、量化、熵编码等操作 slice层 :将视频帧分割成若干个编码单元,包含一定数量的宏块,提高编解码的并行性和容错性。 NAL层 :提升对网络传输和数据存储的亲和性 基准-Baseline Profile:主要用于视频会议、可视电话等实时通信领域;支持IP帧,熵编

    2024年04月29日
    浏览(29)
  • Linux Qt5.15.2 编译QWebEngine源码支持音视频H264

    默认自带的QWebEngine 因版权问题不支持音视频功能,需要自己编译源码以支持。 平台:Linux(UOS V20 1050) Qt:5.15.2 下载 Qt 5.15.2 对应版本源码,使用镜像网站或者Qt Maintenance Tool工具下载。 配置 Qt 环境变量 将以下内容追加到bashrc文件后 刷新使其生效 这个阶段可以预先安装所

    2024年02月05日
    浏览(64)
  • FPGA纯verilog代码实现H264视频压缩 提供工程源码和技术支持

    H264视频压缩与解码在FPGA图传领域应用广泛,Xilinx高端器件已经内嵌了H264加速器,在Linux系统下调用API即可使用,但对于需要定制私有算法或者协议的H264视频压缩与解码应用或者学习研究者而言,纯verilog代码实现H264视频压缩依然具有实用价值,本设计采用纯verilog代码实现

    2024年02月06日
    浏览(57)
  • 视频编码(H264编码)

    基本步骤:    1、打开编码器          2、转换NV12到YUV420,这是因为FFmpeg的x264编码器只支持YUV420          3、准备编码数据AVFrame         创建frame:  创建AVPacket: AVPacket* pck = av_packet_alloc(); 4、H264编码       avcodec_send_frame(#AVCodecContext *avctx#, #const AVFrame *frame#)    

    2024年02月03日
    浏览(47)
  • wireshark导出H264裸流

    下载抓包工具:首先,您需要下载并安装一个网络抓包工具,例如Wireshark(https://www.wireshark.org)或tcpdump(https://www.tcpdump.org)。这些工具可用于捕获网络流量并保存为pcap或pcapng文件。在这里插入代码片 打开抓包工具:打开所选择的抓包工具,并启动网络监控。 下载rtp_h264

    2024年02月15日
    浏览(41)
  • h264编码概述七(SPS解析)

    SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。 H.264标准协议中规定的SPS格式位于文档的7.3.2.1.1,如下图所示: 1、profile_idc 根据《T-REC-H.264-201402-I!!PDF-E》的附件A.2定义,profiles有如下类型: profile_idc的值用来确定码流符

    2024年02月12日
    浏览(40)
  • OpenCV保存H264视频的问题

    windows 10 64bit opencv 4.5.3 看到有小伙伴在聊,如何将目标检测的结果保存成视频的相关问题?本篇我们就来看看。 在绝大多数的目标检测项目中,都是使用 opencv 这个开源的计算机视觉库来进行图片、视频或者摄像头的读写。 关于视频保存,来看下面的代码示例 主要是 VideoW

    2023年04月09日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包