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


前言

上篇说完了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_byte 8 同步标记占1个字节,固定为0x47,当解析器读取到这个字节的时候就知道这是一个包开始位置
    transport_error_indicator 1 传输错误指示符,1'表示在相关的传输包中至少有一个不可纠正的错误位。当被置1后,在错误被纠正之前不能重置为0
    payload_unit_start_indicator 1 负载单元起始标示符,一个完整的数据包开始时标记为1
    transport_priority 1 传输优先级,0为低优先级,1为高优先级,通常取0
    pid 13 包的 ID,用于区分不同的包,注意这个不是唯一的,可能相同类型的包都对应同一个PID,其中PID有几个固定值用于指定类型的包,如PAT包固定值为0x0000
    transport_scrambling_control 2 传输加扰控制,00表示未加密
    adaptation_field_control 2 是否包含自适应区,'00'保留;'01'为无自适应域,仅含有效负载;'10'为仅含自适应域,无有效负载;'11'为同时带有自适应域和有效负载。
    continuity_counter 4 递增计数器,从0-f,起始值不一定取0,但必须是连续的,随着每一个具有相同PID的TS流分组而增加,当它达到最大值后又回复到0。范围为0~15。接收端可判断是否有包丢失及包传送顺序错误
    adaptation_field_length 8 自适应域长度,包含在上图的PRC分段中
    flag 8 取0x50表示包含PCR或0x40表示不包含PCR,包含在上图的PRC分段中
    PCR 40 Program 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_id 8 PAT表固定为0x00
      section_syntax_indicator 1 固定为1
      zero 1 固定为0
      reserved 2 固定为11
      section_length 12 后面数据的长度
      transport_stream_id 16 传输流ID,固定为0x0001
      reserved 2 固定为11
      version_number 5 版本号,固定为00000,如果PAT有变化则版本号加1
      current_next_indicator 1 固定为1,表示这个PAT表可以用,如果为0则要等待下一个PAT表
      section_number 8 固定为0x00
      last_section_number 8 固定为0x00
      开始循环
      program_number 16 为0x0000时表示这是NIT网络信息表,节目号为0x0001时,表示这是PMT
      reserved 3 固定为111
      PID 13 PAT对应PMT的包PID值
      结束循环
      CRC32 32 前面数据的CRC32校验码
    • PMT表,包头的PID不固定,需要通过PAT获取,主要列出了包含的所有流类型及其对于的PID,通过它可以确定当前的包对应的是哪种流,然后针对性的解析,下面是PMT的表结构

      名称 大小(b) 说明
      table_id 8 PMT表固定为0x02
      section_syntax_indicator 1 固定为1
      zero 1 固定为0
      reserved 2 固定为11
      section_length 12 后面数据的长度
      program_number 16 频道号码,表示当前的PMT关联到的频道,取值0x0001
      reserved 2 固定为11
      version_number 5 版本号,固定为00000,如果PAT有变化则版本号加1
      current_next_indicator 1 固定为1
      section_number 8 固定为0x00
      last_section_number 8 固定为0x00
      reserved 3 固定为111
      PCR_PID 13 PCR(节目参考时钟)所在TS分组的PID,指定为视频PID
      reserved 3 固定为111
      program_info_length 12 描述信息,指定为0x000表示没有
      开始循环
      stream_type 8 流类型,标志是Video还是Audio还是其他数据,h.264编码对应0x1b,aac编码对应0x0f,mp3编码对应0x03
      reserved 3 固定为111
      elementary_PID 13 与stream_type对应的PID
      reserved 4 固定为1111
      ES_info_length 12 描述信息,指定为0x000表示没有
      结束循环
      CRC32 32 前面数据的CRC32校验码
    • PES 用于承载基本流数据的数据结构,可以理解成具体的媒体流数据,同样包含header和body,看下包结构图 PES的Header结构很复杂,这里我们说明下重要的几个

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

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

java 复制代码
  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是否适用

java 复制代码
  //在确定使用哪种解析器时会先调用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

java 复制代码
@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

java 复制代码
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

看下公共头的解析过程

java 复制代码
@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

java 复制代码
    @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

java 复制代码
    @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方法里基本上将所有的流类型创建了解析器,可以当一个索引看下

java 复制代码
  @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如何解析

java 复制代码
@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。


版权声明 ©

本文为作者山雨楼原创文章

转载请注明出处

原创不易,觉得有用的话,收藏转发点赞支持

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
车载诊断技术5 小时前
电子电气架构 --- 电子电器新技术及发展趋势
网络·架构·汽车·电子电器框架·车载充电器(obc)·电子电器新技术及发展趋势
呱牛do it5 小时前
【系列专栏】银行IT的云原生架构-混合云弹性架构 13
微服务·云原生·金融·架构
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
uhakadotcom6 小时前
约束求解领域的最新研究进展
人工智能·面试·架构
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
关键帧Keyframe9 小时前
音视频面试题集锦第 19 期 | 读取纹理数据
ios·图像识别·音视频开发