1 /* 2 * Copyright (C) 2012 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 package com.android.cts.verifier.camera.formats; 17 18 import android.app.AlertDialog; 19 import android.graphics.Bitmap; 20 import android.graphics.Color; 21 import android.graphics.ColorMatrix; 22 import android.graphics.ColorMatrixColorFilter; 23 import android.graphics.ImageFormat; 24 import android.graphics.Matrix; 25 import android.graphics.Rect; 26 import android.graphics.SurfaceTexture; 27 import android.hardware.Camera; 28 import android.hardware.Camera.CameraInfo; 29 import android.os.AsyncTask; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.util.Log; 33 import android.util.SparseArray; 34 import android.view.Menu; 35 import android.view.MenuItem; 36 import android.view.Surface; 37 import android.view.TextureView; 38 import android.view.View; 39 import android.widget.AdapterView; 40 import android.widget.ArrayAdapter; 41 import android.widget.Button; 42 import android.widget.ImageButton; 43 import android.widget.ImageView; 44 import android.widget.Spinner; 45 import android.widget.Toast; 46 47 import com.android.cts.verifier.PassFailButtons; 48 import com.android.cts.verifier.R; 49 50 import java.io.IOException; 51 import java.util.ArrayList; 52 import java.util.Comparator; 53 import java.util.HashSet; 54 import java.util.List; 55 import java.util.Optional; 56 import java.util.TreeSet; 57 58 /** 59 * Tests for manual verification of the CDD-required camera output formats 60 * for preview callbacks 61 */ 62 public class CameraFormatsActivity extends PassFailButtons.Activity 63 implements TextureView.SurfaceTextureListener, Camera.PreviewCallback { 64 65 private static final String TAG = "CameraFormats"; 66 67 private TextureView mPreviewView; 68 private SurfaceTexture mPreviewTexture; 69 private int mPreviewTexWidth; 70 private int mPreviewTexHeight; 71 private int mPreviewRotation; 72 73 private ImageView mFormatView; 74 75 private Spinner mCameraSpinner; 76 private Spinner mFormatSpinner; 77 private Spinner mResolutionSpinner; 78 79 private int mCurrentCameraId = -1; 80 private Camera mCamera; 81 82 private List<Camera.Size> mPreviewSizes; 83 private Camera.Size mNextPreviewSize; 84 private Camera.Size mPreviewSize; 85 private List<Integer> mPreviewFormats; 86 private int mNextPreviewFormat; 87 private int mPreviewFormat; 88 private SparseArray<String> mPreviewFormatNames; 89 90 private ColorMatrixColorFilter mYuv2RgbFilter; 91 92 private Bitmap mCallbackBitmap; 93 private int[] mRgbData; 94 private int mRgbWidth; 95 private int mRgbHeight; 96 97 private static final int STATE_OFF = 0; 98 private static final int STATE_PREVIEW = 1; 99 private static final int STATE_NO_CALLBACKS = 2; 100 private int mState = STATE_OFF; 101 private boolean mProcessInProgress = false; 102 private boolean mProcessingFirstFrame = false; 103 104 private final TreeSet<CameraCombination> mTestedCombinations = new TreeSet<>(COMPARATOR); 105 private final TreeSet<CameraCombination> mUntestedCombinations = new TreeSet<>(COMPARATOR); 106 107 private int mAllCombinationsSize = 0; 108 109 // Menu to show the test progress 110 private static final int MENU_ID_PROGRESS = Menu.FIRST + 1; 111 112 private class CameraCombination { 113 private final int mCameraIndex; 114 private final int mResolutionIndex; 115 private final int mFormatIndex; 116 private final int mResolutionWidth; 117 private final int mResolutionHeight; 118 private final String mFormatName; 119 CameraCombination(int cameraIndex, int resolutionIndex, int formatIndex, int resolutionWidth, int resolutionHeight, String formatName)120 private CameraCombination(int cameraIndex, int resolutionIndex, int formatIndex, 121 int resolutionWidth, int resolutionHeight, String formatName) { 122 this.mCameraIndex = cameraIndex; 123 this.mResolutionIndex = resolutionIndex; 124 this.mFormatIndex = formatIndex; 125 this.mResolutionWidth = resolutionWidth; 126 this.mResolutionHeight = resolutionHeight; 127 this.mFormatName = formatName; 128 } 129 130 @Override toString()131 public String toString() { 132 return String.format("Camera %d, %dx%d, %s", 133 mCameraIndex, mResolutionWidth, mResolutionHeight, mFormatName); 134 } 135 } 136 137 private static final Comparator<CameraCombination> COMPARATOR = 138 Comparator.<CameraCombination, Integer>comparing(c -> c.mCameraIndex) 139 .thenComparing(c -> c.mResolutionIndex) 140 .thenComparing(c -> c.mFormatIndex); 141 142 @Override onCreate(Bundle savedInstanceState)143 public void onCreate(Bundle savedInstanceState) { 144 super.onCreate(savedInstanceState); 145 146 setContentView(R.layout.cf_main); 147 148 mAllCombinationsSize = calcAllCombinationsSize(); 149 150 // disable "Pass" button until all combinations are tested 151 setPassButtonEnabled(false); 152 153 setPassFailButtonClickListeners(); 154 setInfoResources(R.string.camera_format, R.string.cf_info, -1); 155 156 mPreviewView = (TextureView) findViewById(R.id.preview_view); 157 mFormatView = (ImageView) findViewById(R.id.format_view); 158 159 mPreviewView.setSurfaceTextureListener(this); 160 161 int numCameras = Camera.getNumberOfCameras(); 162 String[] cameraNames = new String[numCameras]; 163 for (int i = 0; i < numCameras; i++) { 164 cameraNames[i] = "Camera " + i; 165 } 166 mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection); 167 mCameraSpinner.setAdapter( 168 new ArrayAdapter<String>( 169 this, R.layout.camera_list_item, cameraNames)); 170 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 171 172 mFormatSpinner = (Spinner) findViewById(R.id.format_selection); 173 mFormatSpinner.setOnItemSelectedListener(mFormatSelectedListener); 174 175 mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection); 176 mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener); 177 178 // Must be kept in sync with android.graphics.ImageFormat manually 179 mPreviewFormatNames = new SparseArray(7); 180 mPreviewFormatNames.append(ImageFormat.JPEG, "JPEG"); 181 mPreviewFormatNames.append(ImageFormat.NV16, "NV16"); 182 mPreviewFormatNames.append(ImageFormat.NV21, "NV21"); 183 mPreviewFormatNames.append(ImageFormat.RGB_565, "RGB_565"); 184 mPreviewFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN"); 185 mPreviewFormatNames.append(ImageFormat.YUY2, "YUY2"); 186 mPreviewFormatNames.append(ImageFormat.YV12, "YV12"); 187 188 // Need YUV->RGB conversion in many cases 189 190 ColorMatrix y2r = new ColorMatrix(); 191 y2r.setYUV2RGB(); 192 float[] yuvOffset = new float[] { 193 1.f, 0.f, 0.f, 0.f, 0.f, 194 0.f, 1.f, 0.f, 0.f, -128.f, 195 0.f, 0.f, 1.f, 0.f, -128.f, 196 0.f, 0.f, 0.f, 1.f, 0.f 197 }; 198 199 ColorMatrix yOffset = new ColorMatrix(yuvOffset); 200 201 ColorMatrix yTotal = new ColorMatrix(); 202 yTotal.setConcat(y2r, yOffset); 203 204 mYuv2RgbFilter = new ColorMatrixColorFilter(yTotal); 205 206 Button mNextButton = findViewById(R.id.next_button); 207 mNextButton.setOnClickListener(v -> { 208 setUntestedCombination(); 209 startPreview(); 210 }); 211 } 212 213 /** 214 * Set an untested combination of resolution and format for the current camera. 215 * Triggered by next button click. 216 */ setUntestedCombination()217 private void setUntestedCombination() { 218 Optional<CameraCombination> combination = mUntestedCombinations.stream().filter( 219 c -> c.mCameraIndex == mCurrentCameraId).findFirst(); 220 if (!combination.isPresent()) { 221 Toast.makeText(this, "All Camera " + mCurrentCameraId + " tests are done.", 222 Toast.LENGTH_SHORT).show(); 223 return; 224 } 225 226 // There is untested combination for the current camera, set the next untested combination. 227 int mNextResolutionIndex = combination.get().mResolutionIndex; 228 int mNextFormatIndex = combination.get().mFormatIndex; 229 230 mNextPreviewSize = mPreviewSizes.get(mNextResolutionIndex); 231 mResolutionSpinner.setSelection(mNextResolutionIndex); 232 mNextPreviewFormat = mPreviewFormats.get(mNextFormatIndex); 233 mFormatSpinner.setSelection(mNextFormatIndex); 234 } 235 236 @Override onCreateOptionsMenu(Menu menu)237 public boolean onCreateOptionsMenu(Menu menu) { 238 menu.add(Menu.NONE, MENU_ID_PROGRESS, Menu.NONE, "Current Progress"); 239 return super.onCreateOptionsMenu(menu); 240 } 241 242 @Override onOptionsItemSelected(MenuItem item)243 public boolean onOptionsItemSelected(MenuItem item) { 244 boolean ret = true; 245 switch (item.getItemId()) { 246 case MENU_ID_PROGRESS: 247 showCombinationsDialog(); 248 ret = true; 249 break; 250 default: 251 ret = super.onOptionsItemSelected(item); 252 break; 253 } 254 return ret; 255 } 256 showCombinationsDialog()257 private void showCombinationsDialog() { 258 AlertDialog.Builder builder = 259 new AlertDialog.Builder(CameraFormatsActivity.this); 260 builder.setMessage(getTestDetails()) 261 .setTitle("Current Progress") 262 .setPositiveButton("OK", null); 263 builder.show(); 264 } 265 266 @Override onResume()267 public void onResume() { 268 super.onResume(); 269 270 setUpCamera(mCameraSpinner.getSelectedItemPosition()); 271 } 272 273 @Override onPause()274 public void onPause() { 275 super.onPause(); 276 277 shutdownCamera(); 278 mPreviewTexture = null; 279 } 280 281 @Override getTestDetails()282 public String getTestDetails() { 283 StringBuilder reportBuilder = new StringBuilder(); 284 reportBuilder.append("Tested combinations:\n"); 285 for (CameraCombination combination: mTestedCombinations) { 286 reportBuilder.append(combination); 287 reportBuilder.append("\n"); 288 } 289 290 reportBuilder.append("Untested combinations:\n"); 291 for (CameraCombination combination: mUntestedCombinations) { 292 reportBuilder.append(combination); 293 reportBuilder.append("\n"); 294 } 295 return reportBuilder.toString(); 296 } 297 onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)298 public void onSurfaceTextureAvailable(SurfaceTexture surface, 299 int width, int height) { 300 mPreviewTexture = surface; 301 if (mFormatView.getMeasuredWidth() != width 302 || mFormatView.getMeasuredHeight() != height) { 303 mPreviewTexWidth = mFormatView.getMeasuredWidth(); 304 mPreviewTexHeight = mFormatView.getMeasuredHeight(); 305 } else { 306 mPreviewTexWidth = width; 307 mPreviewTexHeight = height; 308 } 309 310 if (mCamera != null) { 311 startPreview(); 312 } 313 } 314 onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)315 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 316 // Ignored, Camera does all the work for us 317 } 318 onSurfaceTextureDestroyed(SurfaceTexture surface)319 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 320 return true; 321 } 322 onSurfaceTextureUpdated(SurfaceTexture surface)323 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 324 // Invoked every time there's a new Camera preview frame 325 } 326 327 private AdapterView.OnItemSelectedListener mCameraSpinnerListener = 328 new AdapterView.OnItemSelectedListener() { 329 public void onItemSelected(AdapterView<?> parent, 330 View view, int pos, long id) { 331 if (mCurrentCameraId != pos) { 332 setUpCamera(pos); 333 } 334 } 335 336 public void onNothingSelected(AdapterView parent) { 337 338 } 339 340 }; 341 342 private AdapterView.OnItemSelectedListener mResolutionSelectedListener = 343 new AdapterView.OnItemSelectedListener() { 344 public void onItemSelected(AdapterView<?> parent, 345 View view, int position, long id) { 346 if (mPreviewSizes.get(position) != mPreviewSize) { 347 mNextPreviewSize = mPreviewSizes.get(position); 348 startPreview(); 349 } 350 } 351 352 public void onNothingSelected(AdapterView parent) { 353 354 } 355 356 }; 357 358 359 private AdapterView.OnItemSelectedListener mFormatSelectedListener = 360 new AdapterView.OnItemSelectedListener() { 361 public void onItemSelected(AdapterView<?> parent, 362 View view, int position, long id) { 363 if (mPreviewFormats.get(position) != mNextPreviewFormat) { 364 mNextPreviewFormat = mPreviewFormats.get(position); 365 startPreview(); 366 } 367 } 368 369 public void onNothingSelected(AdapterView parent) { 370 371 } 372 373 }; 374 setUpCamera(int id)375 private void setUpCamera(int id) { 376 shutdownCamera(); 377 378 mCurrentCameraId = id; 379 mCamera = Camera.open(id); 380 Camera.Parameters p = mCamera.getParameters(); 381 382 // Get preview resolutions 383 384 List<Camera.Size> unsortedSizes = p.getSupportedPreviewSizes(); 385 386 class SizeCompare implements Comparator<Camera.Size> { 387 public int compare(Camera.Size lhs, Camera.Size rhs) { 388 if (lhs.width < rhs.width) return -1; 389 if (lhs.width > rhs.width) return 1; 390 if (lhs.height < rhs.height) return -1; 391 if (lhs.height > rhs.height) return 1; 392 return 0; 393 } 394 } 395 396 SizeCompare s = new SizeCompare(); 397 TreeSet<Camera.Size> sortedResolutions = new TreeSet<Camera.Size>(s); 398 sortedResolutions.addAll(unsortedSizes); 399 400 mPreviewSizes = new ArrayList<Camera.Size>(sortedResolutions); 401 402 String[] availableSizeNames = new String[mPreviewSizes.size()]; 403 for (int i = 0; i < mPreviewSizes.size(); i++) { 404 availableSizeNames[i] = 405 Integer.toString(mPreviewSizes.get(i).width) + " x " + 406 Integer.toString(mPreviewSizes.get(i).height); 407 } 408 mResolutionSpinner.setAdapter( 409 new ArrayAdapter<String>( 410 this, R.layout.camera_list_item, availableSizeNames)); 411 412 // Get preview formats, removing duplicates 413 414 HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats()); 415 mPreviewFormats = new ArrayList<Integer>(formatSet); 416 417 String[] availableFormatNames = new String[mPreviewFormats.size()]; 418 for (int i = 0; i < mPreviewFormats.size(); i++) { 419 availableFormatNames[i] = 420 mPreviewFormatNames.get(mPreviewFormats.get(i)); 421 } 422 mFormatSpinner.setAdapter( 423 new ArrayAdapter<String>( 424 this, R.layout.camera_list_item, availableFormatNames)); 425 426 // Update untested entries 427 428 for (int resolutionIndex = 0; resolutionIndex < mPreviewSizes.size(); resolutionIndex++) { 429 for (int formatIndex = 0; formatIndex < mPreviewFormats.size(); formatIndex++) { 430 CameraCombination combination = new CameraCombination( 431 id, resolutionIndex, formatIndex, 432 mPreviewSizes.get(resolutionIndex).width, 433 mPreviewSizes.get(resolutionIndex).height, 434 mPreviewFormatNames.get(mPreviewFormats.get(formatIndex))); 435 436 if (!mTestedCombinations.contains(combination)) { 437 mUntestedCombinations.add(combination); 438 } 439 } 440 } 441 442 // Set initial values 443 444 mNextPreviewSize = mPreviewSizes.get(0); 445 mResolutionSpinner.setSelection(0); 446 447 mNextPreviewFormat = mPreviewFormats.get(0); 448 mFormatSpinner.setSelection(0); 449 450 451 // Set up correct display orientation 452 453 CameraInfo info = 454 new CameraInfo(); 455 Camera.getCameraInfo(id, info); 456 int rotation = getWindowManager().getDefaultDisplay().getRotation(); 457 int degrees = 0; 458 switch (rotation) { 459 case Surface.ROTATION_0: degrees = 0; break; 460 case Surface.ROTATION_90: degrees = 90; break; 461 case Surface.ROTATION_180: degrees = 180; break; 462 case Surface.ROTATION_270: degrees = 270; break; 463 } 464 465 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 466 mPreviewRotation = (info.orientation + degrees) % 360; 467 mPreviewRotation = (360 - mPreviewRotation) % 360; // compensate the mirror 468 } else { // back-facing 469 mPreviewRotation = (info.orientation - degrees + 360) % 360; 470 } 471 472 mCamera.setDisplayOrientation(mPreviewRotation); 473 474 // Start up preview if display is ready 475 476 if (mPreviewTexture != null) { 477 startPreview(); 478 } 479 480 } 481 shutdownCamera()482 private void shutdownCamera() { 483 if (mCamera != null) { 484 mCamera.setPreviewCallback(null); 485 mCamera.stopPreview(); 486 mCamera.release(); 487 mCamera = null; 488 mState = STATE_OFF; 489 } 490 } 491 492 /** 493 * Rotate and scale the matrix to be applied to the preview or format view, such that no 494 * stretching of the image occurs. To achieve this, the image is centered in the SurfaceTexture 495 * with black bars filling the excess space. 496 */ concatPreviewTransform(Matrix transform)497 private void concatPreviewTransform(Matrix transform) { 498 float widthRatio = mNextPreviewSize.width / (float) mPreviewTexWidth; 499 float heightRatio = mNextPreviewSize.height / (float) mPreviewTexHeight; 500 float scaledWidth = (float) mPreviewTexWidth; 501 float scaledHeight = (float) mPreviewTexHeight; 502 503 if (heightRatio < widthRatio) { 504 scaledHeight = mPreviewTexHeight * (heightRatio / widthRatio); 505 transform.postScale(1, heightRatio / widthRatio); 506 transform.postTranslate(0, 507 mPreviewTexHeight * (1 - heightRatio / widthRatio) / 2); 508 } else { 509 scaledWidth = mPreviewTexWidth * (widthRatio / heightRatio); 510 transform.postScale(widthRatio / heightRatio, 1); 511 transform.postTranslate(mPreviewTexWidth * (1 - widthRatio / heightRatio) / 2, 0); 512 } 513 514 if (mPreviewRotation == 90 || mPreviewRotation == 270) { 515 float scaledAspect = scaledWidth / scaledHeight; 516 float previewAspect = (float) mNextPreviewSize.width / (float) mNextPreviewSize.height; 517 transform.postScale(1.0f, scaledAspect * previewAspect, 518 (float) mPreviewTexWidth / 2, (float) mPreviewTexHeight / 2); 519 } 520 } 521 startPreview()522 private void startPreview() { 523 if (mState != STATE_OFF) { 524 // Stop for a while to drain callbacks 525 mCamera.setPreviewCallback(null); 526 mCamera.stopPreview(); 527 mState = STATE_OFF; 528 Handler h = new Handler(); 529 Runnable mDelayedPreview = new Runnable() { 530 public void run() { 531 startPreview(); 532 } 533 }; 534 h.postDelayed(mDelayedPreview, 300); 535 return; 536 } 537 mState = STATE_PREVIEW; 538 539 Matrix transform = new Matrix(); 540 concatPreviewTransform(transform); 541 mPreviewView.setTransform(transform); 542 543 mPreviewFormat = mNextPreviewFormat; 544 mPreviewSize = mNextPreviewSize; 545 546 Camera.Parameters p = mCamera.getParameters(); 547 p.setPreviewFormat(mPreviewFormat); 548 p.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 549 mCamera.setParameters(p); 550 551 mCamera.setPreviewCallback(this); 552 switch (mPreviewFormat) { 553 case ImageFormat.NV16: 554 case ImageFormat.NV21: 555 case ImageFormat.YUY2: 556 case ImageFormat.YV12: 557 mFormatView.setColorFilter(mYuv2RgbFilter); 558 break; 559 default: 560 mFormatView.setColorFilter(null); 561 break; 562 } 563 564 // Filter out currently untestable formats 565 switch (mPreviewFormat) { 566 case ImageFormat.NV16: 567 case ImageFormat.RGB_565: 568 case ImageFormat.UNKNOWN: 569 case ImageFormat.JPEG: 570 AlertDialog.Builder builder = 571 new AlertDialog.Builder(CameraFormatsActivity.this); 572 builder.setMessage("Unsupported format " + 573 mPreviewFormatNames.get(mPreviewFormat) + 574 "; consider this combination as pass. ") 575 .setTitle("Missing test" ) 576 .setNeutralButton("Back", null); 577 builder.show(); 578 mState = STATE_NO_CALLBACKS; 579 mCamera.setPreviewCallback(null); 580 break; 581 default: 582 // supported 583 break; 584 } 585 586 mProcessingFirstFrame = true; 587 try { 588 mCamera.setPreviewTexture(mPreviewTexture); 589 mCamera.startPreview(); 590 } catch (IOException ioe) { 591 // Something bad happened 592 Log.e(TAG, "Unable to start up preview"); 593 } 594 } 595 596 private class ProcessPreviewDataTask extends AsyncTask<byte[], Void, Boolean> { doInBackground(byte[]... datas)597 protected Boolean doInBackground(byte[]... datas) { 598 byte[] data = datas[0]; 599 try { 600 if (mRgbData == null || 601 mPreviewSize.width != mRgbWidth || 602 mPreviewSize.height != mRgbHeight) { 603 604 mRgbData = new int[mPreviewSize.width * mPreviewSize.height * 4]; 605 mRgbWidth = mPreviewSize.width; 606 mRgbHeight = mPreviewSize.height; 607 } 608 switch(mPreviewFormat) { 609 case ImageFormat.NV21: 610 convertFromNV21(data, mRgbData); 611 break; 612 case ImageFormat.YV12: 613 convertFromYV12(data, mRgbData); 614 break; 615 case ImageFormat.YUY2: 616 convertFromYUY2(data, mRgbData); 617 break; 618 case ImageFormat.NV16: 619 case ImageFormat.RGB_565: 620 case ImageFormat.UNKNOWN: 621 case ImageFormat.JPEG: 622 default: 623 convertFromUnknown(data, mRgbData); 624 break; 625 } 626 627 if (mCallbackBitmap == null || 628 mRgbWidth != mCallbackBitmap.getWidth() || 629 mRgbHeight != mCallbackBitmap.getHeight() ) { 630 mCallbackBitmap = 631 Bitmap.createBitmap( 632 mRgbWidth, mRgbHeight, 633 Bitmap.Config.ARGB_8888); 634 } 635 mCallbackBitmap.setPixels(mRgbData, 0, mRgbWidth, 636 0, 0, mRgbWidth, mRgbHeight); 637 } catch (OutOfMemoryError o) { 638 Log.e(TAG, "Out of memory trying to process preview data"); 639 return false; 640 } 641 return true; 642 } 643 onPostExecute(Boolean result)644 protected void onPostExecute(Boolean result) { 645 if (result) { 646 mFormatView.setImageBitmap(mCallbackBitmap); 647 648 CameraInfo info = new CameraInfo(); 649 Camera.getCameraInfo(mCurrentCameraId, info); 650 651 int rotation = mPreviewRotation; 652 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 653 rotation = (360 - rotation) % 360; // de-compensate the mirror 654 } 655 656 if (rotation != 0 && rotation != 180) { 657 Matrix transform = new Matrix(); 658 mFormatView.setScaleType(ImageView.ScaleType.MATRIX); 659 Rect viewRect = mFormatView.getDrawable().getBounds(); 660 transform.postTranslate(-viewRect.width() / 2, -viewRect.height() / 2); 661 transform.postRotate(rotation); 662 transform.postTranslate(viewRect.height() / 2, viewRect.width() / 2); 663 transform.postScale( 664 mPreviewView.getMeasuredWidth() / (float) viewRect.height(), 665 mPreviewView.getMeasuredHeight() / (float) viewRect.width()); 666 concatPreviewTransform(transform); 667 mFormatView.setImageMatrix(transform); 668 } else { 669 mFormatView.setScaleType(ImageView.ScaleType.FIT_CENTER); 670 } 671 672 if (mProcessingFirstFrame) { 673 mProcessingFirstFrame = false; 674 675 CameraCombination combination = new CameraCombination( 676 mCurrentCameraId, 677 mResolutionSpinner.getSelectedItemPosition(), 678 mFormatSpinner.getSelectedItemPosition(), 679 mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).width, 680 mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).height, 681 mPreviewFormatNames.get( 682 mPreviewFormats.get(mFormatSpinner.getSelectedItemPosition()))); 683 684 mUntestedCombinations.remove(combination); 685 mTestedCombinations.add(combination); 686 687 displayToast(combination.toString()); 688 689 if (mTestedCombinations.size() == mAllCombinationsSize) { 690 setPassButtonEnabled(true); 691 } 692 } 693 } 694 mProcessInProgress = false; 695 } 696 697 } 698 setPassButtonEnabled(boolean enabled)699 private void setPassButtonEnabled(boolean enabled) { 700 ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button); 701 pass_button.setEnabled(enabled); 702 } 703 calcAllCombinationsSize()704 private int calcAllCombinationsSize() { 705 int allCombinationsSize = 0; 706 int numCameras = Camera.getNumberOfCameras(); 707 708 for (int i = 0; i<numCameras; i++) { 709 // must release a Camera object before a new Camera object is created 710 shutdownCamera(); 711 712 mCamera = Camera.open(i); 713 Camera.Parameters p = mCamera.getParameters(); 714 715 HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats()); 716 717 allCombinationsSize += 718 p.getSupportedPreviewSizes().size() * // resolutions 719 formatSet.size(); // unique formats 720 } 721 722 return allCombinationsSize; 723 } 724 displayToast(String combination)725 private void displayToast(String combination) { 726 Toast.makeText(this, "\"" + combination + "\"\n" + " has been tested.", Toast.LENGTH_SHORT) 727 .show(); 728 } 729 onPreviewFrame(byte[] data, Camera camera)730 public void onPreviewFrame(byte[] data, Camera camera) { 731 if (mProcessInProgress || mState != STATE_PREVIEW) return; 732 733 int expectedBytes; 734 switch (mPreviewFormat) { 735 case ImageFormat.YV12: 736 // YV12 may have stride != width. 737 int w = mPreviewSize.width; 738 int h = mPreviewSize.height; 739 int yStride = (int)Math.ceil(w / 16.0) * 16; 740 int uvStride = (int)Math.ceil(yStride / 2 / 16.0) * 16; 741 int ySize = yStride * h; 742 int uvSize = uvStride * h / 2; 743 expectedBytes = ySize + uvSize * 2; 744 break; 745 case ImageFormat.NV21: 746 case ImageFormat.YUY2: 747 default: 748 expectedBytes = mPreviewSize.width * mPreviewSize.height * 749 ImageFormat.getBitsPerPixel(mPreviewFormat) / 8; 750 break; 751 } 752 if (expectedBytes != data.length) { 753 AlertDialog.Builder builder = 754 new AlertDialog.Builder(CameraFormatsActivity.this); 755 builder.setMessage("Mismatched size of buffer! Expected " + 756 expectedBytes + ", but got " + 757 data.length + " bytes instead!") 758 .setTitle("Error trying to use format " 759 + mPreviewFormatNames.get(mPreviewFormat)) 760 .setNeutralButton("Back", null); 761 762 builder.show(); 763 764 mState = STATE_NO_CALLBACKS; 765 mCamera.setPreviewCallback(null); 766 return; 767 } 768 769 mProcessInProgress = true; 770 new ProcessPreviewDataTask().execute(data); 771 } 772 convertFromUnknown(byte[] data, int[] rgbData)773 private void convertFromUnknown(byte[] data, int[] rgbData) { 774 int w = mPreviewSize.width; 775 int h = mPreviewSize.height; 776 // RGBA output 777 int rgbInc = 1; 778 if (mPreviewRotation == 180) { 779 rgbInc = -1; 780 } 781 int index = 0; 782 for (int y = 0; y < h; y++) { 783 int rgbIndex = y * w; 784 if (mPreviewRotation == 180) { 785 rgbIndex = w * (h - y) - 1; 786 } 787 for (int x = 0; x < mPreviewSize.width/3; x++) { 788 int r = data[index + 0] & 0xFF; 789 int g = data[index + 1] & 0xFF; 790 int b = data[index + 2] & 0xFF; 791 rgbData[rgbIndex] = Color.rgb(r,g,b); 792 rgbIndex += rgbInc; 793 index += 3; 794 } 795 } 796 } 797 798 // NV21 is a semi-planar 4:2:0 format, in the order YVU, which means we have: 799 // a W x H-size 1-byte-per-pixel Y plane, then 800 // a W/2 x H/2-size 2-byte-per-pixel plane, where each pixel has V then U. convertFromNV21(byte[] data, int rgbData[])801 private void convertFromNV21(byte[] data, int rgbData[]) { 802 int w = mPreviewSize.width; 803 int h = mPreviewSize.height; 804 // RGBA output 805 int rgbIndex = 0; 806 int rgbInc = 1; 807 if (mPreviewRotation == 180) { 808 rgbIndex = h * w - 1; 809 rgbInc = -1; 810 } 811 int yIndex = 0; 812 int uvRowIndex = w*h; 813 int uvRowInc = 0; 814 for (int y = 0; y < h; y++) { 815 int uvInc = 0; 816 int vIndex = uvRowIndex; 817 int uIndex = uvRowIndex + 1; 818 819 uvRowIndex += uvRowInc * w; 820 uvRowInc = (uvRowInc + 1) & 0x1; 821 822 for (int x = 0; x < w; x++) { 823 int yv = data[yIndex] & 0xFF; 824 int uv = data[uIndex] & 0xFF; 825 int vv = data[vIndex] & 0xFF; 826 rgbData[rgbIndex] = 827 Color.rgb(yv, uv, vv); 828 829 rgbIndex += rgbInc; 830 yIndex += 1; 831 uIndex += uvInc; 832 vIndex += uvInc; 833 uvInc = (uvInc + 2) & 0x2; 834 } 835 } 836 } 837 838 // YV12 is a planar 4:2:0 format, in the order YVU, which means we have: 839 // a W x H-size 1-byte-per-pixel Y plane, then 840 // a W/2 x H/2-size 1-byte-per-pixel V plane, then 841 // a W/2 x H/2-size 1-byte-per-pixel U plane 842 // The stride may not be equal to width, since it has to be a multiple of 843 // 16 pixels for both the Y and UV planes. convertFromYV12(byte[] data, int rgbData[])844 private void convertFromYV12(byte[] data, int rgbData[]) { 845 int w = mPreviewSize.width; 846 int h = mPreviewSize.height; 847 // RGBA output 848 int rgbIndex = 0; 849 int rgbInc = 1; 850 if (mPreviewRotation == 180) { 851 rgbIndex = h * w - 1; 852 rgbInc = -1; 853 } 854 855 int yStride = (int)Math.ceil(w / 16.0) * 16; 856 int uvStride = (int)Math.ceil(yStride/2/16.0) * 16; 857 int ySize = yStride * h; 858 int uvSize = uvStride * h / 2; 859 860 int uRowIndex = ySize + uvSize; 861 int vRowIndex = ySize; 862 863 int uv_w = w/2; 864 for (int y = 0; y < h; y++) { 865 int yIndex = yStride * y; 866 int uIndex = uRowIndex; 867 int vIndex = vRowIndex; 868 869 if ( (y & 0x1) == 1) { 870 uRowIndex += uvStride; 871 vRowIndex += uvStride; 872 } 873 874 int uv = 0, vv = 0; 875 for (int x = 0; x < w; x++) { 876 if ( (x & 0x1) == 0) { 877 uv = data[uIndex] & 0xFF; 878 vv = data[vIndex] & 0xFF; 879 uIndex++; 880 vIndex++; 881 } 882 int yv = data[yIndex] & 0xFF; 883 rgbData[rgbIndex] = 884 Color.rgb(yv, uv, vv); 885 886 rgbIndex += rgbInc; 887 yIndex += 1; 888 } 889 } 890 } 891 892 // YUY2 is an interleaved 4:2:2 format: YU,YV,YU,YV convertFromYUY2(byte[] data, int[] rgbData)893 private void convertFromYUY2(byte[] data, int[] rgbData) { 894 int w = mPreviewSize.width; 895 int h = mPreviewSize.height; 896 // RGBA output 897 int yIndex = 0; 898 int uIndex = 1; 899 int vIndex = 3; 900 int rgbIndex = 0; 901 int rgbInc = 1; 902 if (mPreviewRotation == 180) { 903 rgbIndex = h * w - 1; 904 rgbInc = -1; 905 } 906 907 for (int y = 0; y < h; y++) { 908 for (int x = 0; x < w; x++) { 909 int yv = data[yIndex] & 0xFF; 910 int uv = data[uIndex] & 0xFF; 911 int vv = data[vIndex] & 0xFF; 912 rgbData[rgbIndex] = Color.rgb(yv,uv,vv); 913 rgbIndex += rgbInc; 914 yIndex += 2; 915 if ( (x & 0x1) == 1 ) { 916 uIndex += 4; 917 vIndex += 4; 918 } 919 } 920 } 921 } 922 923 } 924