ExoPlayer架构详解与源码分析(9)——TsExtractor

系列文章目录

ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor


文章目录

  • 系列文章目录
  • 前言
  • TsExtractor
  • TsDurationReader
  • SectionReader
  • PatReader
  • PmtReader
  • DefaultTsPayloadReaderFactory
  • PesReader
  • 总结


前言

上篇说完了Extractor的整体结构,本篇将详细讲解Extractor的实现,主要通过TsExtractor这个实现类来讲解,顾名思义TsExtractor是用于TS容器格式的解析器。

TS(Transport Stream,传输流)是一种封装的格式,它的全称为MPEG2-TS。

MPEG组织于1994年推出MPEG-2压缩标准,以实现视/音频服务与应用互操作的可能性,MPEG-2标准是针对标准数字电视和高清晰度电视在各种应用下的压缩方案和系统层的详细规定。在MPEG-2标准中,为了将一个或更多的音频、视频或其他的基本数据流合成单个或多个数据流,以适应于存储和传送,必须对其重新进行打包编码,在码流中还需插入各种时间标记、系统控制等信息,最后送到信道编码与调制器。这样可以形成两种数据流——传送流(TS)和节目流(PS),分别适用于不同的应用。

MPEG2-TS是一种标准数据容器格式,传输与存储音视频、节目与系统信息协议数据,主要应用于数字广播系统,譬如DVB、ATSC与IPTV。TS流是将视频、音频、PSI等数据打包成传输包进行传送。其整体的设计充分考虑了传输过程中的丢包,数据干扰等问题,特别适合用于节目传输。

科普时间结束,回归主线,看下ExoPlayer 是如何解析TS结构的

TsExtractor

在看ExoPlayer 源码前,必须先来了解下TS的整体结构,然后再结合源码,看下ExoPlayer是如何实现TS的解析的。
首先看下TS容器的结构(网图,侵删)。
(网图,侵删)
可以看到每个TS包大小为188,包含一个header和payload,这种固定块大小的结果特别适合网络传输的场景,运用的也比较多。

  • header

    名称大小(b)说明
    sync_byte8同步标记占1个字节,固定为0x47,当解析器读取到这个字节的时候就知道这是一个包开始位置
    transport_error_indicator1传输错误指示符,1’表示在相关的传输包中至少有一个不可纠正的错误位。当被置1后,在错误被纠正之前不能重置为0
    payload_unit_start_indicator1负载单元起始标示符,一个完整的数据包开始时标记为1
    transport_priority1传输优先级,0为低优先级,1为高优先级,通常取0
    pid13包的 ID,用于区分不同的包,注意这个不是唯一的,可能相同类型的包都对应同一个PID,其中PID有几个固定值用于指定类型的包,如PAT包固定值为0x0000
    transport_scrambling_control2传输加扰控制,00表示未加密
    adaptation_field_control2是否包含自适应区,‘00’保留;‘01’为无自适应域,仅含有效负载;‘10’为仅含自适应域,无有效负载;‘11’为同时带有自适应域和有效负载。
    continuity_counter4递增计数器,从0-f,起始值不一定取0,但必须是连续的,随着每一个具有相同PID的TS流分组而增加,当它达到最大值后又回复到0。范围为0~15。接收端可判断是否有包丢失及包传送顺序错误
    adaptation_field_length8自适应域长度,包含在上图的PRC分段中
    flag8取0x50表示包含PCR或0x40表示不包含PCR,包含在上图的PRC分段中
    PCR40Program Clock Reference,节目时钟参考,用于恢复出与编码端一致的系统时序时钟STC(System Time Clock)。可以理解为当前包的时间戳,时间戳一般是以90 kHz 为单位的时间戳,所以转化成正常时间戳得除以90000,这段同样包含在上图的PRC分段中
  • payload
    payload里主要包含2种类型数据PES和PSI(Program Specific Information:由对于传输流的多路分解以及节目成功再现所必要的标准数据组成)
    PSI 可以认为属于 6 个表:

    1. 节目相关表(PAT)
    2. TS 节目映射表(PMT)
    3. 网络信息表(NIT)
    4. 有条件访问表(CAT)
    5. 传输流描述表
    6. IPMP 控制信息表。

    主要介绍下下面3种

    • PAT表,包头的PID固定为0x0000,包含一个header和一个body,一个payload前3字节为header,包含了body的长度,后面则为body,主要列出所有的PMT表ID,可以通过它确定哪些PID的包是PMT表主要要来查询PMT,下面是PAT的表结构,section_length前为header,后面的属于body

      名称大小(b)说明
      table_id8PAT表固定为0x00
      section_syntax_indicator1固定为1
      zero1固定为0
      reserved2固定为11
      section_length12后面数据的长度
      transport_stream_id16传输流ID,固定为0x0001
      reserved2固定为11
      version_number5版本号,固定为00000,如果PAT有变化则版本号加1
      current_next_indicator1固定为1,表示这个PAT表可以用,如果为0则要等待下一个PAT表
      section_number8固定为0x00
      last_section_number8固定为0x00
      开始循环
      program_number16为0x0000时表示这是NIT网络信息表,节目号为0x0001时,表示这是PMT
      reserved3固定为111
      PID13PAT对应PMT的包PID值
      结束循环
      CRC3232前面数据的CRC32校验码
    • PMT表,包头的PID不固定,需要通过PAT获取,主要列出了包含的所有流类型及其对于的PID,通过它可以确定当前的包对应的是哪种流,然后针对性的解析,下面是PMT的表结构

      名称大小(b)说明
      table_id8PMT表固定为0x02
      section_syntax_indicator1固定为1
      zero1固定为0
      reserved2固定为11
      section_length12后面数据的长度
      program_number16频道号码,表示当前的PMT关联到的频道,取值0x0001
      reserved2固定为11
      version_number5版本号,固定为00000,如果PAT有变化则版本号加1
      current_next_indicator1固定为1
      section_number8固定为0x00
      last_section_number8固定为0x00
      reserved3固定为111
      PCR_PID13PCR(节目参考时钟)所在TS分组的PID,指定为视频PID
      reserved3固定为111
      program_info_length12描述信息,指定为0x000表示没有
      开始循环
      stream_type8流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03
      reserved3固定为111
      elementary_PID13与stream_type对应的PID
      reserved4固定为1111
      ES_info_length12描述信息,指定为0x000表示没有
      结束循环
      CRC3232前面数据的CRC32校验码
    • PES 用于承载基本流数据的数据结构,可以理解成具体的媒体流数据,同样包含header和body,看下包结构图
      在这里插入图片描述
      PES的Header结构很复杂,这里我们说明下重要的几个

      名称大小(b)说明
      packet_start_code_prefix24固定为0x000001,同跟随它的 stream_id 一起组成标识包起始端的包起始码
      stream_id16流ID,音频取值(0xc0-0xdf),通常为0xc0视频取值(0xe0-0xef),通常为0xe0具体参照ISO/IEC 13818-1 2.4.3.7
      PES_packet_length24后面pes数据的长度,0表示长度不限制,只有视频数据长度会超过0xffff
      PTS_DTS_flags2当 PTS_DTS_flags 字段设置为‘10’时,PES 包头中 PTS 字段存在。设置为‘11’时,PES 包头中 PTS 字段和 DTS 字段均存在。设置为‘00’时,PES 包头中既无任何 PTS 字段也无任何 DTS 字段存在。值‘01’禁用
      PES_header_data_length8额外包含的数据长度,包含的PTS或者DTS数据
      PTS33presentation time stamp,显示时间戳,具体参考ISO/IEC 13818-1 2.4.3.7
      DTS33decoding time stamp,解码时间戳,具体参考ISO/IEC 13818-1 2.4.3.7

