1 /*
2  * Copyright (C) 2010 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.media.misc.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 import static org.junit.Assume.assumeTrue;
26 
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.hardware.Camera;
30 import android.hardware.Camera.Size;
31 import android.hardware.cts.helpers.CameraUtils;
32 import android.media.CamcorderProfile;
33 import android.media.EncoderProfiles;
34 import android.media.MediaCodecInfo;
35 import android.media.MediaCodecInfo.CodecProfileLevel;
36 import android.media.MediaCodecList;
37 import android.media.MediaFormat;
38 import android.media.MediaRecorder;
39 import android.util.Log;
40 
41 import androidx.test.InstrumentationRegistry;
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 
44 import com.android.compatibility.common.util.FrameworkSpecificTest;
45 import com.android.compatibility.common.util.NonMainlineTest;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.Arrays;
51 import java.util.List;
52 
53 @FrameworkSpecificTest
54 @NonMainlineTest
55 @RunWith(AndroidJUnit4.class)
56 public class CamcorderProfileTest {
57 
58     private static final String TAG = "CamcorderProfileTest";
59     private static final int MIN_HIGH_SPEED_FPS = 100;
60     private static final Integer[] ALL_SUPPORTED_QUALITIES = {
61         CamcorderProfile.QUALITY_LOW,
62         CamcorderProfile.QUALITY_HIGH,
63         CamcorderProfile.QUALITY_QCIF,
64         CamcorderProfile.QUALITY_CIF,
65         CamcorderProfile.QUALITY_480P,
66         CamcorderProfile.QUALITY_720P,
67         CamcorderProfile.QUALITY_1080P,
68         CamcorderProfile.QUALITY_QVGA,
69         CamcorderProfile.QUALITY_2160P,
70         CamcorderProfile.QUALITY_VGA,
71         CamcorderProfile.QUALITY_4KDCI,
72         CamcorderProfile.QUALITY_QHD,
73         CamcorderProfile.QUALITY_2K,
74         CamcorderProfile.QUALITY_8KUHD,
75 
76         CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
77         CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
78         CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
79         CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
80         CamcorderProfile.QUALITY_TIME_LAPSE_480P,
81         CamcorderProfile.QUALITY_TIME_LAPSE_720P,
82         CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
83         CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
84         CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
85         CamcorderProfile.QUALITY_TIME_LAPSE_VGA,
86         CamcorderProfile.QUALITY_TIME_LAPSE_4KDCI,
87         CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
88         CamcorderProfile.QUALITY_TIME_LAPSE_2K,
89         CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD,
90 
91         CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
92         CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
93         CamcorderProfile.QUALITY_HIGH_SPEED_480P,
94         CamcorderProfile.QUALITY_HIGH_SPEED_720P,
95         CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
96         CamcorderProfile.QUALITY_HIGH_SPEED_2160P,
97         CamcorderProfile.QUALITY_HIGH_SPEED_CIF,
98         CamcorderProfile.QUALITY_HIGH_SPEED_VGA,
99         CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI,
100     };
101     private static final int LAST_QUALITY = CamcorderProfile.QUALITY_8KUHD;
102     private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD;
103     private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI;
104     private static final Integer[] UNKNOWN_QUALITIES = {
105         LAST_QUALITY + 1, // Unknown normal profile quality
106         LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
107         LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
108     };
109 
110     // Uses get without id if cameraId == -1 and get with id otherwise.
getWithOptionalId(int quality, int cameraId)111     private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
112         if (cameraId == -1) {
113             return CamcorderProfile.get(quality);
114         } else {
115             return CamcorderProfile.get(cameraId, quality);
116         }
117     }
118 
checkProfile(CamcorderProfile profile, List<Size> videoSizes)119     private void checkProfile(CamcorderProfile profile, List<Size> videoSizes) {
120         Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
121             "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
122             "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
123             "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
124             profile.duration,
125             profile.quality,
126             profile.fileFormat,
127             profile.videoCodec,
128             profile.videoBitRate,
129             profile.videoFrameRate,
130             profile.videoFrameWidth,
131             profile.videoFrameHeight,
132             profile.audioCodec,
133             profile.audioBitRate,
134             profile.audioSampleRate,
135             profile.audioChannels));
136         assertTrue(profile.duration > 0);
137         assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
138         assertTrue(profile.videoBitRate > 0);
139         assertTrue(profile.videoFrameRate > 0);
140         assertTrue(profile.videoFrameWidth > 0);
141         assertTrue(profile.videoFrameHeight > 0);
142         assertTrue(profile.audioBitRate > 0);
143         assertTrue(profile.audioSampleRate > 0);
144         assertTrue(profile.audioChannels > 0);
145         assertTrue(isSizeSupported(profile.videoFrameWidth,
146                                    profile.videoFrameHeight,
147                                    videoSizes));
148     }
149 
checkAllProfiles(EncoderProfiles allProfiles, CamcorderProfile profile, List<Size> videoSizes)150     private void checkAllProfiles(EncoderProfiles allProfiles, CamcorderProfile profile,
151                                   List<Size> videoSizes) {
152         Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
153             "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
154             "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
155             "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
156             profile.duration,
157             profile.quality,
158             profile.fileFormat,
159             profile.videoCodec,
160             profile.videoBitRate,
161             profile.videoFrameRate,
162             profile.videoFrameWidth,
163             profile.videoFrameHeight,
164             profile.audioCodec,
165             profile.audioBitRate,
166             profile.audioSampleRate,
167             profile.audioChannels));
168         // generic fields must match the corresponding CamcorderProfile
169         assertEquals(profile.duration, allProfiles.getDefaultDurationSeconds());
170         assertEquals(profile.fileFormat, allProfiles.getRecommendedFileFormat());
171         boolean first = true;
172         for (EncoderProfiles.VideoProfile videoProfile : allProfiles.getVideoProfiles()) {
173             if (first) {
174                 // the first profile must be the default profile which must match
175                 // the corresponding CamcorderProfile
176                 assertEquals(profile.videoCodec, videoProfile.getCodec());
177                 assertEquals(profile.videoBitRate, videoProfile.getBitrate());
178                 assertEquals(profile.videoFrameRate, videoProfile.getFrameRate());
179                 first = false;
180 
181                 // The first video profile must be a basic profile: YUV 4:2:0 8-bit SDR.
182                 // This is to ensure backward compatibility with the corresponding
183                 // CamcorderProfile, which is always a basic profile.
184                 assertEquals(EncoderProfiles.VideoProfile.YUV_420,
185                              videoProfile.getChromaSubsampling());
186                 assertEquals(EncoderProfiles.VideoProfile.HDR_NONE, videoProfile.getHdrFormat());
187                 assertEquals(8, videoProfile.getBitDepth());
188             }
189             // all profiles must be the same size
190             assertEquals(profile.videoFrameWidth, videoProfile.getWidth());
191             assertEquals(profile.videoFrameHeight, videoProfile.getHeight());
192             assertTrue(videoProfile.getMediaType() != null);
193             switch (videoProfile.getCodec()) {
194               // don't validate profile for regular codecs as vendors may use vendor specific profile
195             case MediaRecorder.VideoEncoder.H263:
196                 assertEquals(MediaFormat.MIMETYPE_VIDEO_H263, videoProfile.getMediaType());
197                 break;
198             case MediaRecorder.VideoEncoder.H264:
199                 assertEquals(MediaFormat.MIMETYPE_VIDEO_AVC, videoProfile.getMediaType());
200                 break;
201             case MediaRecorder.VideoEncoder.MPEG_4_SP:
202                 assertEquals(MediaFormat.MIMETYPE_VIDEO_MPEG4, videoProfile.getMediaType());
203                 break;
204             case MediaRecorder.VideoEncoder.VP8:
205                 assertEquals(MediaFormat.MIMETYPE_VIDEO_VP8, videoProfile.getMediaType());
206                 break;
207             case MediaRecorder.VideoEncoder.HEVC:
208                   assertEquals(MediaFormat.MIMETYPE_VIDEO_HEVC, videoProfile.getMediaType());
209                   break;
210             case MediaRecorder.VideoEncoder.VP9:
211                   assertEquals(MediaFormat.MIMETYPE_VIDEO_VP9, videoProfile.getMediaType());
212                   break;
213             case MediaRecorder.VideoEncoder.DOLBY_VISION:
214                   assertEquals(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION, videoProfile.getMediaType());
215                   break;
216             case MediaRecorder.VideoEncoder.AV1:
217                   assertEquals(MediaFormat.MIMETYPE_VIDEO_AV1, videoProfile.getMediaType());
218                   break;
219             }
220             // Cannot validate profile as vendors may use vendor specific profile. Just read it.
221             int codecProfile = videoProfile.getProfile();
222         }
223         // there must have been at least one video profile
224         assertFalse("no video profiles in getAll()", first);
225         first = true;
226         for (EncoderProfiles.AudioProfile audioProfile : allProfiles.getAudioProfiles()) {
227             if (first) {
228                 // the first profile must be the default profile which must match
229                 // the corresponding CamcorderProfile
230                 assertEquals(profile.audioCodec, audioProfile.getCodec());
231                 assertEquals(profile.audioBitRate, audioProfile.getBitrate());
232                 assertEquals(profile.audioSampleRate, audioProfile.getSampleRate());
233                 assertEquals(profile.audioChannels, audioProfile.getChannels());
234                 first = false;
235             }
236             assertTrue(audioProfile.getMediaType() != null);
237             switch (audioProfile.getCodec()) {
238             // don't validate profile for regular codecs as vendors may use vendor specific profile
239             case MediaRecorder.AudioEncoder.AMR_NB:
240                 assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_NB, audioProfile.getMediaType());
241                 break;
242             case MediaRecorder.AudioEncoder.AMR_WB:
243                 assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_WB, audioProfile.getMediaType());
244                 break;
245             case MediaRecorder.AudioEncoder.AAC:
246                 assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
247                 break;
248             case MediaRecorder.AudioEncoder.HE_AAC:
249                 assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
250                 assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectHE,
251                              audioProfile.getProfile());
252                 break;
253             case MediaRecorder.AudioEncoder.AAC_ELD:
254                 assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
255                 assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectELD,
256                              audioProfile.getProfile());
257                 break;
258             case MediaRecorder.AudioEncoder.VORBIS:
259                 assertEquals(MediaFormat.MIMETYPE_AUDIO_VORBIS, audioProfile.getMediaType());
260                 break;
261             case MediaRecorder.AudioEncoder.OPUS:
262                 assertEquals(MediaFormat.MIMETYPE_AUDIO_OPUS, audioProfile.getMediaType());
263                 break;
264             default:
265                 // there may be some extended profiles we don't know about and that's OK
266                 break;
267             }
268         }
269     }
270 
assertProfileEquals(CamcorderProfile expectedProfile, CamcorderProfile actualProfile)271     private void assertProfileEquals(CamcorderProfile expectedProfile,
272             CamcorderProfile actualProfile) {
273         assertEquals(expectedProfile.duration, actualProfile.duration);
274         assertEquals(expectedProfile.fileFormat, actualProfile.fileFormat);
275         assertEquals(expectedProfile.videoCodec, actualProfile.videoCodec);
276         assertEquals(expectedProfile.videoBitRate, actualProfile.videoBitRate);
277         assertEquals(expectedProfile.videoFrameRate, actualProfile.videoFrameRate);
278         assertEquals(expectedProfile.videoFrameWidth, actualProfile.videoFrameWidth);
279         assertEquals(expectedProfile.videoFrameHeight, actualProfile.videoFrameHeight);
280         assertEquals(expectedProfile.audioCodec, actualProfile.audioCodec);
281         assertEquals(expectedProfile.audioBitRate, actualProfile.audioBitRate);
282         assertEquals(expectedProfile.audioSampleRate, actualProfile.audioSampleRate);
283         assertEquals(expectedProfile.audioChannels, actualProfile.audioChannels);
284     }
285 
checkSpecificProfileDimensions(CamcorderProfile profile, int quality)286     private void checkSpecificProfileDimensions(CamcorderProfile profile, int quality) {
287         Log.v(TAG, String.format("specific profile: quality=%d, width = %d, height = %d",
288                     profile.quality, profile.videoFrameWidth, profile.videoFrameHeight));
289 
290         switch (quality) {
291             case CamcorderProfile.QUALITY_QCIF:
292             case CamcorderProfile.QUALITY_TIME_LAPSE_QCIF:
293                 assertEquals(176, profile.videoFrameWidth);
294                 assertEquals(144, profile.videoFrameHeight);
295                 break;
296 
297             case CamcorderProfile.QUALITY_CIF:
298             case CamcorderProfile.QUALITY_TIME_LAPSE_CIF:
299                 assertEquals(352, profile.videoFrameWidth);
300                 assertEquals(288, profile.videoFrameHeight);
301                 break;
302 
303             case CamcorderProfile.QUALITY_480P:
304             case CamcorderProfile.QUALITY_TIME_LAPSE_480P:
305                 assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
306                            640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
307                            704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
308                 assertEquals(480, profile.videoFrameHeight);
309                 break;
310 
311             case CamcorderProfile.QUALITY_720P:
312             case CamcorderProfile.QUALITY_TIME_LAPSE_720P:
313                 assertEquals(1280, profile.videoFrameWidth);
314                 assertEquals(720, profile.videoFrameHeight);
315                 break;
316 
317             case CamcorderProfile.QUALITY_1080P:
318             case CamcorderProfile.QUALITY_TIME_LAPSE_1080P:
319                 // 1080p could be either 1920x1088 or 1920x1080.
320                 assertEquals(1920, profile.videoFrameWidth);
321                 assertTrue(1088 == profile.videoFrameHeight ||
322                            1080 == profile.videoFrameHeight);
323                 break;
324             case CamcorderProfile.QUALITY_2K:
325             case CamcorderProfile.QUALITY_TIME_LAPSE_2K:
326                 // 2K could be either 2048x1088 or 2048x1080
327                 assertEquals(2048, profile.videoFrameWidth);
328                 assertTrue(1088 == profile.videoFrameHeight ||
329                            1080 == profile.videoFrameHeight);
330                 break;
331             case CamcorderProfile.QUALITY_QHD:
332             case CamcorderProfile.QUALITY_TIME_LAPSE_QHD:
333                 assertEquals(2560, profile.videoFrameWidth);
334                 assertEquals(1440, profile.videoFrameHeight);
335                 break;
336             case CamcorderProfile.QUALITY_2160P:
337             case CamcorderProfile.QUALITY_TIME_LAPSE_2160P:
338                 assertEquals(3840, profile.videoFrameWidth);
339                 assertEquals(2160, profile.videoFrameHeight);
340                 break;
341             case CamcorderProfile.QUALITY_HIGH_SPEED_480P:
342                 assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
343                 640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
344                 704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
345                 assertEquals(480, profile.videoFrameHeight);
346                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
347                 break;
348             case CamcorderProfile.QUALITY_HIGH_SPEED_720P:
349                 assertEquals(1280, profile.videoFrameWidth);
350                 assertEquals(720, profile.videoFrameHeight);
351                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
352                 break;
353             case CamcorderProfile.QUALITY_HIGH_SPEED_1080P:
354                 // 1080p could be either 1920x1088 or 1920x1080.
355                 assertEquals(1920, profile.videoFrameWidth);
356                 assertTrue(1088 == profile.videoFrameHeight ||
357                            1080 == profile.videoFrameHeight);
358                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
359                 break;
360         }
361     }
362 
363     // Checks if the existing specific profiles have the correct dimensions.
364     // Also checks that the mimimum quality specific profile matches the low profile and
365     // similarly that the maximum quality specific profile matches the high profile.
checkSpecificProfiles( int cameraId, CamcorderProfile low, CamcorderProfile high, int[] specificQualities, List<Size> videoSizes)366     private void checkSpecificProfiles(
367             int cameraId,
368             CamcorderProfile low,
369             CamcorderProfile high,
370             int[] specificQualities,
371             List<Size> videoSizes) {
372 
373         // For high speed levels, low and high quality are optional,skip the test if
374         // they are missing.
375         if (low == null && high == null) {
376             // No profile should be available if low and high are unavailable.
377             for (int quality : specificQualities) {
378                 assertFalse(CamcorderProfile.hasProfile(cameraId, quality));
379             }
380             return;
381         }
382 
383         CamcorderProfile minProfile = null;
384         CamcorderProfile maxProfile = null;
385 
386         for (int i = 0; i < specificQualities.length; i++) {
387             int quality = specificQualities[i];
388             if ((cameraId != -1 && CamcorderProfile.hasProfile(cameraId, quality)) ||
389                 (cameraId == -1 && CamcorderProfile.hasProfile(quality))) {
390                 CamcorderProfile profile = getWithOptionalId(quality, cameraId);
391                 checkSpecificProfileDimensions(profile, quality);
392 
393                 assertTrue(isSizeSupported(profile.videoFrameWidth,
394                                            profile.videoFrameHeight,
395                                            videoSizes));
396 
397                 if (minProfile == null) {
398                     minProfile = profile;
399                 }
400                 maxProfile = profile;
401             }
402         }
403 
404         assertNotNull(minProfile);
405         assertNotNull(maxProfile);
406 
407         Log.v(TAG, String.format("min profile: quality=%d, width = %d, height = %d",
408                     minProfile.quality, minProfile.videoFrameWidth, minProfile.videoFrameHeight));
409         Log.v(TAG, String.format("max profile: quality=%d, width = %d, height = %d",
410                     maxProfile.quality, maxProfile.videoFrameWidth, maxProfile.videoFrameHeight));
411 
412         assertProfileEquals(low, minProfile);
413         assertProfileEquals(high, maxProfile);
414 
415     }
416 
checkGet(int cameraId)417     private void checkGet(int cameraId) {
418         Log.v(TAG, (cameraId == -1)
419                    ? "Checking get without id"
420                    : "Checking get with id = " + cameraId);
421 
422         Camera camera = null;
423         if (cameraId == -1) {
424             camera = Camera.open();
425             assumeTrue("Device does not have a back-facing camera", camera != null);
426         } else {
427             camera = Camera.open(cameraId);
428             assertNotNull("failed to open CameraId " + cameraId, camera);
429         }
430 
431         final List<Size> videoSizes = CameraUtils.getSupportedVideoSizes(camera);
432 
433         camera.release();
434         camera = null;
435 
436         /**
437          * Check all possible supported profiles: get profile should work, and the profile
438          * should be sane. Note that, timelapse and high speed video sizes may not be listed
439          * as supported video sizes from camera, skip the size check.
440          */
441         for (Integer quality : ALL_SUPPORTED_QUALITIES) {
442             if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
443                 List<Size> videoSizesToCheck = null;
444                 if (quality >= CamcorderProfile.QUALITY_LOW &&
445                                 quality <= CamcorderProfile.QUALITY_2K) {
446                     videoSizesToCheck = videoSizes;
447                 }
448                 CamcorderProfile profile = getWithOptionalId(quality, cameraId);
449                 checkProfile(profile, videoSizesToCheck);
450                 if (cameraId >= 0) {
451                     EncoderProfiles allProfiles =
452                         CamcorderProfile.getAll(String.valueOf(cameraId), quality);
453                     checkAllProfiles(allProfiles, profile, videoSizesToCheck);
454                 }
455             } else {
456                 assertNull(CamcorderProfile.getAll(String.valueOf(cameraId), quality));
457             }
458         }
459 
460         /**
461          * Check unknown profiles: hasProfile() should return false.
462          */
463         for (Integer quality : UNKNOWN_QUALITIES) {
464             assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
465                     + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
466         }
467 
468         // High speed low and high profile are optional,
469         // but they should be both present or missing.
470         CamcorderProfile lowHighSpeedProfile = null;
471         CamcorderProfile highHighSpeedProfile = null;
472         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_LOW)) {
473             lowHighSpeedProfile =
474                     getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_LOW, cameraId);
475         }
476         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_HIGH)) {
477             highHighSpeedProfile =
478                     getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_HIGH, cameraId);
479         }
480         if (lowHighSpeedProfile != null) {
481             assertNotNull("high speed high quality profile should be supported if low" +
482                     " is supported ",
483                     highHighSpeedProfile);
484             checkProfile(lowHighSpeedProfile, null);
485             checkProfile(highHighSpeedProfile, null);
486         } else {
487             assertNull("high speed high quality profile shouldn't be supported if " +
488                     "low is unsupported ", highHighSpeedProfile);
489         }
490 
491         int[] specificProfileQualities = {CamcorderProfile.QUALITY_QCIF,
492                                           CamcorderProfile.QUALITY_QVGA,
493                                           CamcorderProfile.QUALITY_CIF,
494                                           CamcorderProfile.QUALITY_480P,
495                                           CamcorderProfile.QUALITY_720P,
496                                           CamcorderProfile.QUALITY_1080P,
497                                           CamcorderProfile.QUALITY_2K,
498                                           CamcorderProfile.QUALITY_QHD,
499                                           CamcorderProfile.QUALITY_2160P};
500 
501         int[] specificTimeLapseProfileQualities = {CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
502                                                    CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
503                                                    CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
504                                                    CamcorderProfile.QUALITY_TIME_LAPSE_480P,
505                                                    CamcorderProfile.QUALITY_TIME_LAPSE_720P,
506                                                    CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
507                                                    CamcorderProfile.QUALITY_TIME_LAPSE_2K,
508                                                    CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
509                                                    CamcorderProfile.QUALITY_TIME_LAPSE_2160P};
510 
511         int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
512                                                    CamcorderProfile.QUALITY_HIGH_SPEED_720P,
513                                                    CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
514                                                    CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
515 
516         CamcorderProfile lowProfile =
517                 getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
518         CamcorderProfile highProfile =
519                 getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
520         CamcorderProfile lowTimeLapseProfile =
521                 getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
522         CamcorderProfile highTimeLapseProfile =
523                 getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
524         checkSpecificProfiles(cameraId, lowProfile, highProfile,
525                 specificProfileQualities, videoSizes);
526         checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
527                 specificTimeLapseProfileQualities, null);
528         checkSpecificProfiles(cameraId, lowHighSpeedProfile, highHighSpeedProfile,
529                 specificHighSpeedProfileQualities, null);
530     }
531 
532     @Test
testGetFirstBackCamera()533     public void testGetFirstBackCamera() {
534         /*
535          * Device may not have rear camera for checkGet(-1).
536          * Checking PackageManager.FEATURE_CAMERA is included or not to decide the flow.
537          * Continue if the feature is included.
538          * Otherwise, exit test.
539          */
540         Context context = InstrumentationRegistry.getContext();
541         assertNotNull("did not find context", context);
542         PackageManager pm = context.getPackageManager();
543         assertNotNull("did not find package manager", pm);
544         if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
545             return;
546         }
547         checkGet(-1);
548     }
549 
550     @Test
testGetWithId()551     public void testGetWithId() {
552         int nCamera = Camera.getNumberOfCameras();
553         Context context = InstrumentationRegistry.getContext();
554         assertNotNull("did not find context", context);
555         for (int cameraId = 0; cameraId < nCamera; cameraId++) {
556             boolean isExternal = false;
557             try {
558                 isExternal = CameraUtils.isExternal(context, cameraId);
559             } catch (Exception e) {
560                 Log.e(TAG, "Unable to query external camera: " + e);
561             }
562 
563             if (!isExternal) {
564                 checkGet(cameraId);
565             }
566         }
567     }
568 
569     MediaCodecList mCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
570 
checkSupportedEncoder( String mediaType, int width, int height, int frameRate, int profile)571     private void checkSupportedEncoder(
572             String mediaType, int width, int height, int frameRate, int profile) {
573         MediaFormat format = MediaFormat.createVideoFormat(mediaType, width, height);
574         format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
575         format.setInteger(MediaFormat.KEY_PROFILE, profile);
576         format.setInteger(MediaFormat.KEY_LEVEL, 0 /* unknown */);
577         for (MediaCodecInfo info : mCodecList.getCodecInfos()) {
578             if (!info.isEncoder() || !info.isHardwareAccelerated()) {
579                 continue;
580             }
581             MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mediaType);
582             if (caps == null) {
583                 continue;
584             }
585             if (caps.isFormatSupported(format)) {
586                 return;
587             }
588         }
589         fail(
590                 "No supported encoder found: "
591                         + width
592                         + "x"
593                         + height
594                         + "@"
595                         + frameRate
596                         + " for "
597                         + mediaType
598                         + " at profile "
599                         + profile);
600     }
601 
checkHdrProfile( int cameraId, String mediaType, int camHdrFormat, List<Integer> mediaHdrProfiles)602     private boolean checkHdrProfile(
603             int cameraId, String mediaType, int camHdrFormat, List<Integer> mediaHdrProfiles) {
604         boolean hasSupportedProfiles = false;
605         for (Integer quality : ALL_SUPPORTED_QUALITIES) {
606             if (!CamcorderProfile.hasProfile(cameraId, quality)) {
607                 continue;
608             }
609             CamcorderProfile profile = getWithOptionalId(quality, cameraId);
610             if (profile == null) {
611                 continue;
612             }
613             EncoderProfiles allProfiles =
614                     CamcorderProfile.getAll(String.valueOf(cameraId), quality);
615             for (EncoderProfiles.VideoProfile videoProfile : allProfiles.getVideoProfiles()) {
616                 Log.i(
617                         TAG,
618                         "Video encoder profile: cameraId="
619                                 + cameraId
620                                 + " mediaType="
621                                 + videoProfile.getMediaType()
622                                 + " hdrFormat="
623                                 + videoProfile.getHdrFormat()
624                                 + " profile="
625                                 + videoProfile.getProfile()
626                                 + " "
627                                 + videoProfile.getWidth()
628                                 + "x"
629                                 + videoProfile.getHeight()
630                                 + "@"
631                                 + videoProfile.getFrameRate());
632                 if (!videoProfile.getMediaType().equals(mediaType)
633                         || videoProfile.getHdrFormat() != camHdrFormat) {
634                     continue;
635                 }
636                 assertTrue(
637                         "Unexpected video profile: "
638                                 + videoProfile.getProfile()
639                                 + " expected to be one in "
640                                 + mediaHdrProfiles,
641                         mediaHdrProfiles.contains(videoProfile.getProfile()));
642                 checkSupportedEncoder(
643                         mediaType,
644                         videoProfile.getWidth(),
645                         videoProfile.getHeight(),
646                         videoProfile.getFrameRate(),
647                         videoProfile.getProfile());
648                 hasSupportedProfiles = true;
649             }
650         }
651         return hasSupportedProfiles;
652     }
653 
checkAllHdrProfile( String mediaType, int camHdrFormat, List<Integer> mediaHdrProfiles)654     private void checkAllHdrProfile(
655             String mediaType, int camHdrFormat, List<Integer> mediaHdrProfiles) {
656         int nCamera = Camera.getNumberOfCameras();
657         Context context = InstrumentationRegistry.getContext();
658         assertNotNull("did not find context", context);
659         boolean hasSupportedProfiles = false;
660         for (int cameraId = 0; cameraId < nCamera; cameraId++) {
661             boolean isExternal = false;
662             try {
663                 isExternal = CameraUtils.isExternal(context, cameraId);
664             } catch (Exception e) {
665                 Log.e(TAG, "Unable to query external camera: " + e);
666             }
667 
668             if (!isExternal) {
669                 if (checkHdrProfile(cameraId, mediaType, camHdrFormat, mediaHdrProfiles)) {
670                     hasSupportedProfiles = true;
671                 }
672             }
673         }
674         assumeTrue(
675                 "No profile detected for mediaType="
676                         + mediaType
677                         + " hdrFormat="
678                         + camHdrFormat,
679                 hasSupportedProfiles);
680     }
681 
682     @Test
testHevcHlgEncoderSupport()683     public void testHevcHlgEncoderSupport() {
684         checkAllHdrProfile(
685                 MediaFormat.MIMETYPE_VIDEO_HEVC,
686                 EncoderProfiles.VideoProfile.HDR_HLG,
687                 List.of(CodecProfileLevel.HEVCProfileMain10));
688     }
689 
690     @Test
testHevcHdr10EncoderSupport()691     public void testHevcHdr10EncoderSupport() {
692         checkAllHdrProfile(
693                 MediaFormat.MIMETYPE_VIDEO_HEVC,
694                 EncoderProfiles.VideoProfile.HDR_HDR10,
695                 List.of(CodecProfileLevel.HEVCProfileMain10HDR10));
696     }
697 
698     @Test
testHevcHdr10PlusEncoderSupport()699     public void testHevcHdr10PlusEncoderSupport() {
700         checkAllHdrProfile(
701                 MediaFormat.MIMETYPE_VIDEO_HEVC,
702                 EncoderProfiles.VideoProfile.HDR_HDR10PLUS,
703                 List.of(CodecProfileLevel.HEVCProfileMain10HDR10Plus));
704     }
705 
706     @Test
testVp9HlgEncoderSupport()707     public void testVp9HlgEncoderSupport() {
708         checkAllHdrProfile(
709                 MediaFormat.MIMETYPE_VIDEO_VP9,
710                 EncoderProfiles.VideoProfile.HDR_HLG,
711                 List.of(CodecProfileLevel.VP9Profile2, CodecProfileLevel.VP9Profile3));
712     }
713 
714     @Test
testVp9Hdr10EncoderSupport()715     public void testVp9Hdr10EncoderSupport() {
716         checkAllHdrProfile(
717                 MediaFormat.MIMETYPE_VIDEO_VP9,
718                 EncoderProfiles.VideoProfile.HDR_HDR10,
719                 List.of(CodecProfileLevel.VP9Profile2HDR, CodecProfileLevel.VP9Profile3HDR));
720     }
721 
722     @Test
testVp9Hdr10PlusEncoderSupport()723     public void testVp9Hdr10PlusEncoderSupport() {
724         checkAllHdrProfile(
725                 MediaFormat.MIMETYPE_VIDEO_VP9,
726                 EncoderProfiles.VideoProfile.HDR_HDR10PLUS,
727                 List.of(CodecProfileLevel.VP9Profile2HDR10Plus,
728                         CodecProfileLevel.VP9Profile3HDR10Plus));
729     }
730 
731     @Test
testAv1HlgEncoderSupport()732     public void testAv1HlgEncoderSupport() {
733         checkAllHdrProfile(
734                 MediaFormat.MIMETYPE_VIDEO_AV1,
735                 EncoderProfiles.VideoProfile.HDR_HLG,
736                 List.of(CodecProfileLevel.AV1ProfileMain10));
737     }
738 
739     @Test
testAv1Hdr10EncoderSupport()740     public void testAv1Hdr10EncoderSupport() {
741         checkAllHdrProfile(
742                 MediaFormat.MIMETYPE_VIDEO_AV1,
743                 EncoderProfiles.VideoProfile.HDR_HDR10,
744                 List.of(CodecProfileLevel.AV1ProfileMain10HDR10));
745     }
746 
747     @Test
testAv1Hdr10PlusEncoderSupport()748     public void testAv1Hdr10PlusEncoderSupport() {
749         checkAllHdrProfile(
750                 MediaFormat.MIMETYPE_VIDEO_AV1,
751                 EncoderProfiles.VideoProfile.HDR_HDR10PLUS,
752                 List.of(CodecProfileLevel.AV1ProfileMain10HDR10Plus));
753     }
754 
isSizeSupported(int width, int height, List<Size> sizes)755     private boolean isSizeSupported(int width, int height, List<Size> sizes) {
756         if (sizes == null) return true;
757 
758         for (Size size: sizes) {
759             if (size.width == width && size.height == height) {
760                 return true;
761             }
762         }
763         Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
764         return false;
765     }
766 
isProfileMandatory(int quality)767     private boolean isProfileMandatory(int quality) {
768         return (quality == CamcorderProfile.QUALITY_LOW) ||
769                 (quality == CamcorderProfile.QUALITY_HIGH) ||
770                 (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
771                 (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
772     }
773 }
774