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