Exoplayer源码解析3之解封装器解析

前言

第一篇介绍了数据的加载流程,但是没有分析具体 数据如何解封装后,拿到需要的数据包,这里稍微介绍一下,本篇大部分代码都集中在extractor模块当中。

正文

根本就是Extractor接口,以及配合使用的ExtractorInput和ExtractorOutput。对外提供的接口则是通过DefaultExtractorsFactory和DefaultExtractorInput。这里代码结构非常简单,不在详细介绍.
一个典型的的extral初始化代码如下:

	ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length);
    this.extractorInput = extractorInput;
    if (extractor != null) {
    
    
      return;
    }
	Extractor[] extractors = extractorsFactory.createExtractors(uri, responseHeaders);
    if (extractors.length == 1) {
    
    
      this.extractor = extractors[0];
    } else {
    
    
      for (Extractor extractor : extractors) {
    
    
        try {
    
    
          if (extractor.sniff(extractorInput)) {
    
    
            this.extractor = extractor;
            break;
          }
        } catch (EOFException e) {
    
    
          // Do nothing.
        } finally {
    
    
          Assertions.checkState(this.extractor != null || extractorInput.getPosition() == position);
          extractorInput.resetPeekPosition();
        }
      }
      if (extractor == null) {
    
    
        throw new UnrecognizedInputFormatException(
            "None of the available extractors ("
                + Util.getCommaDelimitedSimpleClassNames(extractors)
                + ") could read the stream.",
            Assertions.checkNotNull(uri));
      }
    }
    extractor.init(output);

先拿到input和output。然后根据特定的格式加载解封装器,最终通过sniff接口测试次文件是否可以通过我们解封装器的解析,如果资源文件无法通过后缀或者header拿到格式,则吧所有解封装器列表返还回来,挨个测试,output真实类其实就是ProgressiveMediaPeriod。核心接口就是track。返回各个多媒体轨道的容器。代码如下

  @Override
  public TrackOutput track(int id, int type) {
    
    
    return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));
  }
    private TrackOutput prepareTrackOutput(TrackId id) {
    
    
    int trackCount = sampleQueues.length;
    for (int i = 0; i < trackCount; i++) {
    
    
      if (id.equals(sampleQueueTrackIds[i])) {
    
    
        return sampleQueues[i];
      }
    }
    SampleQueue trackOutput =
        SampleQueue.createWithDrm(
            allocator,
            /* playbackLooper= */ handler.getLooper(),
            drmSessionManager,
            drmEventDispatcher);
    trackOutput.setUpstreamFormatChangeListener(this);
    @NullableType
    TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
    sampleQueueTrackIds[trackCount] = id;
    this.sampleQueueTrackIds = Util.castNonNullTypeArray(sampleQueueTrackIds);
    @NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
    sampleQueues[trackCount] = trackOutput;
    this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
    return trackOutput;
  }

这代码其实就是比较简单,把数据存在sampleQueues这个数组中,然后供Renderer取。而SampleQueue 其实也不算复杂,主要提供一个read接口,返回SampleDataQueue中的元素,而SampleDataQueue是一个多媒体数据的队列,方便管理缓存。这些代码都比较简单,不在详细介绍。
下面回到DefaultExtractorInput,这是一个核心是一个Filedatasource。其实主要就是实现了read的接口如下:

@Override
  public int read(byte[] buffer, int offset, int length) throws FileDataSourceException {
    
    
    if (length == 0) {
    
    
      return 0;
    } else if (bytesRemaining == 0) {
    
    
      return C.RESULT_END_OF_INPUT;
    } else {
    
    
      int bytesRead;
      try {
    
    
        bytesRead = castNonNull(file).read(buffer, offset, (int) min(bytesRemaining, length));
      } catch (IOException e) {
    
    
        throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
      }

      if (bytesRead > 0) {
    
    
        bytesRemaining -= bytesRead;
        bytesTransferred(bytesRead);
      }

      return bytesRead;
    }
  }

而DefaultExtractorInput的核心接口则同样是read,不过额外提供一些方便一些的接口,比如skip等,方便使用(内置offsite)。
而真的解封装则是通过read实现的,
我们只介绍一下mkv的解封装模块,只是简要介绍,
检测机制则比较简单:

public boolean sniff(ExtractorInput input) throws IOException {
    
    
    long inputLength = input.getLength();
    //最多寻找1024个字节,其实就是找mkv的标志位,就是0x1A45DFA3
    int bytesToSearch =
        (int)
            (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH
                ? SEARCH_LENGTH
                : inputLength);
    // Find four bytes equal to ID_EBML near the start of the input.
    input.peekFully(scratch.getData(), 0, 4);
    long tag = scratch.readUnsignedInt();
    peekLength = 4;
    while (tag != ID_EBML) {
    
    
      if (++peekLength == bytesToSearch) {
    
    
        return false;
      }
      input.peekFully(scratch.getData(), 0, 1);
      tag = (tag << 8) & 0xFFFFFF00;
      tag |= scratch.getData()[0] & 0xFF;
    }

    // Read the size of the EBML header and make sure it is within the stream.这里是确认mkv是否有头
    long headerSize = readUint(input);
    long headerStart = peekLength;
    if (headerSize == Long.MIN_VALUE
        || (inputLength != C.LENGTH_UNSET && headerStart + headerSize >= inputLength)) {
    
    
      return false;
    }

    // Read the payload elements in the EBML header.跳过头,供后续步骤读取内容
    while (peekLength < headerStart + headerSize) {
    
    
      long id = readUint(input);
      if (id == Long.MIN_VALUE) {
    
    
        return false;
      }
      long size = readUint(input);
      if (size < 0 || size > Integer.MAX_VALUE) {
    
    
        return false;
      }
      if (size != 0) {
    
    
        int sizeInt = (int) size;
        input.advancePeekPosition(sizeInt);
        peekLength += sizeInt;
      }
    }
    return peekLength == headerStart + headerSize;
  }

核心就是检测mkv标志位以及跳过mkv头信息。
真的读取则则需要解析EBML,然后分析内容,具体不在详细介绍,后续补充。

后记

解封装模块比较简单,核心问题是各种封装格式的编码,以及规定,这个各种文档比较充分,这里就不在详细介绍。

猜你喜欢

转载自blog.csdn.net/qq_28282317/article/details/126811957