好了有了上面的知识,我们一起来看下源码是如何解析的
首先从初始化看起


  public TsExtractor(
      @Mode int mode, @Flags int defaultTsPayloadReaderFlags, int timestampSearchBytes) {
    this(
        mode,
        new TimestampAdjuster(0),
        //创建默认的payload的解析工厂类
        new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags),
        timestampSearchBytes);
  }
  
  public TsExtractor(
      @Mode int mode,
      TimestampAdjuster timestampAdjuster,
      TsPayloadReader.Factory payloadReaderFactory,
      int timestampSearchBytes) {
...
    //初始化缓存数据大小为50个TS包大小
    tsPacketBuffer = new ParsableByteArray(new byte[BUFFER_SIZE], 0);
...
    //用于从PRC中计算时长
    durationReader = new TsDurationReader(timestampSearchBytes);
...
    resetPayloadReaders();
  }

  //初始化payload解析器
  private void resetPayloadReaders() {
    trackIds.clear();
    tsPayloadReaders.clear();
    SparseArray<TsPayloadReader> initialPayloadReaders =
        payloadReaderFactory.createInitialPayloadReaders();
    int initialPayloadReadersSize = initialPayloadReaders.size();
    //添加初始化默认的解析器,如果没有自定义工厂会使用DefaultTsPayloadReaderFactory此时不包含任何初始化的解析器
    for (int i = 0; i < initialPayloadReadersSize; i++) {
      tsPayloadReaders.put(initialPayloadReaders.keyAt(i), initialPayloadReaders.valueAt(i));
    }
    //添加包头PAT解析器
    tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader()));
    id3Reader = null;
  }

看下初始化后调用的第一个方法,主要用来确定当前Extractor是否适用

  //在确定使用哪种解析器时会先调用Extractor.sniff决定当前解析器是否可以用于解析,上文中也提到了调用点
  @Override
  public boolean sniff(ExtractorInput input) throws IOException {
    byte[] buffer = tsPacketBuffer.getData();
    input.peekFully(buffer, 0, TS_PACKET_SIZE * SNIFF_TS_PACKET_COUNT);//填充5*118个数据
    for (int startPosCandidate = 0; startPosCandidate < TS_PACKET_SIZE; startPosCandidate++) {
      // Try to identify at least SNIFF_TS_PACKET_COUNT packets starting with TS_SYNC_BYTE.
      boolean isSyncBytePatternCorrect = true;
      //是否有5个0x47字节连续的间隔188的数据
      for (int i = 0; i < SNIFF_TS_PACKET_COUNT; i++) {
        if (buffer[startPosCandidate + i * TS_PACKET_SIZE] != TS_SYNC_BYTE) {
          isSyncBytePatternCorrect = false;
          break;
        }
      }
      if (isSyncBytePatternCorrect) {
        input.skipFully(startPosCandidate);
        return true;
      }
    }
    return false;
  }

然后就开始执行主要的方法read

