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