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 */
16package com.android.rs.livepreview;
17
18//import com.android.cts.verifier.PassFailButtons;
19//import com.android.cts.verifier.R;
20
21import android.app.Activity;
22import android.app.AlertDialog;
23import android.graphics.Bitmap;
24import android.graphics.Color;
25import android.graphics.ColorMatrix;
26import android.graphics.ColorMatrixColorFilter;
27import android.graphics.ImageFormat;
28import android.graphics.Matrix;
29import android.graphics.SurfaceTexture;
30import android.hardware.Camera;
31import android.os.AsyncTask;
32import android.os.Bundle;
33import android.os.Handler;
34import android.util.Log;
35import android.util.SparseArray;
36import android.view.View;
37import android.view.TextureView;
38import android.widget.AdapterView;
39import android.widget.ArrayAdapter;
40import android.widget.ImageView;
41import android.widget.Spinner;
42
43import java.io.IOException;
44import java.lang.InterruptedException;
45import java.lang.Math;
46import java.lang.Thread;
47import java.util.ArrayList;
48import java.util.Comparator;
49import java.util.List;
50import java.util.TreeSet;
51
52import android.renderscript.*;
53
54/**
55 * Tests for manual verification of the CDD-required camera output formats
56 * for preview callbacks
57 */
58public class CameraPreviewActivity extends Activity
59        implements TextureView.SurfaceTextureListener, Camera.PreviewCallback {
60
61    private static final String TAG = "CameraFormats";
62
63    private TextureView mPreviewView;
64    private SurfaceTexture mPreviewTexture;
65    private int mPreviewTexWidth;
66    private int mPreviewTexHeight;
67
68    private ImageView mFormatView;
69
70    private Spinner mCameraSpinner;
71    private Spinner mResolutionSpinner;
72
73    private int mCurrentCameraId = -1;
74    private Camera mCamera;
75
76    private List<Camera.Size> mPreviewSizes;
77    private Camera.Size mNextPreviewSize;
78    private Camera.Size mPreviewSize;
79
80    private Bitmap mCallbackBitmap;
81
82    private static final int STATE_OFF = 0;
83    private static final int STATE_PREVIEW = 1;
84    private static final int STATE_NO_CALLBACKS = 2;
85    private int mState = STATE_OFF;
86    private boolean mProcessInProgress = false;
87    private boolean mProcessingFirstFrame = false;
88
89
90    private RenderScript mRS;
91    private RsYuv mFilterYuv;
92
93    @Override
94    public void onCreate(Bundle savedInstanceState) {
95        super.onCreate(savedInstanceState);
96
97        setContentView(R.layout.cf_main);
98
99        mPreviewView = (TextureView) findViewById(R.id.preview_view);
100        mFormatView = (ImageView) findViewById(R.id.format_view);
101
102        mPreviewView.setSurfaceTextureListener(this);
103
104        int numCameras = Camera.getNumberOfCameras();
105        String[] cameraNames = new String[numCameras];
106        for (int i = 0; i < numCameras; i++) {
107            cameraNames[i] = "Camera " + i;
108        }
109        mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
110        mCameraSpinner.setAdapter(
111            new ArrayAdapter<String>(
112                this, R.layout.cf_format_list_item, cameraNames));
113        mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
114
115        mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
116        mResolutionSpinner.setOnItemSelectedListener(mResolutionSelectedListener);
117
118
119        mRS = RenderScript.create(this);
120    }
121
122    @Override
123    public void onResume() {
124        super.onResume();
125
126        setUpCamera(mCameraSpinner.getSelectedItemPosition());
127    }
128
129    @Override
130    public void onPause() {
131        super.onPause();
132
133        shutdownCamera();
134    }
135
136    public void onSurfaceTextureAvailable(SurfaceTexture surface,
137            int width, int height) {
138        mPreviewTexture = surface;
139        mPreviewTexWidth = width;
140        mPreviewTexHeight = height;
141        if (mCamera != null) {
142            startPreview();
143        }
144    }
145
146    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
147        // Ignored, Camera does all the work for us
148    }
149
150    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
151        return true;
152    }
153
154    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
155        // Invoked every time there's a new Camera preview frame
156    }
157
158    private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
159            new AdapterView.OnItemSelectedListener() {
160                public void onItemSelected(AdapterView<?> parent,
161                        View view, int pos, long id) {
162                    if (mCurrentCameraId != pos) {
163                        setUpCamera(pos);
164                    }
165                }
166
167                public void onNothingSelected(AdapterView parent) {
168
169                }
170
171            };
172
173    private AdapterView.OnItemSelectedListener mResolutionSelectedListener =
174            new AdapterView.OnItemSelectedListener() {
175                public void onItemSelected(AdapterView<?> parent,
176                        View view, int position, long id) {
177                    if (mPreviewSizes.get(position) != mPreviewSize) {
178                        mNextPreviewSize = mPreviewSizes.get(position);
179                        startPreview();
180                    }
181                }
182
183                public void onNothingSelected(AdapterView parent) {
184
185                }
186
187            };
188
189
190    private void setUpCamera(int id) {
191        shutdownCamera();
192
193        mCurrentCameraId = id;
194        mCamera = Camera.open(id);
195        Camera.Parameters p = mCamera.getParameters();
196
197        // Get preview resolutions
198
199        List<Camera.Size> unsortedSizes = p.getSupportedPreviewSizes();
200
201        class SizeCompare implements Comparator<Camera.Size> {
202            public int compare(Camera.Size lhs, Camera.Size rhs) {
203                if (lhs.width < rhs.width) return -1;
204                if (lhs.width > rhs.width) return 1;
205                if (lhs.height < rhs.height) return -1;
206                if (lhs.height > rhs.height) return 1;
207                return 0;
208            }
209        };
210
211        SizeCompare s = new SizeCompare();
212        TreeSet<Camera.Size> sortedResolutions = new TreeSet<Camera.Size>(s);
213        sortedResolutions.addAll(unsortedSizes);
214
215        mPreviewSizes = new ArrayList<Camera.Size>(sortedResolutions);
216
217        String[] availableSizeNames = new String[mPreviewSizes.size()];
218        for (int i = 0; i < mPreviewSizes.size(); i++) {
219            availableSizeNames[i] =
220                    Integer.toString(mPreviewSizes.get(i).width) + " x " +
221                    Integer.toString(mPreviewSizes.get(i).height);
222        }
223        mResolutionSpinner.setAdapter(
224            new ArrayAdapter<String>(
225                this, R.layout.cf_format_list_item, availableSizeNames));
226
227
228        // Set initial values
229
230        mNextPreviewSize = mPreviewSizes.get(0);
231        mResolutionSpinner.setSelection(0);
232
233        if (mPreviewTexture != null) {
234            startPreview();
235        }
236    }
237
238    private void shutdownCamera() {
239        if (mCamera != null) {
240            mCamera.setPreviewCallbackWithBuffer(null);
241            mCamera.stopPreview();
242            mCamera.release();
243            mCamera = null;
244            mState = STATE_OFF;
245        }
246    }
247
248    private void startPreview() {
249        if (mState != STATE_OFF) {
250            // Stop for a while to drain callbacks
251            mCamera.setPreviewCallbackWithBuffer(null);
252            mCamera.stopPreview();
253            mState = STATE_OFF;
254            Handler h = new Handler();
255            Runnable mDelayedPreview = new Runnable() {
256                public void run() {
257                    startPreview();
258                }
259            };
260            h.postDelayed(mDelayedPreview, 300);
261            return;
262        }
263        mState = STATE_PREVIEW;
264
265        Matrix transform = new Matrix();
266        float widthRatio = mNextPreviewSize.width / (float)mPreviewTexWidth;
267        float heightRatio = mNextPreviewSize.height / (float)mPreviewTexHeight;
268
269        transform.setScale(1, heightRatio/widthRatio);
270        transform.postTranslate(0,
271                mPreviewTexHeight * (1 - heightRatio/widthRatio)/2);
272
273        mPreviewView.setTransform(transform);
274
275        mPreviewSize   = mNextPreviewSize;
276
277        Camera.Parameters p = mCamera.getParameters();
278        p.setPreviewFormat(ImageFormat.NV21);
279        p.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
280        mCamera.setParameters(p);
281
282        mCamera.setPreviewCallbackWithBuffer(this);
283        int expectedBytes = mPreviewSize.width * mPreviewSize.height *
284                ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
285        for (int i=0; i < 4; i++) {
286            mCamera.addCallbackBuffer(new byte[expectedBytes]);
287        }
288        //mFormatView.setColorFilter(mYuv2RgbFilter);
289
290        mProcessingFirstFrame = true;
291        try {
292            mCamera.setPreviewTexture(mPreviewTexture);
293            mCamera.startPreview();
294        } catch (IOException ioe) {
295            // Something bad happened
296            Log.e(TAG, "Unable to start up preview");
297        }
298
299    }
300
301
302    private class ProcessPreviewDataTask extends AsyncTask<byte[], Void, Boolean> {
303        protected Boolean doInBackground(byte[]... datas) {
304            byte[] data = datas[0];
305
306            long t1 = java.lang.System.currentTimeMillis();
307
308            mFilterYuv.execute(data, mCallbackBitmap);
309
310            long t2 = java.lang.System.currentTimeMillis();
311            mTiming[mTimingSlot++] = t2 - t1;
312            if (mTimingSlot >= mTiming.length) {
313                float total = 0;
314                for (int i=0; i<mTiming.length; i++) {
315                    total += (float)mTiming[i];
316                }
317                total /= mTiming.length;
318                Log.e(TAG, "time + " + total);
319                mTimingSlot = 0;
320            }
321
322            mCamera.addCallbackBuffer(data);
323            mProcessInProgress = false;
324            return true;
325        }
326
327        protected void onPostExecute(Boolean result) {
328            mFormatView.invalidate();
329        }
330
331    }
332
333    private long mTiming[] = new long[50];
334    private int mTimingSlot = 0;
335
336    public void onPreviewFrame(byte[] data, Camera camera) {
337        if (mProcessInProgress || mState != STATE_PREVIEW) {
338            mCamera.addCallbackBuffer(data);
339            return;
340        }
341        if (data == null) {
342            return;
343        }
344
345        int expectedBytes = mPreviewSize.width * mPreviewSize.height *
346                ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
347
348        if (expectedBytes != data.length) {
349            Log.e(TAG, "Mismatched size of buffer! Expected ");
350
351            mState = STATE_NO_CALLBACKS;
352            mCamera.setPreviewCallbackWithBuffer(null);
353            return;
354        }
355
356        mProcessInProgress = true;
357
358        if (mCallbackBitmap == null ||
359                mPreviewSize.width != mCallbackBitmap.getWidth() ||
360                mPreviewSize.height != mCallbackBitmap.getHeight() ) {
361            mCallbackBitmap =
362                    Bitmap.createBitmap(
363                        mPreviewSize.width, mPreviewSize.height,
364                        Bitmap.Config.ARGB_8888);
365            mFilterYuv = new RsYuv(mRS, getResources(), mPreviewSize.width, mPreviewSize.height);
366            mFormatView.setImageBitmap(mCallbackBitmap);
367        }
368
369
370        mFormatView.invalidate();
371
372        mCamera.addCallbackBuffer(data);
373        mProcessInProgress = true;
374        new ProcessPreviewDataTask().execute(data);
375    }
376
377
378
379}