@Override
  public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
      throws IOException {
    long inputLength = input.getLength();
    if (tracksEnded) {//所有PMT表都解析完 tracksEnded
      //如果tracksEnded了,此时数据的总长度已知,且不为HLS(hls有多个ts,时长记录在m3u8文件里)
      boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS;
      if (canReadDuration && !durationReader.isDurationReadFinished()) {
        //开始读取时长,后面会具体讲到读取方式
        return durationReader.readDuration(input, seekPosition, pcrPid);
      }
      //输出SeekMap表,这里保存播放时间戳和数据位置的对应关系,可以通过时间戳快速定位到数据位置
      //这里不深入了
      maybeOutputSeekMap(inputLength);

      //是否从头开始,这里作用是当Tarck信息解析完毕的时候会返回RESULT_SEEK
      //回到上面讲的外循环再次从头加载数据
      if (pendingSeekToStart) {
        pendingSeekToStart = false;
        seek(/* position= */ 0, /* timeUs= */ 0);
        if (input.getPosition() != 0) {
          seekPosition.position = 0;
          return RESULT_SEEK;
        }
      }

      if (tsBinarySearchSeeker != null && tsBinarySearchSeeker.isSeeking()) {
        return tsBinarySearchSeeker.handlePendingSeek(input, seekPosition);
      }
    }
    //读取至少一段Ts包
    if (!fillBufferWithAtLeastOnePacket(input)) {
      return RESULT_END_OF_INPUT;
    }
    //从上次读取位置查找第一个包的结束位置
    int endOfPacket = findEndOfFirstTsPacketInBuffer();
    int limit = tsPacketBuffer.limit();
    //如果超过limit,其实就是包不足188字节,这里会继续加载
    if (endOfPacket > limit) {
      return RESULT_CONTINUE;
    }

    @TsPayloadReader.Flags int packetHeaderFlags = 0;

    // Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
    //读取4字节,也就是包头的长度
    int tsPacketHeader = tsPacketBuffer.readInt();
    if ((tsPacketHeader & 0x800000) != 0) { // 获取transport_error_indicator不等0,也就是这个包有问题
      // There are uncorrectable errors in this packet.
      tsPacketBuffer.setPosition(endOfPacket);//跳过当前包
      return RESULT_CONTINUE;
    }
    //获取payload_unit_start_indicator位,负载单元起始标示符,一个完整的数据包开始时标记为1
    packetHeaderFlags |= (tsPacketHeader & 0x400000) != 0 ? FLAG_PAYLOAD_UNIT_START_INDICATOR : 0;
    // Ignoring transport_priority (tsPacketHeader & 0x200000)
    获取包PID, &111111111111100000000取4到16位,右移8位去除后8位
    int pid = (tsPacketHeader & 0x1FFF00) >> 8;
    // Ignoring transport_scrambling_control (tsPacketHeader & 0xC0)
    //获取adaptation_field_control第1位,判断adaptationField是否存在
    boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
    //获取adaptation_field_control第2位,判断paload是否存在
    boolean payloadExists = (tsPacketHeader & 0x10) != 0;

    //由于默认只设置了PAT的解析器,所以第一次只有当PID为0时才能获取到解析器
    TsPayloadReader payloadReader = payloadExists ? tsPayloadReaders.get(pid) : null;
    if (payloadReader == null) {//不存在payload,跳过当前包
      tsPacketBuffer.setPosition(endOfPacket);
      return RESULT_CONTINUE;
    }

    // 检查连续性
    if (mode != MODE_HLS) {
      //获取continuity_counter字节
      int continuityCounter = tsPacketHeader & 0xF;
      //获取上一个计数
      int previousCounter = continuityCounters.get(pid, continuityCounter - 1);
      continuityCounters.put(pid, continuityCounter);
      if (previousCounter == continuityCounter) {
        // 相同的counter可能是重传的数据直接跳过
        tsPacketBuffer.setPosition(endOfPacket);
        return RESULT_CONTINUE;
      } else if (continuityCounter != ((previousCounter + 1) & 0xF)) {
        // 非连续性的数据,可能发生了丢包或者seek,通知解析器包不连续重置相关标记位
        payloadReader.seek();
      }
    }

    // 如果存在adaptationField跳过
    if (adaptationFieldExists) {
      //获取adaptation_field_length用于跳过相应数据
      int adaptationFieldLength = tsPacketBuffer.readUnsignedByte();
      int adaptationFieldFlags = tsPacketBuffer.readUnsignedByte();

      packetHeaderFlags |=
          (adaptationFieldFlags & 0x40) != 0 // random_access_indicator.
              ? TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR
              : 0;
      tsPacketBuffer.skipBytes(adaptationFieldLength - 1 /* flags */);
    }

    // 开始读取payload
    boolean wereTracksEnded = tracksEnded;
    if (shouldConsumePacketPayload(pid)) {
      tsPacketBuffer.setLimit(endOfPacket);//设置解析结束位置
      //将数据喂给相应解析器,第一次consume的解析器肯定为PAT解析器,接下来会分析
      payloadReader.consume(tsPacketBuffer, packetHeaderFlags);
      tsPacketBuffer.setLimit(limit);
    }
    //非HLS,track完成(PMT已经读取),且长度已知(非直播流)
    if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) {
      //重新开始再读一遍,因为有可能有些媒体数据在PTM等轨道信息数据之前
      pendingSeekToStart = true;
    }

    tsPacketBuffer.setPosition(endOfPacket);
    return RESULT_CONTINUE;
  }

在分析PAT解析器前这里加个插曲,讲下上面说到的TsDurationReader,看下ExoPlayer是如何计算视频时长的。

TsDurationReader

直入主题readDuration

