From 25c18ca1b461870a0aaad15c339a522a38226627 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 25 Jan 2017 12:41:38 -0800 Subject: [PATCH] Re-add playback tests to Moe migration ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=145583193 --- .../src/androidTest/AndroidManifest.xml | 42 + .../playbacktests/gts/DashTest.java | 1109 +++++++++++++++++ 2 files changed, 1151 insertions(+) create mode 100644 playbacktests/src/androidTest/AndroidManifest.xml create mode 100644 playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java diff --git a/playbacktests/src/androidTest/AndroidManifest.xml b/playbacktests/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..2f7bbe6d7c --- /dev/null +++ b/playbacktests/src/androidTest/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java new file mode 100644 index 0000000000..5752058c4e --- /dev/null +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java @@ -0,0 +1,1109 @@ +/* + * Copyright (C) 2016 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.exoplayer2.playbacktests.gts; + +import android.annotation.TargetApi; +import android.media.MediaDrm; +import android.media.MediaDrm.MediaDrmStateException; +import android.media.UnsupportedSchemeException; +import android.net.Uri; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; +import android.util.Pair; +import android.view.Surface; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; +import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; +import com.google.android.exoplayer2.drm.MediaDrmCallback; +import com.google.android.exoplayer2.drm.OfflineLicenseHelper; +import com.google.android.exoplayer2.drm.UnsupportedDrmException; +import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; +import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; +import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.google.android.exoplayer2.playbacktests.util.ActionSchedule; +import com.google.android.exoplayer2.playbacktests.util.DebugSimpleExoPlayer; +import com.google.android.exoplayer2.playbacktests.util.DecoderCountersUtil; +import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest; +import com.google.android.exoplayer2.playbacktests.util.HostActivity; +import com.google.android.exoplayer2.playbacktests.util.MetricsLogger; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.dash.DashMediaSource; +import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; +import com.google.android.exoplayer2.trackselection.FixedTrackSelection; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; +import com.google.android.exoplayer2.trackselection.RandomTrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.upstream.BandwidthMeter; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import junit.framework.AssertionFailedError; + +/** + * Tests DASH playbacks using {@link ExoPlayer}. + */ +public final class DashTest extends ActivityInstrumentationTestCase2 { + + 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 String REPORT_OBJECT_NAME = "playbacktest"; + 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; + 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/"; + // Clear content manifests. + 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"; + // Widevine encrypted content manifests. + private static final String WIDEVINE_H264_MANIFEST_PREFIX = "manifest-h264-enc"; + private static final String WIDEVINE_H265_MANIFEST_PREFIX = "manifest-h265-enc"; + private static final String WIDEVINE_VP9_MANIFEST_PREFIX = "manifest-vp9-enc"; + private static final String WIDEVINE_H264_23_MANIFEST_PREFIX = "manifest-h264-23-enc"; + private static final String WIDEVINE_H264_24_MANIFEST_PREFIX = "manifest-h264-24-enc"; + private static final String WIDEVINE_H264_29_MANIFEST_PREFIX = "manifest-h264-29-enc"; + private static final String WIDEVINE_L1_SUFFIX = "-hw.mpd"; + private static final String WIDEVINE_L3_SUFFIX = "-sw.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}; + + // Widevine encrypted content representation ids. + private static final String WIDEVINE_AAC_AUDIO_REPRESENTATION_ID = "0"; + private static final String WIDEVINE_H264_BASELINE_240P_VIDEO_REPRESENTATION_ID = "1"; + private static final String WIDEVINE_H264_BASELINE_480P_VIDEO_REPRESENTATION_ID = "2"; + private static final String WIDEVINE_H264_MAIN_240P_VIDEO_REPRESENTATION_ID = "3"; + private static final String WIDEVINE_H264_MAIN_480P_VIDEO_REPRESENTATION_ID = "4"; + // The highest quality H264 format mandated by the Android CDD. + private static final String WIDEVINE_H264_CDD_FIXED = Util.SDK_INT < 23 + ? WIDEVINE_H264_BASELINE_480P_VIDEO_REPRESENTATION_ID + : WIDEVINE_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[] WIDEVINE_H264_CDD_ADAPTIVE = Util.SDK_INT < 24 + ? new String[] { + WIDEVINE_H264_BASELINE_240P_VIDEO_REPRESENTATION_ID, + WIDEVINE_H264_BASELINE_480P_VIDEO_REPRESENTATION_ID} + : new String[] { + WIDEVINE_H264_BASELINE_240P_VIDEO_REPRESENTATION_ID, + WIDEVINE_H264_BASELINE_480P_VIDEO_REPRESENTATION_ID, + WIDEVINE_H264_MAIN_240P_VIDEO_REPRESENTATION_ID, + WIDEVINE_H264_MAIN_480P_VIDEO_REPRESENTATION_ID}; + + private static final String WIDEVINE_H264_BASELINE_480P_23FPS_VIDEO_REPRESENTATION_ID = "2"; + private static final String WIDEVINE_H264_BASELINE_480P_24FPS_VIDEO_REPRESENTATION_ID = "2"; + private static final String WIDEVINE_H264_BASELINE_480P_29FPS_VIDEO_REPRESENTATION_ID = "2"; + + private static final String WIDEVINE_H265_BASELINE_288P_VIDEO_REPRESENTATION_ID = "1"; + private static final String WIDEVINE_H265_BASELINE_360P_VIDEO_REPRESENTATION_ID = "2"; + // The highest quality H265 format mandated by the Android CDD. + private static final String WIDEVINE_H265_CDD_FIXED = + WIDEVINE_H265_BASELINE_360P_VIDEO_REPRESENTATION_ID; + // Multiple H265 formats mandated by the Android CDD. + private static final String[] WIDEVINE_H265_CDD_ADAPTIVE = + new String[] { + WIDEVINE_H265_BASELINE_288P_VIDEO_REPRESENTATION_ID, + WIDEVINE_H265_BASELINE_360P_VIDEO_REPRESENTATION_ID}; + + private static final String WIDEVINE_VORBIS_AUDIO_REPRESENTATION_ID = "0"; + private static final String WIDEVINE_VP9_180P_VIDEO_REPRESENTATION_ID = "1"; + private static final String WIDEVINE_VP9_360P_VIDEO_REPRESENTATION_ID = "2"; + // The highest quality VP9 format mandated by the Android CDD. + private static final String WIDEVINE_VP9_CDD_FIXED = VP9_360P_VIDEO_REPRESENTATION_ID; + // Multiple VP9 formats mandated by the Android CDD. + private static final String[] WIDEVINE_VP9_CDD_ADAPTIVE = + new String[] { + WIDEVINE_VP9_180P_VIDEO_REPRESENTATION_ID, + WIDEVINE_VP9_360P_VIDEO_REPRESENTATION_ID}; + + private static final String WIDEVINE_LICENSE_URL = + "https://proxy.uat.widevine.com/proxy?provider=widevine_test&video_id="; + private static final String WIDEVINE_SW_CRYPTO_CONTENT_ID = "exoplayer_test_1"; + private static final String WIDEVINE_HW_SECURE_DECODE_CONTENT_ID = "exoplayer_test_2"; + private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + private static final String WIDEVINE_SECURITY_LEVEL_1 = "L1"; + private static final String WIDEVINE_SECURITY_LEVEL_3 = "L3"; + private static final String SECURITY_LEVEL_PROPERTY = "securityLevel"; + + // 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, + MimeTypes.VIDEO_H264, 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, false, + MimeTypes.VIDEO_H264, 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, false, MimeTypes.VIDEO_H264, 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, false, MimeTypes.VIDEO_H264, 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, + MimeTypes.VIDEO_H265, 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, false, + MimeTypes.VIDEO_H265, 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, false, MimeTypes.VIDEO_H265, 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, false, MimeTypes.VIDEO_H265, + 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, + MimeTypes.VIDEO_VP9, 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, false, + MimeTypes.VIDEO_VP9, 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, false, MimeTypes.VIDEO_VP9, 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, false, MimeTypes.VIDEO_VP9, + 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, MimeTypes.VIDEO_H264, 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, MimeTypes.VIDEO_H264, 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, MimeTypes.VIDEO_H264, false, H264_BASELINE_480P_29FPS_VIDEO_REPRESENTATION_ID); + } + + // Widevine encrypted media tests. + // H264 CDD. + + public void testWidevineH264Fixed() throws DecoderQueryException { + if (Util.SDK_INT < 18) { + // Pass. + return; + } + String streamName = "test_widevine_h264_fixed"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H264_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H264, false, + WIDEVINE_H264_CDD_FIXED); + } + + public void testWidevineH264Adaptive() throws DecoderQueryException { + if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) { + // Pass. + return; + } + String streamName = "test_widevine_h264_adaptive"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H264_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H264, + ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H264_CDD_ADAPTIVE); + } + + public void testWidevineH264AdaptiveWithSeeking() throws DecoderQueryException { + if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) { + // Pass. + return; + } + String streamName = "test_widevine_h264_adaptive_with_seeking"; + testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, + WIDEVINE_H264_MANIFEST_PREFIX, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_H264, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H264_CDD_ADAPTIVE); + } + + public void testWidevineH264AdaptiveWithRendererDisabling() throws DecoderQueryException { + if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) { + // Pass. + return; + } + String streamName = "test_widevine_h264_adaptive_with_renderer_disabling"; + testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false, + WIDEVINE_H264_MANIFEST_PREFIX, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_H264, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H264_CDD_ADAPTIVE); + } + + // H265 CDD. + + public void testWidevineH265Fixed() throws DecoderQueryException { + if (Util.SDK_INT < 23) { + // Pass. + return; + } + String streamName = "test_widevine_h265_fixed"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H265_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H265, false, + WIDEVINE_H265_CDD_FIXED); + } + + public void testWidevineH265Adaptive() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) { + // Pass. + return; + } + String streamName = "test_widevine_h265_adaptive"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H265_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H265, + ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H265_CDD_ADAPTIVE); + } + + public void testWidevineH265AdaptiveWithSeeking() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) { + // Pass. + return; + } + String streamName = "test_widevine_h265_adaptive_with_seeking"; + testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, + WIDEVINE_H265_MANIFEST_PREFIX, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_H265, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H265_CDD_ADAPTIVE); + } + + public void testWidevineH265AdaptiveWithRendererDisabling() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H265)) { + // Pass. + return; + } + String streamName = "test_widevine_h265_adaptive_with_renderer_disabling"; + testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false, + WIDEVINE_H265_MANIFEST_PREFIX, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_H265, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_H265_CDD_ADAPTIVE); + } + + // VP9 (CDD). + + public void testWidevineVp9Fixed360p() throws DecoderQueryException { + if (Util.SDK_INT < 23) { + // Pass. + return; + } + String streamName = "test_widevine_vp9_fixed_360p"; + testDashPlayback(getActivity(), streamName, WIDEVINE_VP9_MANIFEST_PREFIX, + WIDEVINE_VORBIS_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_VP9, false, + WIDEVINE_VP9_CDD_FIXED); + } + + public void testWidevineVp9Adaptive() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) { + // Pass. + return; + } + String streamName = "test_widevine_vp9_adaptive"; + testDashPlayback(getActivity(), streamName, WIDEVINE_VP9_MANIFEST_PREFIX, + WIDEVINE_VORBIS_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_VP9, + ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_VP9_CDD_ADAPTIVE); + } + + public void testWidevineVp9AdaptiveWithSeeking() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) { + // Pass. + return; + } + String streamName = "test_widevine_vp9_adaptive_with_seeking"; + testDashPlayback(getActivity(), streamName, SEEKING_SCHEDULE, false, + WIDEVINE_VP9_MANIFEST_PREFIX, WIDEVINE_VORBIS_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_VP9, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_VP9_CDD_ADAPTIVE); + } + + public void testWidevineVp9AdaptiveWithRendererDisabling() throws DecoderQueryException { + if (Util.SDK_INT < 24 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_VP9)) { + // Pass. + return; + } + String streamName = "test_widevine_vp9_adaptive_with_renderer_disabling"; + testDashPlayback(getActivity(), streamName, RENDERER_DISABLING_SCHEDULE, false, + WIDEVINE_VP9_MANIFEST_PREFIX, WIDEVINE_VORBIS_AUDIO_REPRESENTATION_ID, true, + MimeTypes.VIDEO_VP9, ALLOW_ADDITIONAL_VIDEO_FORMATS, WIDEVINE_VP9_CDD_ADAPTIVE); + } + + // H264: Other frame-rates for output buffer count assertions. + + // 23.976 fps. + public void testWidevine23FpsH264Fixed() throws DecoderQueryException { + if (Util.SDK_INT < 23) { + // Pass. + return; + } + String streamName = "test_widevine_23fps_h264_fixed"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H264_23_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H264, false, + WIDEVINE_H264_BASELINE_480P_23FPS_VIDEO_REPRESENTATION_ID); + } + + // 24 fps. + public void testWidevine24FpsH264Fixed() throws DecoderQueryException { + if (Util.SDK_INT < 23) { + // Pass. + return; + } + String streamName = "test_widevine_24fps_h264_fixed"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H264_24_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H264, false, + WIDEVINE_H264_BASELINE_480P_24FPS_VIDEO_REPRESENTATION_ID); + } + + // 29.97 fps. + public void testWidevine29FpsH264Fixed() throws DecoderQueryException { + if (Util.SDK_INT < 23) { + // Pass. + return; + } + String streamName = "test_widevine_29fps_h264_fixed"; + testDashPlayback(getActivity(), streamName, WIDEVINE_H264_29_MANIFEST_PREFIX, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, true, MimeTypes.VIDEO_H264, false, + WIDEVINE_H264_BASELINE_480P_29FPS_VIDEO_REPRESENTATION_ID); + } + + // Offline license tests + + public void testWidevineOfflineLicense() throws Exception { + if (Util.SDK_INT < 22) { + // Pass. + return; + } + String streamName = "test_widevine_h264_fixed_offline"; + DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( + WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); + TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); + try { + byte[] keySetId = helper.downloadLicense(); + testDashPlayback(getActivity(), streamName, null, true, parameters, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); + helper.renewLicense(); + } finally { + helper.releaseResources(); + } + } + + public void testWidevineOfflineReleasedLicense() throws Throwable { + if (Util.SDK_INT < 22) { + // Pass. + return; + } + String streamName = "test_widevine_h264_fixed_offline"; + DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( + WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); + TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); + try { + byte[] keySetId = helper.downloadLicense(); + helper.releaseLicense(); // keySetId no longer valid. + try { + testDashPlayback(getActivity(), streamName, null, true, parameters, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); + fail("Playback should fail because the license has been released."); + } catch (Throwable e) { + // Get the root cause + while (true) { + Throwable cause = e.getCause(); + if (cause == null || cause == e) { + break; + } + e = cause; + } + // It should be a MediaDrmStateException instance + if (!(e instanceof MediaDrmStateException)) { + throw e; + } + } + } finally { + helper.releaseResources(); + } + } + + public void testWidevineOfflineExpiredLicense() throws Exception { + if (Util.SDK_INT < 22) { + // Pass. + return; + } + String streamName = "test_widevine_h264_fixed_offline"; + DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( + WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); + TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); + try { + byte[] keySetId = helper.downloadLicense(); + + // Wait until the license expires + long licenseDuration = helper.getLicenseDurationRemainingSec().first; + assertTrue("License duration should be less than 30 sec. " + + "Server settings might have changed.", licenseDuration < 30); + while (licenseDuration > 0) { + synchronized (this) { + wait(licenseDuration * 1000 + 2000); + } + long previousDuration = licenseDuration; + licenseDuration = helper.getLicenseDurationRemainingSec().first; + assertTrue("License duration should be decreasing.", previousDuration > licenseDuration); + } + + // DefaultDrmSessionManager should renew the license and stream play fine + testDashPlayback(getActivity(), streamName, null, true, parameters, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); + } finally { + helper.releaseResources(); + } + } + + public void testWidevineOfflineLicenseExpiresOnPause() throws Exception { + if (Util.SDK_INT < 22) { + // Pass. + return; + } + String streamName = "test_widevine_h264_fixed_offline"; + DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( + WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); + TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); + try { + byte[] keySetId = helper.downloadLicense(); + // During playback pause until the license expires then continue playback + Pair licenseDurationRemainingSec = helper.getLicenseDurationRemainingSec(); + long licenseDuration = licenseDurationRemainingSec.first; + assertTrue("License duration should be less than 30 sec. " + + "Server settings might have changed.", licenseDuration < 30); + ActionSchedule schedule = new ActionSchedule.Builder(TAG) + .delay(3000).pause().delay(licenseDuration * 1000 + 2000).play().build(); + // DefaultDrmSessionManager should renew the license and stream play fine + testDashPlayback(getActivity(), streamName, schedule, true, parameters, + WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); + } finally { + helper.releaseResources(); + } + } + + // Internal. + + private void testDashPlayback(HostActivity activity, String streamName, String manifestFileName, + String audioFormat, boolean isWidevineEncrypted, String videoMimeType, + boolean canIncludeAdditionalVideoFormats, String... videoFormats) { + testDashPlayback(activity, streamName, null, true, manifestFileName, audioFormat, + isWidevineEncrypted, videoMimeType, canIncludeAdditionalVideoFormats, videoFormats); + } + + private void testDashPlayback(HostActivity activity, String streamName, + ActionSchedule actionSchedule, boolean fullPlaybackNoSeeking, String manifestFileName, + String audioFormat, boolean isWidevineEncrypted, String videoMimeType, + boolean canIncludeAdditionalVideoFormats, String... videoFormats) { + testDashPlayback(activity, streamName, actionSchedule, fullPlaybackNoSeeking, + newDashHostedTestEncParameters(manifestFileName, isWidevineEncrypted, videoMimeType), + audioFormat, canIncludeAdditionalVideoFormats, null, videoFormats); + } + + private void testDashPlayback(HostActivity activity, String streamName, + ActionSchedule actionSchedule, boolean fullPlaybackNoSeeking, + DashHostedTestEncParameters parameters, String audioFormat, + boolean canIncludeAdditionalVideoFormats, byte[] offlineLicenseKeySetId, + String... videoFormats) { + MetricsLogger metricsLogger = MetricsLogger.Factory.createDefault(getInstrumentation(), TAG, + REPORT_NAME, REPORT_OBJECT_NAME); + DashHostedTest test = new DashHostedTest(streamName, metricsLogger, fullPlaybackNoSeeking, + audioFormat, canIncludeAdditionalVideoFormats, false, actionSchedule, parameters, + offlineLicenseKeySetId, 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, + REPORT_OBJECT_NAME); + test = new DashHostedTest(streamName, metricsLogger, fullPlaybackNoSeeking, audioFormat, + false, true, actionSchedule, parameters, offlineLicenseKeySetId, videoFormats); + activity.runTest(test, TEST_TIMEOUT_MS); + } + } + + private static DashHostedTestEncParameters newDashHostedTestEncParameters(String manifestFileName, + boolean isWidevineEncrypted, String videoMimeType) { + String manifestPath = MANIFEST_URL_PREFIX + manifestFileName; + return new DashHostedTestEncParameters(manifestPath, isWidevineEncrypted, videoMimeType); + } + + private static boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException { + MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, false); + assertNotNull(decoderInfo); + if (decoderInfo.adaptive) { + return false; + } + assertTrue(Util.SDK_INT < 21); + return true; + } + + private static class DashHostedTestEncParameters { + + public final String manifestUrl; + public final boolean useL1Widevine; + public final String widevineLicenseUrl; + public final boolean isWidevineEncrypted; + + public DashHostedTestEncParameters(String manifestUrl, boolean isWidevineEncrypted, + String videoMimeType) { + this.isWidevineEncrypted = isWidevineEncrypted; + if (!isWidevineEncrypted) { + this.manifestUrl = manifestUrl; + this.useL1Widevine = true; + this.widevineLicenseUrl = null; + } else { + this.useL1Widevine = isL1WidevineAvailable(videoMimeType); + this.widevineLicenseUrl = WIDEVINE_LICENSE_URL + (useL1Widevine + ? WIDEVINE_HW_SECURE_DECODE_CONTENT_ID : WIDEVINE_SW_CRYPTO_CONTENT_ID); + this.manifestUrl = + manifestUrl + (useL1Widevine ? WIDEVINE_L1_SUFFIX : WIDEVINE_L3_SUFFIX); + } + } + + @TargetApi(18) + @SuppressWarnings("ResourceType") + private static boolean isL1WidevineAvailable(String videoMimeType) { + try { + // Force L3 if secure decoder is not available. + if (MediaCodecUtil.getDecoderInfo(videoMimeType, true) == null) { + return false; + } + + MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); + String securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY); + mediaDrm.release(); + return WIDEVINE_SECURITY_LEVEL_1.equals(securityProperty); + } catch (DecoderQueryException | UnsupportedSchemeException e) { + throw new IllegalStateException(e); + } + } + + } + + private static class TestOfflineLicenseHelper { + + private final DashHostedTestEncParameters parameters; + private final OfflineLicenseHelper offlineLicenseHelper; + private final DefaultHttpDataSourceFactory httpDataSourceFactory; + private byte[] offlineLicenseKeySetId; + + public TestOfflineLicenseHelper(DashHostedTestEncParameters parameters) + throws UnsupportedDrmException { + this.parameters = parameters; + httpDataSourceFactory = new DefaultHttpDataSourceFactory("ExoPlayerPlaybackTests"); + offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance( + parameters.widevineLicenseUrl, httpDataSourceFactory); + } + + public byte[] downloadLicense() throws InterruptedException, DrmSessionException, IOException { + assertNull(offlineLicenseKeySetId); + offlineLicenseKeySetId = offlineLicenseHelper + .download(httpDataSourceFactory.createDataSource(), parameters.manifestUrl); + assertNotNull(offlineLicenseKeySetId); + assertTrue(offlineLicenseKeySetId.length > 0); + return offlineLicenseKeySetId; + } + + public void renewLicense() throws DrmSessionException { + assertNotNull(offlineLicenseKeySetId); + offlineLicenseKeySetId = offlineLicenseHelper.renew(offlineLicenseKeySetId); + assertNotNull(offlineLicenseKeySetId); + } + + public void releaseLicense() throws DrmSessionException { + assertNotNull(offlineLicenseKeySetId); + offlineLicenseHelper.release(offlineLicenseKeySetId); + offlineLicenseKeySetId = null; + } + + public Pair getLicenseDurationRemainingSec() throws DrmSessionException { + return offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId); + } + + public void releaseResources() throws DrmSessionException { + if (offlineLicenseKeySetId != null) { + releaseLicense(); + } + if (offlineLicenseHelper != null) { + offlineLicenseHelper.releaseResources(); + } + } + + } + + @TargetApi(16) + private static class DashHostedTest extends ExoHostedTest { + + private final String streamName; + private final MetricsLogger metricsLogger; + private final boolean fullPlaybackNoSeeking; + private final boolean isCddLimitedRetry; + private final DashTestTrackSelector trackSelector; + private final DashHostedTestEncParameters parameters; + private final byte[] offlineLicenseKeySetId; + + private boolean needsCddLimitedRetry; + + /** + * @param streamName The name of the test stream for metric logging. + * @param metricsLogger Logger to log metrics from the test. + * @param fullPlaybackNoSeeking Whether the test will play the entire source with no seeking. + * @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 actionSchedule The action schedule for the test. + * @param parameters Encryption parameters. + * @param offlineLicenseKeySetId The key set id of the license to be used. + * @param videoFormats The video formats. + */ + public DashHostedTest(String streamName, MetricsLogger metricsLogger, + boolean fullPlaybackNoSeeking, String audioFormat, + boolean canIncludeAdditionalVideoFormats, boolean isCddLimitedRetry, + ActionSchedule actionSchedule, DashHostedTestEncParameters parameters, + byte[] offlineLicenseKeySetId, String... videoFormats) { + super(TAG, fullPlaybackNoSeeking); + Assertions.checkArgument(!(isCddLimitedRetry && canIncludeAdditionalVideoFormats)); + this.streamName = streamName; + this.metricsLogger = metricsLogger; + this.fullPlaybackNoSeeking = fullPlaybackNoSeeking; + this.isCddLimitedRetry = isCddLimitedRetry; + this.parameters = parameters; + this.offlineLicenseKeySetId = offlineLicenseKeySetId; + trackSelector = new DashTestTrackSelector(audioFormat, videoFormats, + canIncludeAdditionalVideoFormats); + if (actionSchedule != null) { + setSchedule(actionSchedule); + } + } + + @Override + protected MappingTrackSelector buildTrackSelector(HostActivity host, + BandwidthMeter bandwidthMeter) { + return trackSelector; + } + + @Override + protected final DefaultDrmSessionManager buildDrmSessionManager( + final String userAgent) { + DefaultDrmSessionManager drmSessionManager = null; + if (parameters.isWidevineEncrypted) { + try { + MediaDrmCallback drmCallback = new HttpMediaDrmCallback(parameters.widevineLicenseUrl, + new DefaultHttpDataSourceFactory(userAgent)); + drmSessionManager = DefaultDrmSessionManager.newWidevineInstance(drmCallback, null, + null, null); + if (!parameters.useL1Widevine) { + drmSessionManager.setPropertyString(SECURITY_LEVEL_PROPERTY, WIDEVINE_SECURITY_LEVEL_3); + } + if (offlineLicenseKeySetId != null) { + drmSessionManager.setMode(DefaultDrmSessionManager.MODE_PLAYBACK, + offlineLicenseKeySetId); + } + } catch (UnsupportedDrmException e) { + throw new IllegalStateException(e); + } + } + return drmSessionManager; + } + + @Override + protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, + MappingTrackSelector trackSelector, + DrmSessionManager drmSessionManager) { + SimpleExoPlayer player = new DebugSimpleExoPlayer(host, trackSelector, + new DefaultLoadControl(), drmSessionManager); + player.setVideoSurface(surface); + return player; + } + + @Override + protected MediaSource buildSource(HostActivity host, String userAgent, + TransferListener mediaTransferListener) { + DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent); + DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent, + mediaTransferListener); + Uri manifestUri = Uri.parse(parameters.manifestUrl); + DefaultDashChunkSource.Factory chunkSourceFactory = new DefaultDashChunkSource.Factory( + mediaDataSourceFactory); + return new DashMediaSource(manifestUri, manifestDataSourceFactory, chunkSourceFactory, + MIN_LOADABLE_RETRY_COUNT, 0 /* livePresentationDelayMs */, null, null); + } + + @Override + protected void logMetrics(DecoderCounters audioCounters, DecoderCounters videoCounters) { + metricsLogger.logMetric(MetricsLogger.KEY_TEST_NAME, streamName); + metricsLogger.logMetric(MetricsLogger.KEY_IS_CDD_LIMITED_RETRY, isCddLimitedRetry); + 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(DecoderCounters audioCounters, DecoderCounters videoCounters) { + if (fullPlaybackNoSeeking) { + // We shouldn't have skipped any output buffers. + DecoderCountersUtil.assertSkippedOutputBufferCount(AUDIO_TAG, audioCounters, 0); + DecoderCountersUtil.assertSkippedOutputBufferCount(VIDEO_TAG, videoCounters, 0); + // We allow one fewer output buffer due to the way that MediaCodecRenderer and the + // underlying decoders handle the end of stream. This should be tightened up in the future. + DecoderCountersUtil.assertTotalOutputBufferCount(AUDIO_TAG, audioCounters, + audioCounters.inputBufferCount - 1, audioCounters.inputBufferCount); + DecoderCountersUtil.assertTotalOutputBufferCount(VIDEO_TAG, videoCounters, + videoCounters.inputBufferCount - 1, videoCounters.inputBufferCount); + } + try { + int droppedFrameLimit = (int) Math.ceil(MAX_DROPPED_VIDEO_FRAME_FRACTION + * DecoderCountersUtil.getTotalOutputBuffers(videoCounters)); + // Assert that performance is acceptable. + // Assert that total dropped frames were within limit. + DecoderCountersUtil.assertDroppedOutputBufferLimit(VIDEO_TAG, videoCounters, + droppedFrameLimit); + // Assert that consecutive dropped frames were within limit. + DecoderCountersUtil.assertConsecutiveDroppedOutputBufferLimit(VIDEO_TAG, videoCounters, + MAX_CONSECUTIVE_DROPPED_VIDEO_FRAMES); + } catch (AssertionFailedError e) { + if (trackSelector.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 DashTestTrackSelector extends MappingTrackSelector { + + private final String audioFormatId; + private final String[] videoFormatIds; + private final boolean canIncludeAdditionalVideoFormats; + + public boolean includedAdditionalVideoFormats; + + private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds, + boolean canIncludeAdditionalVideoFormats) { + this.audioFormatId = audioFormatId; + this.videoFormatIds = videoFormatIds; + this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats; + } + + @Override + protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + throws ExoPlaybackException { + Assertions.checkState(rendererCapabilities[VIDEO_RENDERER_INDEX].getTrackType() + == C.TRACK_TYPE_VIDEO); + Assertions.checkState(rendererCapabilities[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[rendererCapabilities.length]; + selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection( + rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), + getVideoTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), + rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, + canIncludeAdditionalVideoFormats), + 0 /* seed */); + selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection( + rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), + getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId)); + includedAdditionalVideoFormats = + selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length; + return selections; + } + + private static int[] getVideoTrackIndices(TrackGroup trackGroup, int[] formatSupport, + String[] formatIds, boolean canIncludeAdditionalFormats) { + List trackIndices = new ArrayList<>(); + + // Always select explicitly listed representations. + for (String formatId : formatIds) { + int trackIndex = getTrackIndex(trackGroup, formatId); + Log.d(TAG, "Adding base video format: " + + Format.toLogString(trackGroup.getFormat(trackIndex))); + trackIndices.add(trackIndex); + } + + // Select additional video representations, if supported by the device. + if (canIncludeAdditionalFormats) { + for (int i = 0; i < trackGroup.length; i++) { + if (!trackIndices.contains(i) && isFormatHandled(formatSupport[i])) { + Log.d(TAG, "Adding extra video format: " + + Format.toLogString(trackGroup.getFormat(i))); + trackIndices.add(i); + } + } + } + + int[] trackIndicesArray = Util.toArray(trackIndices); + Arrays.sort(trackIndicesArray); + return trackIndicesArray; + } + + private static int getTrackIndex(TrackGroup trackGroup, String formatId) { + for (int i = 0; i < trackGroup.length; i++) { + if (trackGroup.getFormat(i).id.equals(formatId)) { + return i; + } + } + throw new IllegalStateException("Format " + formatId + " not found."); + } + + private static boolean isFormatHandled(int formatSupport) { + return (formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK) + == RendererCapabilities.FORMAT_HANDLED; + } + + } + +}