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