public @Extractor.ReadResult int readDuration(
      ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException {
    if (pcrPid <= 0) {
      return finishReadDuration(input);
    }
    if (!isLastPcrValueRead) {
      return readLastPcrValue(input, seekPositionHolder, pcrPid);//获取最后一个PCR的值
    }
    if (lastPcrValue == C.TIME_UNSET) {
      return finishReadDuration(input);
    }
    if (!isFirstPcrValueRead) {
      return readFirstPcrValue(input, seekPositionHolder, pcrPid);//获取最第一个PCR的值
    }
    if (firstPcrValue == C.TIME_UNSET) {
      return finishReadDuration(input);
    }

    long minPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(firstPcrValue);
    long maxPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(lastPcrValue);
    durationUs = maxPcrPositionUs - minPcrPositionUs;//计算差值
    if (durationUs < 0) {
      Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
      durationUs = C.TIME_UNSET;
    }
    return finishReadDuration(input);
  }

  private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
      throws IOException {
    int bytesToSearch = (int) min(timestampSearchBytes, input.getLength());
    int searchStartPosition = 0;//从前往后依次读取包,相应的获取最后一个时就是从后往前依次读取包
    if (input.getPosition() != searchStartPosition) {
      //回到上面讲的ExtractingLoadable外部循环再次从下面指定位置打开源进行读取
      seekPositionHolder.position = searchStartPosition;
      return Extractor.RESULT_SEEK;
    }

    packetBuffer.reset(bytesToSearch);
    input.resetPeekPosition();
    input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch);

    firstPcrValue = readFirstPcrValueFromBuffer(packetBuffer, pcrPid);
    isFirstPcrValueRead = true;
    return Extractor.RESULT_CONTINUE;
  }
  
 //最终调用这个读取包头
 public static long readPcrFromPacket(
      ParsableByteArray packetBuffer, int startOfPacket, int pcrPid) {
    packetBuffer.setPosition(startOfPacket);
    if (packetBuffer.bytesLeft() < 5) {
      // Header = 4 bytes, adaptationFieldLength = 1 byte.
      return C.TIME_UNSET;
    }
    // Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format.
    //读取包头4字节
    int tsPacketHeader = packetBuffer.readInt();
    if ((tsPacketHeader & 0x800000) != 0) {//确保包无错误
      // transport_error_indicator != 0 means there are uncorrectable errors in this packet.
      return C.TIME_UNSET;
    }
    //获取包PID
    int pid = (tsPacketHeader & 0x1FFF00) >> 8;
    if (pid != pcrPid) {
      return C.TIME_UNSET;
    }
    //判断adaptationField是否存在
    boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
    if (!adaptationFieldExists) {
      return C.TIME_UNSET;
    }
    //获取adaptationField长度
    int adaptationFieldLength = packetBuffer.readUnsignedByte();
    //确认长度
    if (adaptationFieldLength >= 7 && packetBuffer.bytesLeft() >= 7) {
      int flags = packetBuffer.readUnsignedByte();
      //获取是否设置pcr
      boolean pcrFlagSet = (flags & 0x10) == 0x10;
      if (pcrFlagSet) {
        byte[] pcrBytes = new byte[6];
        //解析PCR
        packetBuffer.readBytes(pcrBytes, /* offset= */ 0, pcrBytes.length);
        return readPcrValueFromPcrBytes(pcrBytes);
      }
    }
    return C.TIME_UNSET;
  }
  //解析PCR, & 0xFF保持原始字节数据,网络数据大端序读取,只读取了前33位,精度要求不高舍弃后7位
  private static long readPcrValueFromPcrBytes(byte[] pcrBytes) {
    return (pcrBytes[0] & 0xFFL) << 25
        | (pcrBytes[1] & 0xFFL) << 17
        | (pcrBytes[2] & 0xFFL) << 9
        | (pcrBytes[3] & 0xFFL) << 1
        | (pcrBytes[4] & 0xFFL) >> 7;
  }
  
  public static long ptsToUs(long pts) {
    return (pts * C.MICROS_PER_SECOND) / 90000;
  }

这里还有个插曲& 0xFF,这么做的主要原因是因为在java中byte类型为大小为1字节也就是8位,而Long整型是8字节64位,JVM在将byte转为Long时取byte作为最后1字节,其他7字节采用补码的方式填充为0xFFFFFFF,& 0xFF后就可以将前7位恢复为0x0000000保持原始的字节数据,详细可以参考byte为什么要与上0xff?这篇文章

TsDurationReader获取的时长主要通过下面几步

  1. 当流的长度已知(非直播流),从TS文件尾部查找第一个包含PCR的包的PCR值
  2. 从TS文件头部查找第一个包含PCR的包的PCR值
  3. 获取2者的差值即为时长,时间戳一般是以90 kHz 为单位再除以90000就是真实的时间戳了

好了回到主线,看下第一次的PAT解析都干了什么,由于PAT和PMT有着几乎相同的头结构,这里又抽象了一个SectionReader

SectionReader

看下公共头的解析过程

