【webrtc】VCMSessionInfo 合并一个可解码的帧

news2024/12/22 19:38:26

  • 知乎大神的概括:
  • VCMFrameBuffer 帧中包含VCMSessionInfo的处理,对VPX、h264(分析Nalus)的同一帧中的所有包进行过滤并进行完整帧组帧,用于sink给后续的解码。
  • 用于解码器的
  • 所以插入的数据都是VCMPacket
  • frame_buffer指向一帧的起始数据地址,首先能对待插入list 的packet 有序的插入,按照序号来的,
  • 检查是否完整,然后frame_buffer 就是这帧的数据了。
  • 零声有对应的讲解

最大的可解码帧大小2MB

  • session 允许的包队列大小(一个帧的对应的所有包组成了这个队列?)
  • 在这里插入图片描述

1400个包,大约20MB

在这里插入图片描述

反向迭代器ReversePacketIterator 的 base() 方法

  • base()可以将一个反向迭代器变成一个正向迭代器
int VCMSessionInfo::InsertPacket(const VCMPacket& packet,
                                 uint8_t* frame_buffer,
                                 const FrameData& frame_data) {

视频头里有一个视频帧的类型:空帧
  if (packet.video_header.frame_type == VideoFrameType::kEmptyFrame) {
 对于空包,要更新序号,这种不是媒体包 
    // Update sequence number of an empty packet.
    // Only media packets are inserted into the packet list.
    InformOfEmptyPacket(packet.seqNum);
    return 0;
  }

每个frame能接受的包最大1400if (packets_.size() == kMaxPacketsInSession) {
    RTC_LOG(LS_ERROR) << "Max number of packets per frame has been reached.";
    return -1;
  }


反向遍历:找到这个包在包队列中的位置,然后插入。
以相反的顺序循环遍历列表。
  // Find the position of this packet in the packet list in sequence number
  // order and insert it. Loop over the list in reverse order.

以相反的顺序循环遍历列表。
  ReversePacketIterator rit = packets_.rbegin();
  for (; rit != packets_.rend(); ++rit)
  从后向前看,包的序号比较,如果输入的比当前包要新,那么找到了要插入的位置了,breakfor
    if (LatestSequenceNumber(packet.seqNum, (*rit).seqNum) == packet.seqNum)
      break;

检查包是否重复了:
遍历过程中,找到了同序号的包,并且这个包还是有数据的,咋办? 返回错误 -2, 输入的包插入失败。
  // Check for duplicate packets.
  if (rit != packets_.rend() && (*rit).seqNum == packet.seqNum &&
      (*rit).sizeBytes > 0)
    return -2;


  if (packet.codec() == kVideoCodecH264) {
    frame_type_ = packet.video_header.frame_type;
    当前输入包在frame中是首个,并且(还没记录到首个包的序号 或者 记录到了但是比输入包的序号要新),则记录为首个包
    if (packet.is_first_packet_in_frame() &&
        (first_packet_seq_num_ == -1 ||
         IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum))) {
      first_packet_seq_num_ = packet.seqNum;
    }

如果输入包是最后一个包,并且 (还没记录最后包的序号 或者  输入包的序号比记录到的序号要新,那么更新记录到最后包序号为最新的这个包的序号)
    if (packet.markerBit &&
        (last_packet_seq_num_ == -1 ||
         IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) {
      last_packet_seq_num_ = packet.seqNum;
    }
#ifdef OWT_ENABLE_H265
  } else if (packet.codec() == kVideoCodecH265) {
    frame_type_ = packet.video_header.frame_type;
    if (packet.is_first_packet_in_frame() &&
        (first_packet_seq_num_ == -1 ||
         IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum))) {
      first_packet_seq_num_ = packet.seqNum;
    }
    if (packet.markerBit &&
        (last_packet_seq_num_ == -1 ||
         IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) {
      last_packet_seq_num_ = packet.seqNum;
    }
#else
  } else {
#endif


只能在首包和末尾包之间插入:
    // Only insert media packets between first and last packets (when
    // available).
    // Placing check here, as to properly account for duplicate packets.
    // Check if this is first packet (only valid for some codecs)
    // Should only be set for one packet per session.
    if (packet.is_first_packet_in_frame() && first_packet_seq_num_ == -1) {
      // The first packet in a frame signals the frame type.
      frame_type_ = packet.video_header.frame_type;
      // Store the sequence number for the first packet.
      first_packet_seq_num_ = static_cast<int>(packet.seqNum);
    } else if (first_packet_seq_num_ != -1 &&
    如果已经有首个包的序号,输入包竟然比首包来的海湾,那么返回-3
               IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum)) {
      RTC_LOG(LS_WARNING)
          << "Received packet with a sequence number which is out "
             "of frame boundaries";
      return -3;
    } else if (frame_type_ == VideoFrameType::kEmptyFrame &&
               packet.video_header.frame_type != VideoFrameType::kEmptyFrame) {
      // Update the frame type with the type of the first media packet.
      // TODO(mikhal): Can this trigger?
      frame_type_ = packet.video_header.frame_type;
    }

    // Track the marker bit, should only be set for one packet per session.
    if (packet.markerBit && last_packet_seq_num_ == -1) {
      last_packet_seq_num_ = static_cast<int>(packet.seqNum);
    } else if (last_packet_seq_num_ != -1 &&
               IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_)) {
      RTC_LOG(LS_WARNING)
          << "Received packet with a sequence number which is out "
             "of frame boundaries";
      return -3;
    }
  }

