最近在研究哪種播放器能夠支援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函數,也就添加全部的軌道資訊了