@Override
  public void consume(ParsableByteArray data, @Flags int flags) {
    boolean payloadUnitStartIndicator = (flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0;
    int payloadStartPosition = C.INDEX_UNSET;
    if (payloadUnitStartIndicator) {
      int payloadStartOffset = data.readUnsignedByte();
      payloadStartPosition = data.getPosition() + payloadStartOffset;
    }

    if (waitingForPayloadStart) {
      if (!payloadUnitStartIndicator) {
        return;
      }
      waitingForPayloadStart = false;
      data.setPosition(payloadStartPosition);
      bytesRead = 0;
    }

    while (data.bytesLeft() > 0) {//还有剩余数据
      if (bytesRead < SECTION_HEADER_LENGTH) {//解析前3字节
        // Note: see ISO/IEC 13818-1, section 2.4.4.3 for detailed information on the format of
        // the header.
        if (bytesRead == 0) {
          int tableId = data.readUnsignedByte();//获取tableId
          data.setPosition(data.getPosition() - 1);
          if (tableId == 0xFF /* forbidden value */) {//判断合法性
            // No more sections in this ts packet.
            waitingForPayloadStart = true;//跳过当前包
            return;
          }
        }
        int headerBytesToRead = min(data.bytesLeft(), SECTION_HEADER_LENGTH - bytesRead);
        // sectionData is guaranteed to have enough space because it's initialized with a 32-element
        // backing array and headerBytesToRead is at most 3.
        data.readBytes(sectionData.getData(), bytesRead, headerBytesToRead);
        bytesRead += headerBytesToRead;
        if (bytesRead == SECTION_HEADER_LENGTH) {//已将所有header数据读取到sectionData
          sectionData.setPosition(0);
          sectionData.setLimit(SECTION_HEADER_LENGTH);
          sectionData.skipBytes(1); //跳过tableid
          int secondHeaderByte = sectionData.readUnsignedByte();//读取头第2个字节
          int thirdHeaderByte = sectionData.readUnsignedByte();//读取头第3个字节
          sectionSyntaxIndicator = (secondHeaderByte & 0x80) != 0;//获取section_syntax_indicator
          totalSectionLength =//获取section_length
              (((secondHeaderByte & 0x0F) << 8) | thirdHeaderByte) + SECTION_HEADER_LENGTH;
          if (sectionData.capacity() < totalSectionLength) {//确保缓存够大能够放下body
            // Ensure there is enough space to keep the whole section.
            int limit =
                min(MAX_SECTION_LENGTH, max(totalSectionLength, sectionData.capacity() * 2));
            sectionData.ensureCapacity(limit);
          }
        }
      } else {
        // 读取body
        int bodyBytesToRead = min(data.bytesLeft(), totalSectionLength - bytesRead);
        // sectionData has been sized large enough for totalSectionLength when reading the header.
        data.readBytes(sectionData.getData(), bytesRead, bodyBytesToRead);
        bytesRead += bodyBytesToRead;
        if (bytesRead == totalSectionLength) {//已将所有body数据读取到sectionData
          if (sectionSyntaxIndicator) {
            // This section has common syntax as defined in ISO/IEC 13818-1, section 2.4.4.11.
            if (Util.crc32(sectionData.getData(), 0, totalSectionLength, 0xFFFFFFFF) != 0) {//首先CRC校验数据完整性
              // The CRC is invalid so discard the section.
              waitingForPayloadStart = true;
              return;
            }
            sectionData.setLimit(totalSectionLength - 4); // 去除最后的32位校验位
          } else {
            // This is a private section with private defined syntax.
            sectionData.setLimit(totalSectionLength);
          }
          sectionData.setPosition(0);
          reader.consume(sectionData);//将body喂给下个解析器,如果是PAT包这里调用PAT解析器解析
          bytesRead = 0;
        }
      }
    }
  }

SectionReader主要做了公共头的解析,至于body则交给PatReader或者PmtReader解析

PatReader

    @Override
    public void consume(ParsableByteArray sectionData) {
      int tableId = sectionData.readUnsignedByte();
      //PAT tableId 表固定为0x00
      if (tableId != 0x00 /* program_association_section */) {
        // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
        return;
      }
      // section_syntax_indicator(1), '0'(1), reserved(2), section_length(4)
      int secondHeaderByte = sectionData.readUnsignedByte();
      if ((secondHeaderByte & 0x80) == 0) {
        // section_syntax_indicator 必须为 1. See ISO/IEC 13818-1, section 2.4.4.5.
        return;
      }
      // 跳过section_length(8), transport_stream_id (16), reserved (2), version_number (5),
      // current_next_indicator (1), section_number (8), last_section_number (8)
      sectionData.skipBytes(6);

      int programCount = sectionData.bytesLeft() / 4;//一个PMT描述为4字节,计算有多少个PMT表
      for (int i = 0; i < programCount; i++) {
        sectionData.readBytes(patScratch, 4);
        int programNumber = patScratch.readBits(16);//program_number
        patScratch.skipBits(3); // reserved (3)
        if (programNumber == 0) {//program_number==0则为NIT网络信息表,直接跳过
          patScratch.skipBits(13); // network_PID (13)
        } else {
          int pid = patScratch.readBits(13);
          if (tsPayloadReaders.get(pid) == null) {//创建PMT解析器,当下次读取到PMT的包ID时直接调用PMT解析
            tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
            remainingPmts++;
          }
        }
      }
      if (mode != MODE_HLS) {
        tsPayloadReaders.remove(TS_PAT_PID);
      }
    }

PatReader主要工作就是将PMT表解析出来,每个PMT ID对应初始化出一个解析器,当下次读取到这些PID的包时采用对于的PmtReader

PmtReader

    @Override
    public void consume(ParsableByteArray sectionData) {
      int tableId = sectionData.readUnsignedByte();
      //确保是PMT表
      if (tableId != 0x02 /* TS_program_map_section */) {
        // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
        return;
      }
      // 处理时间戳
      TimestampAdjuster timestampAdjuster;
      if (mode == MODE_SINGLE_PMT || mode == MODE_HLS || remainingPmts == 1) {
        timestampAdjuster = timestampAdjusters.get(0);
      } else {
        timestampAdjuster =
            new TimestampAdjuster(timestampAdjusters.get(0).getFirstSampleTimestampUs());
        timestampAdjusters.add(timestampAdjuster);
      }

      // section_syntax_indicator(1), '0'(1), reserved(2), section_length(4)
      int secondHeaderByte = sectionData.readUnsignedByte();
      if ((secondHeaderByte & 0x80) == 0) {
        // section_syntax_indicator 必须为 1. See ISO/IEC 13818-1, section 2.4.4.9.
        return;
      }
      // section_length(8)
      sectionData.skipBytes(1);
      int programNumber = sectionData.readUnsignedShort();

      // Skip 3 bytes (24 bits), including:
      // reserved (2), version_number (5), current_next_indicator (1), section_number (8),
      // last_section_number (8)
      sectionData.skipBytes(3);

      sectionData.readBytes(pmtScratch, 2);
      // reserved (3), PCR_PID (13)
      pmtScratch.skipBits(3);
      pcrPid = pmtScratch.readBits(13);

      // Read program_info_length.
      sectionData.readBytes(pmtScratch, 2);
      pmtScratch.skipBits(4);
      int programInfoLength = pmtScratch.readBits(12);

      // Skip the descriptors.
      sectionData.skipBytes(programInfoLength);

      //初始化ID3解析器
      if (mode == MODE_HLS && id3Reader == null) {
        // Setup an ID3 track regardless of whether there's a corresponding entry, in case one
        // appears intermittently during playback. See [Internal: b/20261500].
        EsInfo id3EsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, Util.EMPTY_BYTE_ARRAY);
        id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, id3EsInfo);
        if (id3Reader != null) {
          id3Reader.init(
              timestampAdjuster,
              output,
              new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
        }
      }

      trackIdToReaderScratch.clear();
      trackIdToPidScratch.clear();
      int remainingEntriesLength = sectionData.bytesLeft();
      while (remainingEntriesLength > 0) {//开始解析PMT表数据
        sectionData.readBytes(pmtScratch, 5);
        int streamType = pmtScratch.readBits(8);
        pmtScratch.skipBits(3); // reserved
        int elementaryPid = pmtScratch.readBits(13);
        pmtScratch.skipBits(4); // reserved
        int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
        EsInfo esInfo = readEsInfo(sectionData, esInfoLength);//读取ESInfo数据
        //0x05 private_sections 0x06 PES packets containing private data
        if (streamType == 0x06 || streamType == 0x05) {
          streamType = esInfo.streamType;//使用esInfo的streamType
        }
        remainingEntriesLength -= esInfoLength + 5;

        int trackId = mode == MODE_HLS ? streamType : elementaryPid;
        if (trackIds.get(trackId)) {
          continue;
        }

        @Nullable
        TsPayloadReader reader =
            mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3
                ? id3Reader
                //根据streamType创建对应的解析器,后面会分析
                : payloadReaderFactory.createPayloadReader(streamType, esInfo);
        if (mode != MODE_HLS
            || elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) {
          trackIdToPidScratch.put(trackId, elementaryPid);
          trackIdToReaderScratch.put(trackId, reader);//用于后续获取
        }
      }

      int trackIdCount = trackIdToPidScratch.size();
      for (int i = 0; i < trackIdCount; i++) {
        int trackId = trackIdToPidScratch.keyAt(i);
        int trackPid = trackIdToPidScratch.valueAt(i);
        trackIds.put(trackId, true);
        trackPids.put(trackPid, true);
        @Nullable TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
        if (reader != null) {
          if (reader != id3Reader) {
          //初始化所有解析器
            reader.init(
                timestampAdjuster,
                output,
                new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE));
          }
          tsPayloadReaders.put(trackPid, reader);
        }
      }

      if (mode == MODE_HLS) {
        if (!tracksEnded) {
          output.endTracks();
          remainingPmts = 0;
          tracksEnded = true;
        }
      } else {
        tsPayloadReaders.remove(pid);//解析完成移除当前PMT解析器
        remainingPmts = mode == MODE_SINGLE_PMT ? 0 : remainingPmts - 1;
        if (remainingPmts == 0) {//所以PMT表都已读取
          output.endTracks();//endTracks,这个时候相当于MediaPeriod的prepare过程结束,已经获取到播放媒体的相关数据
          tracksEnded = true;
        }
      }
    }