在某个位置上插入包: 
插入操作使迭代器失效
base()可以将一个反向迭代器变成一个正向迭代器
  // The insert operation invalidates the iterator |rit|.
 这个会在rit位置的正向位置的后面插入packet
  PacketIterator packet_list_it = packets_.insert(rit.base(), packet);

  size_t returnLength = InsertBuffer(frame_buffer, packet_list_it);
  UpdateCompleteSession();

  return static_cast<int>(returnLength);
}
  • 传递了一个包的正向迭代器:

在这里插入图片描述

InsertBuffer是为了避免内存拷贝?

在这里插入图片描述

  • 知乎大神的分析:
    在这里插入图片描述
  • 分析下源码:
size_t VCMSessionInfo::InsertBuffer(uint8_t* frame_buffer,
                                    PacketIterator packet_it) {
  VCMPacket& packet = *packet_it; //取出这个包
  PacketIterator it;  //正向迭代

  // Calculate the offset into the frame buffer for this packet.
  size_t offset = 0;
  //正向遍历
  for (it = packets_.begin(); it != packet_it; ++it)
  //记录每个包的offset
    offset += (*it).sizeBytes;

//frame_buffer 这个指针用来收集所有的包的数据部分,这样确实可以避免拷贝内存
  // Set the data pointer to pointing to the start of this packet in the
  // frame buffer.
  const uint8_t* packet_buffer = packet.dataPtr;
  packet.dataPtr = frame_buffer + offset;


STAP-A 分判断H.264 数据:用一种特殊的方式去掉两个NAL单元之间的2个字节长度,并且加上起始码
  // We handle H.264 STAP-A packets in a special way as we need to remove the
  // two length bytes between each NAL unit, and potentially add start codes.
  // TODO(pbos): Remove H264 parsing from this step and use a fragmentation
  // header supplied by the H264 depacketizer.
  const size_t kH264NALHeaderLengthInBytes = 1;
#ifdef OWT_ENABLE_H265
  const size_t kH265NALHeaderLengthInBytes = 2;
  const auto* h265 =
      absl::get_if<RTPVideoHeaderH265>(&packet.video_header.video_type_header);
#endif
  const size_t kLengthFieldLength = 2;
  const auto* h264 =
      absl::get_if<RTPVideoHeaderH264>(&packet.video_header.video_type_header);
  if (h264 && h264->packetization_type == kH264StapA) {
    size_t required_length = 0;
    const uint8_t* nalu_ptr = packet_buffer + kH264NALHeaderLengthInBytes;
    while (nalu_ptr < packet_buffer + packet.sizeBytes) {
      size_t length = BufferToUWord16(nalu_ptr);
      required_length +=
          length + (packet.insertStartCode ? kH264StartCodeLengthBytes : 0);
      nalu_ptr += kLengthFieldLength + length;
    }
    ShiftSubsequentPackets(packet_it, required_length);
    nalu_ptr = packet_buffer + kH264NALHeaderLengthInBytes;
    uint8_t* frame_buffer_ptr = frame_buffer + offset;
    while (nalu_ptr < packet_buffer + packet.sizeBytes) {
      size_t length = BufferToUWord16(nalu_ptr);
      nalu_ptr += kLengthFieldLength;
      frame_buffer_ptr += Insert(nalu_ptr, length, packet.insertStartCode,
                                 const_cast<uint8_t*>(frame_buffer_ptr));
      nalu_ptr += length;
    }
    packet.sizeBytes = required_length;
    return packet.sizeBytes;
  }
#ifdef OWT_ENABLE_H265
  else if (h265 && h265->packetization_type == kH265AP) {
    // Similar to H264, for H265 aggregation packets, we rely on jitter buffer
    // to remove the two length bytes between each NAL unit, and potentially add
    // start codes.
    size_t required_length = 0;
    const uint8_t* nalu_ptr =
        packet_buffer + kH265NALHeaderLengthInBytes;  // skip payloadhdr
    while (nalu_ptr < packet_buffer + packet.sizeBytes) {
      size_t length = BufferToUWord16(nalu_ptr);
      required_length +=
          length + (packet.insertStartCode ? kH265StartCodeLengthBytes : 0);
      nalu_ptr += kLengthFieldLength + length;
    }
    ShiftSubsequentPackets(packet_it, required_length);
    nalu_ptr = packet_buffer + kH265NALHeaderLengthInBytes;
    uint8_t* frame_buffer_ptr = frame_buffer + offset;
    while (nalu_ptr < packet_buffer + packet.sizeBytes) {
      size_t length = BufferToUWord16(nalu_ptr);
      nalu_ptr += kLengthFieldLength;
      // since H265 shares the same start code as H264, use the same Insert
      // function to handle start code.
      frame_buffer_ptr += Insert(nalu_ptr, length, packet.insertStartCode,
                                 const_cast<uint8_t*>(frame_buffer_ptr));
      nalu_ptr += length;
    }
    packet.sizeBytes = required_length;
    return packet.sizeBytes;
  }
#endif
  ShiftSubsequentPackets(
      packet_it, packet.sizeBytes +
                     (packet.insertStartCode ? kH264StartCodeLengthBytes : 0));

  packet.sizeBytes =
      Insert(packet_buffer, packet.sizeBytes, packet.insertStartCode,
             const_cast<uint8_t*>(packet.dataPtr));
  return packet.sizeBytes;
}

