Android HTTP Camera Live Preview Tutorial
The Android SDK includes a Camera class that is used to set image capture settings, start/stop preview, snap pictures, and retrieve frames for encoding for video. It is a handy abstraction of the real hardware device. However, the SDK doesn't provide any camera emulation, something that makes testing camera enabled applications quite difficult. In this tutorial, I am going to show you how to use a web camera in order to obtain a live camera preview.
What we are essentially going to do is setup a web camera to publish static images in a predefined URL and then grab these images from our application and present them as motion pictures. Note that this tutorial was inspired by a post named “Live Camera Previews in Android”.
Let's start by creating an Eclipse project under the name “AndroidHttpCameraProject” and an Activity named “MyCamAppActivity”. As a first step, I am going to show you how to use the SDK camera. You can find many resources on how to manipulate the camera, one of them being the CameraPreview class that is included in the SDK's samples. In short, we are going to extend the SurfaceView class and provide our implementation using the device's camera. Then, we use the setContentView method of our Activity in order to set the activity content to that specific View. Let's see what the two classes look like:
MyCamAppActivity
package com.javacodegeeks.android.camera;
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceView;
import android.view.Window;
public class MyCamAppActivity extends Activity {
private SurfaceView cameraPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
cameraPreview = new CameraPreview(this);
setContentView(cameraPreview);
}
}
CameraPreview
package com.javacodegeeks.android.camera;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private Camera camera;
public CameraPreview(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceChanged(SurfaceHolder holder2, int format, int w, int h) {
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(w, h);
camera.setParameters(parameters);
camera.startPreview();
}
@Override
public void surfaceCreated(SurfaceHolder holder1) {
try {
camera = Camera.open();
camera.setPreviewDisplay(holder1);
}
catch (Exception e) {
Log.i("Exception surfaceCreated()", "e=" + e);
camera.release();
camera = null;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
camera.stopPreview();
camera.release();
camera = null;
}
}
In our “CameraPreview” class, we implement the SurfaceHolder.Callback interface so that we receive information about changes to the underlying surface. The methods that have to be implemented are the following:
* surfaceCreated: Called immediately after the surface is first created.
* surfaceChanged: Called immediately after any structural changes (format or size) have been made to the surface.
* surfaceDestroyed: Called immediately before a surface is being destroyed.
To use the Camera class, no constructor is available, but we rather use the open method which creates a new Camera object to access the first back-facing camera on the device. Then, we use the setPreviewDisplay method to set the Surface to be used for the live preview. Whenever a change occurs, we call the startPreview method in order to start capturing and drawing preview frames to the screen, after we have set the appropriate preview size (setPreviewSize) at the Camera.Parameters.
If we now launch the Eclipse configuration, we will come across a totally not-helpful image, as follows:
Now we are going to replace that surface view with a custom one that use static images from a web camera. First, let's see the changes to our activity. The new version is the following:
MyCamAppActivity
package com.javacodegeeks.android.camera;
import android.app.Activity;
import android.os.Bundle;
import android.view.Display;
import android.view.SurfaceView;
import android.view.Window;
public class MyCamAppActivity extends Activity {
private int viewWidth;
private int viewHeight;
private SurfaceView cameraPreview;
private static final boolean useHttpCamera = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
calculateDisplayDimensions();
if (useHttpCamera) {
cameraPreview = new HttpCameraPreview(this, viewWidth, viewHeight);
}
else {
cameraPreview = new CameraPreview(this);
}
setContentView(cameraPreview);
}
private void calculateDisplayDimensions() {
Display display = getWindowManager().getDefaultDisplay();
viewWidth = display.getWidth();
viewHeight = display.getHeight();
}
}
We have added a boolean variable to define which version we want to use, the one with the “real” camera or the one that uses the web cam. The latter, uses a new class named “HttpCameraPreview” which also extends the SurfaceView class. This one requires to know the width and the height of the enclosed view, thus we use the WindowManager class to get the default Display and from that we calculate the dimensions.
Here is what the new class looks like:
HttpCameraPreview
package com.javacodegeeks.android.camera;
import android.content.Context;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class HttpCameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String url = "http://10.0.2.2:8080";
private CanvasThread canvasThread;
private SurfaceHolder holder;
private HttpCamera camera;
private int viewWidth;
private int viewHeight;
public HttpCameraPreview(Context context, int viewWidth, int viewHeight) {
super(context);
holder = getHolder();
holder.addCallback(this);
holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
this.viewWidth = viewWidth;
this.viewHeight = viewHeight;
canvasThread = new CanvasThread();
}
@Override
public void surfaceChanged(SurfaceHolder holder2, int format, int w, int h) {
try {
Canvas c = holder.lockCanvas(null);
camera.captureAndDraw(c);
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
catch (Exception e) {
Log.e(getClass().getSimpleName(), "Error when surface changed", e);
camera = null;
}
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
try {
camera = new HttpCamera(url, viewWidth, viewHeight, true);
canvasThread.setRunning(true);
canvasThread.start();
}
catch (Exception e) {
Log.e(getClass().getSimpleName(), "Error while creating surface", e);
camera = null;
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
camera = null;
boolean retry = true;
canvasThread.setRunning(false);
while (retry) {
try {
canvasThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
private class CanvasThread extends Thread {
private boolean running;
public void setRunning(boolean running){
this.running = running;
}
public void run() {
while (running) {
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
camera.captureAndDraw(c);
}
}
catch (Exception e) {
Log.e(getClass().getSimpleName(), "Error while drawing canvas", e);
}
finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
}
}
}
In the constructor, we start a thread that will be responsible for updating the Canvas of the surface view, which we will describe in a while. We implement again the SurfaceHolder.Callback interface and the three methods describer above. Note that a custom “HttpCamera” object is used, which captures the still images and draws them to the Canvas. The lockCanvas method of the SurfaceHolder is used in order draw into the surface's bitmap and the unlockCanvasAndPost method is invoked after the drawing has completed. The “HttpCamera” object uses a URL which is where the images get published. Note that the IP address is the 10.0.2.2, which actually is the machine hosting the Android emulator. We can not use the classic localhost address (127.0.0.1), since this points to the emulator itself. In the thread class, we have a loop in which we continuously draw on the underlying canvas. We use a boolean variable as a flag to indicate whether the process should continue or not.
Let's now see what this “HttpCamera” class is all about:
HttpCamera
package com.javacodegeeks.android.camera;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
public class HttpCamera {
private static final int CONNECT_TIMEOUT = 1000;
private static final int SOCKET_TIMEOUT = 1000;
private final String url;
private final Rect bounds;
private final boolean preserveAspectRatio;
private final Paint paint = new Paint();
public HttpCamera(String url, int width, int height, boolean preserveAspectRatio) {
this.url = url;
bounds = new Rect(0, 0, width, height);
this.preserveAspectRatio = preserveAspectRatio;
paint.setFilterBitmap(true);
paint.setAntiAlias(true);
}
private Bitmap retrieveBitmap() throws IOException {
Bitmap bitmap = null;
InputStream in = null;
int response = -1;
try {
URL url = new URL(this.url);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setConnectTimeout(CONNECT_TIMEOUT);
httpConn.setReadTimeout(SOCKET_TIMEOUT);
httpConn.setRequestMethod("GET");
httpConn.connect();
response = httpConn.getResponseCode();
if (response == HttpURLConnection.HTTP_OK) {
in = httpConn.getInputStream();
bitmap = BitmapFactory.decodeStream(in);
}
return bitmap;
}
catch (Exception e) {
return null;
}
finally {
if (in != null) try {
in.close();
} catch (IOException e) {
/* ignore */
}
}
}
public boolean captureAndDraw(Canvas canvas) throws IOException {
Bitmap bitmap = retrieveBitmap();
if (bitmap == null) throw new IOException("Response Code: ");
//render it to canvas, scaling if necessary
if (bounds.right == bitmap.getWidth() && bounds.bottom == bitmap.getHeight()) {
canvas.drawBitmap(bitmap, 0, 0, null);
} else {
Rect dest;
if (preserveAspectRatio) {
dest = new Rect(bounds);
dest.bottom = bitmap.getHeight() * bounds.right / bitmap.getWidth();
dest.offset(0, (bounds.bottom - dest.bottom)/2);
}
else {
dest = bounds;
}
canvas.drawBitmap(bitmap, null, dest, paint);
}
return true;
}
}
In the constructor, we pass the URL to where the images from the camera should be found. This could be any web camera HTTP server. Additionally, we pass the view's dimensions and whether the aspect ratio should be preserved (this should be true in most cases). In the “captureAndDraw” method, which is the public method invoked by the custom surface class, we perform HTTP GET requests to the provided URL and retrieve the images as Bitmap objects. We then adjust the dimensions of the image accordingly and draw it on the Canvas using the drawBitmap method.
Let's now see the manifest file:
AndroidManifest
package="com.javacodegeeks.android.camera"
android:versionCode="1"
android:versionName="1.0">
android:label="@string/app_name">
The usual stuff here, just remember to add permissions for using the camera (android.permission.CAMERA) and creating internet connections (android.permission.INTERNET).
Before we test the application, we need to have images published on a web server. Some web cameras already provide that functionality, but in case yours does not, you can accomplish that using a pretty cool application named WebCam2000. Unfortunately, this is only for Windows machines. After you execute the program, it should automatically find your camera. If not, play a little with the options and make sure that in the “Video” menu, the “Microsoft WDM Image Capture” option is enabled. Then, make sure the “Enable Web Server” box is ticked and check that the server is working by pointing your browser to the corresponding URL:
http://localhost:8080/
If you now launch the Eclipse configuration, you will get a camera input to your emulator with the images periodically updated.
You can use this approach in order to provide a “mock camera” to your application and make it easier to test it. The Eclipse project created for this tutorial can be found here.
Enjoy and don't forget to share!
Related Articles :
* “Android Full Application Tutorial” series
* Android Location Based Services Application – GPS location
* Android Reverse Geocoding with Yahoo API - PlaceFinder
* Boost your Android XML parsing with XML Pull
* Android Text-To-Speech Application
* Install Android OS on your PC with VirtualBox