Home | History | Annotate | Download | only in formats
      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
    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
    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
    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 
    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
    207     public void onResume() {
    208         super.onResume();
    209 
    210         setUpCamera(mCameraSpinner.getSelectedItemPosition());
    211     }
    212 
    213     @Override
    214     public void onPause() {
    215         super.onPause();
    216 
    217         shutdownCamera();
    218         mPreviewTexture = null;
    219     }
    220 
    221     @Override
    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 
    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 
    253     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    254         // Ignored, Camera does all the work for us
    255     }
    256 
    257     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    258         return true;
    259     }
    260 
    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 
    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 
    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 
    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> {
    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 
    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 
    593     private void setPassButtonEnabled(boolean enabled) {
    594         ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button);
    595         pass_button.setEnabled(enabled);
    596     }
    597 
    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 
    619     private void displayToast(String combination) {
    620         Toast.makeText(this, "\"" + combination + "\"\n" + " has been tested.", Toast.LENGTH_LONG).show();
    621     }
    622 
    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 
    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.
    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.
    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
    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