From 6340444eb9d2544ba33cfd1f2d844eb85ef36ac5 Mon Sep 17 00:00:00 2001
From: jiangdongguo <765067602@qq.com>
Date: Fri, 29 Sep 2017 23:58:35 +0800
Subject: [PATCH] xuigai
---
app/app.iml | 8 -
app/build.gradle | 2 +-
libusbcamera/build.gradle | 2 +-
libusbcamera/libusbcamera.iml | 24 +-
.../usb/common/AbstractUVCCameraHandler.java | 811 ++++++++++++++++++
.../usb/common/UVCCameraHandler.java | 135 +++
.../common/UVCCameraHandlerMultiSurface.java | 180 ++++
.../usb/widget/AspectRatioTextureView.java | 113 +++
.../usb/widget/CameraViewInterface.java | 46 +
.../usb/widget/UVCCameraTextureView.java | 477 ++++++++++
local.properties | 6 +-
11 files changed, 1777 insertions(+), 27 deletions(-)
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java
create mode 100644 libusbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java
diff --git a/app/app.iml b/app/app.iml
index e03e0af..dc9ae4e 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -82,21 +82,13 @@
-
-
-
-
-
-
-
-
diff --git a/app/build.gradle b/app/build.gradle
index 318544d..171f70e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,7 +5,7 @@ android {
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.jiangdg.usbcamera"
- minSdkVersion 15
+ minSdkVersion 18
targetSdkVersion 25
versionCode 1
versionName "1.0"
diff --git a/libusbcamera/build.gradle b/libusbcamera/build.gradle
index fc0ad3a..2241dab 100644
--- a/libusbcamera/build.gradle
+++ b/libusbcamera/build.gradle
@@ -5,7 +5,7 @@ android {
buildToolsVersion "26.0.1"
defaultConfig {
- minSdkVersion 15
+ minSdkVersion 18
targetSdkVersion 25
versionCode 1
versionName "1.0"
diff --git a/libusbcamera/libusbcamera.iml b/libusbcamera/libusbcamera.iml
index 3ce8d33..a7b8a41 100644
--- a/libusbcamera/libusbcamera.iml
+++ b/libusbcamera/libusbcamera.iml
@@ -64,17 +64,9 @@
-
+
-
-
-
-
-
-
-
-
@@ -83,22 +75,24 @@
-
+
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java b/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java
new file mode 100644
index 0000000..3d138b6
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java
@@ -0,0 +1,811 @@
+package com.serenegiant.usb.common;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
+import android.media.MediaScannerConnection;
+import android.media.SoundPool;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import com.jiangdg.libusbcamera.R;
+import com.serenegiant.usb.IFrameCallback;
+import com.serenegiant.usb.USBMonitor;
+import com.serenegiant.usb.UVCCamera;
+import com.serenegiant.usb.widget.CameraViewInterface;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+abstract class AbstractUVCCameraHandler extends Handler {
+ private static final boolean DEBUG = true; // TODO set false on release
+ private static final String TAG = "AbsUVCCameraHandler";
+
+ public interface CameraCallback {
+ public void onOpen();
+
+ public void onClose();
+
+ public void onStartPreview();
+
+ public void onStopPreview();
+
+ public void onStartRecording();
+
+ public void onStopRecording();
+
+ public void onError(final Exception e);
+ }
+
+ private static final int MSG_OPEN = 0;
+ private static final int MSG_CLOSE = 1;
+ private static final int MSG_PREVIEW_START = 2;
+ private static final int MSG_PREVIEW_STOP = 3;
+ private static final int MSG_CAPTURE_STILL = 4;
+ private static final int MSG_CAPTURE_START = 5;
+ private static final int MSG_CAPTURE_STOP = 6;
+ private static final int MSG_MEDIA_UPDATE = 7;
+ private static final int MSG_RELEASE = 9;
+
+ private final WeakReference mWeakThread;
+ private volatile boolean mReleased;
+
+ protected AbstractUVCCameraHandler(final CameraThread thread) {
+ mWeakThread = new WeakReference(thread);
+ }
+
+ public int getWidth() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null ? thread.getWidth() : 0;
+ }
+
+ public int getHeight() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null ? thread.getHeight() : 0;
+ }
+
+ public boolean isOpened() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null && thread.isCameraOpened();
+ }
+
+ public boolean isPreviewing() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null && thread.isPreviewing();
+ }
+
+ public boolean isRecording() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null && thread.isRecording();
+ }
+
+ public boolean isEqual(final UsbDevice device) {
+ final CameraThread thread = mWeakThread.get();
+ return (thread != null) && thread.isEqual(device);
+ }
+
+ protected boolean isCameraThread() {
+ final CameraThread thread = mWeakThread.get();
+ return thread != null && (thread.getId() == Thread.currentThread().getId());
+ }
+
+ protected boolean isReleased() {
+ final CameraThread thread = mWeakThread.get();
+ return mReleased || (thread == null);
+ }
+
+ protected void checkReleased() {
+ if (isReleased()) {
+ throw new IllegalStateException("already released");
+ }
+ }
+
+ public void open(final USBMonitor.UsbControlBlock ctrlBlock) {
+ checkReleased();
+ sendMessage(obtainMessage(MSG_OPEN, ctrlBlock));
+ }
+
+ public void close() {
+ if (DEBUG) Log.v(TAG, "close:");
+ if (isOpened()) {
+ stopPreview();
+ sendEmptyMessage(MSG_CLOSE);
+ }
+ if (DEBUG) Log.v(TAG, "close:finished");
+ }
+
+ public void resize(final int width, final int height) {
+ checkReleased();
+ throw new UnsupportedOperationException("does not support now");
+ }
+
+ protected void startPreview(final Object surface) {
+ checkReleased();
+ if (!((surface instanceof SurfaceHolder) || (surface instanceof Surface) || (surface instanceof SurfaceTexture))) {
+ throw new IllegalArgumentException("surface should be one of SurfaceHolder, Surface or SurfaceTexture");
+ }
+ sendMessage(obtainMessage(MSG_PREVIEW_START, surface));
+ }
+
+ public void stopPreview() {
+ if (DEBUG) Log.v(TAG, "stopPreview:");
+ removeMessages(MSG_PREVIEW_START);
+ stopRecording();
+ if (isPreviewing()) {
+ final CameraThread thread = mWeakThread.get();
+ if (thread == null) return;
+ synchronized (thread.mSync) {
+ sendEmptyMessage(MSG_PREVIEW_STOP);
+ if (!isCameraThread()) {
+ // wait for actually preview stopped to avoid releasing Surface/SurfaceTexture
+ // while preview is still running.
+ // therefore this method will take a time to execute
+ try {
+ thread.mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ }
+ }
+ if (DEBUG) Log.v(TAG, "stopPreview:finished");
+ }
+
+ protected void captureStill() {
+ checkReleased();
+ sendEmptyMessage(MSG_CAPTURE_STILL);
+ }
+
+ protected void captureStill(final String path) {
+ checkReleased();
+ sendMessage(obtainMessage(MSG_CAPTURE_STILL, path));
+ }
+
+ public void startRecording() {
+ checkReleased();
+ sendEmptyMessage(MSG_CAPTURE_START);
+ }
+
+ public void stopRecording() {
+ sendEmptyMessage(MSG_CAPTURE_STOP);
+ }
+
+ public void release() {
+ mReleased = true;
+ close();
+ sendEmptyMessage(MSG_RELEASE);
+ }
+
+ public void addCallback(final CameraCallback callback) {
+ checkReleased();
+ if (!mReleased && (callback != null)) {
+ final CameraThread thread = mWeakThread.get();
+ if (thread != null) {
+ thread.mCallbacks.add(callback);
+ }
+ }
+ }
+
+ public void removeCallback(final CameraCallback callback) {
+ if (callback != null) {
+ final CameraThread thread = mWeakThread.get();
+ if (thread != null) {
+ thread.mCallbacks.remove(callback);
+ }
+ }
+ }
+
+ protected void updateMedia(final String path) {
+ sendMessage(obtainMessage(MSG_MEDIA_UPDATE, path));
+ }
+
+ public boolean checkSupportFlag(final long flag) {
+ checkReleased();
+ final CameraThread thread = mWeakThread.get();
+ return thread != null && thread.mUVCCamera != null && thread.mUVCCamera.checkSupportFlag(flag);
+ }
+
+ public int getValue(final int flag) {
+ checkReleased();
+ final CameraThread thread = mWeakThread.get();
+ final UVCCamera camera = thread != null ? thread.mUVCCamera : null;
+ if (camera != null) {
+ if (flag == UVCCamera.PU_BRIGHTNESS) {
+ return camera.getBrightness();
+ } else if (flag == UVCCamera.PU_CONTRAST) {
+ return camera.getContrast();
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ public int setValue(final int flag, final int value) {
+ checkReleased();
+ final CameraThread thread = mWeakThread.get();
+ final UVCCamera camera = thread != null ? thread.mUVCCamera : null;
+ if (camera != null) {
+ if (flag == UVCCamera.PU_BRIGHTNESS) {
+ camera.setBrightness(value);
+ return camera.getBrightness();
+ } else if (flag == UVCCamera.PU_CONTRAST) {
+ camera.setContrast(value);
+ return camera.getContrast();
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ public int resetValue(final int flag) {
+ checkReleased();
+ final CameraThread thread = mWeakThread.get();
+ final UVCCamera camera = thread != null ? thread.mUVCCamera : null;
+ if (camera != null) {
+ if (flag == UVCCamera.PU_BRIGHTNESS) {
+ camera.resetBrightness();
+ return camera.getBrightness();
+ } else if (flag == UVCCamera.PU_CONTRAST) {
+ camera.resetContrast();
+ return camera.getContrast();
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final CameraThread thread = mWeakThread.get();
+ if (thread == null) return;
+ switch (msg.what) {
+ case MSG_OPEN:
+ thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj);
+ break;
+ case MSG_CLOSE:
+ thread.handleClose();
+ break;
+ case MSG_PREVIEW_START:
+ thread.handleStartPreview(msg.obj);
+ break;
+ case MSG_PREVIEW_STOP:
+ thread.handleStopPreview();
+ break;
+// case MSG_CAPTURE_STILL:
+// thread.handleCaptureStill((String) msg.obj);
+// break;
+// case MSG_CAPTURE_START:
+// thread.handleStartRecording();
+// break;
+// case MSG_CAPTURE_STOP:
+// thread.handleStopRecording();
+// break;
+ case MSG_MEDIA_UPDATE:
+ thread.handleUpdateMedia((String) msg.obj);
+ break;
+ case MSG_RELEASE:
+ thread.handleRelease();
+ break;
+ default:
+ throw new RuntimeException("unsupported message:what=" + msg.what);
+ }
+ }
+
+ static final class CameraThread extends Thread {
+ private static final String TAG_THREAD = "CameraThread";
+ private final Object mSync = new Object();
+ private final Class extends AbstractUVCCameraHandler> mHandlerClass;
+ private final WeakReference mWeakParent;
+ private final WeakReference mWeakCameraView;
+ private final int mEncoderType;
+ private final Set mCallbacks = new CopyOnWriteArraySet();
+ private int mWidth, mHeight, mPreviewMode;
+ private float mBandwidthFactor;
+ private boolean mIsPreviewing;
+ private boolean mIsRecording;
+ /**
+ * shutter sound
+ */
+ private SoundPool mSoundPool;
+ private int mSoundId;
+ private AbstractUVCCameraHandler mHandler;
+ /**
+ * for accessing UVC camera
+ */
+ private UVCCamera mUVCCamera;
+ /**
+ * muxer for audio/video recording
+ */
+// private MediaMuxerWrapper mMuxer;
+// private MediaVideoBufferEncoder mVideoEncoder;
+
+ /**
+ * @param clazz Class extends AbstractUVCCameraHandler
+ * @param parent parent Activity
+ * @param cameraView for still capturing
+ * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
+ * @param width
+ * @param height
+ * @param format either FRAME_FORMAT_YUYV(0) or FRAME_FORMAT_MJPEG(1)
+ * @param bandwidthFactor
+ */
+ CameraThread(final Class extends AbstractUVCCameraHandler> clazz,
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format,
+ final float bandwidthFactor) {
+
+ super("CameraThread");
+ mHandlerClass = clazz;
+ mEncoderType = encoderType;
+ mWidth = width;
+ mHeight = height;
+ mPreviewMode = format;
+ mBandwidthFactor = bandwidthFactor;
+ mWeakParent = new WeakReference(parent);
+ mWeakCameraView = new WeakReference(cameraView);
+ loadShutterSound(parent);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ Log.i(TAG, "CameraThread#finalize");
+ super.finalize();
+ }
+
+ public AbstractUVCCameraHandler getHandler() {
+ if (DEBUG) Log.v(TAG_THREAD, "getHandler:");
+ synchronized (mSync) {
+ if (mHandler == null)
+ try {
+ mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ return mHandler;
+ }
+
+ public int getWidth() {
+ synchronized (mSync) {
+ return mWidth;
+ }
+ }
+
+ public int getHeight() {
+ synchronized (mSync) {
+ return mHeight;
+ }
+ }
+
+ public boolean isCameraOpened() {
+ synchronized (mSync) {
+ return mUVCCamera != null;
+ }
+ }
+
+ public boolean isPreviewing() {
+ synchronized (mSync) {
+ return mUVCCamera != null && mIsPreviewing;
+ }
+ }
+
+ public boolean isRecording() {
+ synchronized (mSync) {
+ return (mUVCCamera != null) && (mMuxer != null);
+ }
+ }
+
+ public boolean isEqual(final UsbDevice device) {
+ return (mUVCCamera != null) && (mUVCCamera.getDevice() != null) && mUVCCamera.getDevice().equals(device);
+ }
+
+ public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) {
+ if (DEBUG) Log.v(TAG_THREAD, "handleOpen:");
+ handleClose();
+ try {
+ final UVCCamera camera = new UVCCamera();
+ camera.open(ctrlBlock);
+ synchronized (mSync) {
+ mUVCCamera = camera;
+ }
+ callOnOpen();
+ } catch (final Exception e) {
+ callOnError(e);
+ }
+ if (DEBUG)
+ Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null));
+ }
+
+ public void handleClose() {
+ if (DEBUG) Log.v(TAG_THREAD, "handleClose:");
+// handleStopRecording();
+ final UVCCamera camera;
+ synchronized (mSync) {
+ camera = mUVCCamera;
+ mUVCCamera = null;
+ }
+ if (camera != null) {
+ camera.stopPreview();
+ camera.destroy();
+ callOnClose();
+ }
+ }
+
+ public void handleStartPreview(final Object surface) {
+ if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:");
+ if ((mUVCCamera == null) || mIsPreviewing) return;
+ try {
+ mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor);
+ } catch (final IllegalArgumentException e) {
+ try {
+ // fallback to YUV mode
+ mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, UVCCamera.DEFAULT_PREVIEW_MODE, mBandwidthFactor);
+ } catch (final IllegalArgumentException e1) {
+ callOnError(e1);
+ return;
+ }
+ }
+ if (surface instanceof SurfaceHolder) {
+ mUVCCamera.setPreviewDisplay((SurfaceHolder) surface);
+ }
+ if (surface instanceof Surface) {
+ mUVCCamera.setPreviewDisplay((Surface) surface);
+ } else {
+ mUVCCamera.setPreviewTexture((SurfaceTexture) surface);
+ }
+ mUVCCamera.startPreview();
+ mUVCCamera.updateCameraParams();
+ synchronized (mSync) {
+ mIsPreviewing = true;
+ }
+ callOnStartPreview();
+ }
+
+ public void handleStopPreview() {
+ if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:");
+ if (mIsPreviewing) {
+ if (mUVCCamera != null) {
+ mUVCCamera.stopPreview();
+ }
+ synchronized (mSync) {
+ mIsPreviewing = false;
+ mSync.notifyAll();
+ }
+ callOnStopPreview();
+ }
+ if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished");
+ }
+
+// public void handleCaptureStill(final String path) {
+// if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:");
+// final Activity parent = mWeakParent.get();
+// if (parent == null) return;
+// mSoundPool.play(mSoundId, 0.2f, 0.2f, 0, 0, 1.0f); // play shutter sound
+// try {
+// final Bitmap bitmap = mWeakCameraView.get().captureStillImage();
+// // get buffered output stream for saving a captured still image as a file on external storage.
+// // the file name is came from current time.
+// // You should use extension name as same as CompressFormat when calling Bitmap#compress.
+// final File outputFile = TextUtils.isEmpty(path)
+// ? MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".png")
+// : new File(path);
+// final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile));
+// try {
+// try {
+// bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+// os.flush();
+// mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath()));
+// } catch (final IOException e) {
+// }
+// } finally {
+// os.close();
+// }
+// } catch (final Exception e) {
+// callOnError(e);
+// }
+// }
+
+// public void handleStartRecording() {
+// if (DEBUG) Log.v(TAG_THREAD, "handleStartRecording:");
+// try {
+// if ((mUVCCamera == null) || (mMuxer != null)) return;
+// final MediaMuxerWrapper muxer = new MediaMuxerWrapper(".mp4"); // if you record audio only, ".m4a" is also OK.
+// MediaVideoBufferEncoder videoEncoder = null;
+// switch (mEncoderType) {
+// case 1: // for video capturing using MediaVideoEncoder
+// new MediaVideoEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener);
+// break;
+// case 2: // for video capturing using MediaVideoBufferEncoder
+// videoEncoder = new MediaVideoBufferEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener);
+// break;
+// // case 0: // for video capturing using MediaSurfaceEncoder
+// default:
+// new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener);
+// break;
+// }
+// if (true) {
+// // for audio capturing
+// new MediaAudioEncoder(muxer, mMediaEncoderListener);
+// }
+// muxer.prepare();
+// muxer.startRecording();
+// if (videoEncoder != null) {
+// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21);
+// }
+// synchronized (mSync) {
+// mMuxer = muxer;
+// mVideoEncoder = videoEncoder;
+// }
+// callOnStartRecording();
+// } catch (final IOException e) {
+// callOnError(e);
+// Log.e(TAG, "startCapture:", e);
+// }
+// }
+
+// public void handleStopRecording() {
+// if (DEBUG) Log.v(TAG_THREAD, "handleStopRecording:mMuxer=" + mMuxer);
+// final MediaMuxerWrapper muxer;
+// synchronized (mSync) {
+// muxer = mMuxer;
+// mMuxer = null;
+// mVideoEncoder = null;
+// if (mUVCCamera != null) {
+// mUVCCamera.stopCapture();
+// }
+// }
+// try {
+// mWeakCameraView.get().setVideoEncoder(null);
+// } catch (final Exception e) {
+// // ignore
+// }
+// if (muxer != null) {
+// muxer.stopRecording();
+// mUVCCamera.setFrameCallback(null, 0);
+// // you should not wait here
+// callOnStopRecording();
+// }
+// }
+
+ private final IFrameCallback mIFrameCallback = new IFrameCallback() {
+ @Override
+ public void onFrame(final ByteBuffer frame) {
+// final MediaVideoBufferEncoder videoEncoder;
+// synchronized (mSync) {
+// videoEncoder = mVideoEncoder;
+// }
+// if (videoEncoder != null) {
+// videoEncoder.frameAvailableSoon();
+// videoEncoder.encode(frame);
+// }
+ }
+ };
+
+ public void handleUpdateMedia(final String path) {
+ if (DEBUG) Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path);
+ final Activity parent = mWeakParent.get();
+ final boolean released = (mHandler == null) || mHandler.mReleased;
+ if (parent != null && parent.getApplicationContext() != null) {
+ try {
+ if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile");
+ MediaScannerConnection.scanFile(parent.getApplicationContext(), new String[]{path}, null, null);
+ } catch (final Exception e) {
+ Log.e(TAG, "handleUpdateMedia:", e);
+ }
+ if (released || parent.isDestroyed())
+ handleRelease();
+ } else {
+ Log.w(TAG, "MainActivity already destroyed");
+ // give up to add this movie to MediaStore now.
+ // Seeing this movie on Gallery app etc. will take a lot of time.
+ handleRelease();
+ }
+ }
+
+ public void handleRelease() {
+ if (DEBUG) Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording);
+ handleClose();
+ mCallbacks.clear();
+ if (!mIsRecording) {
+ mHandler.mReleased = true;
+ Looper.myLooper().quit();
+ }
+ if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished");
+ }
+
+// private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() {
+// @Override
+// public void onPrepared(final MediaEncoder encoder) {
+// if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder);
+// mIsRecording = true;
+// if (encoder instanceof MediaVideoEncoder)
+// try {
+// mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder);
+// } catch (final Exception e) {
+// Log.e(TAG, "onPrepared:", e);
+// }
+// if (encoder instanceof MediaSurfaceEncoder)
+// try {
+// mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder);
+// mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface());
+// } catch (final Exception e) {
+// Log.e(TAG, "onPrepared:", e);
+// }
+// }
+//
+// @Override
+// public void onStopped(final MediaEncoder encoder) {
+// if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder);
+// if ((encoder instanceof MediaVideoEncoder)
+// || (encoder instanceof MediaSurfaceEncoder))
+// try {
+// mIsRecording = false;
+// final Activity parent = mWeakParent.get();
+// mWeakCameraView.get().setVideoEncoder(null);
+// synchronized (mSync) {
+// if (mUVCCamera != null) {
+// mUVCCamera.stopCapture();
+// }
+// }
+// final String path = encoder.getOutputPath();
+// if (!TextUtils.isEmpty(path)) {
+// mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_MEDIA_UPDATE, path), 1000);
+// } else {
+// final boolean released = (mHandler == null) || mHandler.mReleased;
+// if (released || parent == null || parent.isDestroyed()) {
+// handleRelease();
+// }
+// }
+// } catch (final Exception e) {
+// Log.e(TAG, "onPrepared:", e);
+// }
+// }
+// };
+
+ /**
+ * prepare and load shutter sound for still image capturing
+ */
+ @SuppressWarnings("deprecation")
+ private void loadShutterSound(final Context context) {
+ // get system stream type using reflection
+ int streamType;
+ try {
+ final Class> audioSystemClass = Class.forName("android.media.AudioSystem");
+ final Field sseField = audioSystemClass.getDeclaredField("STREAM_SYSTEM_ENFORCED");
+ streamType = sseField.getInt(null);
+ } catch (final Exception e) {
+ streamType = AudioManager.STREAM_SYSTEM; // set appropriate according to your app policy
+ }
+ if (mSoundPool != null) {
+ try {
+ mSoundPool.release();
+ } catch (final Exception e) {
+ }
+ mSoundPool = null;
+ }
+ // load shutter sound from resource
+ mSoundPool = new SoundPool(2, streamType, 0);
+// mSoundId = mSoundPool.load(context, R.raw.camera_click, 1);
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ AbstractUVCCameraHandler handler = null;
+ try {
+ final Constructor extends AbstractUVCCameraHandler> constructor = mHandlerClass.getDeclaredConstructor(CameraThread.class);
+ handler = constructor.newInstance(this);
+ } catch (final NoSuchMethodException e) {
+ Log.w(TAG, e);
+ } catch (final IllegalAccessException e) {
+ Log.w(TAG, e);
+ } catch (final InstantiationException e) {
+ Log.w(TAG, e);
+ } catch (final InvocationTargetException e) {
+ Log.w(TAG, e);
+ }
+ if (handler != null) {
+ synchronized (mSync) {
+ mHandler = handler;
+ mSync.notifyAll();
+ }
+ Looper.loop();
+ if (mSoundPool != null) {
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ if (mHandler != null) {
+ mHandler.mReleased = true;
+ }
+ }
+ mCallbacks.clear();
+ synchronized (mSync) {
+ mHandler = null;
+ mSync.notifyAll();
+ }
+ }
+
+ private void callOnOpen() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onOpen();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnClose() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onClose();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStartPreview() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStartPreview();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStopPreview() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStopPreview();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStartRecording() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStartRecording();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStopRecording() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStopRecording();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnError(final Exception e) {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onError(e);
+ } catch (final Exception e1) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+ }
+}
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java b/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java
new file mode 100644
index 0000000..d605880
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java
@@ -0,0 +1,135 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.common;
+
+import android.app.Activity;
+
+import com.serenegiant.usb.UVCCamera;
+import com.serenegiant.usb.widget.CameraViewInterface;
+
+public class UVCCameraHandler extends AbstractUVCCameraHandler {
+
+ /**
+ * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param width
+ * @param height
+ * @return
+ */
+ public static final UVCCameraHandler createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int width, final int height) {
+
+ return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG
+ * @param parent
+ * @param cameraView
+ * @param width
+ * @param height
+ * @param bandwidthFactor
+ * @return
+ */
+ public static final UVCCameraHandler createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int width, final int height, final float bandwidthFactor) {
+
+ return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor);
+ }
+
+ /**
+ * create UVCCameraHandler, try MJPEG, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
+ * @param width
+ * @param height
+ * @return
+ */
+ public static final UVCCameraHandler createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height) {
+
+ return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandler, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
+ * @param width
+ * @param height
+ * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
+ * @return
+ */
+ public static final UVCCameraHandler createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format) {
+
+ return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandler
+ * @param parent
+ * @param cameraView
+ * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
+ * @param width
+ * @param height
+ * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
+ * @param bandwidthFactor
+ * @return
+ */
+ public static final UVCCameraHandler createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) {
+
+ final CameraThread thread = new CameraThread(UVCCameraHandler.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor);
+ thread.start();
+ return (UVCCameraHandler)thread.getHandler();
+ }
+
+ protected UVCCameraHandler(final CameraThread thread) {
+ super(thread);
+ }
+
+ @Override
+ public void startPreview(final Object surface) {
+ super.startPreview(surface);
+ }
+
+ @Override
+ public void captureStill() {
+ super.captureStill();
+ }
+
+ @Override
+ public void captureStill(final String path) {
+ super.captureStill(path);
+ }
+}
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java b/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java
new file mode 100644
index 0000000..b0e214a
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java
@@ -0,0 +1,180 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.common;
+
+import android.app.Activity;
+import android.view.Surface;
+
+import com.serenegiant.glutils.RendererHolder;
+import com.serenegiant.usb.UVCCamera;
+import com.serenegiant.usb.widget.CameraViewInterface;
+
+public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler {
+ /**
+ * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param width
+ * @param height
+ * @return
+ */
+ public static final UVCCameraHandlerMultiSurface createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int width, final int height) {
+
+ return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG
+ * @param parent
+ * @param cameraView
+ * @param width
+ * @param height
+ * @param bandwidthFactor
+ * @return
+ */
+ public static final UVCCameraHandlerMultiSurface createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int width, final int height, final float bandwidthFactor) {
+
+ return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor);
+ }
+
+ /**
+ * create UVCCameraHandlerMultiSurface, try MJPEG, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param encoderType
+ * @param width
+ * @param height
+ * @return
+ */
+ public static final UVCCameraHandlerMultiSurface createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height) {
+
+ return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandlerMultiSurface, default bandwidth
+ * @param parent
+ * @param cameraView
+ * @param encoderType
+ * @param width
+ * @param height
+ * @param format
+ * @return
+ */
+ public static final UVCCameraHandlerMultiSurface createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format) {
+
+ return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH);
+ }
+
+ /**
+ * create UVCCameraHandlerMultiSurface
+ * @param parent
+ * @param cameraView
+ * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
+ * @param width
+ * @param height
+ * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
+ * @param bandwidthFactor
+ * @return
+ */
+ public static final UVCCameraHandlerMultiSurface createHandler(
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) {
+
+ final CameraThread thread = new CameraThread(UVCCameraHandlerMultiSurface.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor);
+ thread.start();
+ return (UVCCameraHandlerMultiSurface)thread.getHandler();
+ }
+
+ private RendererHolder mRendererHolder;
+ protected UVCCameraHandlerMultiSurface(final CameraThread thread) {
+ super(thread);
+ mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null);
+ }
+
+ public synchronized void release() {
+ if (mRendererHolder != null) {
+ mRendererHolder.release();
+ mRendererHolder = null;
+ }
+ super.release();
+ }
+
+ public synchronized void resize(final int width, final int height) {
+ super.resize(width, height);
+ if (mRendererHolder != null) {
+ mRendererHolder.resize(width, height);
+ }
+ }
+
+ public synchronized void startPreview() {
+ checkReleased();
+ if (mRendererHolder != null) {
+ super.startPreview(mRendererHolder.getSurface());
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public synchronized void addSurface(final int surfaceId, final Surface surface, final boolean isRecordable) {
+ checkReleased();
+ mRendererHolder.addSurface(surfaceId, surface, isRecordable);
+ }
+
+ public synchronized void removeSurface(final int surfaceId) {
+ if (mRendererHolder != null) {
+ mRendererHolder.removeSurface(surfaceId);
+ }
+ }
+
+ @Override
+ public void captureStill() {
+ checkReleased();
+ super.captureStill();
+ }
+
+ @Override
+ public void captureStill(final String path) {
+ checkReleased();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (UVCCameraHandlerMultiSurface.this) {
+ if (mRendererHolder != null) {
+ mRendererHolder.captureStill(path);
+ updateMedia(path);
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java b/libusbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java
new file mode 100644
index 0000000..1599519
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java
@@ -0,0 +1,113 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+import com.serenegiant.widget.IAspectRatioView;
+
+/**
+ * change the view size with keeping the specified aspect ratio.
+ * if you set this view with in a FrameLayout and set property "android:layout_gravity="center",
+ * you can show this view in the center of screen and keep the aspect ratio of content
+ * XXX it is better that can set the aspect ratio as xml property
+ */
+public class AspectRatioTextureView extends TextureView // API >= 14
+ implements IAspectRatioView {
+
+ private static final boolean DEBUG = true; // TODO set false on release
+ private static final String TAG = "AbstractCameraView";
+
+ private double mRequestedAspect = -1.0;
+ private CameraViewInterface.Callback mCallback;
+
+ public AspectRatioTextureView(final Context context) {
+ this(context, null, 0);
+ }
+
+ public AspectRatioTextureView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setAspectRatio(final double aspectRatio) {
+ if (aspectRatio < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (mRequestedAspect != aspectRatio) {
+ mRequestedAspect = aspectRatio;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setAspectRatio(final int width, final int height) {
+ setAspectRatio(width / (double)height);
+ }
+
+ @Override
+ public double getAspectRatio() {
+ return mRequestedAspect;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ if (mRequestedAspect > 0) {
+ int initialWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int initialHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int horizPadding = getPaddingLeft() + getPaddingRight();
+ final int vertPadding = getPaddingTop() + getPaddingBottom();
+ initialWidth -= horizPadding;
+ initialHeight -= vertPadding;
+
+ final double viewAspectRatio = (double)initialWidth / initialHeight;
+ final double aspectDiff = mRequestedAspect / viewAspectRatio - 1;
+
+ if (Math.abs(aspectDiff) > 0.01) {
+ if (aspectDiff > 0) {
+ // width priority decision
+ initialHeight = (int) (initialWidth / mRequestedAspect);
+ } else {
+ // height priority decision
+ initialWidth = (int) (initialHeight * mRequestedAspect);
+ }
+ initialWidth += horizPadding;
+ initialHeight += vertPadding;
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+}
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java b/libusbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java
new file mode 100644
index 0000000..0c57396
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java
@@ -0,0 +1,46 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.widget;
+
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+
+import com.serenegiant.widget.IAspectRatioView;
+
+public interface CameraViewInterface extends IAspectRatioView {
+ public interface Callback {
+ public void onSurfaceCreated(CameraViewInterface view, Surface surface);
+ public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height);
+ public void onSurfaceDestroy(CameraViewInterface view, Surface surface);
+ }
+ public void onPause();
+ public void onResume();
+ public void setCallback(Callback callback);
+ public SurfaceTexture getSurfaceTexture();
+ public Surface getSurface();
+ public boolean hasSurface();
+// public void setVideoEncoder(final IVideoEncoder encoder);
+ public Bitmap captureStillImage();
+}
diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java b/libusbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java
new file mode 100644
index 0000000..6b4228f
--- /dev/null
+++ b/libusbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java
@@ -0,0 +1,477 @@
+package com.serenegiant.usb.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+
+import com.serenegiant.glutils.EGLBase;
+import com.serenegiant.glutils.GLDrawer2D;
+import com.serenegiant.glutils.es1.GLHelper;
+import com.serenegiant.utils.FpsCounter;
+
+public class UVCCameraTextureView extends AspectRatioTextureView // API >= 14
+ implements TextureView.SurfaceTextureListener, CameraViewInterface {
+
+ private static final boolean DEBUG = true; // TODO set false on release
+ private static final String TAG = "UVCCameraTextureView";
+
+ private boolean mHasSurface;
+ private RenderHandler mRenderHandler;
+ private final Object mCaptureSync = new Object();
+ private Bitmap mTempBitmap;
+ private boolean mReqesutCaptureStillImage;
+ private Callback mCallback;
+ /**
+ * for calculation of frame rate
+ */
+ private final FpsCounter mFpsCounter = new FpsCounter();
+
+ public UVCCameraTextureView(final Context context) {
+ this(context, null, 0);
+ }
+
+ public UVCCameraTextureView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UVCCameraTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ setSurfaceTextureListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ if (DEBUG) Log.v(TAG, "onResume:");
+ if (mHasSurface) {
+ mRenderHandler = RenderHandler.createHandler(mFpsCounter, super.getSurfaceTexture(), getWidth(), getHeight());
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (DEBUG) Log.v(TAG, "onPause:");
+ if (mRenderHandler != null) {
+ mRenderHandler.release();
+ mRenderHandler = null;
+ }
+ if (mTempBitmap != null) {
+ mTempBitmap.recycle();
+ mTempBitmap = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
+ if (DEBUG) Log.v(TAG, "onSurfaceTextureAvailable:" + surface);
+ if (mRenderHandler == null) {
+ mRenderHandler = RenderHandler.createHandler(mFpsCounter, surface, width, height);
+ } else {
+ mRenderHandler.resize(width, height);
+ }
+ mHasSurface = true;
+ if (mCallback != null) {
+ mCallback.onSurfaceCreated(this, getSurface());
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
+ if (DEBUG) Log.v(TAG, "onSurfaceTextureSizeChanged:" + surface);
+ if (mRenderHandler != null) {
+ mRenderHandler.resize(width, height);
+ }
+ if (mCallback != null) {
+ mCallback.onSurfaceChanged(this, getSurface(), width, height);
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {
+ if (DEBUG) Log.v(TAG, "onSurfaceTextureDestroyed:" + surface);
+ if (mRenderHandler != null) {
+ mRenderHandler.release();
+ mRenderHandler = null;
+ }
+ mHasSurface = false;
+ if (mCallback != null) {
+ mCallback.onSurfaceDestroy(this, getSurface());
+ }
+ if (mPreviewSurface != null) {
+ mPreviewSurface.release();
+ mPreviewSurface = null;
+ }
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
+ synchronized (mCaptureSync) {
+ if (mReqesutCaptureStillImage) {
+ mReqesutCaptureStillImage = false;
+ if (mTempBitmap == null)
+ mTempBitmap = getBitmap();
+ else
+ getBitmap(mTempBitmap);
+ mCaptureSync.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public boolean hasSurface() {
+ return mHasSurface;
+ }
+
+ /**
+ * capture preview image as a bitmap
+ * this method blocks current thread until bitmap is ready
+ * if you call this method at almost same time from different thread,
+ * the returned bitmap will be changed while you are processing the bitmap
+ * (because we return same instance of bitmap on each call for memory saving)
+ * if you need to call this method from multiple thread,
+ * you should change this method(copy and return)
+ */
+ @Override
+ public Bitmap captureStillImage() {
+ synchronized (mCaptureSync) {
+ mReqesutCaptureStillImage = true;
+ try {
+ mCaptureSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ return mTempBitmap;
+ }
+ }
+
+ @Override
+ public SurfaceTexture getSurfaceTexture() {
+ return mRenderHandler != null ? mRenderHandler.getPreviewTexture() : null;
+ }
+
+ private Surface mPreviewSurface;
+
+ @Override
+ public Surface getSurface() {
+ if (DEBUG) Log.v(TAG, "getSurface:hasSurface=" + mHasSurface);
+ if (mPreviewSurface == null) {
+ final SurfaceTexture st = getSurfaceTexture();
+ if (st != null) {
+ mPreviewSurface = new Surface(st);
+ }
+ }
+ return mPreviewSurface;
+ }
+
+// @Override
+// public void setVideoEncoder(final IVideoEncoder encoder) {
+// if (mRenderHandler != null)
+// mRenderHandler.setVideoEncoder(encoder);
+// }
+
+ @Override
+ public void setCallback(final Callback callback) {
+ mCallback = callback;
+ }
+
+ public void resetFps() {
+ mFpsCounter.reset();
+ }
+
+ /**
+ * update frame rate of image processing
+ */
+ public void updateFps() {
+ mFpsCounter.update();
+ }
+
+ /**
+ * get current frame rate of image processing
+ *
+ * @return
+ */
+ public float getFps() {
+ return mFpsCounter.getFps();
+ }
+
+ /**
+ * get total frame rate from start
+ *
+ * @return
+ */
+ public float getTotalFps() {
+ return mFpsCounter.getTotalFps();
+ }
+
+ /**
+ * render camera frames on this view on a private thread
+ *
+ * @author saki
+ */
+ private static final class RenderHandler extends Handler
+ implements SurfaceTexture.OnFrameAvailableListener {
+
+ private static final int MSG_REQUEST_RENDER = 1;
+ private static final int MSG_SET_ENCODER = 2;
+ private static final int MSG_CREATE_SURFACE = 3;
+ private static final int MSG_RESIZE = 4;
+ private static final int MSG_TERMINATE = 9;
+
+ private RenderThread mThread;
+ private boolean mIsActive = true;
+ private final FpsCounter mFpsCounter;
+
+ public static final RenderHandler createHandler(final FpsCounter counter,
+ final SurfaceTexture surface, final int width, final int height) {
+
+ final RenderThread thread = new RenderThread(counter, surface, width, height);
+ thread.start();
+ return thread.getHandler();
+ }
+
+ private RenderHandler(final FpsCounter counter, final RenderThread thread) {
+ mThread = thread;
+ mFpsCounter = counter;
+ }
+
+// public final void setVideoEncoder(final IVideoEncoder encoder) {
+// if (DEBUG) Log.v(TAG, "setVideoEncoder:");
+// if (mIsActive)
+// sendMessage(obtainMessage(MSG_SET_ENCODER, encoder));
+// }
+
+ public final SurfaceTexture getPreviewTexture() {
+ if (DEBUG) Log.v(TAG, "getPreviewTexture:");
+ if (mIsActive) {
+ synchronized (mThread.mSync) {
+ sendEmptyMessage(MSG_CREATE_SURFACE);
+ try {
+ mThread.mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ return mThread.mPreviewSurface;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public void resize(final int width, final int height) {
+ if (DEBUG) Log.v(TAG, "resize:");
+ if (mIsActive) {
+ synchronized (mThread.mSync) {
+ sendMessage(obtainMessage(MSG_RESIZE, width, height));
+ try {
+ mThread.mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ public final void release() {
+ if (DEBUG) Log.v(TAG, "release:");
+ if (mIsActive) {
+ mIsActive = false;
+ removeMessages(MSG_REQUEST_RENDER);
+ removeMessages(MSG_SET_ENCODER);
+ sendEmptyMessage(MSG_TERMINATE);
+ }
+ }
+
+ @Override
+ public final void onFrameAvailable(final SurfaceTexture surfaceTexture) {
+ if (mIsActive) {
+ mFpsCounter.count();
+ sendEmptyMessage(MSG_REQUEST_RENDER);
+ }
+ }
+
+ @Override
+ public final void handleMessage(final Message msg) {
+ if (mThread == null) return;
+ switch (msg.what) {
+// case MSG_REQUEST_RENDER:
+// mThread.onDrawFrame();
+// break;
+// case MSG_SET_ENCODER:
+// mThread.setEncoder((MediaEncoder)msg.obj);
+// break;
+ case MSG_CREATE_SURFACE:
+ mThread.updatePreviewSurface();
+ break;
+ case MSG_RESIZE:
+ mThread.resize(msg.arg1, msg.arg2);
+ break;
+ case MSG_TERMINATE:
+ Looper.myLooper().quit();
+ mThread = null;
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ private static final class RenderThread extends Thread {
+ private final Object mSync = new Object();
+ private final SurfaceTexture mSurface;
+ private RenderHandler mHandler;
+ private EGLBase mEgl;
+ /**
+ * IEglSurface instance related to this TextureView
+ */
+ private EGLBase.IEglSurface mEglSurface;
+ private GLDrawer2D mDrawer;
+ private int mTexId = -1;
+ /**
+ * SurfaceTexture instance to receive video images
+ */
+ private SurfaceTexture mPreviewSurface;
+ private final float[] mStMatrix = new float[16];
+ // private MediaEncoder mEncoder;
+ private int mViewWidth, mViewHeight;
+ private final FpsCounter mFpsCounter;
+
+ /**
+ * constructor
+ *
+ * @param surface: drawing surface came from TexureView
+ */
+ public RenderThread(final FpsCounter fpsCounter, final SurfaceTexture surface, final int width, final int height) {
+ mFpsCounter = fpsCounter;
+ mSurface = surface;
+ mViewWidth = width;
+ mViewHeight = height;
+ setName("RenderThread");
+ }
+
+ public final RenderHandler getHandler() {
+ if (DEBUG) Log.v(TAG, "RenderThread#getHandler:");
+ synchronized (mSync) {
+ // create rendering thread
+ if (mHandler == null)
+ try {
+ mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ return mHandler;
+ }
+
+ public void resize(final int width, final int height) {
+ if (((width > 0) && (width != mViewWidth)) || ((height > 0) && (height != mViewHeight))) {
+ mViewWidth = width;
+ mViewHeight = height;
+ updatePreviewSurface();
+ } else {
+ synchronized (mSync) {
+ mSync.notifyAll();
+ }
+ }
+ }
+
+ public final void updatePreviewSurface() {
+ if (DEBUG) Log.i(TAG, "RenderThread#updatePreviewSurface:");
+ synchronized (mSync) {
+ if (mPreviewSurface != null) {
+ if (DEBUG) Log.d(TAG, "updatePreviewSurface:release mPreviewSurface");
+ mPreviewSurface.setOnFrameAvailableListener(null);
+ mPreviewSurface.release();
+ mPreviewSurface = null;
+ }
+ mEglSurface.makeCurrent();
+ if (mTexId >= 0) {
+ mDrawer.deleteTex(mTexId);
+ }
+ // create texture and SurfaceTexture for input from camera
+ mTexId = mDrawer.initTex();
+ if (DEBUG) Log.v(TAG, "updatePreviewSurface:tex_id=" + mTexId);
+ mPreviewSurface = new SurfaceTexture(mTexId);
+ mPreviewSurface.setDefaultBufferSize(mViewWidth, mViewHeight);
+ mPreviewSurface.setOnFrameAvailableListener(mHandler);
+ // notify to caller thread that previewSurface is ready
+ mSync.notifyAll();
+ }
+ }
+
+// public final void onDrawFrame() {
+// mEglSurface.makeCurrent();
+// // update texture(came from camera)
+// mPreviewSurface.updateTexImage();
+// // get texture matrix
+// mPreviewSurface.getTransformMatrix(mStMatrix);
+// // notify video encoder if it exist
+// if (mEncoder != null) {
+// // notify to capturing thread that the camera frame is available.
+// if (mEncoder instanceof MediaVideoEncoder)
+// ((MediaVideoEncoder) mEncoder).frameAvailableSoon(mStMatrix);
+// else
+// mEncoder.frameAvailableSoon();
+// }
+// // draw to preview screen
+// mDrawer.draw(mTexId, mStMatrix, 0);
+// mEglSurface.swap();
+// }
+
+ @Override
+ public final void run() {
+ Log.d(TAG, getName() + " started");
+ init();
+ Looper.prepare();
+ synchronized (mSync) {
+ mHandler = new RenderHandler(mFpsCounter, this);
+ mSync.notify();
+ }
+
+ Looper.loop();
+
+ Log.d(TAG, getName() + " finishing");
+ release();
+ synchronized (mSync) {
+ mHandler = null;
+ mSync.notify();
+ }
+ }
+
+ private final void init() {
+ if (DEBUG) Log.v(TAG, "RenderThread#init:");
+ // create EGLContext for this thread
+ mEgl = EGLBase.createFrom(null, false, false);
+ mEglSurface = mEgl.createFromSurface(mSurface);
+ mEglSurface.makeCurrent();
+ // create drawing object
+ mDrawer = new GLDrawer2D(true);
+ }
+
+ private final void release() {
+ if (DEBUG) Log.v(TAG, "RenderThread#release:");
+ if (mDrawer != null) {
+ mDrawer.release();
+ mDrawer = null;
+ }
+ if (mPreviewSurface != null) {
+ mPreviewSurface.release();
+ mPreviewSurface = null;
+ }
+ if (mTexId >= 0) {
+ GLHelper.deleteTex(mTexId);
+ mTexId = -1;
+ }
+ if (mEglSurface != null) {
+ mEglSurface.release();
+ mEglSurface = null;
+ }
+ if (mEgl != null) {
+ mEgl.release();
+ mEgl = null;
+ }
+ }
+ }
+ }
+}
diff --git a/local.properties b/local.properties
index 3ec53d5..64ad2b1 100644
--- a/local.properties
+++ b/local.properties
@@ -1,10 +1,12 @@
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
-# This file should *NOT* be checked into Version Control Systems,
+# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
-sdk.dir=E\:\\Environment\\android-sdk-windows
\ No newline at end of file
+#Fri Sep 29 23:06:03 CST 2017
+ndk.dir=E\:\\Android\\Evironment\\android-sdk-windows\\ndk-bundle
+sdk.dir=E\:\\Android\\Evironment\\android-sdk-windows