xref: /aosp_15_r20/cts/tests/mediapc/common/README.md (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1# Writing an MPC Test
2
3Using
4[this CL](https://android-review.googlesource.com/c/platform/cts/+/3185540) as a
5guide focusing on requirement
6[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media):
7
8-   R: MUST support 6 instances of hardware video decoder sessions (AVC or HEVC)
9    in any codec combination running concurrently at 720p resolution@30 fps.
10-   S: MUST support 6 instances of hardware video decoder sessions (AVC, HEVC,
11    VP9* or later) in any codec combination running concurrently at 720p
12    resolution@30 fps. *Only 2 instances are required if VP9 codec is present.
13-   Tiramisu: MUST support 6 instances of hardware video decoder sessions (AVC,
14    HEVC, VP9, AV1 or later) in any codec combination running concurrently at
15    1080p resolution@30 fps.
16-   Upside-Down Cake: MUST support 6 instances of 8-bit (SDR) hardware video
17    decoder sessions (AVC, HEVC, VP9, AV1 or later) in any codec combination
18    running concurrently with 3 sessions at 1080p resolution@30 fps and 3
19    sessions at 4k resolution@30fps, unless AV1. AV1 codecs are only required to
20    support 1080p resolution, but are still required to support 6 instances at
21    1080p30fps.
22-   Vanilla Ice Cream: MUST support 6 instances of 8-bit (SDR) hardware video
23    decoder sessions (AVC, HEVC, VP9, AV1, or later) in any codec combination
24    running concurrently with 3 sessions at 1080p resolution@30 fps and 3
25    sessions at 4k resolution@30fps, unless AV1. For all sessions, there MUST
26    NOT be more than 1 frame dropped per second. AV1 codecs are only required to
27    support 1080p resolution, but are still required to support 6 instances at
28    1080p30fps.
29
30## Define Requirements
31
32Each requirement needs to be defined in
33[requirements.txtpb](https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/mediapc/requirements/requirements.txtpb).
34The information in this file is then used to generate code to be used in tests
35for said requirement.
36
37### Give the Requirement a Name
38
39Each requirement needs to be given a human-readable name describing what the
40requirement is testing for. Additionally, each requirement name needs to be
41unique. This name is then used for the class name when generating code.
42
43For example, we gave
44[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media)
45the name `"Concurrent Video Decoder Sessions"`. This will then generate the
46following class:
47
48```
49android.mediapc.cts.common.Requirements.ConcurrentVideoDecoderSessionsRequirement
50```
51
52### Define Test Configs
53
54A test config describes different set ups for a given requirement that change
55which performance classes are being tested for said requirement.
56
57For example: requirement,
58[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media),
59there's 3 test configs: - 720p: which describes tests ran at 720p (makes sense),
60these tests only check for performance classes R and S. - 1080p: these tests
61only check for performance class Tiramisu. - 4k: these tests only check for
62performance classes Upside-down Cake and Vanilla Ice Cream
63
64Additionally each test config needs to be given a proto field number. This
65number must be unique for *all* test configs within
66[requirements.txtpb](https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/mediapc/requirements/requirements.txtpb).
67
68```
69test_configs: {
70  key: "720p"
71  value: {
72    description: "Tests running at 720p"
73    proto_field_number: 4
74  }
75}
76test_configs: {
77  key: "1080p"
78  value: {
79    description: "Tests running at 1080p"
80    proto_field_number: 5
81  }
82}
83test_configs: {
84  key: "4k"
85  value: {
86    description: "Tests running at 4k"
87    proto_field_number: 6
88  }
89}
90```
91
92NOTE: The changelist for
93[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media)
94describes an `id` field. This field has since been deprecated and removed, so
95please ignore it.
96
97NOTE: The changelist for
98[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media)
99does not describe proto field numbers. These were yet to be implemented when the
100change was created. They are implemented now.
101
102TIP: Most requirements only use one test configuration to describe a singular
103test which tests for all performance classes. For these requirements, you only
104need to specify a blank default test config. Example:
105[cl/666945690](https://android-review.googlesource.com/c/platform/cts/+/3237331)
106
107```
108test_configs: {
109  key: ""
110  value: {
111    description: "Default test config"
112    proto_field_number: 47
113  }
114}
115```
116
117### Define Variants
118
119In addition to test configs, any variants for a given requirement must also be
120defined.
121
122A variant describes a different set of thresholds a requirement must meet
123depending on the test setup. The main difference between a variant and a test
124config is variants do NOT affect which performance classes are being tested.
125
126Variants do NOT need proto field numbers.
127
128For our requirement,
129[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media),
130when testing with a VP9 codec at 720p, 2 instances are required for S and none
131for R, and when testing with an AV1 or other codec, there is no requirement for
132R. Therefore, we have created 2 variants:
133
134```
135variants: {
136  key: "VP9"
137  value: {
138    description: "When one of the codecs is VP9, variant used in 720p tests"
139  }
140}
141variants: {
142  key: "AV1"
143  value: {
144    description: "When one of the codecs is AV1, variant used in 720p tests"
145  }
146}
147```
148
149NOTE: Sometimes, it can be confusing whether to make a variant or a test config
150when defining requirements. If unsure, the recommendation is to make another
151test config rather than another variant.
152
153NOTE: A variant does not need to be used in every test config.
154
155### Define Measurements
156
157A set of measurements for the requirement must also be defined. Each measurement
158needs a name, a measurement_type, a comparison, and a proto field number.
159
160The measurement name is described in the `key` field for each measurement. It
161needs to be able to be used as a field for a proto, so lowercase, underscores,
162no spaces, etc.
163
164The `measurement_type` describes the data type of the measurement. It can be one
165of the following types:
166
167-   MEASUREMENT_TYPE_BOOL
168-   MEASUREMENT_TYPE_DOUBLE
169-   MEASUREMENT_TYPE_INT
170-   MEASUREMENT_TYPE_STRING
171-   MEASUREMENT_TYPE_LONG
172-   MEASUREMENT_TYPE_FLOAT
173
174The `comparison` describes how the measurement will be tested when evaluating
175the performance class. It can be one of the following types:
176
177-   COMPARISON_EQUAL
178-   COMPARISON_LESS_THAN
179-   COMPARISON_LESS_THAN_OR_EQUAL
180-   COMPARISON_GREATER_THAN
181-   COMPARISON_GREATER_THAN_OR_EQUAL
182-   COMPARISON_INFO_ONLY
183-   COMPARISON_CONFIG
184
185NOTE: Config comparison types are measurements that describe how the test was
186set up. These values are automatically set, and the thresholds defined later
187must all be the same per test config.
188
189Finally, like with test configs, each measurement needs a `proto_field_number`.
190For measurements, the number must be greater than or equal to 3, and only needs
191to be unique among the other measurements for a given requirement.
192
193For our requirement,
194[5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media),
195we've defined the following:
196
197```
198measurements: {
199  key: "concurrent_fps"
200  value: {
201    description: "The number of frames per second that can be decoded concurrently"
202    measurement_type: MEASUREMENT_TYPE_DOUBLE
203    comparison: COMPARISON_GREATER_THAN_OR_EQUAL
204    proto_field_number: 3
205  }
206}
207measurements: {
208  key: "frame_drops_per_sec"
209  value: {
210    description: "The number of frames dropped per second"
211    measurement_type: MEASUREMENT_TYPE_DOUBLE
212    comparison: COMPARISON_LESS_THAN_OR_EQUAL
213    proto_field_number: 4
214  }
215}
216measurements: {
217  key: "resolution"
218  value: {
219    description: "The resolution the test was run at"
220    measurement_type: MEASUREMENT_TYPE_INT
221    comparison: COMPARISON_CONFIG
222    proto_field_number: 5
223  }
224}
225```
226
227### Define Specs
228
229Lastly, the specs for the requirement need to be defined. A spec describes the
230required thresholds for a given performance class. It has the following fields:
231`mpc`, `specification`, `test_config_id`, and `required_values`. Additionally it
232is stored as a map within
233[requirements.txtpb](https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/mediapc/requirements/requirements.txtpb),
234so it needs a `key` field which corresponds to performance class the spec
235describes.
236
237The field `mpc` is an enum that also describes the performance class associated
238with the spec. As such, it should correspond to `key` field. -
239MEDIA_PERFORMANCE_CLASS_11 corresponds to 30 - MEDIA_PERFORMANCE_CLASS_12
240corresponds to 31 - MEDIA_PERFORMANCE_CLASS_13 corresponds to 33 -
241MEDIA_PERFORMANCE_CLASS_14 corresponds to 34 - MEDIA_PERFORMANCE_CLASS_15
242corresponds to 35
243
244The field `specification` describes in text what the requirement is and the
245threshold that must be met. This text is copied directly from the
246[Android CDD](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media)
247for a given requirement/performance class.
248
249The field `test_config_id` describes the associated `test_config_id` which was
250defined previously. If it is the default blank `test_config_id`, the field does
251not have to be set.
252
253#### Define Required Values
254
255Finally, `required_values` must be defined for all measurements associated with
256the specified performance class. These values are stored as a map where the
257`key` corresponds to the measurement name, and the value corresponds to the
258required threshold.
259
260NOTE: if a measurement is not needed for a given performance class level it does
261not have to be specified
262
263NOTE: config measurements must have the same threshold for all performance class
264levels for a given test config
265
266##### Define Variant Required Values
267
268Additionally, `required_values` must be set for variants. A described variant
269does not have to correspond to every test config, but if described for given
270test config, it must be described for all specs with the same test config.
271
272### Example Generated Class for [5.1/H-1-2](https://source.android.com/docs/compatibility/15/android-15-cdd#2271_media)
273
274```
275/**
276  * Add a new ConcurrentVideoDecoderSessionsRequirement for requirement 5.1/H-1-2 to a
277  * {@code PerformanceClassEvaluator} instance.
278  *
279  * Concurrent video decoder sessions
280  */
281public static ConcurrentVideoDecoderSessionsRequirement.With addR5_1__H_1_2() {
282    return new ConcurrentVideoDecoderSessionsRequirement.With();
283}
284
285/**
286  * 5.1/H-1-2 Concurrent Video Decoder Sessions
287  *
288  * Concurrent video decoder sessions
289  */
290public static final class ConcurrentVideoDecoderSessionsRequirement extends Requirement {
291
292    public static final class With {
293        private With() {}
294        public static final class Config1080P {
295            private Config1080P() {}
296            public ConcurrentVideoDecoderSessionsRequirement to(PerformanceClassEvaluator pce) {
297                return pce.addRequirement(ConcurrentVideoDecoderSessionsRequirement.create1080P());
298            }
299        }
300        public static final class Config4K {
301            private Config4K() {}
302            public ConcurrentVideoDecoderSessionsRequirement to(PerformanceClassEvaluator pce) {
303                return pce.addRequirement(ConcurrentVideoDecoderSessionsRequirement.create4K());
304            }
305        }
306        public static final class Config720P {
307            private Config720P() {}
308            public ConcurrentVideoDecoderSessionsRequirement to(PerformanceClassEvaluator pce) {
309                return pce.addRequirement(ConcurrentVideoDecoderSessionsRequirement.create720P());
310            }
311            public Config720PAndVariantAV1 withVariantAV1() {
312                return new Config720PAndVariantAV1();
313            }
314            public Config720PAndVariantVP9 withVariantVP9() {
315                return new Config720PAndVariantVP9();
316            }
317        }
318        public static final class VariantAV1 {
319            private VariantAV1() {}
320            public Config720PAndVariantAV1 withConfig720P() {
321                return new Config720PAndVariantAV1();
322            }
323        }
324        public static final class VariantVP9 {
325            private VariantVP9() {}
326            public Config720PAndVariantVP9 withConfig720P() {
327                return new Config720PAndVariantVP9();
328            }
329        }
330        public static final class Config720PAndVariantAV1 {
331            private Config720PAndVariantAV1() {}
332            public ConcurrentVideoDecoderSessionsRequirement to(PerformanceClassEvaluator pce) {
333                return pce.addRequirement(ConcurrentVideoDecoderSessionsRequirement.create720PAV1());
334            }
335        }
336        public static final class Config720PAndVariantVP9 {
337            private Config720PAndVariantVP9() {}
338            public ConcurrentVideoDecoderSessionsRequirement to(PerformanceClassEvaluator pce) {
339                return pce.addRequirement(ConcurrentVideoDecoderSessionsRequirement.create720PVP9());
340            }
341        }
342        public Config1080P withConfig1080P() {
343            return new Config1080P();
344        }
345        public Config4K withConfig4K() {
346            return new Config4K();
347        }
348        public Config720P withConfig720P() {
349            return new Config720P();
350        }
351        public VariantAV1 withVariantAV1() {
352            return new VariantAV1();
353        }
354        public VariantVP9 withVariantVP9() {
355            return new VariantVP9();
356        }
357    }
358
359    /**
360      * 5.1/H-1-2 Concurrent Video Decoder Sessions
361      *
362      * Concurrent video decoder sessions
363      */
364    private static ConcurrentVideoDecoderSessionsRequirement create1080P() {
365        var concurrentFps =
366            RequiredMeasurement.<Double>builder()
367                .setId("concurrent_fps")
368                .setPredicate(RequirementConstants.DOUBLE_GTE)
369                .addRequiredValue(VERSION_CODES.TIRAMISU, 171.000000)
370                .build();
371        var frameDropsPerSec =
372            RequiredMeasurement.<Double>builder()
373                .setId("frame_drops_per_sec")
374                .setPredicate(RequirementConstants.DOUBLE_LTE)
375                .build();
376        var resolution =
377            RequiredMeasurement.<Integer>builder()
378                .setId("resolution")
379                .setPredicate(RequirementConstants.INTEGER_INFO)
380                .addRequiredValue(VERSION_CODES.TIRAMISU, 1080)
381                .build();
382
383        ConcurrentVideoDecoderSessionsRequirement req =
384            new ConcurrentVideoDecoderSessionsRequirement(
385                "r5_1__h_1_2__1080_p",
386                concurrentFps,
387                frameDropsPerSec,
388                resolution);
389        req.setMeasuredValue("resolution",1080);
390        return req;
391    }
392
393    /**
394      * 5.1/H-1-2 Concurrent Video Decoder Sessions
395      *
396      * Concurrent video decoder sessions
397      */
398    private static ConcurrentVideoDecoderSessionsRequirement create4K() {
399        var concurrentFps =
400            RequiredMeasurement.<Double>builder()
401                .setId("concurrent_fps")
402                .setPredicate(RequirementConstants.DOUBLE_GTE)
403                .addRequiredValue(VERSION_CODES.UPSIDE_DOWN_CAKE, 171.000000)
404                .addRequiredValue(VERSION_CODES.VANILLA_ICE_CREAM, 171.000000)
405                .build();
406        var frameDropsPerSec =
407            RequiredMeasurement.<Double>builder()
408                .setId("frame_drops_per_sec")
409                .setPredicate(RequirementConstants.DOUBLE_LTE)
410                .addRequiredValue(VERSION_CODES.VANILLA_ICE_CREAM, 1.000000)
411                .build();
412        var resolution =
413            RequiredMeasurement.<Integer>builder()
414                .setId("resolution")
415                .setPredicate(RequirementConstants.INTEGER_INFO)
416                .addRequiredValue(VERSION_CODES.UPSIDE_DOWN_CAKE, 2160)
417                .addRequiredValue(VERSION_CODES.VANILLA_ICE_CREAM, 2160)
418                .build();
419
420        ConcurrentVideoDecoderSessionsRequirement req =
421            new ConcurrentVideoDecoderSessionsRequirement(
422                "r5_1__h_1_2__4_k",
423                concurrentFps,
424                frameDropsPerSec,
425                resolution);
426        req.setMeasuredValue("resolution",2160);
427        return req;
428    }
429
430    /**
431      * 5.1/H-1-2 Concurrent Video Decoder Sessions
432      *
433      * Concurrent video decoder sessions
434      */
435    private static ConcurrentVideoDecoderSessionsRequirement create720P() {
436        var concurrentFps =
437            RequiredMeasurement.<Double>builder()
438                .setId("concurrent_fps")
439                .setPredicate(RequirementConstants.DOUBLE_GTE)
440                .addRequiredValue(VERSION_CODES.R, 171.000000)
441                .addRequiredValue(VERSION_CODES.S, 171.000000)
442                .build();
443        var frameDropsPerSec =
444            RequiredMeasurement.<Double>builder()
445                .setId("frame_drops_per_sec")
446                .setPredicate(RequirementConstants.DOUBLE_LTE)
447                .build();
448        var resolution =
449            RequiredMeasurement.<Integer>builder()
450                .setId("resolution")
451                .setPredicate(RequirementConstants.INTEGER_INFO)
452                .addRequiredValue(VERSION_CODES.R, 720)
453                .addRequiredValue(VERSION_CODES.S, 720)
454                .build();
455
456        ConcurrentVideoDecoderSessionsRequirement req =
457            new ConcurrentVideoDecoderSessionsRequirement(
458                "r5_1__h_1_2__720_p",
459                concurrentFps,
460                frameDropsPerSec,
461                resolution);
462        req.setMeasuredValue("resolution",720);
463        return req;
464    }
465
466    /**
467      * 5.1/H-1-2 Concurrent Video Decoder Sessions When one of the codecs is AV1, variant used in 720p tests
468      *
469      * Concurrent video decoder sessions
470      */
471    private static ConcurrentVideoDecoderSessionsRequirement create720PAV1() {
472        var concurrentFps = RequiredMeasurement
473                .<Double>builder()
474                .setId("concurrent_fps")
475                .setPredicate(RequirementConstants.DOUBLE_GTE)
476                .addRequiredValue(VERSION_CODES.S, 171.000000)
477                .build();
478        var frameDropsPerSec = RequiredMeasurement
479                .<Double>builder()
480                .setId("frame_drops_per_sec")
481                .setPredicate(RequirementConstants.DOUBLE_LTE)
482                .build();
483        var resolution = RequiredMeasurement
484                .<Integer>builder()
485                .setId("resolution")
486                .setPredicate(RequirementConstants.INTEGER_INFO)
487                .addRequiredValue(VERSION_CODES.R, 720)
488                .addRequiredValue(VERSION_CODES.S, 720)
489                .build();
490        ConcurrentVideoDecoderSessionsRequirement req =
491            new ConcurrentVideoDecoderSessionsRequirement(
492                "r5_1__h_1_2",
493                concurrentFps,
494                frameDropsPerSec,
495                resolution);
496        req.setMeasuredValue("resolution",720);
497        return req;
498    }
499
500    /**
501      * 5.1/H-1-2 Concurrent Video Decoder Sessions When one of the codecs is VP9, variant used in 720p tests
502      *
503      * Concurrent video decoder sessions
504      */
505    private static ConcurrentVideoDecoderSessionsRequirement create720PVP9() {
506        var concurrentFps = RequiredMeasurement
507                .<Double>builder()
508                .setId("concurrent_fps")
509                .setPredicate(RequirementConstants.DOUBLE_GTE)
510                .addRequiredValue(VERSION_CODES.S, 57.000000)
511                .build();
512        var frameDropsPerSec = RequiredMeasurement
513                .<Double>builder()
514                .setId("frame_drops_per_sec")
515                .setPredicate(RequirementConstants.DOUBLE_LTE)
516                .build();
517        var resolution = RequiredMeasurement
518                .<Integer>builder()
519                .setId("resolution")
520                .setPredicate(RequirementConstants.INTEGER_INFO)
521                .addRequiredValue(VERSION_CODES.R, 720)
522                .addRequiredValue(VERSION_CODES.S, 720)
523                .build();
524        ConcurrentVideoDecoderSessionsRequirement req =
525            new ConcurrentVideoDecoderSessionsRequirement(
526                "r5_1__h_1_2",
527                concurrentFps,
528                frameDropsPerSec,
529                resolution);
530        req.setMeasuredValue("resolution",720);
531        return req;
532    }
533
534    /** The number of frames per second that can be decoded concurrently */
535    public void setConcurrentFps(double v) {
536        this.setMeasuredValue("concurrent_fps", v);
537    }
538
539    /** The number of frames dropped per second */
540    public void setFrameDropsPerSec(double v) {
541        this.setMeasuredValue("frame_drops_per_sec", v);
542    }
543
544    /** The resolution the test was run at */
545    public int getResolution() {
546        return this.getMeasuredValue("resolution", Integer.class);
547    }
548
549    private ConcurrentVideoDecoderSessionsRequirement(String id, RequiredMeasurement<?>... reqs) {
550        super(id, reqs);
551    }
552}
553```
554
555## Update Test to Report Data Using PerformanceClassEvaluator
556
557Now that we have a requirement defined we just need to update our test to use
558PerformanceClassEvaluator.
559
560First we need to add the following to our test class: @Rule public final
561TestName mTestName = new TestName();
562
563### Initializing the Requirement Objects
564
565Next we will create the evaluator and add our newly defined requirement. This
566can be done at any point during the test, but typically test writers choose to
567do this at the end of the test:
568
569```
570PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
571ConcurrentVideoDecoderSessionsRequirement r5_1__h_1_2 =
572    Requirements.addR5_1__H_1_2().withConfigX().withVariantY().to(pce);
573```
574
575NOTE: `withConfigX` should be replaced with the proper config, ex:
576`withConfig1080P`. If using the default blank config, this should be left out.
577
578NOTE: `withVariantY` should be replaced with the proper variant, ex:
579`withVariantVP9`. If the test is not associated with a variant, this should also
580be left out.
581
582NOTE: the order configs and variants are specified does not matter, i.e.
583`withVariantY().withConfigX()` is also valid
584
585### Setting the Measured Values
586
587The generated class for the given requirement also generates with set methods
588for each measurement.
589
590After the test, once our required measurement(s) have been calculated, we use
591the set measurement method(s) generated to report them:
592
593```
594r5_1__H_1_2.setConcurrentFps(achievedFrameRate);
595r5_1__H_1_2.setFrameDropsPerSec(frameDropsPerSec);
596```
597
598NOTE: if a measurement is not associated with the specified test config, it does
599not have to be set.
600
601NOTE: config measurement generate get methods instead of set, and do not need to
602be set
603
604### Submitting the Test Results
605
606Finally, we just need to submit our results. The submit method should be called
607only once at the very end of the test. If we are writing our test CTS, we should
608use `submitAndCheck`; if we are writing our test under CTS-Verifier or ITS, we
609should use `submitAndVerify`. Ex:
610
611```
612pce.submitAndCheck();
613```
614
615The test results are then processed and reported creating a file
616media_performance_class_test_cases.reportlog.json which will eventually have its
617data uploaded and processed.
618
619You can view the file with
620
621```shell
622adb root
623adb shell cat /storage/emulated/0/report-log-files/MediaPerformanceClassTestCases.reportlog.json
624```
625