xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2013 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 com.android.cts.verifier.camera.its;
18 
19 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
20 
21 import android.graphics.ImageFormat;
22 import android.graphics.Rect;
23 import android.hardware.camera2.CameraAccessException;
24 import android.hardware.camera2.CameraCharacteristics;
25 import android.hardware.camera2.CameraDevice;
26 import android.hardware.camera2.CameraManager;
27 import android.hardware.camera2.CameraMetadata;
28 import android.hardware.camera2.CaptureRequest;
29 import android.hardware.camera2.params.MeteringRectangle;
30 import android.hardware.camera2.params.StreamConfigurationMap;
31 import android.media.CamcorderProfile;
32 import android.media.EncoderProfiles;
33 import android.media.Image;
34 import android.media.Image.Plane;
35 import android.media.MediaCodec;
36 import android.media.MediaCodecInfo;
37 import android.media.MediaFormat;
38 import android.media.MediaMuxer;
39 import android.os.Build;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.util.Log;
43 import android.util.Pair;
44 import android.util.Size;
45 
46 import androidx.annotation.ChecksSdkIntAtLeast;
47 
48 import com.android.ex.camera2.blocking.BlockingCameraManager;
49 import com.android.ex.camera2.blocking.BlockingStateCallback;
50 
51 import org.json.JSONArray;
52 import org.json.JSONObject;
53 
54 import java.nio.ByteBuffer;
55 import java.nio.charset.Charset;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Comparator;
59 import java.util.List;
60 import java.util.Set;
61 import java.util.concurrent.Semaphore;
62 
63 public class ItsUtils {
64     public static final String TAG = ItsUtils.class.getSimpleName();
65     // The tokenizer must be the same as CAMERA_ID_TOKENIZER in device.py
66     public static final String CAMERA_ID_TOKENIZER = ".";
67 
jsonToByteBuffer(JSONObject jsonObj)68     public static ByteBuffer jsonToByteBuffer(JSONObject jsonObj) {
69         return ByteBuffer.wrap(jsonObj.toString().getBytes(Charset.defaultCharset()));
70     }
71 
getJsonWeightedRectsFromArray( JSONArray a, boolean normalized, int width, int height)72     public static MeteringRectangle[] getJsonWeightedRectsFromArray(
73             JSONArray a, boolean normalized, int width, int height)
74             throws ItsException {
75         try {
76             // Returns [x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  ...]
77             assert(a.length() % 5 == 0);
78             MeteringRectangle[] ma = new MeteringRectangle[a.length() / 5];
79             for (int i = 0; i < a.length(); i += 5) {
80                 int x,y,w,h;
81                 if (normalized) {
82                     x = (int)Math.floor(a.getDouble(i+0) * width + 0.5f);
83                     y = (int)Math.floor(a.getDouble(i+1) * height + 0.5f);
84                     w = (int)Math.floor(a.getDouble(i+2) * width + 0.5f);
85                     h = (int)Math.floor(a.getDouble(i+3) * height + 0.5f);
86                 } else {
87                     x = a.getInt(i+0);
88                     y = a.getInt(i+1);
89                     w = a.getInt(i+2);
90                     h = a.getInt(i+3);
91                 }
92                 x = Math.max(x, 0);
93                 y = Math.max(y, 0);
94                 w = Math.min(w, width-x);
95                 h = Math.min(h, height-y);
96                 int wgt = a.getInt(i+4);
97                 ma[i/5] = new MeteringRectangle(x,y,w,h,wgt);
98             }
99             return ma;
100         } catch (org.json.JSONException e) {
101             throw new ItsException("JSON error: ", e);
102         }
103     }
104 
getOutputSpecs(JSONObject jsonObjTop)105     public static JSONArray getOutputSpecs(JSONObject jsonObjTop)
106             throws ItsException {
107         try {
108             if (jsonObjTop.has("outputSurfaces")) {
109                 return jsonObjTop.getJSONArray("outputSurfaces");
110             }
111             return null;
112         } catch (org.json.JSONException e) {
113             throw new ItsException("JSON error: ", e);
114         }
115     }
116 
getRaw16OutputSizes(CameraCharacteristics ccs)117     public static Size[] getRaw16OutputSizes(CameraCharacteristics ccs)
118             throws ItsException {
119         return getOutputSizes(ccs, ImageFormat.RAW_SENSOR, false);
120     }
121 
getRaw16MaxResulolutionOutputSizes(CameraCharacteristics ccs)122     public static Size[] getRaw16MaxResulolutionOutputSizes(CameraCharacteristics ccs)
123         throws ItsException {
124         return getOutputSizes(ccs, ImageFormat.RAW_SENSOR, true);
125     }
126 
getRaw10OutputSizes(CameraCharacteristics ccs)127     public static Size[] getRaw10OutputSizes(CameraCharacteristics ccs)
128         throws ItsException {
129         return getOutputSizes(ccs, ImageFormat.RAW10, false);
130     }
131 
getRaw10MaxResulolutionOutputSizes(CameraCharacteristics ccs)132     public static Size[] getRaw10MaxResulolutionOutputSizes(CameraCharacteristics ccs)
133         throws ItsException {
134         return getOutputSizes(ccs, ImageFormat.RAW10, true);
135     }
136 
getRaw12OutputSizes(CameraCharacteristics ccs)137     public static Size[] getRaw12OutputSizes(CameraCharacteristics ccs)
138             throws ItsException {
139         return getOutputSizes(ccs, ImageFormat.RAW12, false);
140     }
141 
getJpegOutputSizes(CameraCharacteristics ccs)142     public static Size[] getJpegOutputSizes(CameraCharacteristics ccs)
143             throws ItsException {
144         return getOutputSizes(ccs, ImageFormat.JPEG, false);
145     }
146 
getYuvOutputSizes(CameraCharacteristics ccs)147     public static Size[] getYuvOutputSizes(CameraCharacteristics ccs)
148             throws ItsException {
149         return getOutputSizes(ccs, ImageFormat.YUV_420_888, false);
150     }
151 
getY8OutputSizes(CameraCharacteristics ccs)152     public static Size[] getY8OutputSizes(CameraCharacteristics ccs)
153             throws ItsException {
154         return getOutputSizes(ccs, ImageFormat.Y8, false);
155     }
156 
getMaxOutputSize(CameraCharacteristics ccs, int format)157     public static Size getMaxOutputSize(CameraCharacteristics ccs, int format)
158             throws ItsException {
159         return getMaxSize(getOutputSizes(ccs, format, false));
160     }
161 
getActiveArrayCropRegion(CameraCharacteristics ccs, boolean isMaximumResolution)162     public static Rect getActiveArrayCropRegion(CameraCharacteristics ccs,
163         boolean isMaximumResolution) {
164         Rect cropRegion = null;
165         if (isMaximumResolution) {
166             cropRegion = ccs.get(
167                 CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION);
168         } else {
169             cropRegion = ccs.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
170         }
171         return cropRegion;
172     }
173 
getOutputSizes(CameraCharacteristics ccs, int format, boolean isMaximumResolution)174     private static Size[] getOutputSizes(CameraCharacteristics ccs, int format,
175         boolean isMaximumResolution) throws ItsException {
176         StreamConfigurationMap configMap = null;
177         if (isMaximumResolution) {
178             configMap = ccs.get(
179                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION);
180         } else {
181             configMap = ccs.get(
182                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
183         }
184 
185         if (configMap == null) {
186             throw new ItsException("Failed to get stream config");
187         }
188         Size[] normalSizes = configMap.getOutputSizes(format);
189         Size[] slowSizes = configMap.getHighResolutionOutputSizes(format);
190         Size[] allSizes = null;
191         if (normalSizes != null && slowSizes != null) {
192             allSizes = new Size[normalSizes.length + slowSizes.length];
193             System.arraycopy(normalSizes, 0, allSizes, 0, normalSizes.length);
194             System.arraycopy(slowSizes, 0, allSizes, normalSizes.length, slowSizes.length);
195         } else if (normalSizes != null) {
196             allSizes = normalSizes;
197         } else if (slowSizes != null) {
198             allSizes = slowSizes;
199         }
200         return allSizes;
201     }
202 
getMaxSize(Size[] sizes)203     public static Size getMaxSize(Size[] sizes) {
204         if (sizes == null || sizes.length == 0) {
205             throw new IllegalArgumentException("sizes was empty");
206         }
207 
208         Size maxSize = sizes[0];
209         int maxArea = maxSize.getWidth() * maxSize.getHeight();
210         for (int i = 1; i < sizes.length; i++) {
211             int area = sizes[i].getWidth() * sizes[i].getHeight();
212             if (area > maxArea ||
213                     (area == maxArea && sizes[i].getWidth() > maxSize.getWidth())) {
214                 maxSize = sizes[i];
215                 maxArea = area;
216             }
217         }
218 
219         return maxSize;
220     }
221 
getDataFromImage(Image image, Semaphore quota)222     public static byte[] getDataFromImage(Image image, Semaphore quota)
223             throws ItsException {
224         int format = image.getFormat();
225         int width = image.getWidth();
226         int height = image.getHeight();
227         byte[] data = null;
228 
229         // Read image data
230         Plane[] planes = image.getPlanes();
231 
232         // Check image validity
233         if (!checkAndroidImageFormat(image)) {
234             throw new ItsException(
235                     "Invalid image format passed to getDataFromImage: " + image.getFormat());
236         }
237 
238         if ((format == ImageFormat.JPEG) || (format == ImageFormat.JPEG_R)) {
239             // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
240             ByteBuffer buffer = planes[0].getBuffer();
241             if (quota != null) {
242                 try {
243                     Logt.i(TAG, "Start waiting for quota Semaphore");
244                     quota.acquire(buffer.capacity());
245                     Logt.i(TAG, "Acquired quota Semaphore. Start reading image");
246                 } catch (java.lang.InterruptedException e) {
247                     Logt.e(TAG, "getDataFromImage error acquiring memory quota. Interrupted", e);
248                 }
249             }
250             data = new byte[buffer.capacity()];
251             buffer.get(data);
252             Logt.i(TAG, "Done reading jpeg image");
253             return data;
254         } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR
255                 || format == ImageFormat.RAW10 || format == ImageFormat.RAW12
256                 || format == ImageFormat.Y8) {
257             int offset = 0;
258             int dataSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
259             if (quota != null) {
260                 try {
261                     Logt.i(TAG, "Start waiting for quota Semaphore");
262                     quota.acquire(dataSize);
263                     Logt.i(TAG, "Acquired quota Semaphore. Start reading image");
264                 } catch (java.lang.InterruptedException e) {
265                     Logt.e(TAG, "getDataFromImage error acquiring memory quota. Interrupted", e);
266                 }
267             }
268             data = new byte[dataSize];
269             int maxRowSize = planes[0].getRowStride();
270             for (int i = 0; i < planes.length; i++) {
271                 if (maxRowSize < planes[i].getRowStride()) {
272                     maxRowSize = planes[i].getRowStride();
273                 }
274             }
275             byte[] rowData = new byte[maxRowSize];
276             for (int i = 0; i < planes.length; i++) {
277                 ByteBuffer buffer = planes[i].getBuffer();
278                 int rowStride = planes[i].getRowStride();
279                 int pixelStride = planes[i].getPixelStride();
280                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
281                 Logt.i(TAG, String.format(
282                         "Reading image: fmt %d, plane %d, w %d, h %d," +
283                         "rowStride %d, pixStride %d, bytesPerPixel %d",
284                         format, i, width, height, rowStride, pixelStride, bytesPerPixel));
285                 // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
286                 int w = (i == 0) ? width : width / 2;
287                 int h = (i == 0) ? height : height / 2;
288                 for (int row = 0; row < h; row++) {
289                     if (pixelStride == bytesPerPixel) {
290                         // Special case: optimized read of the entire row
291                         int length = w * bytesPerPixel;
292                         buffer.get(data, offset, length);
293                         // Advance buffer the remainder of the row stride
294                         if (row < h - 1) {
295                             buffer.position(buffer.position() + rowStride - length);
296                         }
297                         offset += length;
298                     } else {
299                         // Generic case: should work for any pixelStride but slower.
300                         // Use intermediate buffer to avoid read byte-by-byte from
301                         // DirectByteBuffer, which is very bad for performance.
302                         // Also need avoid access out of bound by only reading the available
303                         // bytes in the bytebuffer.
304                         int readSize = rowStride;
305                         if (buffer.remaining() < readSize) {
306                             readSize = buffer.remaining();
307                         }
308                         buffer.get(rowData, 0, readSize);
309                         if (pixelStride >= 1) {
310                             for (int col = 0; col < w; col++) {
311                                 data[offset++] = rowData[col * pixelStride];
312                             }
313                         } else {
314                             // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for
315                             // example with RAW10. Just copy the buffer, dropping any padding at
316                             // the end of the row.
317                             int length = (w * ImageFormat.getBitsPerPixel(format)) / 8;
318                             System.arraycopy(rowData,0,data,offset,length);
319                             offset += length;
320                         }
321                     }
322                 }
323             }
324             Logt.i(TAG, String.format("Done reading image, format %d", format));
325             return data;
326         } else {
327             throw new ItsException("Unsupported image format: " + format);
328         }
329     }
330 
checkAndroidImageFormat(Image image)331     private static boolean checkAndroidImageFormat(Image image) {
332         int format = image.getFormat();
333         Plane[] planes = image.getPlanes();
334         switch (format) {
335             case ImageFormat.YUV_420_888:
336             case ImageFormat.NV21:
337             case ImageFormat.YV12:
338                 return 3 == planes.length;
339             case ImageFormat.RAW_SENSOR:
340             case ImageFormat.RAW10:
341             case ImageFormat.RAW12:
342             case ImageFormat.JPEG:
343             case ImageFormat.JPEG_R:
344             case ImageFormat.Y8:
345                 return 1 == planes.length;
346             default:
347                 return false;
348         }
349     }
350 
351     public static class ItsCameraIdList {
352         // Short form camera Ids (including both CameraIdList and hidden physical cameras
353         public List<String> mCameraIds;
354         // Camera Id combos (ids from CameraIdList, and hidden physical camera Ids
355         // in the form of [logical camera id]:[hidden physical camera id]
356         public List<String> mCameraIdCombos;
357         // Primary rear and front camera Ids (as defined in MPC)
358         public String mPrimaryRearCameraId;
359         public String mPrimaryFrontCameraId;
360     }
361 
getItsCompatibleCameraIds(CameraManager manager)362     public static ItsCameraIdList getItsCompatibleCameraIds(CameraManager manager)
363             throws ItsException {
364         if (manager == null) {
365             throw new IllegalArgumentException("CameraManager is null");
366         }
367 
368         ItsCameraIdList outList = new ItsCameraIdList();
369         outList.mCameraIds = new ArrayList<String>();
370         outList.mCameraIdCombos = new ArrayList<String>();
371         try {
372             String[] cameraIds = manager.getCameraIdList();
373             for (String id : cameraIds) {
374                 CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
375                 int[] actualCapabilities = characteristics.get(
376                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
377                 boolean haveBC = false;
378                 boolean isMultiCamera = false;
379                 final int BACKWARD_COMPAT =
380                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
381                 final int LOGICAL_MULTI_CAMERA =
382                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
383 
384                 final Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
385                 if (facing != null) {
386                     if (facing == CameraMetadata.LENS_FACING_BACK
387                             && outList.mPrimaryRearCameraId == null) {
388                         outList.mPrimaryRearCameraId = id;
389                     } else if (facing == CameraMetadata.LENS_FACING_FRONT
390                             && outList.mPrimaryFrontCameraId == null) {
391                         outList.mPrimaryFrontCameraId = id;
392                     }
393                 }
394 
395                 for (int capability : actualCapabilities) {
396                     if (capability == BACKWARD_COMPAT) {
397                         haveBC = true;
398                     }
399                     if (capability == LOGICAL_MULTI_CAMERA) {
400                         isMultiCamera = true;
401                     }
402                 }
403 
404                 // Skip devices that does not support BACKWARD_COMPATIBLE capability
405                 if (!haveBC) continue;
406 
407                 int hwLevel = characteristics.get(
408                         CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
409                 if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
410                         hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
411                     // Skip LEGACY and EXTERNAL devices
412                     continue;
413                 }
414                 outList.mCameraIds.add(id);
415                 outList.mCameraIdCombos.add(id);
416 
417                 // Only add hidden physical cameras for multi-camera.
418                 if (!isMultiCamera) continue;
419 
420                 float defaultFocalLength = getLogicalCameraDefaultFocalLength(manager, id);
421                 Set<String> physicalIds = characteristics.getPhysicalCameraIds();
422                 for (String physicalId : physicalIds) {
423                     if (Arrays.asList(cameraIds).contains(physicalId)) continue;
424 
425                     CameraCharacteristics physicalChar =
426                             manager.getCameraCharacteristics(physicalId);
427                     hwLevel = physicalChar.get(
428                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
429                     if (hwLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY ||
430                             hwLevel ==
431                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL) {
432                         // Skip LEGACY and EXTERNAL devices
433                         continue;
434                     }
435 
436                     int[] physicalActualCapabilities = physicalChar.get(
437                             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
438                     boolean physicalHaveBC = false;
439                     for (int capability : physicalActualCapabilities) {
440                         if (capability == BACKWARD_COMPAT) {
441                             physicalHaveBC = true;
442                             break;
443                         }
444                     }
445                     if (!physicalHaveBC) {
446                         continue;
447                     }
448                     // To reduce duplicate tests, only additionally test hidden physical cameras
449                     // with different focal length compared to the default focal length of the
450                     // logical camera.
451                     float[] physicalFocalLengths = physicalChar.get(
452                             CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
453                     if (defaultFocalLength != physicalFocalLengths[0]) {
454                         outList.mCameraIds.add(physicalId);
455                         outList.mCameraIdCombos.add(id + CAMERA_ID_TOKENIZER + physicalId);
456                     }
457                 }
458 
459             }
460         } catch (CameraAccessException e) {
461             Logt.e(TAG,
462                     "Received error from camera service while checking device capabilities: " + e);
463             throw new ItsException("Failed to get device ID list", e);
464         }
465         return outList;
466     }
467 
getLogicalCameraDefaultFocalLength(CameraManager manager, String cameraId)468     public static float getLogicalCameraDefaultFocalLength(CameraManager manager,
469             String cameraId) throws ItsException {
470         BlockingCameraManager blockingManager = new BlockingCameraManager(manager);
471         BlockingStateCallback listener = new BlockingStateCallback();
472         HandlerThread cameraThread = new HandlerThread("ItsUtilThread");
473         cameraThread.start();
474         Handler cameraHandler = new Handler(cameraThread.getLooper());
475         CameraDevice camera = null;
476         float defaultFocalLength = 0.0f;
477 
478         try {
479             camera = blockingManager.openCamera(cameraId, listener, cameraHandler);
480             CaptureRequest.Builder previewBuilder =
481                     camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
482             defaultFocalLength = previewBuilder.get(CaptureRequest.LENS_FOCAL_LENGTH);
483         } catch (Exception e) {
484             throw new ItsException("Failed to query default focal length for logical camera", e);
485         } finally {
486             if (camera != null) {
487                 camera.close();
488             }
489             if (cameraThread != null) {
490                 cameraThread.quitSafely();
491             }
492         }
493         return defaultFocalLength;
494     }
495 
496     public static class MediaCodecListener extends MediaCodec.Callback {
497         private final MediaMuxer mMediaMuxer;
498         private final Object mCondition;
499         private int mTrackId = -1;
500         private boolean mEndOfStream = false;
501 
MediaCodecListener(MediaMuxer mediaMuxer, Object condition)502         public MediaCodecListener(MediaMuxer mediaMuxer, Object condition) {
503             mMediaMuxer = mediaMuxer;
504             mCondition = condition;
505         }
506 
507         @Override
onInputBufferAvailable(MediaCodec codec, int index)508         public void onInputBufferAvailable(MediaCodec codec, int index) {
509             Log.e(TAG, "Unexpected input buffer available callback!");
510         }
511 
512         @Override
onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info)513         public void onOutputBufferAvailable(MediaCodec codec, int index,
514                 MediaCodec.BufferInfo info) {
515             synchronized (mCondition) {
516                 if (mTrackId < 0) {
517                     return;
518                 }
519 
520                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
521                     mEndOfStream = true;
522                     mCondition.notifyAll();
523                 }
524 
525                 if (!mEndOfStream) {
526                     mMediaMuxer.writeSampleData(mTrackId, codec.getOutputBuffer(index), info);
527                     codec.releaseOutputBuffer(index, false);
528                 }
529             }
530         }
531 
532         @Override
onError(MediaCodec codec, MediaCodec.CodecException e)533         public void onError(MediaCodec codec, MediaCodec.CodecException e) {
534             Log.e(TAG, "Codec error: " + e.getDiagnosticInfo());
535         }
536 
537         @Override
onOutputFormatChanged(MediaCodec codec, MediaFormat format)538         public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
539             synchronized (mCondition) {
540                 mTrackId = mMediaMuxer.addTrack(format);
541                 mMediaMuxer.start();
542             }
543         }
544     }
545 
546     public static final long SESSION_CLOSE_TIMEOUT_MS  = 3000;
547 
548     // used to find a good-enough recording bitrate for a given resolution. "Good enough" for the
549     // ITS test to run its calculations and still be supported by the HAL.
550     // NOTE: Keep sorted for convenience
551     public static final List<Pair<Integer, Integer>> RESOLUTION_TO_CAMCORDER_PROFILE = List.of(
552             Pair.create(176  * 144,  CamcorderProfile.QUALITY_QCIF),
553             Pair.create(320  * 240,  CamcorderProfile.QUALITY_QVGA),
554             Pair.create(352  * 288,  CamcorderProfile.QUALITY_CIF),
555             Pair.create(640  * 480,  CamcorderProfile.QUALITY_VGA),
556             Pair.create(720  * 480,  CamcorderProfile.QUALITY_480P),
557             Pair.create(1280 * 720,  CamcorderProfile.QUALITY_720P),
558             Pair.create(1920 * 1080, CamcorderProfile.QUALITY_1080P),
559             Pair.create(2048 * 1080, CamcorderProfile.QUALITY_2K),
560             Pair.create(2560 * 1440, CamcorderProfile.QUALITY_QHD),
561             Pair.create(3840 * 2160, CamcorderProfile.QUALITY_2160P),
562             Pair.create(4096 * 2160, CamcorderProfile.QUALITY_4KDCI)
563             // should be safe to assume that we don't have previews over 4k
564     );
565 
566     /**
567      * Initialize a HLG10 MediaFormat instance with size, bitrate, and videoFrameRate.
568      */
initializeHLG10Format(Size videoSize, int videoBitRate, int videoFrameRate)569     public static MediaFormat initializeHLG10Format(Size videoSize, int videoBitRate,
570             int videoFrameRate) {
571         MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC,
572                 videoSize.getWidth(), videoSize.getHeight());
573         format.setInteger(MediaFormat.KEY_PROFILE, HEVCProfileMain10);
574         format.setInteger(MediaFormat.KEY_BIT_RATE, videoBitRate);
575         format.setInteger(MediaFormat.KEY_FRAME_RATE, videoFrameRate);
576         format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
577                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
578         format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
579         format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_FULL);
580         format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_HLG);
581         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
582         return format;
583     }
584 
585     // Default bitrate to use for recordings when querying CamcorderProfile fails.
586     private static final int DEFAULT_RECORDING_BITRATE = 25_000_000; // 25 Mbps
587 
588     /**
589      * Looks up a reasonable recording bitrate from {@link CamcorderProfile} for the given
590      * {@code previewSize} and {@code maxFps}. This is not the most optimal bitrate, but should be
591      * good enough for ITS tests to run their analyses.
592      */
calculateBitrate(int cameraId, Size previewSize, int maxFps)593     public static int calculateBitrate(int cameraId, Size previewSize, int maxFps)
594             throws ItsException {
595         int previewResolution = previewSize.getHeight() * previewSize.getWidth();
596 
597         List<Pair<Integer, Integer>> resToProfile =
598                 new ArrayList<>(RESOLUTION_TO_CAMCORDER_PROFILE);
599         // ensure that the list is sorted in ascending order of resolution
600         resToProfile.sort(Comparator.comparingInt(a -> a.first));
601 
602         // Choose the first available resolution that is >= the requested preview size.
603         for (Pair<Integer, Integer> entry : resToProfile) {
604             if (previewResolution > entry.first) continue;
605             if (!CamcorderProfile.hasProfile(cameraId, entry.second)) continue;
606 
607             EncoderProfiles profiles = CamcorderProfile.getAll(
608                     String.valueOf(cameraId), entry.second);
609             if (profiles == null) continue;
610 
611             List<EncoderProfiles.VideoProfile> videoProfiles = profiles.getVideoProfiles();
612 
613             // Find a profile which can achieve the requested max frame rate
614             for (EncoderProfiles.VideoProfile profile : videoProfiles) {
615                 if (profile == null) continue;
616                 if (profile.getFrameRate() >= maxFps) {
617                     Logt.i(TAG, "Recording bitrate: " + profile.getBitrate()
618                             + ", fps " + profile.getFrameRate());
619                     return  profile.getBitrate();
620                 }
621             }
622         }
623 
624         // TODO(b/223439995): There is a bug where some devices might populate result of
625         //                    CamcorderProfile.getAll with nulls even when a given quality is
626         //                    supported. Until this bug is fixed, fall back to the "deprecated"
627         //                    CamcorderProfile.get call to get the video bitrate. This logic can be
628         //                    removed once the bug is fixed.
629         Logt.i(TAG, "No matching EncoderProfile found. Falling back to CamcorderProfiles");
630         // Mimic logic from above, but use CamcorderProfiles instead
631         for (Pair<Integer, Integer> entry : resToProfile) {
632             if (previewResolution > entry.first) continue;
633             if (!CamcorderProfile.hasProfile(cameraId, entry.second)) continue;
634 
635             CamcorderProfile profile = CamcorderProfile.get(cameraId, entry.second);
636             if (profile == null) continue;
637 
638             int profileFrameRate = profile.videoFrameRate;
639             float bitRateScale = (profileFrameRate < maxFps)
640                     ? 1.0f * maxFps / profileFrameRate : 1.0f;
641             Logt.i(TAG, "Recording bitrate: " + profile.videoBitRate + " * " + bitRateScale);
642             return (int) (profile.videoBitRate * bitRateScale);
643         }
644 
645         // Ideally, we should always find a Camcorder/Encoder Profile corresponding
646         // to the preview size.
647         Logt.w(TAG, "Could not find bitrate for any resolution >= " + previewSize
648                 + " for cameraId " + cameraId + ". Using default bitrate");
649         return DEFAULT_RECORDING_BITRATE;
650     }
651 
652     /**
653      * Check if the device is running on at least Android V.
654      */
655     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
isAtLeastV()656     public static boolean isAtLeastV() {
657         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
658     }
659 }
660