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