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 com.android.cts.verifier.PassFailButtons; 19 import com.android.cts.verifier.R; 20 21 import android.app.AlertDialog; 22 import android.graphics.Bitmap; 23 import android.graphics.Color; 24 import android.graphics.ColorMatrix; 25 import android.graphics.ColorMatrixColorFilter; 26 import android.graphics.ImageFormat; 27 import android.graphics.Matrix; 28 import android.graphics.SurfaceTexture; 29 import android.hardware.Camera; 30 import android.hardware.Camera.CameraInfo; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.util.Log; 35 import android.util.SparseArray; 36 import android.view.Menu; 37 import android.view.MenuItem; 38 import android.view.View; 39 import android.view.Surface; 40 import android.view.TextureView; 41 import android.widget.AdapterView; 42 import android.widget.ArrayAdapter; 43 import android.widget.Button; 44 import android.widget.ImageButton; 45 import android.widget.ImageView; 46 import android.widget.Spinner; 47 import android.widget.Toast; 48 49 import java.io.IOException; 50 import java.lang.Math; 51 import java.util.ArrayList; 52 import java.util.HashSet; 53 import java.util.Comparator; 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.cf_format_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.cf_format_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.cf_format_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 if (mPreviewRotation != 0 && mPreviewRotation != 180) { 472 Log.w(TAG, 473 "Display orientation correction is not 0 or 180, as expected!"); 474 } 475 476 mCamera.setDisplayOrientation(mPreviewRotation); 477 478 // Start up preview if display is ready 479 480 if (mPreviewTexture != null) { 481 startPreview(); 482 } 483 484 } 485 shutdownCamera()486 private void shutdownCamera() { 487 if (mCamera != null) { 488 mCamera.setPreviewCallback(null); 489 mCamera.stopPreview(); 490 mCamera.release(); 491 mCamera = null; 492 mState = STATE_OFF; 493 } 494 } 495 startPreview()496 private void startPreview() { 497 if (mState != STATE_OFF) { 498 // Stop for a while to drain callbacks 499 mCamera.setPreviewCallback(null); 500 mCamera.stopPreview(); 501 mState = STATE_OFF; 502 Handler h = new Handler(); 503 Runnable mDelayedPreview = new Runnable() { 504 public void run() { 505 startPreview(); 506 } 507 }; 508 h.postDelayed(mDelayedPreview, 300); 509 return; 510 } 511 mState = STATE_PREVIEW; 512 513 Matrix transform = new Matrix(); 514 float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth; 515 float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight; 516 517 if (heightRatio < widthRatio) { 518 transform.setScale(1, heightRatio/widthRatio); 519 transform.postTranslate(0, 520 mPreviewTexHeight * (1 - heightRatio/widthRatio)/2); 521 } else { 522 transform.setScale(widthRatio/heightRatio, 1); 523 transform.postTranslate(mPreviewTexWidth * (1 - widthRatio/heightRatio)/2, 524 0); 525 } 526 527 mPreviewView.setTransform(transform); 528 529 mPreviewFormat = mNextPreviewFormat; 530 mPreviewSize = mNextPreviewSize; 531 532 Camera.Parameters p = mCamera.getParameters(); 533 p.setPreviewFormat(mPreviewFormat); 534 p.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 535 mCamera.setParameters(p); 536 537 mCamera.setPreviewCallback(this); 538 switch (mPreviewFormat) { 539 case ImageFormat.NV16: 540 case ImageFormat.NV21: 541 case ImageFormat.YUY2: 542 case ImageFormat.YV12: 543 mFormatView.setColorFilter(mYuv2RgbFilter); 544 break; 545 default: 546 mFormatView.setColorFilter(null); 547 break; 548 } 549 550 // Filter out currently untestable formats 551 switch (mPreviewFormat) { 552 case ImageFormat.NV16: 553 case ImageFormat.RGB_565: 554 case ImageFormat.UNKNOWN: 555 case ImageFormat.JPEG: 556 AlertDialog.Builder builder = 557 new AlertDialog.Builder(CameraFormatsActivity.this); 558 builder.setMessage("Unsupported format " + 559 mPreviewFormatNames.get(mPreviewFormat) + 560 "; consider this combination as pass. ") 561 .setTitle("Missing test" ) 562 .setNeutralButton("Back", null); 563 builder.show(); 564 mState = STATE_NO_CALLBACKS; 565 mCamera.setPreviewCallback(null); 566 break; 567 default: 568 // supported 569 break; 570 } 571 572 mProcessingFirstFrame = true; 573 try { 574 mCamera.setPreviewTexture(mPreviewTexture); 575 mCamera.startPreview(); 576 } catch (IOException ioe) { 577 // Something bad happened 578 Log.e(TAG, "Unable to start up preview"); 579 } 580 } 581 582 private class ProcessPreviewDataTask extends AsyncTask<byte[], Void, Boolean> { doInBackground(byte[]... datas)583 protected Boolean doInBackground(byte[]... datas) { 584 byte[] data = datas[0]; 585 try { 586 if (mRgbData == null || 587 mPreviewSize.width != mRgbWidth || 588 mPreviewSize.height != mRgbHeight) { 589 590 mRgbData = new int[mPreviewSize.width * mPreviewSize.height * 4]; 591 mRgbWidth = mPreviewSize.width; 592 mRgbHeight = mPreviewSize.height; 593 } 594 switch(mPreviewFormat) { 595 case ImageFormat.NV21: 596 convertFromNV21(data, mRgbData); 597 break; 598 case ImageFormat.YV12: 599 convertFromYV12(data, mRgbData); 600 break; 601 case ImageFormat.YUY2: 602 convertFromYUY2(data, mRgbData); 603 break; 604 case ImageFormat.NV16: 605 case ImageFormat.RGB_565: 606 case ImageFormat.UNKNOWN: 607 case ImageFormat.JPEG: 608 default: 609 convertFromUnknown(data, mRgbData); 610 break; 611 } 612 613 if (mCallbackBitmap == null || 614 mRgbWidth != mCallbackBitmap.getWidth() || 615 mRgbHeight != mCallbackBitmap.getHeight() ) { 616 mCallbackBitmap = 617 Bitmap.createBitmap( 618 mRgbWidth, mRgbHeight, 619 Bitmap.Config.ARGB_8888); 620 } 621 mCallbackBitmap.setPixels(mRgbData, 0, mRgbWidth, 622 0, 0, mRgbWidth, mRgbHeight); 623 } catch (OutOfMemoryError o) { 624 Log.e(TAG, "Out of memory trying to process preview data"); 625 return false; 626 } 627 return true; 628 } 629 onPostExecute(Boolean result)630 protected void onPostExecute(Boolean result) { 631 if (result) { 632 mFormatView.setImageBitmap(mCallbackBitmap); 633 if (mProcessingFirstFrame) { 634 mProcessingFirstFrame = false; 635 636 CameraCombination combination = new CameraCombination( 637 mCurrentCameraId, 638 mResolutionSpinner.getSelectedItemPosition(), 639 mFormatSpinner.getSelectedItemPosition(), 640 mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).width, 641 mPreviewSizes.get(mResolutionSpinner.getSelectedItemPosition()).height, 642 mPreviewFormatNames.get( 643 mPreviewFormats.get(mFormatSpinner.getSelectedItemPosition()))); 644 645 mUntestedCombinations.remove(combination); 646 mTestedCombinations.add(combination); 647 648 displayToast(combination.toString()); 649 650 if (mTestedCombinations.size() == mAllCombinationsSize) { 651 setPassButtonEnabled(true); 652 } 653 } 654 } 655 mProcessInProgress = false; 656 } 657 658 } 659 setPassButtonEnabled(boolean enabled)660 private void setPassButtonEnabled(boolean enabled) { 661 ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button); 662 pass_button.setEnabled(enabled); 663 } 664 calcAllCombinationsSize()665 private int calcAllCombinationsSize() { 666 int allCombinationsSize = 0; 667 int numCameras = Camera.getNumberOfCameras(); 668 669 for (int i = 0; i<numCameras; i++) { 670 // must release a Camera object before a new Camera object is created 671 shutdownCamera(); 672 673 mCamera = Camera.open(i); 674 Camera.Parameters p = mCamera.getParameters(); 675 676 HashSet<Integer> formatSet = new HashSet<>(p.getSupportedPreviewFormats()); 677 678 allCombinationsSize += 679 p.getSupportedPreviewSizes().size() * // resolutions 680 formatSet.size(); // unique formats 681 } 682 683 return allCombinationsSize; 684 } 685 displayToast(String combination)686 private void displayToast(String combination) { 687 Toast.makeText(this, "\"" + combination + "\"\n" + " has been tested.", Toast.LENGTH_SHORT) 688 .show(); 689 } 690 onPreviewFrame(byte[] data, Camera camera)691 public void onPreviewFrame(byte[] data, Camera camera) { 692 if (mProcessInProgress || mState != STATE_PREVIEW) return; 693 694 int expectedBytes; 695 switch (mPreviewFormat) { 696 case ImageFormat.YV12: 697 // YV12 may have stride != width. 698 int w = mPreviewSize.width; 699 int h = mPreviewSize.height; 700 int yStride = (int)Math.ceil(w / 16.0) * 16; 701 int uvStride = (int)Math.ceil(yStride / 2 / 16.0) * 16; 702 int ySize = yStride * h; 703 int uvSize = uvStride * h / 2; 704 expectedBytes = ySize + uvSize * 2; 705 break; 706 case ImageFormat.NV21: 707 case ImageFormat.YUY2: 708 default: 709 expectedBytes = mPreviewSize.width * mPreviewSize.height * 710 ImageFormat.getBitsPerPixel(mPreviewFormat) / 8; 711 break; 712 } 713 if (expectedBytes != data.length) { 714 AlertDialog.Builder builder = 715 new AlertDialog.Builder(CameraFormatsActivity.this); 716 builder.setMessage("Mismatched size of buffer! Expected " + 717 expectedBytes + ", but got " + 718 data.length + " bytes instead!") 719 .setTitle("Error trying to use format " 720 + mPreviewFormatNames.get(mPreviewFormat)) 721 .setNeutralButton("Back", null); 722 723 builder.show(); 724 725 mState = STATE_NO_CALLBACKS; 726 mCamera.setPreviewCallback(null); 727 return; 728 } 729 730 mProcessInProgress = true; 731 new ProcessPreviewDataTask().execute(data); 732 } 733 convertFromUnknown(byte[] data, int[] rgbData)734 private void convertFromUnknown(byte[] data, int[] rgbData) { 735 int w = mPreviewSize.width; 736 int h = mPreviewSize.height; 737 // RGBA output 738 int rgbInc = 1; 739 if (mPreviewRotation == 180) { 740 rgbInc = -1; 741 } 742 int index = 0; 743 for (int y = 0; y < h; y++) { 744 int rgbIndex = y * w; 745 if (mPreviewRotation == 180) { 746 rgbIndex = w * (h - y) - 1; 747 } 748 for (int x = 0; x < mPreviewSize.width/3; x++) { 749 int r = data[index + 0] & 0xFF; 750 int g = data[index + 1] & 0xFF; 751 int b = data[index + 2] & 0xFF; 752 rgbData[rgbIndex] = Color.rgb(r,g,b); 753 rgbIndex += rgbInc; 754 index += 3; 755 } 756 } 757 } 758 759 // NV21 is a semi-planar 4:2:0 format, in the order YVU, which means we have: 760 // a W x H-size 1-byte-per-pixel Y plane, then 761 // a W/2 x H/2-size 2-byte-per-pixel plane, where each pixel has V then U. convertFromNV21(byte[] data, int rgbData[])762 private void convertFromNV21(byte[] data, int rgbData[]) { 763 int w = mPreviewSize.width; 764 int h = mPreviewSize.height; 765 // RGBA output 766 int rgbIndex = 0; 767 int rgbInc = 1; 768 if (mPreviewRotation == 180) { 769 rgbIndex = h * w - 1; 770 rgbInc = -1; 771 } 772 int yIndex = 0; 773 int uvRowIndex = w*h; 774 int uvRowInc = 0; 775 for (int y = 0; y < h; y++) { 776 int uvInc = 0; 777 int vIndex = uvRowIndex; 778 int uIndex = uvRowIndex + 1; 779 780 uvRowIndex += uvRowInc * w; 781 uvRowInc = (uvRowInc + 1) & 0x1; 782 783 for (int x = 0; x < w; x++) { 784 int yv = data[yIndex] & 0xFF; 785 int uv = data[uIndex] & 0xFF; 786 int vv = data[vIndex] & 0xFF; 787 rgbData[rgbIndex] = 788 Color.rgb(yv, uv, vv); 789 790 rgbIndex += rgbInc; 791 yIndex += 1; 792 uIndex += uvInc; 793 vIndex += uvInc; 794 uvInc = (uvInc + 2) & 0x2; 795 } 796 } 797 } 798 799 // YV12 is a planar 4:2:0 format, in the order YVU, which means we have: 800 // a W x H-size 1-byte-per-pixel Y plane, then 801 // a W/2 x H/2-size 1-byte-per-pixel V plane, then 802 // a W/2 x H/2-size 1-byte-per-pixel U plane 803 // The stride may not be equal to width, since it has to be a multiple of 804 // 16 pixels for both the Y and UV planes. convertFromYV12(byte[] data, int rgbData[])805 private void convertFromYV12(byte[] data, int rgbData[]) { 806 int w = mPreviewSize.width; 807 int h = mPreviewSize.height; 808 // RGBA output 809 int rgbIndex = 0; 810 int rgbInc = 1; 811 if (mPreviewRotation == 180) { 812 rgbIndex = h * w - 1; 813 rgbInc = -1; 814 } 815 816 int yStride = (int)Math.ceil(w / 16.0) * 16; 817 int uvStride = (int)Math.ceil(yStride/2/16.0) * 16; 818 int ySize = yStride * h; 819 int uvSize = uvStride * h / 2; 820 821 int uRowIndex = ySize + uvSize; 822 int vRowIndex = ySize; 823 824 int uv_w = w/2; 825 for (int y = 0; y < h; y++) { 826 int yIndex = yStride * y; 827 int uIndex = uRowIndex; 828 int vIndex = vRowIndex; 829 830 if ( (y & 0x1) == 1) { 831 uRowIndex += uvStride; 832 vRowIndex += uvStride; 833 } 834 835 int uv = 0, vv = 0; 836 for (int x = 0; x < w; x++) { 837 if ( (x & 0x1) == 0) { 838 uv = data[uIndex] & 0xFF; 839 vv = data[vIndex] & 0xFF; 840 uIndex++; 841 vIndex++; 842 } 843 int yv = data[yIndex] & 0xFF; 844 rgbData[rgbIndex] = 845 Color.rgb(yv, uv, vv); 846 847 rgbIndex += rgbInc; 848 yIndex += 1; 849 } 850 } 851 } 852 853 // YUY2 is an interleaved 4:2:2 format: YU,YV,YU,YV convertFromYUY2(byte[] data, int[] rgbData)854 private void convertFromYUY2(byte[] data, int[] rgbData) { 855 int w = mPreviewSize.width; 856 int h = mPreviewSize.height; 857 // RGBA output 858 int yIndex = 0; 859 int uIndex = 1; 860 int vIndex = 3; 861 int rgbIndex = 0; 862 int rgbInc = 1; 863 if (mPreviewRotation == 180) { 864 rgbIndex = h * w - 1; 865 rgbInc = -1; 866 } 867 868 for (int y = 0; y < h; y++) { 869 for (int x = 0; x < w; x++) { 870 int yv = data[yIndex] & 0xFF; 871 int uv = data[uIndex] & 0xFF; 872 int vv = data[vIndex] & 0xFF; 873 rgbData[rgbIndex] = Color.rgb(yv,uv,vv); 874 rgbIndex += rgbInc; 875 yIndex += 2; 876 if ( (x & 0x1) == 1 ) { 877 uIndex += 4; 878 vIndex += 4; 879 } 880 } 881 } 882 } 883 884 } 885