1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.mediapc.cts;
18 
19 import android.media.MediaFormat;
20 import android.util.Log;
21 import android.view.Surface;
22 
23 import androidx.test.rule.ActivityTestRule;
24 
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Rule;
28 
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 
34 import static android.mediapc.cts.CodecTestBase.selectCodecs;
35 import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assert.assertTrue;
38 import static org.junit.Assume.assumeFalse;
39 import static org.junit.Assume.assumeTrue;
40 
41 public class FrameDropTestBase {
42     private static final String LOG_TAG = FrameDropTestBase.class.getSimpleName();
43     static final boolean[] boolStates = {false, true};
44     static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
45     static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
46     static final String VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
47     static final String VP9 = MediaFormat.MIMETYPE_VIDEO_VP9;
48     static final String AV1 = MediaFormat.MIMETYPE_VIDEO_AV1;
49     static final String AAC = MediaFormat.MIMETYPE_AUDIO_AAC;
50     static final String AAC_LOAD_FILE_NAME = "bbb_1c_128kbps_aac_audio.mp4";
51     static final String AVC_LOAD_FILE_NAME = "bbb_1280x720_3mbps_30fps_avc.mp4";
52     static final long DECODE_31S = 31000; // In ms
53     static final int MAX_ADAPTIVE_PLAYBACK_FRAME_DROP = 0;
54     static final int FRAME_RATE = Utils.isSPerfClass() ? 60 : 30;
55     static final int MAX_FRAME_DROP_FOR_30S;
56 
57     final String mMime;
58     final String mDecoderName;
59     final boolean mIsAsync;
60     Surface mSurface;
61 
62     private LoadStatus mLoadStatus = null;
63     private Thread mTranscodeLoadThread = null;
64     private Thread mAudioPlaybackLoadThread = null;
65     private Exception mTranscodeLoadException = null;
66     private Exception mAudioPlaybackLoadException = null;
67 
68     static String AVC_DECODER_NAME;
69     static String AVC_ENCODER_NAME;
70     static String AAC_DECODER_NAME;
71     static Map<String, String> m540pTestFiles = new HashMap<>();
72     static Map<String, String> m1080pTestFiles = new HashMap<>();
73     static {
74         if (Utils.isSPerfClass()) {
75             // Two frame drops per 10 seconds at 60 fps is 6 drops per 30 seconds
76             MAX_FRAME_DROP_FOR_30S = 6;
m540pTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4")77             m540pTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4");
m540pTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4")78             m540pTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4");
m540pTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm")79             m540pTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm");
m540pTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm")80             m540pTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm");
m540pTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4")81             m540pTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4");
82 
m1080pTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4")83             m1080pTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4");
m1080pTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4")84             m1080pTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4");
m1080pTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm")85             m1080pTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm");
m1080pTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm")86             m1080pTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm");
m1080pTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4")87             m1080pTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4");
88         } else if (Utils.isRPerfClass()) {
89             // One frame drops per 10 seconds at 30 fps is 3 drops per 30 seconds
90             MAX_FRAME_DROP_FOR_30S = 3;
m540pTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4")91             m540pTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4");
m540pTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4")92             m540pTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4");
m540pTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm")93             m540pTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm");
m540pTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm")94             m540pTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm");
m540pTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4")95             m540pTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4");
96 
m1080pTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4")97             m1080pTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
m1080pTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4")98             m1080pTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
m1080pTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm")99             m1080pTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm");
m1080pTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm")100             m1080pTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
m1080pTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4")101             m1080pTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
102         } else {
103             MAX_FRAME_DROP_FOR_30S = 0;
Log.e(LOG_TAG, "Unknown performance class.")104             Log.e(LOG_TAG, "Unknown performance class.");
105         }
106     }
107 
108     @Before
setUp()109     public void setUp() throws Exception {
110         assumeTrue("Test requires performance class.", Utils.isPerfClass());
111 
112         ArrayList<String> listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false);
113         assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty());
114         AVC_DECODER_NAME = listOfAvcHwDecoders.get(0);
115 
116         ArrayList<String> listOfAvcHwEncoders = selectHardwareCodecs(AVC, null, null, true);
117         assumeFalse("Test requires h/w avc encoder", listOfAvcHwEncoders.isEmpty());
118         AVC_ENCODER_NAME = listOfAvcHwEncoders.get(0);
119 
120         ArrayList<String> listOfAacDecoders = selectCodecs(AAC, null, null, false);
121         assertFalse("Test requires aac decoder", listOfAacDecoders.isEmpty());
122         AAC_DECODER_NAME = listOfAacDecoders.get(0);
123 
124         createSurface();
125         startLoad();
126     }
127 
128     @After
tearDown()129     public void tearDown() throws Exception {
130         stopLoad();
131         releaseSurface();
132     }
133 
134     @Rule
135     public ActivityTestRule<TestActivity> mActivityRule =
136             new ActivityTestRule<>(TestActivity.class);
137 
FrameDropTestBase(String mimeType, String decoderName, boolean isAsync)138     public FrameDropTestBase(String mimeType, String decoderName, boolean isAsync) {
139         mMime = mimeType;
140         mDecoderName = decoderName;
141         mIsAsync = isAsync;
142     }
143 
144     // Returns the list of objects with mimeTypes and their hardware decoders supporting the
145     // given features combining with sync and async modes.
prepareArgumentsList(String[] features)146     static List<Object[]> prepareArgumentsList(String[] features) {
147         final List<Object[]> argsList = new ArrayList<>();
148         final String[] mimesList = new String[] {AVC, HEVC, VP8, VP9, AV1};
149         for (String mime : mimesList) {
150             MediaFormat format = MediaFormat.createVideoFormat(mime, 1920, 1080);
151             format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
152             ArrayList<MediaFormat> formats = new ArrayList<>();
153             formats.add(format);
154             ArrayList<String> listOfDecoders =
155                     selectHardwareCodecs(mime, formats, features, false);
156             for (String decoder : listOfDecoders) {
157                 for (boolean isAsync : boolStates) {
158                     argsList.add(new Object[]{mime, decoder, isAsync});
159                 }
160             }
161         }
162         return argsList;
163     }
164 
createSurface()165     private void createSurface() throws InterruptedException {
166         mActivityRule.getActivity().waitTillSurfaceIsCreated();
167         mSurface = mActivityRule.getActivity().getSurface();
168         assertTrue("Surface created is null.", mSurface != null);
169         assertTrue("Surface created is invalid.", mSurface.isValid());
170         // As we display 1920x1080 and 960x540 only which are of same aspect ratio, we will
171         // be setting screen params to 1920x1080
172         mActivityRule.getActivity().setScreenParams(1920, 1080, true);
173     }
174 
releaseSurface()175     private void releaseSurface() {
176         if (mSurface != null) {
177             mSurface.release();
178             mSurface = null;
179         }
180     }
181 
createTranscodeLoad()182     private Thread createTranscodeLoad() {
183         Thread transcodeLoadThread = new Thread(() -> {
184             try {
185                 TranscodeLoad transcodeLoad = new TranscodeLoad(AVC, AVC_LOAD_FILE_NAME,
186                         AVC_DECODER_NAME, AVC_ENCODER_NAME, mLoadStatus);
187                 transcodeLoad.doTranscode();
188             } catch (Exception e) {
189                 mTranscodeLoadException = e;
190             }
191         });
192         return transcodeLoadThread;
193     }
194 
createAudioPlaybackLoad()195     private Thread createAudioPlaybackLoad() {
196         Thread audioPlaybackLoadThread = new Thread(() -> {
197             try {
198                 AudioPlaybackLoad audioPlaybackLoad = new AudioPlaybackLoad(AAC, AAC_LOAD_FILE_NAME,
199                         AAC_DECODER_NAME, mLoadStatus);
200                 audioPlaybackLoad.doDecodeAndPlayback();
201             } catch (Exception e) {
202                 mAudioPlaybackLoadException = e;
203             }
204         });
205         return audioPlaybackLoadThread;
206     }
207 
startLoad()208     private void startLoad() {
209         // TODO: b/183671436
210         // Start Transcode load (Decoder(720p) + Encoder(720p))
211         mLoadStatus = new LoadStatus();
212         mTranscodeLoadThread = createTranscodeLoad();
213         mTranscodeLoadThread.start();
214         // Start 128kbps AAC audio playback
215         mAudioPlaybackLoadThread = createAudioPlaybackLoad();
216         mAudioPlaybackLoadThread.start();
217     }
218 
stopLoad()219     private void stopLoad() throws Exception {
220         if (mLoadStatus != null) {
221             mLoadStatus.setLoadFinished();
222             mLoadStatus = null;
223         }
224         if (mTranscodeLoadThread != null) {
225             mTranscodeLoadThread.join();
226             mTranscodeLoadThread = null;
227         }
228         if (mAudioPlaybackLoadThread != null) {
229             mAudioPlaybackLoadThread.join();
230             mAudioPlaybackLoadThread = null;
231         }
232         if (mTranscodeLoadException != null) throw mTranscodeLoadException;
233         if (mAudioPlaybackLoadException != null) throw mAudioPlaybackLoadException;
234     }
235 }
236