PmtReader主要作用就是获取其中的流类型,然后创建出对应的解析器,最后所以PMT初始化完成后通知上层trackEnded
那么解析器具体是如何创建的呢,这部分工作PmtReader交由payloadReaderFactory,默认实现了DefaultTsPayloadReaderFactory

DefaultTsPayloadReaderFactory

这个createPayloadReader方法里基本上将所有的流类型创建了解析器,可以当一个索引看下

  @Override
  @Nullable
  public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
    switch (streamType) {
      case TsExtractor.TS_STREAM_TYPE_MPA:
      case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
        return new PesReader(new MpegAudioReader(esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_AAC_ADTS:
        return isSet(FLAG_IGNORE_AAC_STREAM)
            ? null
            : new PesReader(new AdtsReader(false, esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_AAC_LATM:
        return isSet(FLAG_IGNORE_AAC_STREAM)
            ? null
            : new PesReader(new LatmReader(esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_AC3:
      case TsExtractor.TS_STREAM_TYPE_E_AC3:
        return new PesReader(new Ac3Reader(esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_AC4:
        return new PesReader(new Ac4Reader(esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
        if (!isSet(FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS)) {
          return null;
        }
        // Fall through.
      case TsExtractor.TS_STREAM_TYPE_DTS:
        return new PesReader(new DtsReader(esInfo.language));
      case TsExtractor.TS_STREAM_TYPE_H262:
      case TsExtractor.TS_STREAM_TYPE_DC2_H262:
        return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
      case TsExtractor.TS_STREAM_TYPE_H263:
        return new PesReader(new H263Reader(buildUserDataReader(esInfo)));
      case TsExtractor.TS_STREAM_TYPE_H264:
        return isSet(FLAG_IGNORE_H264_STREAM)
            ? null
            : new PesReader(
                new H264Reader(
                    buildSeiReader(esInfo),
                    isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES),
                    isSet(FLAG_DETECT_ACCESS_UNITS)));
      case TsExtractor.TS_STREAM_TYPE_H265:
        return new PesReader(new H265Reader(buildSeiReader(esInfo)));
      case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
        return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
            ? null
            : new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_SCTE35));
      case TsExtractor.TS_STREAM_TYPE_ID3:
        return new PesReader(new Id3Reader());
      case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
        return new PesReader(new DvbSubtitleReader(esInfo.dvbSubtitleInfos));
      case TsExtractor.TS_STREAM_TYPE_AIT:
        return new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_AIT));
      default:
        return null;
    }
  }

这里关注下目前比较主流的H.264,可以看到首先是创建Pes解析器解析PES,然后从PES中解析H.264数据,组后在H.264数据中解析SEI信息

PesReader

看下PES如何解析

@Override
  public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException {
    Assertions.checkStateNotNull(timestampAdjuster); // Asserts init has been called.

    //一个状态机
...

    while (data.bytesLeft() > 0) {
      switch (state) {
        case STATE_FINDING_HEADER:
          data.skipBytes(data.bytesLeft());
          break;
        case STATE_READING_HEADER:
          if (continueRead(data, pesScratch.data, HEADER_SIZE)) {
            setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER);
          }
          break;
        case STATE_READING_HEADER_EXTENSION:
          int readLength = min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength);
          // Read as much of the extended header as we're interested in, and skip the rest.
          if (continueRead(data, pesScratch.data, readLength)
              && continueRead(data, /* target= */ null, extendedHeaderLength)) {
            parseHeaderExtension();
            flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0;
            reader.packetStarted(timeUs, flags);
            setState(STATE_READING_BODY);
          }
          break;
        case STATE_READING_BODY:
          readLength = data.bytesLeft();
          int padding = payloadSize == C.LENGTH_UNSET ? 0 : readLength - payloadSize;
          if (padding > 0) {
            readLength -= padding;
            data.setLimit(data.getPosition() + readLength);
          }
          reader.consume(data);//调用下层解析器解析body,如H264Reader
          if (payloadSize != C.LENGTH_UNSET) {
            payloadSize -= readLength;
            if (payloadSize == 0) {
              reader.packetFinished();
              setState(STATE_READING_HEADER);
            }
          }
          break;
        default:
          throw new IllegalStateException();
      }
    }
  }
  //解析header
  private boolean parseHeader() {
    // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of
    // the header.
    pesScratch.setPosition(0);
    int startCodePrefix = pesScratch.readBits(24);
    if (startCodePrefix != 0x000001) {//校验合法性
      Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix);
      payloadSize = C.LENGTH_UNSET;
      return false;
    }

    pesScratch.skipBits(8); // stream_id.
    int packetLength = pesScratch.readBits(16);//获取长度
    pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1)
    dataAlignmentIndicator = pesScratch.readBit();
    pesScratch.skipBits(2); // copyright (1), original_or_copy (1)
    ptsFlag = pesScratch.readBit();//PTS_flags
    dtsFlag = pesScratch.readBit();//DTS_flags
    // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
    // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1)
    pesScratch.skipBits(6);
    extendedHeaderLength = pesScratch.readBits(8);//获取额外数据长度用于DTS PTS解析
...
    return true;
  }
  
  @RequiresNonNull("timestampAdjuster")
  private void parseHeaderExtension() {//解析出PTS和DTS用于后续H264解析器
    pesScratch.setPosition(0);
    timeUs = C.TIME_UNSET;
    if (ptsFlag) {
      pesScratch.skipBits(4); // '0010' or '0011'
      long pts = (long) pesScratch.readBits(3) << 30;
      pesScratch.skipBits(1); // marker_bit
      pts |= pesScratch.readBits(15) << 15;
      pesScratch.skipBits(1); // marker_bit
      pts |= pesScratch.readBits(15);
      pesScratch.skipBits(1); // marker_bit
      if (!seenFirstDts && dtsFlag) {
        pesScratch.skipBits(4); // '0011'
        long dts = (long) pesScratch.readBits(3) << 30;
        pesScratch.skipBits(1); // marker_bit
        dts |= pesScratch.readBits(15) << 15;
        pesScratch.skipBits(1); // marker_bit
        dts |= pesScratch.readBits(15);
        pesScratch.skipBits(1); // marker_bit
        timestampAdjuster.adjustTsTimestamp(dts);
        seenFirstDts = true;
      }
      timeUs = timestampAdjuster.adjustTsTimestamp(pts);
    }
  }

