diff --git a/samples/android/CMakeLists.txt b/samples/android/CMakeLists.txt
index bf96bdf5a..097e02d8c 100644
--- a/samples/android/CMakeLists.txt
+++ b/samples/android/CMakeLists.txt
@@ -17,6 +17,8 @@ add_subdirectory(tutorial-2-opencvcamera)
add_subdirectory(tutorial-3-native)
add_subdirectory(tutorial-4-mixed)
+add_subdirectory(camera-preview)
+
#hello-android sample
if(HAVE_opencv_highgui)
ocv_include_modules_recurse(opencv_highgui opencv_core)
diff --git a/samples/android/camera-preview/.classpath b/samples/android/camera-preview/.classpath
new file mode 100644
index 000000000..a4763d1ee
--- /dev/null
+++ b/samples/android/camera-preview/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/samples/android/camera-preview/.project b/samples/android/camera-preview/.project
new file mode 100644
index 000000000..d8e9953e8
--- /dev/null
+++ b/samples/android/camera-preview/.project
@@ -0,0 +1,33 @@
+
+
+ CameraWriter
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/samples/android/camera-preview/.settings/org.eclipse.jdt.core.prefs b/samples/android/camera-preview/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..53e049515
--- /dev/null
+++ b/samples/android/camera-preview/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+#Wed Jun 29 04:36:40 MSD 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/samples/android/camera-preview/AndroidManifest.xml b/samples/android/camera-preview/AndroidManifest.xml
new file mode 100644
index 000000000..8be9e9485
--- /dev/null
+++ b/samples/android/camera-preview/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/android/camera-preview/CMakeLists.txt b/samples/android/camera-preview/CMakeLists.txt
new file mode 100644
index 000000000..b04c24a37
--- /dev/null
+++ b/samples/android/camera-preview/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(sample example-camera-preview)
+
+add_android_project(${sample} "${CMAKE_CURRENT_SOURCE_DIR}" LIBRARY_DEPS ${OpenCV_BINARY_DIR} SDK_TARGET 11 ${ANDROID_SDK_TARGET})
+if(TARGET ${sample})
+ add_dependencies(opencv_android_examples ${sample})
+endif()
diff --git a/samples/android/camera-preview/res/drawable/ic_action_search.png b/samples/android/camera-preview/res/drawable/ic_action_search.png
new file mode 100644
index 000000000..67de12dec
Binary files /dev/null and b/samples/android/camera-preview/res/drawable/ic_action_search.png differ
diff --git a/samples/android/camera-preview/res/drawable/ic_launcher.png b/samples/android/camera-preview/res/drawable/ic_launcher.png
new file mode 100644
index 000000000..a301d5795
Binary files /dev/null and b/samples/android/camera-preview/res/drawable/ic_launcher.png differ
diff --git a/samples/android/camera-preview/res/layout/activity_camera_writer.xml b/samples/android/camera-preview/res/layout/activity_camera_writer.xml
new file mode 100644
index 000000000..2e27d58ad
--- /dev/null
+++ b/samples/android/camera-preview/res/layout/activity_camera_writer.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/samples/android/camera-preview/res/menu/activity_camera_writer.xml b/samples/android/camera-preview/res/menu/activity_camera_writer.xml
new file mode 100644
index 000000000..cfc10fd52
--- /dev/null
+++ b/samples/android/camera-preview/res/menu/activity_camera_writer.xml
@@ -0,0 +1,6 @@
+
diff --git a/samples/android/camera-preview/res/values-v11/styles.xml b/samples/android/camera-preview/res/values-v11/styles.xml
new file mode 100644
index 000000000..d408cbc37
--- /dev/null
+++ b/samples/android/camera-preview/res/values-v11/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/android/camera-preview/res/values/strings.xml b/samples/android/camera-preview/res/values/strings.xml
new file mode 100644
index 000000000..52c8f4b56
--- /dev/null
+++ b/samples/android/camera-preview/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ CameraWriter
+ Settings
+ CameraWriterActivity
+
+
\ No newline at end of file
diff --git a/samples/android/camera-preview/res/values/styles.xml b/samples/android/camera-preview/res/values/styles.xml
new file mode 100644
index 000000000..4dba0d0a4
--- /dev/null
+++ b/samples/android/camera-preview/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/android/camera-preview/src/org/opencv/test/camerawriter/CameraWriterActivity.java b/samples/android/camera-preview/src/org/opencv/test/camerawriter/CameraWriterActivity.java
new file mode 100644
index 000000000..4f9027db4
--- /dev/null
+++ b/samples/android/camera-preview/src/org/opencv/test/camerawriter/CameraWriterActivity.java
@@ -0,0 +1,72 @@
+package org.opencv.test.camerawriter;
+
+import org.opencv.android.BaseLoaderCallback;
+import org.opencv.android.LoaderCallbackInterface;
+import org.opencv.android.OpenCVLoader;
+import org.opencv.core.Mat;
+import org.opencv.test.camerawriter.OpenCvCameraBridgeViewBase.CvCameraViewListener;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.util.Log;
+import android.view.Menu;
+
+public class CameraWriterActivity extends Activity implements CvCameraViewListener {
+
+ protected static final String TAG = "CameraWriterActivity";
+
+
+ private OpenCvCameraBridgeViewBase mCameraView;
+
+
+ private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
+ @Override
+ public void onManagerConnected(int status) {
+ switch (status) {
+ case LoaderCallbackInterface.SUCCESS:
+ Log.i(TAG, "OpenCV loaded successfully");
+ // Create and set View
+ mCameraView.setMaxFrameSize(640, 480);
+ mCameraView.enableView();
+ break;
+ default:
+ super.onManagerConnected(status);
+ break;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_camera_writer);
+
+ mCameraView = (OpenCvCameraBridgeViewBase)findViewById(R.id.camera_surface_view);
+ mCameraView.setCvCameraViewListener(this);
+ OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this, mLoaderCallback);
+
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_camera_writer, menu);
+ return true;
+ }
+
+ @Override
+ public void onCameraViewStarted(int width, int height) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onCameraViewStopped() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Mat onCameraFrame(Mat inputFrame) {
+ return inputFrame;
+ }
+}
diff --git a/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvCameraBridgeViewBase.java b/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvCameraBridgeViewBase.java
new file mode 100644
index 000000000..0395cbd6e
--- /dev/null
+++ b/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvCameraBridgeViewBase.java
@@ -0,0 +1,301 @@
+package org.opencv.test.camerawriter;
+
+import java.util.List;
+
+import org.opencv.android.Utils;
+import org.opencv.core.Mat;
+import org.opencv.core.Size;
+import org.opencv.highgui.Highgui;
+import org.opencv.highgui.VideoCapture;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * This is a basic class, implementing the interaction with Camera and OpenCV library.
+ * The main responsibility of it - is to control when camera can be enabled, process the frame,
+ * call external listener to make any adjustments to the frame and then draw the resulting
+ * frame to the screen.
+ * The clients shall implement CvCameraViewListener
+ */
+public abstract class OpenCvCameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
+
+ private static final int MAX_UNSPECIFIED = -1;
+
+ protected int mFrameWidth;
+ protected int mFrameHeight;
+
+ protected int mMaxHeight;
+ protected int mMaxWidth;
+
+ private Bitmap mCacheBitmap;
+
+
+ public OpenCvCameraBridgeViewBase(Context context, AttributeSet attrs) {
+ super(context,attrs);
+ getHolder().addCallback(this);
+ mMaxWidth = MAX_UNSPECIFIED;
+ mMaxHeight = MAX_UNSPECIFIED;
+
+ }
+
+ public interface CvCameraViewListener {
+ /**
+ * This method is invoked when camera preview has started. After this method is invoked
+ * the frames will start to be delivered to client via the onCameraFrame() callback.
+ * @param width - the width of the frames that will be delivered
+ * @param height - the height of the frames that will be delivered
+ */
+ public void onCameraViewStarted(int width, int height);
+
+ /**
+ * This method is invoked when camera preview has been stopped for some reason.
+ * No frames will be delivered via onCameraFrame() callback after this method is called.
+ */
+ public void onCameraViewStopped();
+
+ /**
+ * This method is invoked when delivery of the frame needs to be done.
+ * The returned values - is a modified frame which needs to be displayed on the screen.
+ */
+ public Mat onCameraFrame(Mat inputFrame);
+
+ }
+
+
+ private static final int STOPPED = 0;
+ private static final int STARTED = 1;
+ private static final String TAG = "SampleCvBase";
+
+ private CvCameraViewListener mListener;
+ private int mState = STOPPED;
+
+ private boolean mEnabled;
+ private boolean mSurfaceExist;
+
+
+
+ private Object mSyncObject = new Object();
+
+ public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
+ synchronized(mSyncObject) {
+ if (!mSurfaceExist) {
+ mSurfaceExist = true;
+ checkCurrentState();
+ } else {
+ /** Surface changed. We need to stop camera and restart with new parameters */
+ /* Pretend that old surface has been destroyed */
+ mSurfaceExist = false;
+ checkCurrentState();
+ /* Now use new surface. Say we have it now */
+ mSurfaceExist = true;
+ checkCurrentState();
+ }
+ }
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ /* Do nothing. Wait until surfaceChanged delivered */
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ synchronized(mSyncObject) {
+ mSurfaceExist = false;
+ checkCurrentState();
+ }
+ }
+
+
+ /**
+ * This method is provided for clients, so they can enable the camera connection.
+ * The actuall onCameraViewStarted callback will be delivered only after both this method is called and surface is available
+ */
+ public void enableView() {
+ synchronized(mSyncObject) {
+ mEnabled = true;
+ checkCurrentState();
+ }
+ }
+
+ /**
+ * This method is provided for clients, so they can disable camera connection and stop
+ * the delivery of frames eventhough the surfaceview itself is not destroyed and still stays on the scren
+ */
+ public void disableView() {
+ synchronized(mSyncObject) {
+ mEnabled = false;
+ checkCurrentState();
+ }
+ }
+
+
+ public void setCvCameraViewListener(CvCameraViewListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * This method sets the maximum size that camera frame is allowed to be. When selecting
+ * size - the biggest size which less or equal the size set will be selected.
+ * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
+ * preview frame will be selected with 176x152 size.
+ * This method is usefull when need to restrict the size of preview frame for some reason (for example for video recording)
+ * @param maxWidth - the maximum width allowed for camera frame.
+ * @param maxHeight - the maxumum height allowed for camera frame
+ */
+ public void setMaxFrameSize(int maxWidth, int maxHeight) {
+ mMaxWidth = maxWidth;
+ mMaxHeight = maxHeight;
+ }
+
+ /**
+ * Called when mSyncObject lock is held
+ */
+ private void checkCurrentState() {
+ int targetState;
+
+ if (mEnabled && mSurfaceExist) {
+ targetState = STARTED;
+ } else {
+ targetState = STOPPED;
+ }
+
+ if (targetState != mState) {
+ /* The state change detected. Need to exit the current state and enter target state */
+ processExitState(mState);
+ mState = targetState;
+ processEnterState(mState);
+ }
+ }
+
+ private void processEnterState(int state) {
+ switch(state) {
+ case STARTED:
+ onEnterStartedState();
+ if (mListener != null) {
+ mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
+ }
+ break;
+ case STOPPED:
+ onEnterStoppedState();
+ if (mListener != null) {
+ mListener.onCameraViewStopped();
+ }
+ break;
+ };
+ }
+
+
+ private void processExitState(int state) {
+ switch(state) {
+ case STARTED:
+ onExitStartedState();
+ break;
+ case STOPPED:
+ onExitStoppedState();
+ break;
+ };
+ }
+
+ private void onEnterStoppedState() {
+ /* nothing to do */
+ }
+
+ private void onExitStoppedState() {
+ /* nothing to do */
+ }
+
+ private void onEnterStartedState() {
+
+ connectCamera(getWidth(), getHeight());
+ /* Now create cahe Bitmap */
+ mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
+
+ }
+
+ private void onExitStartedState() {
+
+ disconnectCamera();
+ if (mCacheBitmap != null) {
+ mCacheBitmap.recycle();
+ }
+ }
+
+
+ /**
+ * This method shall be called by the subclasses when they have valid
+ * object and want it to be delivered to external client (via callback) and
+ * then displayed on the screen.
+ * @param frame - the current frame to be delivered
+ */
+ protected void deliverAndDrawFrame(Mat frame) {
+ Mat modified;
+
+ synchronized(mSyncObject) {
+ if (mListener != null) {
+ modified = mListener.onCameraFrame(frame);
+ } else {
+ modified = frame;
+ }
+
+ if (modified != null) {
+ Utils.matToBitmap(modified, mCacheBitmap);
+ }
+
+ if (mCacheBitmap != null) {
+ Canvas canvas = getHolder().lockCanvas();
+ if (canvas != null) {
+ canvas.drawBitmap(mCacheBitmap, (canvas.getWidth() - mCacheBitmap.getWidth()) / 2, (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, null);
+ getHolder().unlockCanvasAndPost(canvas);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is invoked shall perform concrete operation to initialize the camera.
+ * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
+ * initialized with the size of the Camera frames that will be delivered to external processor.
+ * @param width - the width of this SurfaceView
+ * @param height - the height of this SurfaceView
+ */
+ protected abstract void connectCamera(int width, int height);
+
+ /**
+ * Disconnects and release the particular camera object beeing connected to this surface view.
+ * Called when syncObject lock is held
+ */
+ protected abstract void disconnectCamera();
+
+
+ /**
+ * This helper method can be called by subclasses to select camera preview size.
+ * It goes over the list of the supported preview sizes and selects the maximum one which
+ * fits both values set via setMaxFrameSize() and surface frame allocated for this view
+ * @param supportedSizes
+ * @param surfaceWidth
+ * @param surfaceHeight
+ * @return
+ */
+ protected Size calculateCameraFrameSize(List supportedSizes, int surfaceWidth, int surfaceHeight) {
+ int calcWidth = 0;
+ int calcHeight = 0;
+
+ int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
+ int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
+
+ for (Size size : supportedSizes) {
+ if (size.width <= maxAllowedWidth && size.height <= maxAllowedHeight) {
+ if (size.width >= calcWidth && size.height >= calcHeight) {
+ calcWidth = (int) size.width;
+ calcHeight = (int) size.height;
+ }
+ }
+ }
+ return new Size(calcWidth, calcHeight);
+ }
+}
diff --git a/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvNativeCameraView.java b/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvNativeCameraView.java
new file mode 100644
index 000000000..e7df36df4
--- /dev/null
+++ b/samples/android/camera-preview/src/org/opencv/test/camerawriter/OpenCvNativeCameraView.java
@@ -0,0 +1,126 @@
+package org.opencv.test.camerawriter;
+
+import org.opencv.android.Utils;
+import org.opencv.core.Mat;
+import org.opencv.core.Size;
+import org.opencv.highgui.Highgui;
+import org.opencv.highgui.VideoCapture;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * This class is an implementation of a bridge between SurfaceView and native OpenCV camera.
+ * Due to the big amount of work done, by the base class this child is only responsible
+ * for creating camera, destroying camera and delivering frames while camera is enabled
+ */
+public class OpenCvNativeCameraView extends OpenCvCameraBridgeViewBase {
+
+ public static final String TAG = "OpenCvNativeCameraView";
+ private boolean mStopThread;
+ private Thread mThread;
+ private VideoCapture mCamera;
+
+
+ public OpenCvNativeCameraView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+
+ @Override
+ protected void connectCamera(int width, int height) {
+
+ /* 1. We need to instantiate camera
+ * 2. We need to start thread which will be getting frames
+ */
+ /* First step - initialize camera connection */
+ initializeCamera(getWidth(), getHeight());
+
+ /* now we can start update thread */
+ mThread = new Thread(new CameraWorker(getWidth(), getHeight()));
+ mThread.start();
+ }
+
+ @Override
+ protected void disconnectCamera() {
+ /* 1. We need to stop thread which updating the frames
+ * 2. Stop camera and release it
+ */
+ try {
+ mStopThread = true;
+ mThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ mThread = null;
+ mStopThread = false;
+ }
+
+ /* Now release camera */
+ releaseCamera();
+
+ }
+
+ private void initializeCamera(int width, int height) {
+ mCamera = new VideoCapture(Highgui.CV_CAP_ANDROID);
+ //TODO: improve error handling
+
+ java.util.List sizes = mCamera.getSupportedPreviewSizes();
+
+ /* Select the size that fits surface considering maximum size allowed */
+ Size frameSize = calculateCameraFrameSize(sizes, width, height);
+
+
+ double frameWidth = frameSize.width;
+ double frameHeight = frameSize.height;
+
+
+ mCamera.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, frameWidth);
+ mCamera.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, frameHeight);
+
+ mFrameWidth = (int)frameWidth;
+ mFrameHeight = (int)frameHeight;
+
+ Log.i(TAG, "Selected camera frame size = (" + mFrameWidth + ", " + mFrameHeight + ")");
+ }
+
+ private void releaseCamera() {
+ if (mCamera != null) {
+ mCamera.release();
+ }
+ }
+
+ private class CameraWorker implements Runnable {
+
+ private Mat mRgba = new Mat();
+ private int mWidth;
+ private int mHeight;
+
+ CameraWorker(int w, int h) {
+ mWidth = w;
+ mHeight = h;
+ }
+
+ @Override
+ public void run() {
+ Mat modified;
+
+
+ do {
+ if (!mCamera.grab()) {
+ Log.e(TAG, "Camera frame grab failed");
+ break;
+ }
+ mCamera.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
+
+ deliverAndDrawFrame(mRgba);
+
+ } while (!mStopThread);
+
+ }
+ }
+
+}