修改ExoPlayer源码播放hls显示多音轨

最近在研究哪种播放器能够支持drm视频播放,查找了一些资料,比较多的都是推荐谷歌播放器ExoPlayer,测试drm是可以正常播放,但是无法显示出多音轨,默认都是只有一条音轨和一条字幕轨,使用了官方提供的测试链接,发现官方的可以正常显示出来,于是我对比了公司和官方的m3u8文件,发现官方的会多一个这样的信息

#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2218327,BANDWIDTH=2227464,CODECS="avc1.640020,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"

也就是#EXT-X-STREAM-INF,这个表示该m3u8流的一些音频轨、字幕轨等信息,既然找到了这个差别,那么在ExoPlayer例子中全局查找关于这个标志,在HlsPlaylistParser这个类中有关于这个标志的定义

private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF";

接下来我们可以找找关于这个标志位的使用

在HlsPlaylistParser类中的parse(Uri uri, InputStream inputStream)函数,有对这个标志位的判断

if (line.isEmpty()) {
  // Do nothing.
} else if (line.startsWith(TAG_STREAM_INF)) {//有TAG_STREAM_INF的走这里
  extraLines.add(line);
  return parseMasterPlaylist(new LineIterator(extraLines, reader), uri.toString());
} else if (line.startsWith(TAG_TARGET_DURATION)
    || line.startsWith(TAG_MEDIA_SEQUENCE)
    || line.startsWith(TAG_MEDIA_DURATION)
    || line.startsWith(TAG_KEY)
    || line.startsWith(TAG_BYTERANGE)
    || line.equals(TAG_DISCONTINUITY)
    || line.equals(TAG_DISCONTINUITY_SEQUENCE)
    || line.equals(TAG_ENDLIST)) {
    //无TAG_STREAM_INF的走这里(公司的流就是走的这里)
  extraLines.add(line);
  return parseMediaPlaylist(
      masterPlaylist, new LineIterator(extraLines, reader), uri.toString());
} else {
  extraLines.add(line);
}

很明显,有无TAG_STREAM_INF处理是不一样的,让我们看看parseMasterPlaylist这个函数

从上面可以看出该函数定义了关于audios、videos、subtitles等的ArrayList数组,中间省略的就是对m3u8文件中关于音频、视频、字幕轨道信息的读取,然后再赋值给audios、videos、subtitles,而parseMediaPlaylist函数中则没有相关音频、视频、字幕轨道信息的读取

上面好像看不出什么有用的信息,那么我们从音轨只有一条入手,从选择轨道的代码中看起,在TrackSelectionDialog类的createForTrackSelector函数中,有设置轨道的代码

if (!overrides.isEmpty()) {
  builder.setSelectionOverride(
      /* rendererIndex= */ i,
      mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i),
      overrides.get(0));
}

 

我们可以从 mappedTrackInfo.getTrackGroups(i)入手,getTrackGroups函数获取到的是TrackGroupArray[],这里的TrackGroupArray[]表示有多少个轨道组,而TrackGroupArray类中有TrackGroup[],表示了该类型的轨道下有多少个轨道信息,那么我们可以看看哪里对这个TrackGroup[]赋值

public TrackGroupArray(TrackGroup... trackGroups) {
  this.trackGroups = trackGroups;
  this.length = trackGroups.length;
}

接下来查找哪里调用了TrackGroupArray的构造函数,因为我们使用的是hls,所以对查找结果做了一些过滤,剩下了这2个地方

经过对这2处地方的研究,HlsMediaPeriod类中有一个HlsSampleStreamWrapper[] sampleStreamWrappers

@Override
public void onPrepared() {
  if (--pendingPrepareCount > 0) {
    return;
  }
  int totalTrackGroupCount = 0;
  for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
    totalTrackGroupCount += sampleStreamWrapper.getTrackGroups().length;
  }
  TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
  int trackGroupIndex = 0;
  for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
    int wrapperTrackGroupCount = sampleStreamWrapper.getTrackGroups().length;
    for (int j = 0; j < wrapperTrackGroupCount; j++) {
      trackGroupArray[trackGroupIndex++] = sampleStreamWrapper.getTrackGroups().get(j);
    }
  }
  trackGroups = new TrackGroupArray(trackGroupArray);
  callback.onPrepared(this);
}

在HlsMediaPeriod的onPrepared函数中,最后有对trackGroups的赋值操作,而且trackGroups的长度则是sampleStreamWrappers数组中每个轨道组长度之和,那么接下来应该重点研究sampleStreamWrappers数组中每个轨道是如何赋值的,通过TrackGroupArray构造函数在HlsSampleStreamWrapper类中的使用,我们可以在HlsSampleStreamWrapper类中找到createTrackGroupArrayWithDrmInfo(TrackGroup[] trackGroups)这个函数,在定位该函数的调用,一共有2处,buildTracksFromSampleStreams()和prepareWithMasterPlaylistInfo(),根据名称我们可以很容易排除prepareWithMasterPlaylistInfo