判断是否是完整的一帧:UpdateCompleteSession

  • 有头、有尾的包,
  • 如果InSequence == false ,那么 不完整。
  • 也就说InSequence 比如返回true:
void VCMSessionInfo::UpdateCompleteSession() {
  if (HaveFirstPacket() && HaveLastPacket()) {
    // Do we have all the packets in this session?
    bool complete_session = true;
    PacketIterator it = packets_.begin();
    PacketIterator prev_it = it;
    ++it;
    for (; it != packets_.end(); ++it) {
    //第一次,it是第二个包,previt是第一个包:
      if (!InSequence(it, prev_it)) {
        complete_session = false;
        break;
      }
      //prev 会变为it的位置,第一次后就是第二个包
      prev_it = it;
    }
    complete_ = complete_session;
  }
}

InSequence 返回true 就是连续的包

// 判断并更新帧是否完整(组包完成),即判断packets_之间是否有空洞(不连续)

  • 为啥相等也是连续的???

在这里插入图片描述

ShiftSubsequentPackets 移动后续的包:

void VCMSessionInfo::ShiftSubsequentPackets(PacketIterator it,
                                            int steps_to_shift) {
                                            //从当前it的后面开始
  ++it;
  if (it == packets_.end())  //it 后刚好是最后一个 ,退出
    return;
    //后面那个开始,作为第一个包,获取数据部分的指针
  uint8_t* first_packet_ptr = const_cast<uint8_t*>((*it).dataPtr);
  int shift_length = 0; //需要移动的长度
计算总共要移动的长度,提前移动数据指针
  // Calculate the total move length and move the data pointers in advance.
  //遍历
  for (; it != packets_.end(); ++it) {
  //每个包移动的长度 累加
    shift_length += (*it).sizeBytes;
    //改变数据指针的指向的内存位置:每个包的数据部分都后移steps_to_shift 这么大???
    if ((*it).dataPtr != NULL)
      (*it).dataPtr += steps_to_shift;
  }
  //搬运内存,把当前包后面的包的所有数据(first_packet_ptr, shift_length) 移动到first_packet_ptr + steps_to_shift 开始?
  memmove(first_packet_ptr + steps_to_shift, first_packet_ptr, shift_length);
}