PesReader主要是将Pes的Header解析,获取ES数据的长度,以及PES与DTS数据,然后将这些数据传递给下层ES解析器。


总结

关于TsExtractor的内容先写到这里,ES解析器可能比TS更加复杂,计划将ES解析的内容单独一篇来解析,计划以目前最为普遍的H.264格式作为分析对象,也就是对应ExoPlayer中的H264Reader。


版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 阻塞机制及等待队列

原文地址: http://www.cnblogs.com/gdk-0078/p/5172941.html 阻塞与非阻塞是设备访问的两种方式。驱动程序需要提供阻塞&#xff08;等待队列&#xff0c;中断&#xff09;和非阻塞方式&#xff08;轮询&#xff0c;异步通知&#xff09;访问设备。在写阻塞与非阻塞的驱动程序时…

DB9串口引脚介绍

一、公头和母头 图片示意源于网络: 二、 每个引脚的功能定义 公头&#xff1a;所有排针式的接头&#xff08;5针朝上&#xff0c;从左到右序号依次是1~9&#xff09; 母头&#xff1a;所有插槽式的接孔&#xff08;5孔朝上&#xff0c;从右到左序号依次是1~9&#xff09; 针…

Go 之 captcha 生成图像验证码

目前 chptcha 好像只可以生成纯数字的图像验证码&#xff0c;不过对于普通简单应用来说也足够了。captcha默认将store封装到内部&#xff0c;未提供对外操作的接口&#xff0c;因此使用自己显式生成的store&#xff0c;可以通过store自定义要生成的验证码。 package mainimpor…

“升级图片管理,优化工作流程——轻松将JPG转为PNG“

