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.cts;
18 
19 
20 import android.content.pm.PackageManager;
21 import android.hardware.Camera;
22 import android.hardware.Camera.Parameters;
23 import android.hardware.Camera.Size;
24 import android.media.CamcorderProfile;
25 import android.test.AndroidTestCase;
26 import android.util.Log;
27 
28 import java.util.Arrays;
29 import java.util.List;
30 
31 public class CamcorderProfileTest extends AndroidTestCase {
32 
33     private static final String TAG = "CamcorderProfileTest";
34     private static final int MIN_HIGH_SPEED_FPS = 100;
35     private static final Integer[] ALL_SUPPORTED_QUALITIES = {
36         CamcorderProfile.QUALITY_LOW,
37         CamcorderProfile.QUALITY_HIGH,
38         CamcorderProfile.QUALITY_QCIF,
39         CamcorderProfile.QUALITY_CIF,
40         CamcorderProfile.QUALITY_480P,
41         CamcorderProfile.QUALITY_720P,
42         CamcorderProfile.QUALITY_1080P,
43         CamcorderProfile.QUALITY_QVGA,
44         CamcorderProfile.QUALITY_2160P,
45         CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
46         CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
47         CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
48         CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
49         CamcorderProfile.QUALITY_TIME_LAPSE_480P,
50         CamcorderProfile.QUALITY_TIME_LAPSE_720P,
51         CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
52         CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
53         CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
54         CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
55         CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
56         CamcorderProfile.QUALITY_HIGH_SPEED_480P,
57         CamcorderProfile.QUALITY_HIGH_SPEED_720P,
58         CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
59         CamcorderProfile.QUALITY_HIGH_SPEED_2160P
60     };
61     private static final int LAST_QUALITY = CamcorderProfile.QUALITY_2160P;
62     private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_2160P;
63     private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_2160P;
64     private static final Integer[] UNKNOWN_QUALITIES = {
65         LAST_QUALITY + 1, // Unknown normal profile quality
66         LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
67         LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
68     };
69 
70     // Uses get without id if cameraId == -1 and get with id otherwise.
getWithOptionalId(int quality, int cameraId)71     private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
72         if (cameraId == -1) {
73             return CamcorderProfile.get(quality);
74         } else {
75             return CamcorderProfile.get(cameraId, quality);
76         }
77     }
78 
checkProfile(CamcorderProfile profile, List<Size> videoSizes)79     private void checkProfile(CamcorderProfile profile, List<Size> videoSizes) {
80         Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
81             "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
82             "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
83             "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
84             profile.duration,
85             profile.quality,
86             profile.fileFormat,
87             profile.videoCodec,
88             profile.videoBitRate,
89             profile.videoFrameRate,
90             profile.videoFrameWidth,
91             profile.videoFrameHeight,
92             profile.audioCodec,
93             profile.audioBitRate,
94             profile.audioSampleRate,
95             profile.audioChannels));
96         assertTrue(profile.duration > 0);
97         assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
98         assertTrue(profile.videoBitRate > 0);
99         assertTrue(profile.videoFrameRate > 0);
100         assertTrue(profile.videoFrameWidth > 0);
101         assertTrue(profile.videoFrameHeight > 0);
102         assertTrue(profile.audioBitRate > 0);
103         assertTrue(profile.audioSampleRate > 0);
104         assertTrue(profile.audioChannels > 0);
105         assertTrue(isSizeSupported(profile.videoFrameWidth,
106                                    profile.videoFrameHeight,
107                                    videoSizes));
108     }
109 
assertProfileEquals(CamcorderProfile expectedProfile, CamcorderProfile actualProfile)110     private void assertProfileEquals(CamcorderProfile expectedProfile,
111             CamcorderProfile actualProfile) {
112         assertEquals(expectedProfile.duration, actualProfile.duration);
113         assertEquals(expectedProfile.fileFormat, actualProfile.fileFormat);
114         assertEquals(expectedProfile.videoCodec, actualProfile.videoCodec);
115         assertEquals(expectedProfile.videoBitRate, actualProfile.videoBitRate);
116         assertEquals(expectedProfile.videoFrameRate, actualProfile.videoFrameRate);
117         assertEquals(expectedProfile.videoFrameWidth, actualProfile.videoFrameWidth);
118         assertEquals(expectedProfile.videoFrameHeight, actualProfile.videoFrameHeight);
119         assertEquals(expectedProfile.audioCodec, actualProfile.audioCodec);
120         assertEquals(expectedProfile.audioBitRate, actualProfile.audioBitRate);
121         assertEquals(expectedProfile.audioSampleRate, actualProfile.audioSampleRate);
122         assertEquals(expectedProfile.audioChannels, actualProfile.audioChannels);
123     }
124 
checkSpecificProfileDimensions(CamcorderProfile profile, int quality)125     private void checkSpecificProfileDimensions(CamcorderProfile profile, int quality) {
126         Log.v(TAG, String.format("specific profile: quality=%d, width = %d, height = %d",
127                     profile.quality, profile.videoFrameWidth, profile.videoFrameHeight));
128 
129         switch (quality) {
130             case CamcorderProfile.QUALITY_QCIF:
131             case CamcorderProfile.QUALITY_TIME_LAPSE_QCIF:
132                 assertEquals(176, profile.videoFrameWidth);
133                 assertEquals(144, profile.videoFrameHeight);
134                 break;
135 
136             case CamcorderProfile.QUALITY_CIF:
137             case CamcorderProfile.QUALITY_TIME_LAPSE_CIF:
138                 assertEquals(352, profile.videoFrameWidth);
139                 assertEquals(288, profile.videoFrameHeight);
140                 break;
141 
142             case CamcorderProfile.QUALITY_480P:
143             case CamcorderProfile.QUALITY_TIME_LAPSE_480P:
144                 assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
145                            640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
146                            704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
147                 assertEquals(480, profile.videoFrameHeight);
148                 break;
149 
150             case CamcorderProfile.QUALITY_720P:
151             case CamcorderProfile.QUALITY_TIME_LAPSE_720P:
152                 assertEquals(1280, profile.videoFrameWidth);
153                 assertEquals(720, profile.videoFrameHeight);
154                 break;
155 
156             case CamcorderProfile.QUALITY_1080P:
157             case CamcorderProfile.QUALITY_TIME_LAPSE_1080P:
158                 // 1080p could be either 1920x1088 or 1920x1080.
159                 assertEquals(1920, profile.videoFrameWidth);
160                 assertTrue(1088 == profile.videoFrameHeight ||
161                            1080 == profile.videoFrameHeight);
162                 break;
163             case CamcorderProfile.QUALITY_2160P:
164             case CamcorderProfile.QUALITY_TIME_LAPSE_2160P:
165                 assertEquals(3840, profile.videoFrameWidth);
166                 assertEquals(2160, profile.videoFrameHeight);
167                 break;
168             case CamcorderProfile.QUALITY_HIGH_SPEED_480P:
169                 assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
170                 640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
171                 704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
172                 assertEquals(480, profile.videoFrameHeight);
173                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
174                 break;
175             case CamcorderProfile.QUALITY_HIGH_SPEED_720P:
176                 assertEquals(1280, profile.videoFrameWidth);
177                 assertEquals(720, profile.videoFrameHeight);
178                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
179                 break;
180             case CamcorderProfile.QUALITY_HIGH_SPEED_1080P:
181                 // 1080p could be either 1920x1088 or 1920x1080.
182                 assertEquals(1920, profile.videoFrameWidth);
183                 assertTrue(1088 == profile.videoFrameHeight ||
184                            1080 == profile.videoFrameHeight);
185                 assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
186                 break;
187         }
188     }
189 
190     // Checks if the existing specific profiles have the correct dimensions.
191     // Also checks that the mimimum quality specific profile matches the low profile and
192     // similarly that the maximum quality specific profile matches the high profile.
checkSpecificProfiles( int cameraId, CamcorderProfile low, CamcorderProfile high, int[] specificQualities, List<Size> videoSizes)193     private void checkSpecificProfiles(
194             int cameraId,
195             CamcorderProfile low,
196             CamcorderProfile high,
197             int[] specificQualities,
198             List<Size> videoSizes) {
199 
200         // For high speed levels, low and high quality are optional,skip the test if
201         // they are missing.
202         if (low == null && high == null) {
203             // No profile should be available if low and high are unavailable.
204             for (int quality : specificQualities) {
205                 assertFalse(CamcorderProfile.hasProfile(cameraId, quality));
206             }
207             return;
208         }
209 
210         CamcorderProfile minProfile = null;
211         CamcorderProfile maxProfile = null;
212 
213         for (int i = 0; i < specificQualities.length; i++) {
214             int quality = specificQualities[i];
215             if ((cameraId != -1 && CamcorderProfile.hasProfile(cameraId, quality)) ||
216                 (cameraId == -1 && CamcorderProfile.hasProfile(quality))) {
217                 CamcorderProfile profile = getWithOptionalId(quality, cameraId);
218                 checkSpecificProfileDimensions(profile, quality);
219 
220                 assertTrue(isSizeSupported(profile.videoFrameWidth,
221                                            profile.videoFrameHeight,
222                                            videoSizes));
223 
224                 if (minProfile == null) {
225                     minProfile = profile;
226                 }
227                 maxProfile = profile;
228             }
229         }
230 
231         assertNotNull(minProfile);
232         assertNotNull(maxProfile);
233 
234         Log.v(TAG, String.format("min profile: quality=%d, width = %d, height = %d",
235                     minProfile.quality, minProfile.videoFrameWidth, minProfile.videoFrameHeight));
236         Log.v(TAG, String.format("max profile: quality=%d, width = %d, height = %d",
237                     maxProfile.quality, maxProfile.videoFrameWidth, maxProfile.videoFrameHeight));
238 
239         assertProfileEquals(low, minProfile);
240         assertProfileEquals(high, maxProfile);
241 
242     }
243 
checkGet(int cameraId)244     private void checkGet(int cameraId) {
245         Log.v(TAG, (cameraId == -1)
246                    ? "Checking get without id"
247                    : "Checking get with id = " + cameraId);
248 
249         final List<Size> videoSizes = getSupportedVideoSizes(cameraId);
250 
251         /**
252          * Check all possible supported profiles: get profile should work, and the profile
253          * should be sane. Note that, timelapse and high speed video sizes may not be listed
254          * as supported video sizes from camera, skip the size check.
255          */
256         for (Integer quality : ALL_SUPPORTED_QUALITIES) {
257             if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
258                 List<Size> videoSizesToCheck = null;
259                 if (quality >= CamcorderProfile.QUALITY_LOW &&
260                                 quality <= CamcorderProfile.QUALITY_2160P) {
261                     videoSizesToCheck = videoSizes;
262                 }
263                 CamcorderProfile profile = getWithOptionalId(quality, cameraId);
264                 checkProfile(profile, videoSizesToCheck);
265             }
266         }
267 
268         /**
269          * Check unknown profiles: hasProfile() should return false.
270          */
271         for (Integer quality : UNKNOWN_QUALITIES) {
272             assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
273                     + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
274         }
275 
276         // High speed low and high profile are optional,
277         // but they should be both present or missing.
278         CamcorderProfile lowHighSpeedProfile = null;
279         CamcorderProfile highHighSpeedProfile = null;
280         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_LOW)) {
281             lowHighSpeedProfile =
282                     getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_LOW, cameraId);
283         }
284         if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_HIGH)) {
285             highHighSpeedProfile =
286                     getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_HIGH, cameraId);
287         }
288         if (lowHighSpeedProfile != null) {
289             assertNotNull("high speed high quality profile should be supported if low" +
290                     " is supported ",
291                     highHighSpeedProfile);
292             checkProfile(lowHighSpeedProfile, null);
293             checkProfile(highHighSpeedProfile, null);
294         } else {
295             assertNull("high speed high quality profile shouldn't be supported if " +
296                     "low is unsupported ", highHighSpeedProfile);
297         }
298 
299         int[] specificProfileQualities = {CamcorderProfile.QUALITY_QCIF,
300                                           CamcorderProfile.QUALITY_QVGA,
301                                           CamcorderProfile.QUALITY_CIF,
302                                           CamcorderProfile.QUALITY_480P,
303                                           CamcorderProfile.QUALITY_720P,
304                                           CamcorderProfile.QUALITY_1080P,
305                                           CamcorderProfile.QUALITY_2160P};
306 
307         int[] specificTimeLapseProfileQualities = {CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
308                                                    CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
309                                                    CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
310                                                    CamcorderProfile.QUALITY_TIME_LAPSE_480P,
311                                                    CamcorderProfile.QUALITY_TIME_LAPSE_720P,
312                                                    CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
313                                                    CamcorderProfile.QUALITY_TIME_LAPSE_2160P};
314 
315         int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
316                                                    CamcorderProfile.QUALITY_HIGH_SPEED_720P,
317                                                    CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
318                                                    CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
319 
320         CamcorderProfile lowProfile =
321                 getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
322         CamcorderProfile highProfile =
323                 getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
324         CamcorderProfile lowTimeLapseProfile =
325                 getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
326         CamcorderProfile highTimeLapseProfile =
327                 getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
328         checkSpecificProfiles(cameraId, lowProfile, highProfile,
329                 specificProfileQualities, videoSizes);
330         checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
331                 specificTimeLapseProfileQualities, null);
332         checkSpecificProfiles(cameraId, lowHighSpeedProfile, highHighSpeedProfile,
333                 specificHighSpeedProfileQualities, null);
334     }
335 
testGet()336     public void testGet() {
337         /*
338          * Device may not have rear camera for checkGet(-1).
339          * Checking PackageManager.FEATURE_CAMERA is included or not to decide the flow.
340          * Continue if the feature is included.
341          * Otherwise, exit test.
342          */
343         PackageManager pm = mContext.getPackageManager();
344         if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
345             return;
346         }
347         checkGet(-1);
348     }
349 
testGetWithId()350     public void testGetWithId() {
351         int nCamera = Camera.getNumberOfCameras();
352         for (int cameraId = 0; cameraId < nCamera; cameraId++) {
353             checkGet(cameraId);
354         }
355     }
356 
getSupportedVideoSizes(int cameraId)357     private List<Size> getSupportedVideoSizes(int cameraId) {
358         Camera camera = (cameraId == -1)? Camera.open(): Camera.open(cameraId);
359         Parameters parameters = camera.getParameters();
360         List<Size> videoSizes = parameters.getSupportedVideoSizes();
361         if (videoSizes == null) {
362             videoSizes = parameters.getSupportedPreviewSizes();
363             assertNotNull(videoSizes);
364         }
365         camera.release();
366         return videoSizes;
367     }
368 
isSizeSupported(int width, int height, List<Size> sizes)369     private boolean isSizeSupported(int width, int height, List<Size> sizes) {
370         if (sizes == null) return true;
371 
372         for (Size size: sizes) {
373             if (size.width == width && size.height == height) {
374                 return true;
375             }
376         }
377         Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
378         return false;
379     }
380 
isProfileMandatory(int quality)381     private boolean isProfileMandatory(int quality) {
382         return (quality == CamcorderProfile.QUALITY_LOW) ||
383                 (quality == CamcorderProfile.QUALITY_HIGH) ||
384                 (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
385                 (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
386     }
387 }
388