每个包都删除一部分数据?

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Django系列:Django简介与MTV架构体系概述

Django系列 Django简介与MTV架构体系概述 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/132890054 【介…

企业架构LNMP学习笔记43

memcached的使用&#xff1a; 命令行连接和操作&#xff1a; telnet连接使用&#xff1a; memcached默认使用启动服务占用tcp 11211端口&#xff0c;可以通过telnet进行连接使用。 安装telnet进行连接&#xff1a; 连接成功&#xff0c;敲击多次&#xff0c;如果看到error&…

Linux常用命令字典篇

Linux命令 1. 翻页查看文件 less [-N] 文件名&#xff1a;可以向后翻页&#xff0c;也可以向前翻页&#xff0c;-N表示显示行号 more 文件名&#xff1a;仅可以向后翻页 2. 端口占用信息查看 netstat -tunlp | grep 端口号&#xff1a;查看端口号对应的信息 lsof i: 端口号…

Marin说PCB之封装设计系列---(02)--异形焊盘的封装设计总结

每天下班回家看电视本来是一件很美好的事情&#xff0c;可是正当我磕着瓜子看着异人之下的时候&#xff0c;手机突然响起来了&#xff0c;我以为是我们组哪个同事找我呢。一接电话居然是我的老朋友陈世美陈总&#xff0c;江湖人称少妇杀手。给我打电话主要是说他最近遇到一个异…

vite和webpack的区别

vite和webpack的区别 1、前言2、Webpack2.1 Webpack简述2.2 Webpack常用插件 3、Vite3.1 Vite简述3.2 Vite插件推荐 4、区别4.1 开发模式不同4.2 打包效率不同4.3 插件生态不同4.4 配置复杂度不同4.5 热更新机制不同 5、总结 1、前言 Webpack和Vite是现代前端开发中非常重要的…

线性代数的本质(九)——二次型与合同

文章目录 二次型与合同二次型与标准型二次型的分类度量矩阵与合同 二次型与合同 二次型与标准型 Grant&#xff1a;二次型研究的是二次曲面在不同基下的坐标变换 由解析几何的知识&#xff0c;我们了解到二次函数的一次项和常数项只是对函数图像进行平移&#xff0c;并不会改变…

HSRP(热备份路由选择协议)的概念,原理与配置实验

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 梦想从未散场&#xff0c;传奇永不落幕&#xff0c;持续更新优质网络知识、Python知识、Linux知识以及各种小技巧&#xff0c;愿你我共同在CSDN进步 目录 一、了解HSRP协议 1. 什么是HSRP协议 2、HSRP协议的…

2023年主流固定资管理系统的特征

随着科技的不断发展&#xff0c;固定资产管理系统也在不断演进&#xff0c;以满足企业日益增长的管理需求。在2023年&#xff0c;主流固定资产管理系统将呈现出一些重要的特征&#xff0c;包括RFID功能、低代码平台功能和云计算功能。易点易动固定资产管理系统正是结合了这些特…

UWB芯片DW3000之PDOA测向实现源码

