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); } } } }; }