I've been looking into getting a live camera preview working in the Android emulator. Currently the Android emulator just gives a black and white chess board animation. After having a look around I found the web site of Tom Gibara who has done some great work to get a live preview working in the emulator. The link to his work can be found here. The basics are that you run the WebcamBroadcaster as a standard java app on your PC. If there are any video devices attached to you PC, it will pick them up and broadcast the frames captured over a socket connection. You then run a SocketCamera class as part of an app in the android emulator, and as long as you have the correct ip address and port it should display the captured images in the emulator. On looking into Tom's code I saw that it seemed to be written for an older version of the Android API so I thought I'd have a go at updating it. As a starting point I'm going to use the CameraPreview sample code available on the android developers website. My aim was to take this code and with as little changes as possible make it so it could be used to give a live camera preview in the emulator.
So the first thing I did was to create a new class called SocketCamera, this is based on Tom's version of the SocketCamera, but unlike Tom's version I am trying to implement a subset of the new camera class android.hardware.Camera and not the older class android.hardware.CameraDevice. Please keep in mind that I've implemented just a subset of the Camera class API. The code was put together fairly quickly and is a bit rough round the edges. Anyhow, here's my new SocketCamera class:
public void setParameters(Camera.Parameters parameters) { //Bit of a hack so the interface looks like that of Log.i(LOG_TAG, "Setting Socket Camera parameters"); parametersCamera.setParameters(parameters); Size size = parameters.getPreviewSize(); bounds = new Rect(0, 0, size.width, size.height); } public Camera.Parameters getParameters() { Log.i(LOG_TAG, "Getting Socket Camera parameters"); return parametersCamera.getParameters(); }
public void release() { Log.i(LOG_TAG, "Releasing Socket Camera parameters"); //TODO need to implement this function }
private class CameraCapture extends Thread {
private boolean capturing = false;
public boolean isCapturing() { return capturing; }
public void setCapturing(boolean capturing) { this.capturing = capturing; }
@Override public void run() { while (capturing) { Canvas c = null; try { c = surfaceHolder.lockCanvas(null); synchronized (surfaceHolder) { Socket socket = null; try { socket = new Socket(); socket.bind(null); socket.setSoTimeout(SOCKET_TIMEOUT); socket.connect(new InetSocketAddress(address, port), SOCKET_TIMEOUT);
//obtain the bitmap InputStream in = socket.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(in);
//render it to canvas, scaling if necessary if ( bounds.right == bitmap.getWidth() && bounds.bottom == bitmap.getHeight()) { c.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; } if (c != null) { c.drawBitmap(bitmap, null, dest, paint); } }
// do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { surfaceHolder.unlockCanvasAndPost(c); } } } Log.i(LOG_TAG, "Socket Camera capture stopped"); } }
}
Make sure that you change the ip address to that of your PC.
Now we just need to make a few small modifications to the original CameraPreview. In this class look for the Preview class that extends the SurfaceView. Now we just need to comments out three lines and replace them with our own:
// Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = getHolder(); mHolder.addCallback(this); //mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); } public void surfaceCreated(SurfaceHolder holder) { // The Surface has been created, acquire the camera and tell it where // to draw. //mCamera = Camera.open(); mCamera = SocketCamera.open(); try { mCamera.setPreviewDisplay(holder); } catch (IOException exception) { mCamera.release(); mCamera = null; // TODO: add more exception handling logic here } }
Here i've change three lines:
1. Camera mCamera is replaced with SocketCamera mCamera 2. mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); is replaced with mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); 3. mCamera = Camera.open(); is replaced with mCamera = SocketCamera.open();.
So that's it.Now just make sure WebcamBroadcaster is running and start up the CameraPreview app in the Android emulator, you should now be seeing live previews in the emulator. Here's a short video of my emulator with the live preview: (Yes i know, it's me waving a book around)
Note: if the WebcamBroadcaster is not picking up your devices you most probably have a classpath issue. Make sure that you classpath points to the jmf.jar that is in the same folder as the jmf.properties file. If JMstudio works ok, its very likely that you have a classpath issue.
Oh, one last thing. I also updated the WebCamBroadcaster so that it can be used with YUV format cameras, so here's the code for that as well:
/** * A disposable class that uses JMF to serve a still sequence captured from a * webcam over a socket connection. It doesn't use TCP, it just blindly * captures a still, JPEG compresses it, and pumps it out over any incoming * socket connection. * * @author Tom Gibara * */
public class WebcamBroadcaster {
public static boolean RAW = false;
private static Player createPlayer(int width, int height) { try { Vector<CaptureDeviceInfo> devices = CaptureDeviceManager.getDeviceList(null); for (CaptureDeviceInfo info : devices) { DataSource source; Format[] formats = info.getFormats(); for (Format format : formats) { if ((format instanceof RGBFormat)) { RGBFormat rgb = (RGBFormat) format; Dimension size = rgb.getSize(); if (size.width != width || size.height != height) continue; if (rgb.getPixelStride() != 3) continue; if (rgb.getBitsPerPixel() != 24) continue; if ( rgb.getLineStride() != width*3 ) continue; MediaLocator locator = info.getLocator(); source = Manager.createDataSource(locator); source.connect(); System.out.println("RGB Format Found"); ((CaptureDevice)source).getFormatControls()[0].setFormat(rgb); } else if ((format instanceof YUVFormat)) { YUVFormat yuv = (YUVFormat) format; Dimension size = yuv.getSize(); if (size.width != width || size.height != height) continue; MediaLocator locator = info.getLocator(); source = Manager.createDataSource(locator); source.connect(); System.out.println("YUV Format Found"); ((CaptureDevice)source).getFormatControls()[0].setFormat(yuv); } else { continue; }
public static void main(String[] args) { int[] values = new int[args.length]; for (int i = 0; i < values.length; i++) { values[i] = Integer.parseInt(args[i]); }
WebcamBroadcaster wb; if (values.length == 0) { wb = new WebcamBroadcaster(); } else if (values.length == 1) { wb = new WebcamBroadcaster(values[0]); } else if (values.length == 2) { wb = new WebcamBroadcaster(values[0], values[1]); } else { wb = new WebcamBroadcaster(values[0], values[1], values[2]); }
wb.start(); }
public static final int DEFAULT_PORT = 9889; public static final int DEFAULT_WIDTH = 320; public static final int DEFAULT_HEIGHT = 240;
private final Object lock = new Object();
private final int width; private final int height; private final int port;
My Name is Neil Davies and this is my blog. I live in the UK in the beautiful city of Bath. I've worked as a software engineer for a number of years in a variety of roles and with a wide range of technologies and programming languages. My special interests are in UI design and UX. Currently I'm enjoying programming on the Android platform. When I'm not programming I enjoy messing about on the water, which includes sailing, kayaking and surfing. If you'd like to get in touch email me at interfuser at googlemail dot com or just leave a comment.