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 static android.mediapc.cts.CodecTestBase.codecFilter;
20 import static android.mediapc.cts.CodecTestBase.codecPrefix;
21 import static android.mediapc.cts.CodecTestBase.mediaTypePrefix;
22 import static android.mediapc.cts.CodecTestBase.selectCodecs;
23 import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
24 
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assume.assumeFalse;
28 
29 import android.media.MediaFormat;
30 import android.mediapc.cts.common.Utils;
31 import android.os.Build;
32 import android.view.Surface;
33 
34 import androidx.test.rule.ActivityTestRule;
35 
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Rule;
39 
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 public class FrameDropTestBase {
46     private static final String LOG_TAG = FrameDropTestBase.class.getSimpleName();
47     static final boolean[] boolStates = {false, true};
48     static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
49     static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
50     static final String VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
51     static final String VP9 = MediaFormat.MIMETYPE_VIDEO_VP9;
52     static final String AV1 = MediaFormat.MIMETYPE_VIDEO_AV1;
53     static final String AAC = MediaFormat.MIMETYPE_AUDIO_AAC;
54     static final String AAC_LOAD_FILE_NAME = "bbb_1c_128kbps_aac_audio.mp4";
55     static final String AVC_LOAD_FILE_NAME = "bbb_1920x1080_8mbps_60fps_avc.mp4";
56     static final long DECODE_31S = 31000; // In ms
57     static final int MAX_FRAME_DROP_FOR_30S;
58     // For perf class R, one frame drop per 10 seconds at 30 fps i.e. 3 drops per 30 seconds
59     static final int MAX_FRAME_DROP_FOR_30S_30FPS_PC_R = 3;
60     // For perf class S, two frame drops per 10 seconds at 60 fps i.e. 6 drops per 30 seconds
61     static final int MAX_FRAME_DROP_FOR_30S_60FPS_PC_S = 6;
62     // For perf class T, one frame drop per 10 seconds at 60 fps i.e. 3 drops per 30 seconds
63     static final int MAX_FRAME_DROP_FOR_30S_60FPS_PC_T = 3;
64 
65     final String mMediaType;
66     final String mDecoderName;
67     final boolean mIsAsync;
68     Surface mSurface;
69 
70     private LoadStatus mLoadStatus = null;
71     private Thread mTranscodeLoadThread = null;
72     private Thread mAudioPlaybackLoadThread = null;
73     private Exception mTranscodeLoadException = null;
74     private Exception mAudioPlaybackLoadException = null;
75 
76     static String AVC_DECODER_NAME;
77     static String AVC_ENCODER_NAME;
78     static String AAC_DECODER_NAME;
79     static Map<String, String> m540p30FpsTestFiles = new HashMap<>();
80     static Map<String, String> m1080p30FpsTestFiles = new HashMap<>();
81     static Map<String, String> m540p60FpsTestFiles = new HashMap<>();
82     static Map<String, String> m1080p60FpsTestFiles = new HashMap<>();
83     static Map<String, String> m2160p60FpsTestFiles = new HashMap<>();
84     static {
m540p60FpsTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4")85         m540p60FpsTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4");
m540p60FpsTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4")86         m540p60FpsTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4");
m540p60FpsTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm")87         m540p60FpsTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm");
m540p60FpsTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm")88         m540p60FpsTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm");
m540p60FpsTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4")89         m540p60FpsTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4");
90 
m1080p60FpsTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4")91         m1080p60FpsTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4");
m1080p60FpsTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4")92         m1080p60FpsTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4");
m1080p60FpsTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm")93         m1080p60FpsTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm");
m1080p60FpsTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm")94         m1080p60FpsTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm");
m1080p60FpsTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4")95         m1080p60FpsTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4");
96 
m2160p60FpsTestFiles.put(AVC, "bbb_3840x2160_24mbps_60fps_avc.mp4")97         m2160p60FpsTestFiles.put(AVC, "bbb_3840x2160_24mbps_60fps_avc.mp4");
m2160p60FpsTestFiles.put(HEVC, "bbb_3840x2160_18mbps_60fps_hevc.mkv")98         m2160p60FpsTestFiles.put(HEVC, "bbb_3840x2160_18mbps_60fps_hevc.mkv");
m2160p60FpsTestFiles.put(VP8, "bbb_3840x2160_24mbps_60fps_vp8.webm")99         m2160p60FpsTestFiles.put(VP8, "bbb_3840x2160_24mbps_60fps_vp8.webm");
m2160p60FpsTestFiles.put(VP9, "bbb_3840x2160_18mbps_60fps_vp9.webm")100         m2160p60FpsTestFiles.put(VP9, "bbb_3840x2160_18mbps_60fps_vp9.webm");
101         // Limit AV1 4k tests to 1080p as per PC14 requirements
m2160p60FpsTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4")102         m2160p60FpsTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4");
103 
m540p30FpsTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4")104         m540p30FpsTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4");
m540p30FpsTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4")105         m540p30FpsTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4");
m540p30FpsTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm")106         m540p30FpsTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm");
m540p30FpsTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm")107         m540p30FpsTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm");
m540p30FpsTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4")108         m540p30FpsTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4");
109 
m1080p30FpsTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4")110         m1080p30FpsTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
m1080p30FpsTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4")111         m1080p30FpsTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
m1080p30FpsTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm")112         m1080p30FpsTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm");
m1080p30FpsTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm")113         m1080p30FpsTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
m1080p30FpsTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4")114         m1080p30FpsTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
115 
116         switch (Utils.getPerfClass()) {
117             case Build.VERSION_CODES.TIRAMISU:
118                 MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_60FPS_PC_T;
119                 break;
120             case Build.VERSION_CODES.S:
121                 MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_60FPS_PC_S;
122                 break;
123             case Build.VERSION_CODES.R:
124             default:
125                 MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_30FPS_PC_R;
126                 break;
127         }
128     }
129 
130     @Before
setUp()131     public void setUp() throws Exception {
132         Utils.assumeDeviceMeetsPerformanceClassPreconditions();
133 
134         ArrayList<String> listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false);
135         assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty());
136         AVC_DECODER_NAME = listOfAvcHwDecoders.get(0);
137 
138         ArrayList<String> listOfAvcHwEncoders = selectHardwareCodecs(AVC, null, null, true);
139         assumeFalse("Test requires h/w avc encoder", listOfAvcHwEncoders.isEmpty());
140         AVC_ENCODER_NAME = listOfAvcHwEncoders.get(0);
141 
142         ArrayList<String> listOfAacDecoders = selectCodecs(AAC, null, null, false);
143         assertFalse("Test requires aac decoder", listOfAacDecoders.isEmpty());
144         AAC_DECODER_NAME = listOfAacDecoders.get(0);
145 
146         createSurface();
147         startLoad();
148     }
149 
150     @After
tearDown()151     public void tearDown() throws Exception {
152         stopLoad();
153         releaseSurface();
154     }
155 
156     @Rule
157     public ActivityTestRule<TestActivity> mActivityRule =
158             new ActivityTestRule<>(TestActivity.class);
159 
FrameDropTestBase(String mediaType, String decoderName, boolean isAsync)160     public FrameDropTestBase(String mediaType, String decoderName, boolean isAsync) {
161         mMediaType = mediaType;
162         mDecoderName = decoderName;
163         mIsAsync = isAsync;
164     }
165 
166     // Returns the list of objects with mediaTypes and their hardware decoders supporting the
167     // given features combining with sync and async modes.
prepareArgumentsList(String[] features)168     static List<Object[]> prepareArgumentsList(String[] features) {
169         final List<Object[]> argsList = new ArrayList<>();
170         final String[] mediaTypesList = new String[] {AVC, HEVC, VP8, VP9, AV1};
171         for (String mediaType : mediaTypesList) {
172             if (mediaTypePrefix != null && !mediaType.startsWith(mediaTypePrefix)) {
173                 continue;
174             }
175             MediaFormat format = MediaFormat.createVideoFormat(mediaType, 1920, 1080);
176             format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
177             ArrayList<MediaFormat> formats = new ArrayList<>();
178             formats.add(format);
179             ArrayList<String> listOfDecoders =
180                     selectHardwareCodecs(mediaType, formats, features, false);
181             for (String decoder : listOfDecoders) {
182                 if ((codecPrefix != null && !decoder.startsWith(codecPrefix))
183                         || (codecFilter != null && !codecFilter.matcher(decoder).matches())) {
184                     continue;
185                 }
186                 for (boolean isAsync : boolStates) {
187                     argsList.add(new Object[]{mediaType, decoder, isAsync});
188                 }
189             }
190         }
191         return argsList;
192     }
193 
getAchievedPerfClass(int frameRate, int frameDropCount)194     protected int getAchievedPerfClass(int frameRate, int frameDropCount) {
195         int pc = 0;
196         if (frameRate == 30) {
197             pc = frameDropCount <= MAX_FRAME_DROP_FOR_30S_30FPS_PC_R ? Build.VERSION_CODES.R : 0;
198         } else {
199             pc = frameDropCount <= MAX_FRAME_DROP_FOR_30S_60FPS_PC_T ? Build.VERSION_CODES.TIRAMISU
200                     : frameDropCount <= MAX_FRAME_DROP_FOR_30S_60FPS_PC_S ? Build.VERSION_CODES.S
201                     : 0;
202         }
203         return pc;
204     }
205 
createSurface()206     private void createSurface() throws InterruptedException {
207         mActivityRule.getActivity().waitTillSurfaceIsCreated();
208         mSurface = mActivityRule.getActivity().getSurface();
209         assertTrue("Surface created is null.", mSurface != null);
210         assertTrue("Surface created is invalid.", mSurface.isValid());
211         // As we display 1920x1080 and 960x540 only which are of same aspect ratio, we will
212         // be setting screen params to 1920x1080
213         mActivityRule.getActivity().setScreenParams(1920, 1080, true);
214     }
215 
releaseSurface()216     private void releaseSurface() {
217         if (mSurface != null) {
218             mSurface.release();
219             mSurface = null;
220         }
221     }
222 
createTranscodeLoad()223     private Thread createTranscodeLoad() {
224         Thread transcodeLoadThread = new Thread(() -> {
225             try {
226                 TranscodeLoad transcodeLoad = new TranscodeLoad(AVC, AVC_LOAD_FILE_NAME,
227                         AVC_DECODER_NAME, AVC_ENCODER_NAME, mLoadStatus);
228                 transcodeLoad.doTranscode();
229             } catch (Exception e) {
230                 mTranscodeLoadException = e;
231             }
232         });
233         return transcodeLoadThread;
234     }
235 
createAudioPlaybackLoad()236     private Thread createAudioPlaybackLoad() {
237         Thread audioPlaybackLoadThread = new Thread(() -> {
238             try {
239                 AudioPlaybackLoad audioPlaybackLoad = new AudioPlaybackLoad(AAC, AAC_LOAD_FILE_NAME,
240                         AAC_DECODER_NAME, mLoadStatus);
241                 audioPlaybackLoad.doDecodeAndPlayback();
242             } catch (Exception e) {
243                 mAudioPlaybackLoadException = e;
244             }
245         });
246         return audioPlaybackLoadThread;
247     }
248 
startLoad()249     private void startLoad() {
250         // Start Transcode load (Decoder(1080p) + Encoder(720p))
251         mLoadStatus = new LoadStatus();
252         mTranscodeLoadThread = createTranscodeLoad();
253         mTranscodeLoadThread.start();
254         // Start 128kbps AAC audio playback
255         mAudioPlaybackLoadThread = createAudioPlaybackLoad();
256         mAudioPlaybackLoadThread.start();
257     }
258 
stopLoad()259     private void stopLoad() throws Exception {
260         if (mLoadStatus != null) {
261             mLoadStatus.setLoadFinished();
262         }
263         if (mTranscodeLoadThread != null) {
264             mTranscodeLoadThread.join();
265             mTranscodeLoadThread = null;
266         }
267         if (mAudioPlaybackLoadThread != null) {
268             mAudioPlaybackLoadThread.join();
269             mAudioPlaybackLoadThread = null;
270         }
271         if (mTranscodeLoadException != null) throw mTranscodeLoadException;
272         if (mAudioPlaybackLoadException != null) throw mAudioPlaybackLoadException;
273         mLoadStatus = null;
274     }
275 }
276