介绍 DW3000芯片的双天线端口特性可以测量无线输入信号的相位。当与天线响应的信息相结合时,这些信息可以用来帮助确定到达的方向和传输的位置。 根据设备的不同,将有一个或两个天线端口。具有两个天线端口的设备称为PDoA部件,而其他是非PDoA部件(见表1)。当涉及到到达相位…

算法分析与设计编程题 贪心算法

活动安排问题 题目描述 解题代码 vector<bool> greedySelector(vector<vector<int>>& intervals) {int n intervals.size();// 将活动区间按结束时间的从小到大排序auto cmp [](vector<int>& interval1, vector<int>& interval2…

(文末赠书)我为什么推荐应该人手一本《人月神话》

能点进来的朋友&#xff0c;说明你肯定是计算机工作的朋友或者对这本书正在仔细琢磨着的朋友。 文章目录 1、人人都会编程的时代&#xff0c;我们如何留存?2、小故事说明项目管理着为什么必看这本书3、如何评价《人月神话&#xff1a;纪念典藏版》4、本书的目录&#xff08;好…

模方新建工程时,显示空三与模型坐标系不一致怎么解决

答:检查空三xml与模型的metadata.xml的坐标系是否一致&#xff0c;metadata文件是否有在data目录外面。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.0新增单体化建模模块&#xff0c;…

无人机+三维实景建模助力古建筑保护,传承历史记忆

历史文化建筑&#xff0c;承载着过去各个时代的文化记忆。无论是保存还是修缮古建筑&#xff0c;都需要将其基本信息进行数字化建档&#xff0c;为修缮提供精准参考。根据住建部的要求&#xff0c;从2020年开始到2022年&#xff0c;全国需完成历史建筑100%测绘及系统录入工作&a…

OPENCV实现人类识别(包括眼睛、鼻子、嘴巴)

人脸识别步骤 # -*- coding:utf-8 -*- """ 作者:794919561 日期:2023/9/14 """ import cv2 import numpy as np # load xml face_xml = cv2.CascadeClassifier(F:\\learnOpenCV\\opencv\\data\\haarcascades\\haarcascade_frontalface_defaul…

计算机提示vcomp120.dll丢失怎样修复,vcomp120.dll丢失的4个修复方法分享

随着科技的飞速发展&#xff0c;计算机已经成为了人们日常生活和工作中不可或缺的重要工具。在享受计算机带来的便利的同时&#xff0c;我们也会遇到各种各样的问题&#xff0c;其中计算机丢失vcomp120.dll文件就是一种常见的困扰。vcomp120.dll是 Visual C Redistributable 的…

【操作系统】聊聊协程为什么可以支撑高并发服务

在实际的业务开发中&#xff0c;比如针对一个业务流程&#xff0c;调用三方&#xff0c;然后存储数据&#xff0c;从oss上获取数据。其实都是进行的同步调用&#xff0c;说白了就是A完成之后&#xff0c;B在继续完成。如果整个过程中A、B、C 分别耗时100、300、200毫秒。那么整…

vue基础知识十:Vue中组件和插件有什么区别?

一、组件是什么 回顾以前对组件的定义&#xff1a; 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念&#xff08;组件&#xff09;来实现开发的模式&#xff0c;在Vue中每一个.vue文件都可以视为一个组件 组件的优势 降低整个系统的耦合度&#xff0c;在保持接口不…

【VSCode】自动生成Jupyter(ipynb)文件的目录

下载插件 一键生成 然后就出来咯&#xff5e;

关于HTTP协议的概述

HTTP 的报文大概分为三大部分。第一部分是请求行&#xff0c;第二部分是请求的首部&#xff0c;第三部分才是请求的正文实体。 POST 往往是用来创建一个资源的&#xff0c;而 PUT 往往是用来修改一个资源的。 Accept-Charset&#xff0c;表示客户端可以接受的字符集。防止传过…

Python工程师Java之路(p)Module和Package

文章目录 1、Python的Module和Package2、Java的Module和Package2.1、Module2.1.1、分模块开发意义2.1.2、模块的调用 2.2、Package Module通常译作模块&#xff0c;Package通常译作包 1、Python的Module和Package Python模块&#xff08;Module&#xff09;&#xff1a;1个以.…