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