@EnsuresNonNull({"trackGroups", "optionalTrackGroups", "trackGroupToSampleQueueIndex"})
private void buildTracksFromSampleStreams() {
  android.util.Log.e("zbj", "HlsSampleStreamWrapper" + "_" + "buildTracksFromSampleStreams: ");
  // Iterate through the extractor tracks to discover the "primary" track type, and the index
  // of the single track of this type.
  int primaryExtractorTrackType = C.TRACK_TYPE_NONE;
  int primaryExtractorTrackIndex = C.INDEX_UNSET;
  int extractorTrackCount = sampleQueues.length;
  ...
  TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup();
  int chunkSourceTrackCount = chunkSourceTrackGroup.length;

  // Instantiate the necessary internal data-structures.
  primaryTrackGroupIndex = C.INDEX_UNSET;
  trackGroupToSampleQueueIndex = new int[extractorTrackCount];
  for (int i = 0; i < extractorTrackCount; i++) {
    trackGroupToSampleQueueIndex[i] = i;
  }

  // Construct the set of exposed track groups.
  TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
  ...
  this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups);
  android.util.Log.e("HlsSampleStreamWrapper",
      "_buildTracksFromSampleStreams , " + "trackGroups.size = " + trackGroups.length);
  Assertions.checkState(optionalTrackGroups == null);
  optionalTrackGroups = Collections.emptySet();
}

在buildTracksFromSampleStreams函数中,我们可以看到trackGroups的长度与sampleQueues的长度有关

private FormatAdjustingSampleQueue[] sampleQueues;

接下来,继续查找sampleQueues的赋值,在createSampleQueue(int id, int type)函数中

private SampleQueue createSampleQueue(int id, int type) {
  int trackCount = sampleQueues.length;

  boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
  FormatAdjustingSampleQueue trackOutput =
      new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData,
          type);
  if (isAudioVideo) {
    trackOutput.setDrmInitData(drmInitData);
  }
  trackOutput.setSampleOffsetUs(sampleOffsetUs);
  trackOutput.sourceId(chunkUid);
  trackOutput.setUpstreamFormatChangeListener(this);
  sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
  sampleQueueTrackIds[trackCount] = id;
  sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
  ...
}

最后一行就是对sampleQueues做扩展,而该函数只有被一个函数引用,那就是track(int id, int type),当我们点击track引用到的类时,大部分类都是...Reader

上面比较明显就是一些音频和视频解码相关的,我们随机点击一个进入,基本都是createTracks这个函数调用,我们再次跟踪createTracks这个函数调用

大部分都是提取器,提取器看名称就知道是专门对应,而有一个PesReader,这个所有视频都会用到,我们进入PesReader调用处

@Override
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
    TrackIdGenerator idGenerator) {
  this.timestampAdjuster = timestampAdjuster;
  reader.createTracks(extractorOutput, idGenerator);//run 3 times -zbj
}

继续跟踪init函数,发现都是在TsExtractor类中consume(ParsableByteArray sectionData)函数中调用,这个函数是对视频pid、pmt等的读取,其中trackIds判断了当前是否包含了trackId ,如果有的话就不走下面init函数了,通过打印日志,证实了音轨的init都只走了1次

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

我们注意到trackId在HLS的情况是只取streamType的,我们可以修改为

int trackId =  elementaryPid;

这样trackId就不会有重复了,保证都走到下面的init创建轨道的流程

虽然上面修改后保证了都有走init函数,但是测试结果还是不理想,轨道信息还是只有一条,通过对HlsSampleStreamWrapper类的track(int id, int type)函数加打印,发现createSampleQueue函数只走了1遍,在track函数中

@Override
public TrackOutput track(int id, int type) {
  @Nullable TrackOutput trackOutput = null;
  if (MAPPABLE_TYPES.contains(type)) {
    // Track types in MAPPABLE_TYPES are handled manually to ignore IDs.
    trackOutput = getMappedTrackOutput(id, type);
  } else /* non-mappable type track */ {
    for (int i = 0; i < sampleQueues.length; i++) {
      if (sampleQueueTrackIds[i] == id) {
        trackOutput = sampleQueues[i];
        break;
      }
    }
  }

  if (trackOutput == null) {
    if (tracksEnded) {
      return createDummyTrackOutput(id, type);
    } else {
      // The relevant SampleQueue hasn't been constructed yet - so construct it.
      trackOutput = createSampleQueue(id, type);
    }
  }
  ...
}

MAPPABLE_TYPES是一个存放Integer的Set集合,要想走createSampleQueue函数,那得保证trackOutput 为空,可以看到上面对trackOutput 的赋值,首先从MAPPABLE_TYPES中查找,没有的话再从sampleQueues中查找,到这里我们可以对MAPPABLE_TYPES的判断做一下修改,让MAPPABLE_TYPES去存放id,而非type,这样根据id不同就会走createSampleQueue函数,也就添加全部的轨道信息了

if (MAPPABLE_TYPES.contains(id)) {

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值