1// Copyright 2010 Google Inc.
2// All Rights Reserved.
3
4package com.example.android.videochatcameratest;
5
6import android.app.Activity;
7import android.graphics.ImageFormat;
8import android.hardware.Camera;
9import android.hardware.Camera.Size;
10import android.os.AsyncTask;
11import android.os.Bundle;
12import android.util.Log;
13import android.view.Surface;
14import android.view.TextureView;
15import android.view.View;
16import android.view.View.OnClickListener;
17import android.view.Window;
18import android.view.WindowManager;
19import android.widget.Button;
20import android.widget.CheckBox;
21import android.widget.FrameLayout;
22import android.widget.TextView;
23
24import java.io.IOException;
25import java.lang.UnsupportedOperationException;
26import java.util.ArrayList;
27import java.util.List;
28
29public class VideoChatTestActivity extends Activity {
30
31    static final private int NUM_CAMERA_PREVIEW_BUFFERS = 2;
32    static final boolean sRunningOnHoneycomb;
33    static final private String TAG = "VideoChatTest";
34    TextView mTextStatusHistory;
35    static {
36        sRunningOnHoneycomb =
37                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB;
38    }
39
40    public VideoChatTestActivity() {
41    }
42
43    /** Called with the activity is first created. */
44    @Override
45    public void onCreate(Bundle savedInstanceState) {
46        super.onCreate(savedInstanceState);
47
48        Window window = getWindow();
49        window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
50                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
51
52        // Inflate our UI from its XML layout description.
53        setContentView(R.layout.videochatcameratest_activity);
54
55        FrameLayout fl = (FrameLayout)findViewById(R.id.previewFrame);
56
57        if (sRunningOnHoneycomb) {
58            fl.addView(new SurfaceTextureView(this));
59        } else {
60            fl.addView(new CameraPreviewView(this));
61        }
62
63        ((Button) findViewById(R.id.gobutton)).setOnClickListener(mGoListener);
64
65        ((TextView)findViewById(R.id.statushistory)).setVerticalScrollBarEnabled(true);
66        mTextStatusHistory = (TextView) findViewById(R.id.statushistory);
67
68        logMessage("Display Orientation " + getDisplayOrientation());
69        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
70            dumpCameraCaps(i);
71        }
72    }
73
74    private void logMessage(String message) {
75        Log.v(TAG, message);
76        mTextStatusHistory.append(message + "\r\n");
77    }
78
79    public int getCameraOrientation(int id) {
80        Camera.CameraInfo info =
81            new Camera.CameraInfo();
82        Camera.getCameraInfo(id, info);
83        return info.orientation;
84    }
85
86    private void dumpCameraCaps(int id) {
87        Camera cam = Camera.open(id);
88        Camera.Parameters params = cam.getParameters();
89        List<Integer> formats = params.getSupportedPreviewFormats();
90        List<int[]> frameRates = params.getSupportedPreviewFpsRange();
91        List<Camera.Size> sizes = params.getSupportedPreviewSizes();
92        logMessage("Camera " + id);
93        logMessage("Orientation " + getCameraOrientation(id));
94        logMessage("Sizes");
95        for (Size size : sizes) {
96            logMessage(size.width + "x" + size.height);
97        }
98        logMessage("frameRates");
99        for (int[] rates : frameRates) {
100            logMessage(rates[0] + "-" + rates[1]);
101        }
102        logMessage("formats");
103        for (Integer format : formats) {
104            logMessage(format.toString());
105        }
106        cam.release();
107    }
108    /**
109     * Called when the activity is about to start interacting with the user.
110     */
111    @Override
112    protected void onResume() {
113        super.onResume();
114    }
115
116    private int getDisplayOrientation() {
117        int rotation = getWindowManager().getDefaultDisplay().getRotation();
118        int degrees = 0;
119        switch (rotation) {
120            case Surface.ROTATION_0:
121                degrees = 0;
122                break;
123            case Surface.ROTATION_90:
124                degrees = 90;
125                break;
126            case Surface.ROTATION_180:
127                degrees = 180;
128                break;
129            case Surface.ROTATION_270:
130                degrees = 270;
131                break;
132        }
133        return degrees;
134    }
135
136    /**
137     * A call-back for when the user presses the back button.
138     */
139    OnClickListener mGoListener = new OnClickListener() {
140        @Override
141        public void onClick(View v) {
142            int degrees = getDisplayOrientation();
143            new CameraTestRunner().execute(new Integer[] { degrees });
144        }
145    };
146
147    private class CameraTestRunner extends AsyncTask<Integer, String, Void> {
148
149        TextView mTextStatus;
150        private int mDisplayOrientation;
151        private volatile boolean mClearStatusOnNextUpdate;
152
153        @Override
154        protected Void doInBackground(Integer... params) {
155            mDisplayOrientation = params[0];
156            mTextStatus = (TextView) findViewById(R.id.status);
157            boolean testFrontCamera =
158                    ((CheckBox) findViewById(R.id.frontcameracheckbox)).isChecked();
159            boolean testBackCamera = ((CheckBox) findViewById(R.id.backcameracheckbox)).isChecked();
160            boolean testQVGA = ((CheckBox) findViewById(R.id.qvgacheckbox)).isChecked();
161            boolean testVGA = ((CheckBox) findViewById(R.id.vgacheckbox)).isChecked();
162            boolean test15fps = ((CheckBox) findViewById(R.id.fps15checkbox)).isChecked();
163            boolean test30fps = ((CheckBox) findViewById(R.id.fps30checkbox)).isChecked();
164            boolean testRotate0 = ((CheckBox) findViewById(R.id.rotate0checkbox)).isChecked();
165            boolean testRotate90 = ((CheckBox) findViewById(R.id.rotate90checkbox)).isChecked();
166            boolean testRotate180 = ((CheckBox) findViewById(R.id.rotate180checkbox)).isChecked();
167            boolean testRotate270 = ((CheckBox) findViewById(R.id.rotate270checkbox)).isChecked();
168            boolean testRotateAuto = ((CheckBox) findViewById(R.id.rotateautocheckbox)).isChecked();
169
170            ArrayList<Integer> setDisplayOrentationAngles = new ArrayList<Integer>();
171
172            if (testRotate0) {
173                setDisplayOrentationAngles.add(0);
174            }
175            if (testRotate90) {
176                setDisplayOrentationAngles.add(90);
177            }
178            if (testRotate180) {
179                setDisplayOrentationAngles.add(180);
180            }
181            if (testRotate270) {
182                setDisplayOrentationAngles.add(270);
183            }
184            if (testRotateAuto) {
185                setDisplayOrentationAngles.add(-1);
186            }
187
188            final int widths[] = new int[] {320, 640};
189            final int heights[] = new int[] {240, 480};
190
191            final int framerates[] = new int[] {15, 30};
192
193            ArrayList<Integer> whichCameras = new ArrayList<Integer>();
194            int numCameras = Camera.getNumberOfCameras();
195            if (testFrontCamera) {
196                for (int i = 0; i < numCameras; i++) {
197                    Camera.CameraInfo info = new Camera.CameraInfo();
198                    Camera.getCameraInfo(i, info);
199                    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
200                        whichCameras.add(i);
201                        break;
202                    }
203                }
204            }
205            if (testBackCamera) {
206                for (int i = 0; i < numCameras; i++) {
207                    Camera.CameraInfo info = new Camera.CameraInfo();
208                    Camera.getCameraInfo(i, info);
209                    if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
210                        whichCameras.add(i);
211                        break;
212                    }
213                }
214            }
215            do {
216                mClearStatusOnNextUpdate = true;
217                for (Integer whichCamera : whichCameras) {
218                    for (int whichResolution = 0; whichResolution < 2; whichResolution++) {
219                        if (whichResolution == 0 && !testQVGA) {
220                            continue;
221                        }
222                        if (whichResolution == 1 && !testVGA) {
223                            continue;
224                        }
225
226                        for (int whichFramerate = 0; whichFramerate < 2; whichFramerate++) {
227                            if (whichFramerate == 0 && !test15fps) {
228                                continue;
229                            }
230                            if (whichFramerate == 1 && !test30fps) {
231                                continue;
232                            }
233
234                            TestCamera(whichCamera, widths[whichResolution],
235                                    heights[whichResolution], framerates[whichFramerate],
236                                    setDisplayOrentationAngles);
237                        }
238                    }
239                }
240            } while (((CheckBox) findViewById(R.id.repeatcheckbox)).isChecked());
241            // start tests
242
243            return null;
244        }
245
246        @Override
247        protected void onPostExecute(Void result) {
248            final String allDoneString = "Test complete";
249            Log.v(TAG, allDoneString);
250            mTextStatus.setText(allDoneString);
251            mTextStatusHistory.append(allDoneString + "\r\n");
252        }
253
254
255        private class FrameCatcher implements Camera.PreviewCallback {
256            public int mFrames = 0;
257            private final int mExpectedSize;
258            public FrameCatcher(int width, int height) {
259                mExpectedSize = width * height * 3 / 2;
260            }
261
262            @Override
263            public void onPreviewFrame(byte[] data, Camera camera) {
264                if (mExpectedSize != data.length) {
265                    throw new UnsupportedOperationException("bad size, got " + data.length + " expected " + mExpectedSize);
266                }
267                mFrames++;
268                camera.addCallbackBuffer(data);
269            }
270
271        }
272
273        private void setupCallback(Camera camera, FrameCatcher catcher, int bufferSize) {
274            camera.setPreviewCallbackWithBuffer(null);
275            camera.setPreviewCallbackWithBuffer(catcher);
276            for (int i = 0; i < NUM_CAMERA_PREVIEW_BUFFERS; i++) {
277                byte [] cameraBuffer = new byte[bufferSize];
278                camera.addCallbackBuffer(cameraBuffer);
279            }
280        }
281
282        private int getAutoDisplayOrientation(int displayOrientationDegrees,
283                int cameraId, android.hardware.Camera camera) {
284            android.hardware.Camera.CameraInfo info =
285                    new android.hardware.Camera.CameraInfo();
286            android.hardware.Camera.getCameraInfo(cameraId, info);
287
288            int result;
289            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
290                result = (info.orientation + displayOrientationDegrees) % 360;
291                result = (360 - result) % 360; // compensate the mirror
292            } else { // back-facing
293                result = (info.orientation - displayOrientationDegrees + 360) % 360;
294            }
295            return result;
296        }
297
298        protected void TestCamera(int whichCamera,
299                int width, int height,
300                int frameRate,
301                List<Integer> setDisplayOrentationAngles) {
302            String baseStatus = "Camera id " + whichCamera + " " +
303                width + "x" + height + " " +
304                frameRate + "fps";
305            publishProgress("Initializing " + baseStatus);
306            String status = "";
307            boolean succeeded = true;
308            Log.v(TAG, "Start test -- id " + whichCamera + " " + width + "x" + height +
309                    " " + frameRate + "fps");
310            Camera camera;
311            FrameLayout previewBlock = (FrameLayout)findViewById(R.id.previewFrame);
312            SurfaceTextureView surfaceTextureView = null;
313            CameraPreviewView previewView = null;
314            if (sRunningOnHoneycomb) {
315                surfaceTextureView = (SurfaceTextureView)previewBlock.getChildAt(0);
316            } else {
317                previewView = (CameraPreviewView)previewBlock.getChildAt(0);
318            }
319
320            camera = Camera.open(whichCamera);
321            publishProgress("Opened " + baseStatus);
322            try {
323                try {
324                    if (sRunningOnHoneycomb) {
325                        camera.setPreviewTexture(surfaceTextureView.getSurfaceTexture());
326                    } else {
327                        camera.setPreviewDisplay(previewView.mHolder);
328                    }
329                } catch (IOException exception) {
330                    succeeded = false;
331                    status = exception.toString();
332                    return;
333                }
334
335                camera.setPreviewCallbackWithBuffer(null);
336                Camera.Parameters parameters = camera.getParameters();
337
338                publishProgress("Changing preview parameters " + width + "x" + height + baseStatus);
339
340                parameters.setPreviewSize(width, height);
341                parameters.setPreviewFormat(ImageFormat.NV21);
342
343                parameters.setPreviewFrameRate(frameRate);
344                camera.setParameters(parameters);
345
346                publishProgress("Validating preview parameters " + baseStatus);
347
348                parameters = camera.getParameters();
349                Size setSize = parameters.getPreviewSize();
350                if (setSize.width != width || setSize.height != height) {
351                    status += "Bad reported size, wanted " + width + "x" + height + ", got " +
352                    setSize.width + "x" + setSize.height;
353                    succeeded = false;
354                }
355
356                if (parameters.getPreviewFrameRate() != frameRate) {
357                    status += "Bad reported frame rate, wanted " + frameRate
358                    + ", got " + parameters.getPreviewFrameRate();
359                    succeeded = false;
360                }
361
362                publishProgress("Initializing callback buffers " + baseStatus);
363                int imageFormat = parameters.getPreviewFormat();
364                if (imageFormat != ImageFormat.NV21) {
365                    status = "Bad reported image format, wanted NV21 (" + ImageFormat.NV21 +
366                            ") got " + imageFormat;
367                    succeeded = false;
368                    throw new UnsupportedOperationException(status);
369                }
370                int bufferSize;
371                bufferSize = setSize.width * setSize.height
372                                * ImageFormat.getBitsPerPixel(imageFormat) / 8;
373                int sizeWeShouldHave = (width * height * 3 / 2);
374                if (bufferSize != sizeWeShouldHave) {
375                    status = "Bad calculate size. Should have been " + (width * height * 3 / 2) +
376                            " but got " + imageFormat;
377                    succeeded = false;
378                    throw new UnsupportedOperationException(status);
379                }
380
381                FrameCatcher catcher = new FrameCatcher(setSize.width, setSize.height);
382
383                if (succeeded) {
384                    publishProgress("Starting " + baseStatus);
385                } else {
386                    publishProgress("Starting " + baseStatus + " -- but " + status);
387                }
388
389                int numPasses;
390                boolean doSetDisplayOrientation;
391                if (setDisplayOrentationAngles == null || setDisplayOrentationAngles.size() == 0) {
392                    numPasses = 1;
393                    doSetDisplayOrientation = false;
394                } else {
395                    numPasses = setDisplayOrentationAngles.size();
396                    doSetDisplayOrientation = true;
397                }
398
399                for (int i = 0; i < numPasses; i++) {
400                    if (doSetDisplayOrientation) {
401                        int rotation = setDisplayOrentationAngles.get(i);
402                        if (rotation == -1) {
403                            rotation = getAutoDisplayOrientation(mDisplayOrientation,
404                                    whichCamera, camera);
405                        }
406                        publishProgress("setDisplayOrientation to " + rotation);
407                        try {
408                            camera.setDisplayOrientation(rotation);
409                        } catch (RuntimeException exception) {
410                            succeeded = false;
411                            status = exception.toString();
412                            return;
413                        }
414                    }
415                    if (sRunningOnHoneycomb) {
416                        surfaceTextureView.resetFrameCounter();
417                        surfaceTextureView.setCameraEnabled(true);
418                    } else {
419                        setupCallback(camera, catcher, bufferSize);
420                    }
421                    camera.startPreview();
422                    try {
423                        Thread.sleep(5000);
424                    } catch (InterruptedException exception) {
425                        succeeded = false;
426                        status = exception.toString();
427                        return;
428                    }
429                    if (sRunningOnHoneycomb) {
430                        surfaceTextureView.setCameraEnabled(false);
431                    } else {
432                        camera.setPreviewCallbackWithBuffer(null);
433                    }
434                    camera.stopPreview();
435                }
436
437                int frames;
438                if (sRunningOnHoneycomb) {
439                    frames = surfaceTextureView.getFrameCounter();
440                } else {
441                    frames = catcher.mFrames;
442                }
443                if (frames == 0) {
444                    succeeded = false;
445                    publishProgress("Preview callback received no frames from " + baseStatus);
446                } else {
447                    publishProgress("Preview callback got " + frames + " frames (~" +
448                            Math.round(((double)frames)/(5.0 * numPasses)) + "fps) " +
449                            baseStatus);
450                }
451                try {
452                    camera.setPreviewDisplay(null);
453                } catch (IOException exception) {
454                    succeeded = false;
455                    status = exception.toString();
456                    return;
457                }
458            } finally {
459                Log.v(TAG, "Releasing camera");
460
461                if (succeeded) {
462                    publishProgress("Success " + baseStatus);
463                } else {
464                    publishProgress("Finished " + baseStatus + " -- but " + status);
465                }
466
467                camera.release();
468            }
469        }
470
471        @Override
472        protected void onProgressUpdate(String... message) {
473            if (mClearStatusOnNextUpdate) {
474                mClearStatusOnNextUpdate = false;
475                mTextStatusHistory.setText("");
476            }
477            Log.v(TAG, message[0]);
478            mTextStatus.setText(message[0]);
479            mTextStatusHistory.append(message[0] + "\r\n");
480        }
481    }
482}
483