mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add DashTest to V2
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=124831095
This commit is contained in:
parent
f4e4fd51c6
commit
266c0dce40
5 changed files with 587 additions and 58 deletions
|
|
@ -135,6 +135,7 @@ public interface AudioTrackRendererEventListener {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
counters.ensureUpdated();
|
||||||
listener.onAudioDisabled(counters);
|
listener.onAudioDisabled(counters);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ public abstract class TrackSelectionPolicy {
|
||||||
|
|
||||||
private InvalidationListener listener;
|
private InvalidationListener listener;
|
||||||
|
|
||||||
|
/* package */ final void init(InvalidationListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be invoked by subclasses when a selection parameter has changed, invalidating previous
|
* Must be invoked by subclasses when a selection parameter has changed, invalidating previous
|
||||||
* selections.
|
* selections.
|
||||||
|
|
@ -55,13 +59,9 @@ public abstract class TrackSelectionPolicy {
|
||||||
* defined by the {@link TrackRenderer} {@code FORMAT_*} constants.
|
* defined by the {@link TrackRenderer} {@code FORMAT_*} constants.
|
||||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||||
*/
|
*/
|
||||||
/* package */ abstract TrackSelection[] selectTracks(TrackRenderer[] renderers,
|
protected abstract TrackSelection[] selectTracks(TrackRenderer[] renderers,
|
||||||
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
|
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
|
||||||
throws ExoPlaybackException;
|
throws ExoPlaybackException;
|
||||||
|
|
||||||
/* package */ void init(InvalidationListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ public interface VideoTrackRendererEventListener {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
counters.ensureUpdated();
|
||||||
listener.onVideoDisabled(counters);
|
listener.onVideoDisabled(counters);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,548 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.playbacktests.gts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.CodecCounters;
|
||||||
|
import com.google.android.exoplayer.DecoderInfo;
|
||||||
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.MediaCodecUtil;
|
||||||
|
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||||
|
import com.google.android.exoplayer.SampleSource;
|
||||||
|
import com.google.android.exoplayer.TrackGroup;
|
||||||
|
import com.google.android.exoplayer.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
|
import com.google.android.exoplayer.TrackSelection;
|
||||||
|
import com.google.android.exoplayer.TrackSelectionPolicy;
|
||||||
|
import com.google.android.exoplayer.dash.DashSampleSource;
|
||||||
|
import com.google.android.exoplayer.playbacktests.util.ActionSchedule;
|
||||||
|
import com.google.android.exoplayer.playbacktests.util.CodecCountersUtil;
|
||||||
|
import com.google.android.exoplayer.playbacktests.util.ExoHostedTest;
|
||||||
|
import com.google.android.exoplayer.playbacktests.util.HostActivity;
|
||||||
|
import com.google.android.exoplayer.playbacktests.util.MetricsLogger;
|
||||||
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
|
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
|
import android.util.Log;
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests DASH playbacks using {@link ExoPlayer}.
|
||||||
|
*/
|
||||||
|
public final class DashTest extends ActivityInstrumentationTestCase2<HostActivity> {
|
||||||
|
|
||||||
|
private static final String TAG = "DashTest";
|
||||||
|
private static final String VIDEO_TAG = TAG + ":Video";
|
||||||
|
private static final String AUDIO_TAG = TAG + ":Audio";
|
||||||
|
private static final String REPORT_NAME = "GtsExoPlayerTestCases";
|
||||||
|
private static final int VIDEO_RENDERER_INDEX = 0;
|
||||||
|
private static final int AUDIO_RENDERER_INDEX = 1;
|
||||||
|
|
||||||
|
private static final long TEST_TIMEOUT_MS = 5 * 60 * 1000;
|
||||||
|
private static final int MIN_LOADABLE_RETRY_COUNT = 10; // TODO[REFACTOR]: Use this again.
|
||||||
|
private static final int MAX_CONSECUTIVE_DROPPED_VIDEO_FRAMES = 10;
|
||||||
|
private static final float MAX_DROPPED_VIDEO_FRAME_FRACTION = 0.01f;
|
||||||
|
|
||||||
|
private static final String MANIFEST_URL_PREFIX = "https://storage.googleapis.com/exoplayer-test-"
|
||||||
|
+ "media-1/gen-3/screens/dash-vod-single-segment/";
|
||||||
|
private static final String H264_MANIFEST = "manifest-h264.mpd";
|
||||||
|
private static final String H265_MANIFEST = "manifest-h265.mpd";
|
||||||
|
private static final String VP9_MANIFEST = "manifest-vp9.mpd";
|
||||||
|
private static final String H264_23_MANIFEST = "manifest-h264-23.mpd";
|
||||||
|
private static final String H264_24_MANIFEST = "manifest-h264-24.mpd";
|
||||||
|
private static final String H264_29_MANIFEST = "manifest-h264-29.mpd";
|
||||||
|
|
||||||
|
private static final String AAC_AUDIO_REPRESENTATION_ID = "141";
|
||||||
|
private static final String H264_BASELINE_240P_VIDEO_REPRESENTATION_ID = "avc-baseline-240";
|
||||||
|
private static final String H264_BASELINE_480P_VIDEO_REPRESENTATION_ID = "avc-baseline-480";
|
||||||
|
private static final String H264_MAIN_240P_VIDEO_REPRESENTATION_ID = "avc-main-240";
|
||||||
|
private static final String H264_MAIN_480P_VIDEO_REPRESENTATION_ID = "avc-main-480";
|
||||||
|
// The highest quality H264 format mandated by the Android CDD.
|
||||||
|
private static final String H264_CDD_FIXED = Util.SDK_INT < 23
|
||||||
|
? H264_BASELINE_480P_VIDEO_REPRESENTATION_ID : H264_MAIN_480P_VIDEO_REPRESENTATION_ID;
|
||||||
|
// Multiple H264 formats mandated by the Android CDD. Note: The CDD actually mandated main profile
|
||||||
|
// support from API level 23, but we opt to test only from 24 due to known issues on API level 23
|
||||||
|
// when switching between baseline and main profiles on certain devices.
|
||||||
|
private static final String[] H264_CDD_ADAPTIVE = Util.SDK_INT < 24
|
||||||
|
? new String[] {
|
||||||
|
H264_BASELINE_240P_VIDEO_REPRESENTATION_ID,
|
||||||
|
H264_BASELINE_480P_VIDEO_REPRESENTATION_ID}
|
||||||
|
: new String[] {
|
||||||
|
H264_BASELINE_240P_VIDEO_REPRESENTATION_ID,
|
||||||
|
H264_BASELINE_480P_VIDEO_REPRESENTATION_ID,
|
||||||
|
H264_MAIN_240P_VIDEO_REPRESENTATION_ID,
|
||||||
|
H264_MAIN_480P_VIDEO_REPRESENTATION_ID};
|
||||||
|
|
||||||
|
private static final String H264_BASELINE_480P_23FPS_VIDEO_REPRESENTATION_ID =
|
||||||
|
"avc-baseline-480-23";
|
||||||
|
private static final String H264_BASELINE_480P_24FPS_VIDEO_REPRESENTATION_ID =
|
||||||
|
"avc-baseline-480-24";
|
||||||
|
private static final String H264_BASELINE_480P_29FPS_VIDEO_REPRESENTATION_ID =
|
||||||
|
"avc-baseline-480-29";
|
||||||
|
|
||||||
|
private static final String H265_BASELINE_288P_VIDEO_REPRESENTATION_ID = "hevc-main-288";
|
||||||
|
private static final String H265_BASELINE_360P_VIDEO_REPRESENTATION_ID = "hevc-main-360";
|
||||||
|
// The highest quality H265 format mandated by the Android CDD.
|
||||||
|
private static final String H265_CDD_FIXED = H265_BASELINE_360P_VIDEO_REPRESENTATION_ID;
|
||||||
|
// Multiple H265 formats mandated by the Android CDD.
|
||||||
|
private static final String[] H265_CDD_ADAPTIVE =
|
||||||
|
new String[] {
|
||||||
|
H265_BASELINE_288P_VIDEO_REPRESENTATION_ID,
|
||||||
|
H265_BASELINE_360P_VIDEO_REPRESENTATION_ID};
|
||||||
|
|
||||||
|
private static final String VORBIS_AUDIO_REPRESENTATION_ID = "4";
|
||||||
|
private static final String VP9_180P_VIDEO_REPRESENTATION_ID = "0";
|
||||||
|
private static final String VP9_360P_VIDEO_REPRESENTATION_ID = "1";
|
||||||
|
// The highest quality VP9 format mandated by the Android CDD.
|
||||||
|
private static final String VP9_CDD_FIXED = VP9_360P_VIDEO_REPRESENTATION_ID;
|
||||||
|
// Multiple VP9 formats mandated by the Android CDD.
|
||||||
|
private static final String[] VP9_CDD_ADAPTIVE =
|
||||||
|
new String[] {
|
||||||
|
VP9_180P_VIDEO_REPRESENTATION_ID,
|
||||||
|
VP9_360P_VIDEO_REPRESENTATION_ID};
|
||||||
|
|
||||||
|
// Whether adaptive tests should enable video formats beyond those mandated by the Android CDD
|
||||||
|
// if the device advertises support for them.
|
||||||
|
private static final boolean ALLOW_ADDITIONAL_VIDEO_FORMATS = Util.SDK_INT >= 24;
|
||||||
|
|
||||||
|
private static final ActionSchedule SEEKING_SCHEDULE = new ActionSchedule.Builder(TAG)
|
||||||
|
.delay(10000).seek(15000)
|
||||||
|
.delay(10000).seek(30000).seek(31000).seek(32000).seek(33000).seek(34000)
|
||||||
|
.delay(1000).pause().delay(1000).play()
|
||||||
|
.delay(1000).pause().seek(120000).delay(1000).play()
|
||||||
|
.build();
|
||||||
|
private static final ActionSchedule RENDERER_DISABLING_SCHEDULE = new ActionSchedule.Builder(TAG)
|
||||||
|
// Wait 10 seconds, disable the video renderer, wait another 10 seconds and enable it again.
|
||||||
|
.delay(10000).disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.delay(10000).enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
// Ditto for the audio renderer.
|
||||||
|
.delay(10000).disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.delay(10000).enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
// Wait 10 seconds, then disable and enable the video renderer 5 times in quick succession.
|
||||||
|
.delay(10000).disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(VIDEO_RENDERER_INDEX)
|
||||||
|
// Ditto for the audio renderer.
|
||||||
|
.delay(10000).disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.disableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.enableRenderer(AUDIO_RENDERER_INDEX)
|
||||||
|
.delay(10000).seek(120000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public DashTest() {
|
||||||
|
super(HostActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// H264 CDD.
|
||||||
|
|
||||||
|
public void testH264Fixed() {
|
||||||
|
if (Util.SDK_INT < 16) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h264_fixed";
|
||||||
|
testDashPlayback(getActivity(), streamName, H264_MANIFEST, AAC_AUDIO_REPRESENTATION_ID, false,
|
||||||
|
H264_CDD_FIXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH264Adaptive() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 16 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h264_adaptive";
|
||||||
|
testDashPlayback(getActivity(), streamName, H264_MANIFEST, AAC_AUDIO_REPRESENTATION_ID,
|
||||||
|
ALLOW_ADDITIONAL_VIDEO_FORMATS, H264_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH264AdaptiveWithSeeking() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 16 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h264_adaptive_with_seeking";
|
||||||
|
testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, H264_MANIFEST,
|
||||||
|
AAC_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS, H264_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH264AdaptiveWithRendererDisabling() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 16 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h264_adaptive_with_renderer_disabling";
|
||||||
|
testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false, H264_MANIFEST,
|
||||||
|
AAC_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS, H264_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// H265 CDD.
|
||||||
|
|
||||||
|
public void testH265Fixed() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h265_fixed";
|
||||||
|
testDashPlayback(getActivity(), streamName, H265_MANIFEST, AAC_AUDIO_REPRESENTATION_ID, false,
|
||||||
|
H265_CDD_FIXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH265Adaptive() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h265_adaptive";
|
||||||
|
testDashPlayback(getActivity(), streamName, H265_MANIFEST, AAC_AUDIO_REPRESENTATION_ID,
|
||||||
|
ALLOW_ADDITIONAL_VIDEO_FORMATS, H265_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH265AdaptiveWithSeeking() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h265_adaptive_with_seeking";
|
||||||
|
testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, H265_MANIFEST,
|
||||||
|
AAC_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS, H265_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testH265AdaptiveWithRendererDisabling() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_h265_adaptive_with_renderer_disabling";
|
||||||
|
testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false,
|
||||||
|
H265_MANIFEST, AAC_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS,
|
||||||
|
H265_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VP9 (CDD).
|
||||||
|
|
||||||
|
public void testVp9Fixed360p() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_vp9_fixed_360p";
|
||||||
|
testDashPlayback(getActivity(), streamName, VP9_MANIFEST, VORBIS_AUDIO_REPRESENTATION_ID, false,
|
||||||
|
VP9_CDD_FIXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testVp9Adaptive() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_vp9_adaptive";
|
||||||
|
testDashPlayback(getActivity(), streamName, VP9_MANIFEST, VORBIS_AUDIO_REPRESENTATION_ID,
|
||||||
|
ALLOW_ADDITIONAL_VIDEO_FORMATS, VP9_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testVp9AdaptiveWithSeeking() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_vp9_adaptive_with_seeking";
|
||||||
|
testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, VP9_MANIFEST,
|
||||||
|
VORBIS_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS, VP9_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testVp9AdaptiveWithRendererDisabling() throws DecoderQueryException {
|
||||||
|
if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_vp9_adaptive_with_renderer_disabling";
|
||||||
|
testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false,
|
||||||
|
VP9_MANIFEST, VORBIS_AUDIO_REPRESENTATION_ID, ALLOW_ADDITIONAL_VIDEO_FORMATS,
|
||||||
|
VP9_CDD_ADAPTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// H264: Other frame-rates for output buffer count assertions.
|
||||||
|
|
||||||
|
// 23.976 fps.
|
||||||
|
public void test23FpsH264Fixed() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_23fps_h264_fixed";
|
||||||
|
testDashPlayback(getActivity(), streamName, H264_23_MANIFEST, AAC_AUDIO_REPRESENTATION_ID,
|
||||||
|
false, H264_BASELINE_480P_23FPS_VIDEO_REPRESENTATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 24 fps.
|
||||||
|
public void test24FpsH264Fixed() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_24fps_h264_fixed";
|
||||||
|
testDashPlayback(getActivity(), streamName, H264_24_MANIFEST, AAC_AUDIO_REPRESENTATION_ID,
|
||||||
|
false, H264_BASELINE_480P_24FPS_VIDEO_REPRESENTATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 29.97 fps.
|
||||||
|
public void test29FpsH264Fixed() {
|
||||||
|
if (Util.SDK_INT < 23) {
|
||||||
|
// Pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String streamName = "test_29fps_h264_fixed";
|
||||||
|
testDashPlayback(getActivity(), streamName, H264_29_MANIFEST, AAC_AUDIO_REPRESENTATION_ID,
|
||||||
|
false, H264_BASELINE_480P_29FPS_VIDEO_REPRESENTATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal.
|
||||||
|
|
||||||
|
private void testDashPlayback(HostActivity activity, String streamName, String manifestFileName,
|
||||||
|
String audioFormat, boolean canIncludeAdditionalVideoFormats, String... videoFormats) {
|
||||||
|
testDashPlayback(activity, streamName, null, true, manifestFileName, audioFormat,
|
||||||
|
canIncludeAdditionalVideoFormats, videoFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testDashPlayback(HostActivity activity, String streamName,
|
||||||
|
ActionSchedule actionSchedule, boolean fullPlaybackNoSeeking, String manifestFileName,
|
||||||
|
String audioFormat, boolean canIncludeAdditionalVideoFormats, String... videoFormats) {
|
||||||
|
Uri manifestUri = Uri.parse(MANIFEST_URL_PREFIX + manifestFileName);
|
||||||
|
MetricsLogger metricsLogger = MetricsLogger.Factory.createDefault(getInstrumentation(), TAG,
|
||||||
|
REPORT_NAME, streamName);
|
||||||
|
DashHostedTest test = new DashHostedTest(streamName, manifestUri, metricsLogger,
|
||||||
|
fullPlaybackNoSeeking, audioFormat, canIncludeAdditionalVideoFormats, false, actionSchedule,
|
||||||
|
videoFormats);
|
||||||
|
activity.runTest(test, TEST_TIMEOUT_MS);
|
||||||
|
// Retry test exactly once if adaptive test fails due to excessive dropped buffers when playing
|
||||||
|
// non-CDD required formats (b/28220076).
|
||||||
|
if (test.needsCddLimitedRetry) {
|
||||||
|
metricsLogger = MetricsLogger.Factory.createDefault(getInstrumentation(), TAG, REPORT_NAME,
|
||||||
|
streamName + "_cdd_limited_retry");
|
||||||
|
test = new DashHostedTest(streamName, manifestUri, metricsLogger, fullPlaybackNoSeeking,
|
||||||
|
audioFormat, false, true, actionSchedule, videoFormats);
|
||||||
|
activity.runTest(test, TEST_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException {
|
||||||
|
DecoderInfo decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, false);
|
||||||
|
assertNotNull(decoderInfo);
|
||||||
|
if (decoderInfo.adaptive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assertTrue(Util.SDK_INT < 21);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static class DashHostedTest extends ExoHostedTest {
|
||||||
|
|
||||||
|
private final String streamName;
|
||||||
|
private final Uri manifestUri;
|
||||||
|
private final MetricsLogger metricsLogger;
|
||||||
|
private final boolean fullPlaybackNoSeeking;
|
||||||
|
private final DashTestTrackSelectionPolicy trackSelectionPolicy;
|
||||||
|
|
||||||
|
private boolean needsCddLimitedRetry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param streamName The name of the test stream for metric logging.
|
||||||
|
* @param manifestUri The manifest uri.
|
||||||
|
* @param metricsLogger Logger to log metrics from the test.
|
||||||
|
* @param fullPlaybackNoSeeking True if the test will play the entire source with no seeking.
|
||||||
|
* False otherwise.
|
||||||
|
* @param audioFormat The audio format.
|
||||||
|
* @param canIncludeAdditionalVideoFormats Whether to use video formats in addition to those
|
||||||
|
* listed in the videoFormats argument, if the device is capable of playing them.
|
||||||
|
* @param isCddLimitedRetry Whether this is a CDD limited retry following a previous failure.
|
||||||
|
* @param videoFormats The video formats.
|
||||||
|
*/
|
||||||
|
public DashHostedTest(String streamName, Uri manifestUri, MetricsLogger metricsLogger,
|
||||||
|
boolean fullPlaybackNoSeeking, String audioFormat, boolean canIncludeAdditionalVideoFormats,
|
||||||
|
boolean isCddLimitedRetry, ActionSchedule actionSchedule, String... videoFormats) {
|
||||||
|
super(TAG, fullPlaybackNoSeeking);
|
||||||
|
Assertions.checkArgument(!(isCddLimitedRetry && canIncludeAdditionalVideoFormats));
|
||||||
|
this.streamName = streamName;
|
||||||
|
this.manifestUri = manifestUri;
|
||||||
|
this.metricsLogger = metricsLogger;
|
||||||
|
this.fullPlaybackNoSeeking = fullPlaybackNoSeeking;
|
||||||
|
trackSelectionPolicy = new DashTestTrackSelectionPolicy(new String[] {audioFormat},
|
||||||
|
videoFormats, canIncludeAdditionalVideoFormats);
|
||||||
|
if (actionSchedule != null) {
|
||||||
|
setSchedule(actionSchedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrackSelectionPolicy buildTrackSelectionPolicy(HostActivity host) {
|
||||||
|
return trackSelectionPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleSource buildSource(HostActivity host, DataSourceFactory dataSourceFactory,
|
||||||
|
BandwidthMeter bandwidthMeter) {
|
||||||
|
return new DashSampleSource(manifestUri, dataSourceFactory, bandwidthMeter, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void logMetrics(CodecCounters audioCounters, CodecCounters videoCounters) {
|
||||||
|
metricsLogger.logMetric(MetricsLogger.KEY_TEST_NAME, streamName);
|
||||||
|
metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_DROPPED_COUNT,
|
||||||
|
videoCounters.droppedOutputBufferCount);
|
||||||
|
metricsLogger.logMetric(MetricsLogger.KEY_MAX_CONSECUTIVE_FRAMES_DROPPED_COUNT,
|
||||||
|
videoCounters.maxConsecutiveDroppedOutputBufferCount);
|
||||||
|
metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_SKIPPED_COUNT,
|
||||||
|
videoCounters.skippedOutputBufferCount);
|
||||||
|
metricsLogger.logMetric(MetricsLogger.KEY_FRAMES_RENDERED_COUNT,
|
||||||
|
videoCounters.renderedOutputBufferCount);
|
||||||
|
metricsLogger.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assertPassed(CodecCounters audioCounters, CodecCounters videoCounters) {
|
||||||
|
if (fullPlaybackNoSeeking) {
|
||||||
|
// We shouldn't have skipped any output buffers.
|
||||||
|
CodecCountersUtil.assertSkippedOutputBufferCount(AUDIO_TAG, audioCounters, 0);
|
||||||
|
CodecCountersUtil.assertSkippedOutputBufferCount(VIDEO_TAG, videoCounters, 0);
|
||||||
|
// We allow one fewer output buffer due to the way that MediaCodecTrackRenderer and the
|
||||||
|
// underlying decoders handle the end of stream. This should be tightened up in the future.
|
||||||
|
CodecCountersUtil.assertTotalOutputBufferCount(AUDIO_TAG, audioCounters,
|
||||||
|
audioCounters.inputBufferCount - 1, audioCounters.inputBufferCount);
|
||||||
|
CodecCountersUtil.assertTotalOutputBufferCount(VIDEO_TAG, videoCounters,
|
||||||
|
videoCounters.inputBufferCount - 1, videoCounters.inputBufferCount);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int droppedFrameLimit = (int) Math.ceil(MAX_DROPPED_VIDEO_FRAME_FRACTION
|
||||||
|
* CodecCountersUtil.getTotalOutputBuffers(videoCounters));
|
||||||
|
// Assert that performance is acceptable.
|
||||||
|
// Assert that total dropped frames were within limit.
|
||||||
|
CodecCountersUtil.assertDroppedOutputBufferLimit(VIDEO_TAG, videoCounters,
|
||||||
|
droppedFrameLimit);
|
||||||
|
// Assert that consecutive dropped frames were within limit.
|
||||||
|
CodecCountersUtil.assertConsecutiveDroppedOutputBufferLimit(VIDEO_TAG, videoCounters,
|
||||||
|
MAX_CONSECUTIVE_DROPPED_VIDEO_FRAMES);
|
||||||
|
} catch (AssertionFailedError e) {
|
||||||
|
if (trackSelectionPolicy.includedAdditionalVideoFormats) {
|
||||||
|
// Retry limiting to CDD mandated formats (b/28220076).
|
||||||
|
Log.e(TAG, "Too many dropped or consecutive dropped frames.", e);
|
||||||
|
needsCddLimitedRetry = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DashTestTrackSelectionPolicy extends TrackSelectionPolicy {
|
||||||
|
|
||||||
|
private final String[] audioFormatIds;
|
||||||
|
private final String[] videoFormatIds;
|
||||||
|
private final boolean canIncludeAdditionalVideoFormats;
|
||||||
|
|
||||||
|
public boolean includedAdditionalVideoFormats;
|
||||||
|
|
||||||
|
private DashTestTrackSelectionPolicy(String[] audioFormatIds, String[] videoFormatIds,
|
||||||
|
boolean canIncludeAdditionalVideoFormats) {
|
||||||
|
this.audioFormatIds = audioFormatIds;
|
||||||
|
this.videoFormatIds = videoFormatIds;
|
||||||
|
this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TrackSelection[] selectTracks(TrackRenderer[] renderers,
|
||||||
|
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
|
||||||
|
throws ExoPlaybackException {
|
||||||
|
Assertions.checkState(renderers[VIDEO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_VIDEO);
|
||||||
|
Assertions.checkState(renderers[AUDIO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_AUDIO);
|
||||||
|
Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1);
|
||||||
|
Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1);
|
||||||
|
TrackSelection[] selections = new TrackSelection[renderers.length];
|
||||||
|
selections[VIDEO_RENDERER_INDEX] = new TrackSelection(0,
|
||||||
|
getTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
|
||||||
|
rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds,
|
||||||
|
canIncludeAdditionalVideoFormats));
|
||||||
|
selections[AUDIO_RENDERER_INDEX] = new TrackSelection(0,
|
||||||
|
getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
|
||||||
|
rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false));
|
||||||
|
includedAdditionalVideoFormats =
|
||||||
|
selections[VIDEO_RENDERER_INDEX].length > videoFormatIds.length;
|
||||||
|
return selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] getTrackIndices(TrackGroup trackGroup, int[] formatSupport,
|
||||||
|
String[] formatIds, boolean canIncludeAdditionalFormats) {
|
||||||
|
List<Integer> trackIndices = new ArrayList<>();
|
||||||
|
|
||||||
|
// Always select explicitly listed representations, failing if they're missing.
|
||||||
|
for (String formatId : formatIds) {
|
||||||
|
boolean foundIndex = false;
|
||||||
|
for (int j = 0; j < trackGroup.length && !foundIndex; j++) {
|
||||||
|
if (trackGroup.getFormat(j).id.equals(formatId)) {
|
||||||
|
trackIndices.add(j);
|
||||||
|
foundIndex = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundIndex) {
|
||||||
|
throw new IllegalStateException("Format " + formatId + " not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select additional video representations, if supported by the device.
|
||||||
|
if (canIncludeAdditionalFormats) {
|
||||||
|
for (int i = 0; i < trackGroup.length; i++) {
|
||||||
|
if (!trackIndices.contains(i) && (formatSupport[i] & TrackRenderer.FORMAT_SUPPORT_MASK)
|
||||||
|
== TrackRenderer.FORMAT_HANDLED) {
|
||||||
|
Log.d(TAG, "Adding video format: " + trackGroup.getFormat(i).id);
|
||||||
|
trackIndices.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] trackIndicesArray = Util.toArray(trackIndices);
|
||||||
|
Arrays.sort(trackIndicesArray);
|
||||||
|
return trackIndicesArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link HostedTest} for {@link ExoPlayer} playback tests.
|
* A {@link HostedTest} for {@link ExoPlayer} playback tests.
|
||||||
|
|
@ -50,8 +51,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
AudioTrack.failOnSpuriousAudioTimestamp = true;
|
AudioTrack.failOnSpuriousAudioTimestamp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final long MAX_PLAYING_TIME_DISCREPANCY_MS = 2000;
|
||||||
|
|
||||||
private final String tag;
|
private final String tag;
|
||||||
private final boolean failOnPlayerError;
|
private final boolean fullPlaybackNoSeeking;
|
||||||
|
private final CodecCounters videoCodecCounters;
|
||||||
|
private final CodecCounters audioCodecCounters;
|
||||||
|
|
||||||
private ActionSchedule pendingSchedule;
|
private ActionSchedule pendingSchedule;
|
||||||
private Handler actionHandler;
|
private Handler actionHandler;
|
||||||
|
|
@ -63,27 +68,19 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
private boolean playing;
|
private boolean playing;
|
||||||
private long totalPlayingTimeMs;
|
private long totalPlayingTimeMs;
|
||||||
private long lastPlayingStartTimeMs;
|
private long lastPlayingStartTimeMs;
|
||||||
|
private long sourceDurationMs;
|
||||||
private CodecCounters videoCodecCounters;
|
|
||||||
private CodecCounters audioCodecCounters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a test that fails if a player error occurs.
|
|
||||||
*
|
|
||||||
* @param tag A tag to use for logging.
|
|
||||||
*/
|
|
||||||
public ExoHostedTest(String tag) {
|
|
||||||
this(tag, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param tag A tag to use for logging.
|
* @param tag A tag to use for logging.
|
||||||
* @param failOnPlayerError True if a player error should be considered a test failure. False
|
* @param fullPlaybackNoSeeking Whether the test will play the target media in full without
|
||||||
* otherwise.
|
* seeking. If set to true, the test will assert that the total time spent playing the media
|
||||||
|
* was within {@link #MAX_PLAYING_TIME_DISCREPANCY_MS} of the media duration.
|
||||||
*/
|
*/
|
||||||
public ExoHostedTest(String tag, boolean failOnPlayerError) {
|
public ExoHostedTest(String tag, boolean fullPlaybackNoSeeking) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
this.failOnPlayerError = failOnPlayerError;
|
this.fullPlaybackNoSeeking = fullPlaybackNoSeeking;
|
||||||
|
videoCodecCounters = new CodecCounters();
|
||||||
|
audioCodecCounters = new CodecCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,6 +121,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
@Override
|
@Override
|
||||||
public final void onStop() {
|
public final void onStop() {
|
||||||
actionHandler.removeCallbacksAndMessages(null);
|
actionHandler.removeCallbacksAndMessages(null);
|
||||||
|
sourceDurationMs = player.getDuration();
|
||||||
player.release();
|
player.release();
|
||||||
player = null;
|
player = null;
|
||||||
}
|
}
|
||||||
|
|
@ -135,11 +133,20 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onFinished() {
|
public final void onFinished() {
|
||||||
if (failOnPlayerError && playerError != null) {
|
if (playerError != null) {
|
||||||
throw new Error(playerError);
|
throw new Error(playerError);
|
||||||
}
|
}
|
||||||
logMetrics();
|
logMetrics(audioCodecCounters, videoCodecCounters);
|
||||||
assertPassed();
|
if (fullPlaybackNoSeeking) {
|
||||||
|
// Assert that the playback spanned the correct duration of time.
|
||||||
|
long minAllowedActualPlayingTimeMs = sourceDurationMs - MAX_PLAYING_TIME_DISCREPANCY_MS;
|
||||||
|
long maxAllowedActualPlayingTimeMs = sourceDurationMs + MAX_PLAYING_TIME_DISCREPANCY_MS;
|
||||||
|
Assert.assertTrue("Total playing time: " + totalPlayingTimeMs + ". Actual media duration: "
|
||||||
|
+ sourceDurationMs, minAllowedActualPlayingTimeMs <= totalPlayingTimeMs
|
||||||
|
&& totalPlayingTimeMs <= maxAllowedActualPlayingTimeMs);
|
||||||
|
}
|
||||||
|
// Make any additional assertions.
|
||||||
|
assertPassed(audioCodecCounters, videoCodecCounters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExoPlayer.Listener
|
// ExoPlayer.Listener
|
||||||
|
|
@ -189,14 +196,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
@Override
|
@Override
|
||||||
public void onAudioFormatChanged(Format format) {
|
public void onAudioFormatChanged(Format format) {
|
||||||
Log.d(tag, "audioFormatChanged [" + format.id + "]");
|
Log.d(tag, "audioFormatChanged [" + format.id + "]");
|
||||||
if (format != null) {
|
|
||||||
audioCodecCounters = player.getVideoCodecCounters();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioDisabled(CodecCounters counters) {
|
public void onAudioDisabled(CodecCounters counters) {
|
||||||
Log.d(tag, "audioDisabled");
|
Log.d(tag, "audioDisabled");
|
||||||
|
audioCodecCounters.merge(counters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -213,14 +218,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
@Override
|
@Override
|
||||||
public void onVideoFormatChanged(Format format) {
|
public void onVideoFormatChanged(Format format) {
|
||||||
Log.d(tag, "videoFormatChanged [" + format.id + "]");
|
Log.d(tag, "videoFormatChanged [" + format.id + "]");
|
||||||
if (format != null) {
|
|
||||||
videoCodecCounters = player.getVideoCodecCounters();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoDisabled(CodecCounters counters) {
|
public void onVideoDisabled(CodecCounters counters) {
|
||||||
Log.d(tag, "videoDisabled");
|
Log.d(tag, "videoDisabled");
|
||||||
|
videoCodecCounters.merge(counters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -258,36 +261,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
// Do nothing. Interested subclasses may override.
|
// Do nothing. Interested subclasses may override.
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertPassed() {
|
protected void logMetrics(CodecCounters audioCounters, CodecCounters videoCounters) {
|
||||||
// Do nothing. Subclasses may override to add additional assertions.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void logMetrics() {
|
|
||||||
// Do nothing. Subclasses may override to log metrics.
|
// Do nothing. Subclasses may override to log metrics.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility methods and actions for subclasses.
|
protected void assertPassed(CodecCounters audioCounters, CodecCounters videoCounters) {
|
||||||
|
// Do nothing. Subclasses may override to add additional assertions.
|
||||||
protected final long getTotalPlayingTimeMs() {
|
|
||||||
return totalPlayingTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final ExoPlaybackException getError() {
|
|
||||||
return playerError;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final CodecCounters getLastVideoCodecCounters() {
|
|
||||||
if (videoCodecCounters != null) {
|
|
||||||
videoCodecCounters.ensureUpdated();
|
|
||||||
}
|
|
||||||
return videoCodecCounters;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final CodecCounters getLastAudioCodecCounters() {
|
|
||||||
if (audioCodecCounters != null) {
|
|
||||||
audioCodecCounters.ensureUpdated();
|
|
||||||
}
|
|
||||||
return audioCodecCounters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue