AndroidでUVCカメラ使うためのライブラリ

AndroidでUVC(USB Video Class)カメラ使うためのライブラリを公開しました。
中身はV4L2でごーりごーりやってるのをかるーくJavaでラップしただけです。

ソースコードはGithubで公開してます。

https://github.com/crimsonwoods/UVCCapture_jni


あとAndroid標準のCameraとは互換性がまったく無いので注意。
割と無理矢理な感じだけどAndroidのCameraとまとめて使えるようにするためのglue codeを書くとこんな感じかなっていうのを書いてみた。

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.graphics.ImageFormat;
import android.hardware.Camera.Parameters;

import crimsonwoods.android.libs.uvccap.PixelFormat;
import crimsonwoods.android.libs.uvccap.UVCCamera;

public class Camera {
	private android.hardware.Camera androidCamera;
	private UVCCamera uvcCamera;
	private boolean isPreviewing = false;
	private PreviewThread previewThread;
	private PreviewCallback previewCallback;
	private AutoFocusCallback autoFocusCallback;
	
	public Camera(int id, boolean isUvc) {
		if (isUvc) {
			uvcCamera = UVCCamera.open(id);
		} else {
			try {
				Method m = android.hardware.Camera.class.getMethod("open", int.class);
				if (null == m) {
					throw new NoSuchMethodException();
				}
				androidCamera = (android.hardware.Camera)m.invoke(null, id);
			} catch (IllegalAccessException e) {
			} catch (InvocationTargetException e) {
			} catch (NoSuchMethodException e) {
			}
			if (null == androidCamera) {
				androidCamera = android.hardware.Camera.open();
			}
		}
	}
	
	@Override
	protected void finalize() throws Throwable {
		try {
			super.finalize();
		} finally {
			release();
		}
	}
	
	public synchronized void init(int width, int height) throws IOException {
		if (null != androidCamera) {
			final Parameters params = androidCamera.getParameters();
			params.setPreviewSize(width, height);
			params.setPreviewFormat(ImageFormat.NV21);
		}
		if (null != uvcCamera) {
			uvcCamera.init(width, height, PixelFormat.YUYV);
			if (uvcCamera.getPixelFormat() != PixelFormat.YUYV) {
				uvcCamera.release();
				throw new IOException("Unsupported pixel format.");
			}
		}
	}
	
	public synchronized void release() {
		if (null != androidCamera) {
			androidCamera.release();
			androidCamera = null;
		}
		if (null != uvcCamera) {
			uvcCamera.release();
			uvcCamera = null;
		}
	}
	
	public synchronized void startPreview() {
		if (isPreviewing) {
			return;
		}
		if (null != androidCamera) {
			androidCamera.startPreview();
			isPreviewing = true;
		}
		if (null != uvcCamera) {
			if (null == previewThread) {
				previewThread = new PreviewThread(uvcCamera);
			}
			if (null != previewCallback) {
				previewThread.setPreviewCallback(previewCallback);
			}
			previewThread.start();
			isPreviewing = true;
		}
	}
	
	public void stopPreview() {
		synchronized(this) {
			if (!isPreviewing) {
				return;
			}
			if (null != androidCamera) {
				androidCamera.stopPreview();
				isPreviewing = false;
			}
			if (null == uvcCamera) {
				return;
			}
			previewThread.interrupt();
		}
		
		if (Thread.currentThread().getId() != previewThread.getId()) {
			try {
				previewThread.join();
			} catch (InterruptedException e) {
			} finally {
				previewThread = null;
			}
		}
	}
	
	public synchronized void setPreviewCallback(PreviewCallback cb) {
		previewCallback = cb;
		if (null != androidCamera) {
			if (null != cb) {
				androidCamera.setPreviewCallback(androidCameraPreviewCallback);
			} else {
				androidCamera.setPreviewCallback(null);
			}
		}
		if (null != uvcCamera) {
			if (null == previewThread) {
				previewThread = new PreviewThread(uvcCamera);
			}
			previewThread.setPreviewCallback(cb);
		}
	}
	
	public synchronized void autoFocus(AutoFocusCallback cb) {
		autoFocusCallback = cb;
		if (null != androidCamera) {
			androidCamera.autoFocus(androidCameraAutoFocusCallback);
		}
	}
	
	public synchronized void cancelAutoFocus() {
		if (null != androidCamera) {
			androidCamera.cancelAutoFocus();
		}
		autoFocusCallback = null;
	}
	
	public synchronized int getPreviewWidth() {
		if (null != androidCamera) {
			final Parameters params = androidCamera.getParameters();
			return params.getPreviewSize().width;
		}
		if (null != uvcCamera) {
			return uvcCamera.getWidth();
		}
		throw new IllegalStateException();
	}
	
	public synchronized int getPreviewHeight() {
		if (null != androidCamera) {
			final Parameters params = androidCamera.getParameters();
			return params.getPreviewSize().height;
		}
		if (null != uvcCamera) {
			return uvcCamera.getHeight();
		}
		throw new IllegalStateException();
	}
	
	public synchronized PixelFormat getPixelFormat() {
		if (null != androidCamera) {
			switch(androidCamera.getParameters().getPreviewFormat()) {
			case ImageFormat.NV21:
				return PixelFormat.NV21;
			case ImageFormat.YUY2:
				return PixelFormat.YUYV;
			default:
				// Others are not supported.
				return PixelFormat.UNKNOWN;
			}
		}
		if (null != uvcCamera) {
			return uvcCamera.getPixelFormat();
		}
		return PixelFormat.UNKNOWN;
	}
	
	private final class PreviewThread extends Thread {
		private UVCCamera camera_;
		private PreviewCallback callback_;
		
		PreviewThread(UVCCamera camera) {
			camera_ = camera;
			setName(getClass().getSimpleName());
		}
		
		public synchronized void setPreviewCallback(PreviewCallback cb) {
			callback_ = cb;
		}
		
		private void callback(byte[] pixels) {
			final PreviewCallback cb;
			synchronized(this) {
				if (null == callback_) {
					return;
				}
				cb = callback_;
			}
			cb.onPreviewFrame(pixels, Camera.this);
		}
		
		@Override
		public void run() {
			final byte[][] pixels = new byte[4][camera_.getFrameSize()];
			int idx = 0;
			try {
				while(!Thread.interrupted()) {
					camera_.capture(pixels[idx]);
					callback(pixels[idx]);
					idx = (idx + 1) % pixels.length; 
				}
			} catch (Throwable t) {
				Log.e(t, "Uncaught exception.");
			}
		}
	}
	
	public interface PreviewCallback {
		void onPreviewFrame(byte[] data, Camera camera);
	}
	
	public interface AutoFocusCallback {
		void onAutoFocus(boolean success, Camera camera);
	}
	
	private android.hardware.Camera.PreviewCallback androidCameraPreviewCallback = new android.hardware.Camera.PreviewCallback() {
		@Override
		public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
			final PreviewCallback cb;
			synchronized(Camera.this) {
				cb = previewCallback;
			}
			if (null != cb) {
				cb.onPreviewFrame(data, Camera.this);
			}
		}
	};
	
	private android.hardware.Camera.AutoFocusCallback androidCameraAutoFocusCallback = new android.hardware.Camera.AutoFocusCallback() {
		@Override
		public void onAutoFocus(boolean success, android.hardware.Camera camera) {
			synchronized(Camera.this) {
				final AutoFocusCallback cb;
				synchronized(Camera.this) {
					cb = autoFocusCallback;
				}
				if (null != cb) {
					cb.onAutoFocus(success, Camera.this);
				}
			}
		}
	};
}