Android HTTP Camera Live Preview Tutorial
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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 | 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 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <? xml version = "1.0" encoding = "utf-8" ?> package = "com.javacodegeeks.android.camera" android:versionCode = "1" android:versionName = "1.0" > < application android:icon = "@drawable/icon" android:label = "@string/app_name" > < activity android:name = ".MyCamAppActivity" android:label = "@string/app_name" > < intent-filter > < action android:name = "android.intent.action.MAIN" /> < category android:name = "android.intent.category.LAUNCHER" /> </ intent-filter > </ activity > </ application > < uses-sdk android:minSdkVersion = "3" /> < uses-permission android:name = "android.permission.CAMERA" /> < uses-permission android:name = "android.permission.INTERNET" /> </ manifest > |
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:
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!
hi,
how do i edit the application such that i can view the webcam live view from my android phone?
please email me at keithwb@gmail.com. thanks!
this application not running
Actually this project is running but is displaying blank screen and i am confused how it get images from a web cam without running an application on local machine. I don’t think this will get images just providing ip and port no.
Actually this project is running but is displaying blank screen
Actually this project is running in mobile device but displaying blank screen and following exception occurred.
exception is Error while drawing canvas=’HttpCamera.captureAndDraw(HttpCamera.java:73)’,
‘HttpCameraPreview$CanvasThread.run(HttpCameraPreview.java:92)’.
Error when surface changed =’HttpCamera.captureAndDraw(HttpCamera.java:73)’,HttpCameraPreview.surfaceChanged(HttpCameraPreview.java:52)’
please help me???
pls do not try , this will not work… you will just waist you time like me …