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