在图片时代&#xff0c;无论是工作还是生活&#xff0c;图片管理都显得尤为重要。批量处理图片&#xff0c;将JPG格式轻松转换为PNG格式&#xff0c;能够使您的图片管理更优化&#xff0c;提高工作效率。 首先&#xff0c;我们进入首助编辑高手主页面&#xff0c;会看到有多种…

Springboot更新用户密码

UserController PatchMapping("/updatePwd")//RequestBody注解&#xff0c;mvc框架才能自动的去读取请求体里的json数据&#xff0c;转换成map集合对象public Result updatePwd(RequestBody Map<String,String> params){//1.校验数据String oldPwd params.get…

Leetcode——最长递增子序列

1. 题目链接&#xff1a;300. 最长递增子序列 2. 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&a…

C++项目案例圆和点的关系 (涉及知识点:头文件定义类,cpp文件实现类,类和作用域,linux编译运行c++项目)

一.项目描述 点与圆有三种关系&#xff1a; 点在圆外 点在圆上 点在圆内计算点到圆心的距离就能判断点在圆的哪个地方。二.项目结构 三.include文件 3.1 Circle类的声明 Circle.h // 防止头文件重复包含 #pragma once // #include<iostream> #include "Point.h&…

JPA整合Sqlite解决Dialect报错问题, 最新版Hibernate6

前言 我个人项目中&#xff0c;不想使用太重的数据库&#xff0c;而内嵌数据库中SQLite又是最受欢迎的&#xff0c; 因此决定采用这个数据库。 可是JPA并不支持Sqlite&#xff0c;这篇文章就是记录如何解决这个问题的。 原因 JPA屏蔽了底层的各个数据库差异&#xff0c; 但是…

【每日一题】数位和相等数对的最大和

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希表 写在最后 Tag 【哈希表】【数组】【2023-11-18】 题目来源 2342. 数位和相等数对的最大和 题目解读 在数组中找出数位和相等数对的和的最大值。 解题思路 方法一&#xff1a;哈希表 维护一个不同的数位和表…

36 mysql 主键冲突 和 唯一索引冲突

前言 我们这里 来看一下 我们经常碰到的 "duplicate key xxx" 测试表结构如下 CREATE TABLE tz_test (id int(11) unsigned NOT NULL AUTO_INCREMENT,field1 varchar(128) DEFAULT NULL,PRIMARY KEY (id) USING BTREE,KEY field1 (field1) USING BTREE ) ENGINEI…

upload-labs关卡9(基于win特性data流绕过)通关思路

文章目录 前言一、靶场需要了解的知识1::$data是什么 二、靶场第九关通关思路1、看源码2、bp抓包修改后缀名3、检查是否成功上传 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚未授…

ACM练习——第五天

还有两天就要比赛了&#xff0c;进入正题吧 题目一&#xff1a;小红的签到题 小红的签到题 (nowcoder.com) 这道题也就是热身水平&#xff0c;机会很清楚的发现只需要c/a就可以得出答案了 参考代码&#xff1a; #include <iostream>using namespace std;int main(){int a…

动态头像如何制作?这个方法请收藏

照片是记录生活的一种方式&#xff0c;但是静态图片有时候不能够完全表达我们的情感。而动态的图片能够让图片以更生动的方式来展示我们的想象力和内心情感。那么&#xff0c;大家知道动态图片制作的方法有哪些吗&#xff1f;使用gif动画制作&#xff08;https://www.gif.cn/&a…

【Linux系统编程十九】:(进程通信)--匿名管道/模拟实现进程池

【Linux系统编程十九】&#xff1a;匿名管道原理/模拟实现进程池 一.进程通信理解二.通信实现原理三.系统接口四.五大特性与四种情况五.应用场景--进程池 一.进程通信理解 什么是通信&#xff1f; 通信其实就是一个进程想把数据给另一个进程&#xff0c;但因为进程具有独立性…

使用ADS进行serdes仿真时,Tx_Diff中EQ的设置对发送端波形的影响。

研究并记录一下ADS仿真中Tx_Diff的EQ设置。原理图如下&#xff1a; 最上面是选择均衡方法Choose equalization method&#xff1a;Specify FIR taps&#xff0c;Specify de-emphasis和none。 当选择Specify de-emphasis选项时&#xff0c;下方可以输入去加重具体的dB值&#x…

泛微E-Cology CheckServer.jspSQL注入漏洞(QVD-2023-9849) 复现

泛微E-Cology CheckServer.jspSQL注入漏洞(QVD-2023-9849) 复现 1.漏洞描述 泛微 Ecology OA 系统对用户传入的数据过滤处理不当&#xff0c;导致存在 SQL 注入漏洞&#xff0c;未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令&#xff0c;从而窃取数据库敏感信息。 …

深度学习交通车辆流量分析 - 目标检测与跟踪 - python opencv 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 DeepSORT车辆跟踪3.1 Deep SORT多目标跟踪算法3.2 算法流程 4 YOLOV5算法4.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

python_面向对象中的特殊成员

一、几个常见的特殊成员 # 都只是语法&#xff0c;无特殊意义 class Foo(object):def __init__(self,a1,a2):self.a1 a1self.a2 a2def __call__(self,*args,**kwargs):print(11111,args,kwargs)return 123def __getitem__(self, item):print(item)return 8def __setitem__(s…

简单算法——回溯、贪心、动态规划

回溯法 回溯法深度优先剪枝&#xff0c;实质就是用递归代替for循环。 仍然是一种暴力遍历的手段&#xff0c;通常与递归配合使用&#xff0c;用于解决单纯for循环无法处理的问题&#xff0c;比如组合、切割、子集、排列等问题——比如求n个数里的长度为k的组合&#xff0c;需要…