From 353e8492b91c7fd2500c44ffa75727c397c4e46e Mon Sep 17 00:00:00 2001 From: donghongyu Date: Thu, 24 Feb 2022 17:47:33 +0800 Subject: [PATCH] =?UTF-8?q?[Change]=20=E4=BF=AE=E6=94=B9=E9=80=9F=E5=BA=A6?= =?UTF-8?q?=E5=8F=96=E5=80=BC=EF=BC=8C=E6=9B=B4=E6=96=B0=E8=A1=8C=E8=BD=A6?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E4=BB=AA=E4=BB=A3=E7=A0=81=E3=80=82=20DEMO?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E4=BE=9D=E9=99=84=E4=BA=8E=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E8=8E=B7=E5=8F=96YUV=E6=95=B0=E6=8D=AE=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E7=AC=A6=E5=90=88=E9=9C=80=E6=B1=82=EF=BC=8C=E8=BF=99=E9=87=8C?= =?UTF-8?q?=E5=85=88=E6=8E=A5=E5=85=A5=E7=9B=B4=E6=92=AD=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E7=BB=AD=E9=9C=80=E8=A6=81=E6=8C=89=E7=85=A7=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E6=8A=BD=E7=A6=BB=E7=9B=B4=E6=8E=A5=E9=80=9A=E8=BF=87USB?= =?UTF-8?q?=E8=8E=B7=E5=8F=96YUV=E7=9A=84=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: donghongyu --- config.gradle | 1 + .../adapter/MoGoAdasListenerImpl.java | 2 +- .../mogo-core-function-carcorder/build.gradle | 4 + .../gradle.properties | 2 +- .../src/main/AndroidManifest.xml | 13 + .../carcorder/service/CarcorderService.kt | 162 ++++++++ .../hmi/ui/carcorder/CarcorderPreviewView.kt | 4 + gradle.properties | 1 + .../com/mogo/usbcamera/USBCameraHelper.java | 39 ++ .../com/mogo/usbcamera/UVCCameraHelper.java | 26 +- .../serenegiant/dialog/DialogFragmentEx.java | 23 ++ .../dialog/MessageDialogFragment.java | 136 +++++++ .../dialog/MessageDialogFragmentV4.java | 213 +++++++++++ .../com/serenegiant/usb/CameraDialog.java | 354 +++++++++--------- .../com/serenegiant/usb/DeviceFilter.java | 23 ++ .../com/serenegiant/usb/IFrameCallback.java | 23 ++ .../main/java/com/serenegiant/usb/Size.java | 23 ++ .../java/com/serenegiant/usb/USBMonitor.java | 86 ++--- .../java/com/serenegiant/usb/USBVendorId.java | 23 ++ .../java/com/serenegiant/usb/UVCCamera.java | 23 ++ .../usb/common/AbstractUVCCameraHandler.java | 140 ++----- .../serenegiant/usb/common/BaseActivity.java | 321 ++++++++++++++++ .../serenegiant/usb/common/BaseFragment.java | 348 +++++++++++++++++ .../serenegiant/usb/common/BaseService.java | 108 ++++++ .../usb/common/UVCCameraHandler.java | 25 +- .../common/UVCCameraHandlerMultiSurface.java | 28 +- .../usb/encoder/IAudioEncoder.java | 23 ++ .../usb/encoder/IVideoEncoder.java | 23 ++ .../usb/encoder/MediaAudioEncoder.java | 23 ++ .../usb/encoder/MediaSurfaceEncoder.java | 23 ++ .../usb/encoder/MediaVideoBufferEncoder.java | 23 ++ .../serenegiant/usb/encoder/RecordParams.java | 6 +- .../usb/encoder/biz/AACEncodeConsumer.java | 178 ++++----- .../usb/encoder/biz/H264EncodeConsumer.java | 2 + .../usb/encoder/biz/Mp4MediaMuxer.java | 6 +- .../serenegiant/utils/PermissionCheck.java | 186 +++++++++ .../main/jniLibs/arm64-v8a/libUVCCamera.so | Bin 160456 -> 160456 bytes .../jniLibs/arm64-v8a/libjpeg-turbo1500.so | Bin 313296 -> 313296 bytes .../src/main/jniLibs/arm64-v8a/libusb100.so | Bin 108352 -> 108352 bytes .../src/main/jniLibs/arm64-v8a/libuvc.so | Bin 100448 -> 100448 bytes .../main/jniLibs/armeabi-v7a/libUVCCamera.so | Bin 150444 -> 150444 bytes .../jniLibs/armeabi-v7a/libjpeg-turbo1500.so | Bin 288420 -> 288420 bytes .../src/main/jniLibs/armeabi-v7a/libusb100.so | Bin 112160 -> 112160 bytes .../src/main/jniLibs/armeabi-v7a/libuvc.so | Bin 104176 -> 104176 bytes .../src/main/jniLibs/x86/libUVCCamera.so | Bin 154480 -> 154480 bytes .../src/main/jniLibs/x86/libjpeg-turbo1500.so | Bin 509540 -> 509540 bytes .../src/main/jniLibs/x86/libusb100.so | Bin 99784 -> 99784 bytes .../src/main/jniLibs/x86/libuvc.so | Bin 104112 -> 104112 bytes .../src/main/jniLibs/x86_64/libUVCCamera.so | Bin 168928 -> 168928 bytes .../main/jniLibs/x86_64/libjpeg-turbo1500.so | Bin 497888 -> 497888 bytes .../src/main/jniLibs/x86_64/libusb100.so | Bin 104512 -> 104512 bytes .../src/main/jniLibs/x86_64/libuvc.so | Bin 108912 -> 108912 bytes .../main/res/values/strings_permissions.xml | 32 ++ modules/mogo-module-main/build.gradle | 3 + .../com/mogo/module/main/MainActivity.java | 15 +- .../apm/ApmCrashReportProvider.java | 12 +- 56 files changed, 2268 insertions(+), 438 deletions(-) create mode 100644 core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt create mode 100644 libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java create mode 100644 libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java create mode 100644 libraries/map-usbcamera/src/main/res/values/strings_permissions.xml diff --git a/config.gradle b/config.gradle index a9bb3a0e99..c1c27bc09f 100644 --- a/config.gradle +++ b/config.gradle @@ -238,6 +238,7 @@ ext { mogo_core_function_v2x : "com.mogo.eagle.core.function.impl:v2x:${MOGO_CORE_FUNCTION_V2X_VERSION}", mogo_core_function_api : "com.mogo.eagle.core.function:api:${MOGO_CORE_FUNCTION_API_VERSION}", mogo_core_function_call : "com.mogo.eagle.core.function:call:${MOGO_CORE_FUNCTION_CALL_VERSION}", + mogo_core_function_carcorder : "com.mogo.eagle.core.function:carcorder:${MOGO_CORE_FUNCTION_CARCORDER_VERSION}", mogo_core_data : "com.mogo.eagle.core:data:${MOGO_CORE_DATA_VERSION}", mogo_core_res : "com.mogo.eagle.core:res:${MOGO_CORE_RES_VERSION}", mogo_core_utils : "com.mogo.eagle.core:utils:${MOGO_CORE_UTILS_VERSION}", diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java index 6e1fbd06c9..dce789946c 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasListenerImpl.java @@ -137,7 +137,7 @@ public class MoGoAdasListenerImpl implements OnAdasListener { data.putOpt("lon", stateInfo.getValues().getLon()); data.putOpt("lat", stateInfo.getValues().getLat()); data.putOpt("alt", stateInfo.getValues().getAlt()); - data.putOpt("speed", stateInfo.getValues().getGnss_speed()); + data.putOpt("speed", stateInfo.getValues().getVehicle_speed()); data.putOpt("heading", stateInfo.getValues().getHeading()); data.putOpt("acceleration", stateInfo.getValues().getAcceleration()); data.putOpt("yawRate", stateInfo.getValues().getYaw_rate()); diff --git a/core/function-impl/mogo-core-function-carcorder/build.gradle b/core/function-impl/mogo-core-function-carcorder/build.gradle index f909f820b5..d612fd342f 100644 --- a/core/function-impl/mogo-core-function-carcorder/build.gradle +++ b/core/function-impl/mogo-core-function-carcorder/build.gradle @@ -52,6 +52,8 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { implementation rootProject.ext.dependencies.mogoserviceapi implementation rootProject.ext.dependencies.modulecommon + + implementation project(':libraries:map-usbcamera') implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_function_api @@ -61,6 +63,8 @@ dependencies { implementation project(':services:mogo-service-api') implementation project(':modules:mogo-module-common') + implementation project(':libraries:map-usbcamera') + implementation project(':core:mogo-core-utils') implementation project(':core:mogo-core-function-api') implementation project(':core:mogo-core-function-call') diff --git a/core/function-impl/mogo-core-function-carcorder/gradle.properties b/core/function-impl/mogo-core-function-carcorder/gradle.properties index 3a7512bbca..ba1462e8ad 100644 --- a/core/function-impl/mogo-core-function-carcorder/gradle.properties +++ b/core/function-impl/mogo-core-function-carcorder/gradle.properties @@ -1,3 +1,3 @@ GROUP=com.mogo.eagle.core.function.impl -POM_ARTIFACT_ID=devatools +POM_ARTIFACT_ID=carcorder VERSION_CODE=1 diff --git a/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml index 3e174f9fb6..6c1ea8112e 100644 --- a/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml +++ b/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml @@ -1,4 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt new file mode 100644 index 0000000000..c355159e07 --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/CarcorderService.kt @@ -0,0 +1,162 @@ +package com.mogo.eagle.core.function.carcorder.service + +import android.content.Intent +import android.hardware.usb.UsbDevice +import android.os.IBinder +import android.util.Log +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.usbcamera.UVCCameraHelper +import com.serenegiant.usb.IFrameCallback +import com.serenegiant.usb.USBMonitor +import com.serenegiant.usb.USBMonitor.OnDeviceConnectListener +import com.serenegiant.usb.USBMonitor.UsbControlBlock +import com.serenegiant.usb.UVCCamera +import com.serenegiant.usb.common.BaseService +import com.serenegiant.usb.encoder.MediaVideoBufferEncoder + +/** + * 行车记录仪服务 + * @author donghongyu + */ +class CarcorderService : BaseService() { + private val DEBUG = true + val TAG = CarcorderService::class.java.name + + // 挂载的USB设备集合 + private var mDeviceList: List? = null + + // USB 设备连接工具 + private var mUSBMonitor: USBMonitor? = null + + // 用于接入UVC摄像机 + private var mUVCCamera: UVCCamera? = null + + // 相机控制 + private var mCtrlBlock: UsbControlBlock? = null + + /** + * 配置相机基本按书 + */ + private val previewWidth = 640 + private val previewHeight = 480 + + // Default using MJPEG + // if your device is connected,but have no images + // please try to change it to FRAME_FORMAT_YUYV + val FRAME_FORMAT_MJPEG: Int = UVCCamera.FRAME_FORMAT_MJPEG + val MODE_BRIGHTNESS = UVCCamera.PU_BRIGHTNESS + val MODE_CONTRAST = UVCCamera.PU_CONTRAST + private val mFrameFormat = UVCCameraHelper.FRAME_FORMAT_MJPEG + + override fun onCreate() { + super.onCreate() + if (DEBUG) { + Logger.d(TAG, "onCreate……") + } + if (mUSBMonitor == null) { + mUSBMonitor = USBMonitor(applicationContext, mOnDeviceConnectListener) + mUSBMonitor!!.register() + mDeviceList = mUSBMonitor!!.deviceList + } + + } + + override fun onDestroy() { + super.onDestroy() + if (DEBUG) Log.d(TAG, "onDestroy:") + if (mUSBMonitor != null) { + mUSBMonitor!!.unregister() + mUSBMonitor = null + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + return super.onStartCommand(intent, flags, startId) + } + + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onRebind(intent: Intent) { + if (DEBUG) Log.d(TAG, "onRebind:$intent") + } + + + override fun onUnbind(intent: Intent): Boolean { + if (DEBUG) Log.d(TAG, "onUnbind:$intent") + + if (DEBUG) Log.d(TAG, "onUnbind:finished") + return true + } + + //********************************************************************************************************************************** + private val mSync: Any = Any() + + private val mVideoEncoder: MediaVideoBufferEncoder? = null + + + /** + * USB 设备连接监听 + */ + private val mOnDeviceConnectListener: OnDeviceConnectListener = object : OnDeviceConnectListener { + override fun onAttach(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onAttach:${device.deviceName}---mDeviceList:${mDeviceList?.size}") + mUSBMonitor!!.requestPermission(device) + } + + override fun onConnect(device: UsbDevice, ctrlBlock: UsbControlBlock, createNew: Boolean) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onConnect:${device.deviceName}") + openCamera(device, ctrlBlock, createNew) + } + + override fun onDisconnect(device: UsbDevice, ctrlBlock: UsbControlBlock) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onDisconnect:${device.deviceName}") + } + + override fun onDettach(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onDettach:${device.deviceName}") + } + + override fun onCancel(device: UsbDevice) { + if (DEBUG) Log.d(TAG, "OnDeviceConnectListener#onCancel:${device.deviceName}") + } + } + + /** + * 连接相机 + */ + private fun openCamera(device: UsbDevice, ctrlBlock: UsbControlBlock, createNew: Boolean) { + if (mUVCCamera == null) { + mUVCCamera = UVCCamera() + mUVCCamera!!.open(ctrlBlock) + mUVCCamera!!.setStatusCallback { statusClass, event, selector, statusAttribute, data -> + if (DEBUG) Log.d(TAG, "IStatusCallback#onStatus(statusClass=${statusClass},event=${event},selector=${selector},statusAttribute=${statusAttribute},data=${data})") + } + + try { + mUVCCamera!!.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.FRAME_FORMAT_MJPEG) + } catch (e: Exception) { + e.printStackTrace() + try { + mUVCCamera!!.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.DEFAULT_PREVIEW_MODE) + } catch (e: Exception) { + e.printStackTrace() + mUVCCamera!!.destroy() + } + } + + mUVCCamera!!.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP) + mUVCCamera!!.startPreview() + } + } + + /** + * 视频帧回掉 + */ + private val mIFrameCallback = IFrameCallback { frame -> + if (DEBUG) Log.d(TAG, "IFrameCallback#onFrame:${frame}") + } + +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt index 52b9fb36a5..9ddb5dda02 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt @@ -160,6 +160,10 @@ class CarcorderPreviewView private constructor( Log.d(TAG, "onDisConnectDev") showShortMsg("相机断开连接") } + + override fun onCancelDev(device: UsbDevice?) { + Log.d(TAG, "onCancelDev:" + device?.deviceName) + } } diff --git a/gradle.properties b/gradle.properties index ba61ae3480..d6323c0ba7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -107,6 +107,7 @@ MOGO_CORE_FUNCTION_V2X_VERSION=0.0.58.10 MOGO_CORE_DATA_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_API_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_CALL_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_CARCORDER_VERSION=0.0.58.10 MOGO_CORE_FUNCTION_DEVATOOLS_VERSION=0.0.58.10 MOGO_CORE_RES_VERSION=0.0.58.10 MOGO_CORE_UTILS_VERSION=0.0.58.10 diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java new file mode 100644 index 0000000000..e61d6478d0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/USBCameraHelper.java @@ -0,0 +1,39 @@ +package com.mogo.usbcamera; + +import com.serenegiant.usb.USBMonitor; + +/** + * @author donghongyu + * USB摄像头方案,获取权限、获取YUV数据 + */ +public class USBCameraHelper { + private static USBCameraHelper mCameraHelper; + /** + * USB Manager + */ + private USBMonitor mUSBMonitor; + + private USBCameraHelper() { + } + + public static USBCameraHelper getInstance() { + if (mCameraHelper == null) { + mCameraHelper = new USBCameraHelper(); + } + return mCameraHelper; + } + + + + public void registerUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.register(); + } + } + + public void unregisterUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.unregister(); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java index 554cd199a9..4a14cd62f3 100644 --- a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java @@ -74,6 +74,9 @@ public class UVCCameraHelper { void onConnectDev(UsbDevice device, boolean isConnected); void onDisConnectDev(UsbDevice device); + + void onCancelDev(UsbDevice device); + } public void initUSBMonitor(Activity activity, CameraViewInterface cameraView, final OnMyDevConnectListener listener) { @@ -106,18 +109,15 @@ public class UVCCameraHelper { public void onConnect(final UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) { mCtrlBlock = ctrlBlock; openCamera(ctrlBlock); - new Thread(new Runnable() { - @Override - public void run() { - // wait for camera created - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // start previewing - startPreview(mCamView); + new Thread(() -> { + // wait for camera created + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); } + // start previewing + startPreview(mCamView); }).start(); if (listener != null) { listener.onConnectDev(device, true); @@ -135,6 +135,9 @@ public class UVCCameraHelper { @Override public void onCancel(UsbDevice device) { + if (listener != null) { + listener.onCancelDev(device); + } } }); @@ -249,7 +252,6 @@ public class UVCCameraHelper { public void capturePicture(String savePath, AbstractUVCCameraHandler.OnCaptureListener listener) { if (mCameraHandler != null && mCameraHandler.isOpened()) { - File file = new File(savePath); if (!Objects.requireNonNull(file.getParentFile()).exists()) { file.getParentFile().mkdirs(); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java new file mode 100644 index 0000000000..d1a390497c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/DialogFragmentEx.java @@ -0,0 +1,23 @@ +package com.serenegiant.dialog; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + +public abstract class DialogFragmentEx extends DialogFragment { + protected static final String ARGS_KEY_REQUEST_CODE = "requestCode"; + protected static final String ARGS_KEY_ID_TITLE = "title"; + protected static final String ARGS_KEY_ID_MESSAGE = "message"; + protected static final String ARGS_KEY_TAG = "tag"; + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + final Bundle args = getArguments(); + if (args != null) { + outState.putAll(args); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java new file mode 100644 index 0000000000..bfdf3595f1 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragment.java @@ -0,0 +1,136 @@ +package com.serenegiant.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; + +import com.serenegiant.utils.BuildCheck; + +/** + * パーミッション要求前に説明用のダイアログを表示するためのDialogFragment + */ +@SuppressWarnings("deprecation") +@Deprecated +public class MessageDialogFragment extends DialogFragment { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageDialogFragment.class.getSimpleName(); + + public static interface MessageDialogListener { + public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result); + } + + public static MessageDialogFragment showDialog(final Activity parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions); + dialog.show(parent.getFragmentManager(), TAG); + return dialog; + } + + public static MessageDialogFragment showDialog(final Fragment parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions); + dialog.setTargetFragment(parent, parent.getId()); + dialog.show(parent.getFragmentManager(), TAG); + return dialog; + } + + public static MessageDialogFragment newInstance(final int requestCode, final int id_title, final int id_message, final String[] permissions) { + final MessageDialogFragment fragment = new MessageDialogFragment(); + final Bundle args = new Bundle(); + // ここでパラメータをセットする + args.putInt("requestCode", requestCode); + args.putInt("title", id_title); + args.putInt("message", id_message); + args.putStringArray("permissions", permissions != null ? permissions : new String[]{}); + fragment.setArguments(args); + return fragment; + } + + private MessageDialogListener mDialogListener; + + public MessageDialogFragment() { + super(); + // デフォルトコンストラクタが必要 + } + + @SuppressLint("NewApi") + @Override + public void onAttach(final Activity activity) { + super.onAttach(activity); + // コールバックインターフェースを取得 + if (activity instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)activity; + } + if (mDialogListener == null) { + final Fragment fragment = getTargetFragment(); + if (fragment instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)fragment; + } + } + if (mDialogListener == null) { + if (BuildCheck.isAndroid4_2()) { + final Fragment target = getParentFragment(); + if (target instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)target; + } + } + } + if (mDialogListener == null) { +// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener"); + throw new ClassCastException(activity.toString()); + } + } + +// @Override +// public void onCreate(final Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); +// } + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + final int requestCode = getArguments().getInt("requestCode"); + final int id_title = getArguments().getInt("title"); + final int id_message = getArguments().getInt("message"); + final String[] permissions = args.getStringArray("permissions"); + + + return new AlertDialog.Builder(getActivity()) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(id_title) + .setMessage(id_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int whichButton) { + // 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので + // 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ + try { + mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, true); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + ) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int whichButton) { + try { + mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, false); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + ) + .create(); + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java new file mode 100644 index 0000000000..b55b276159 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/dialog/MessageDialogFragmentV4.java @@ -0,0 +1,213 @@ +package com.serenegiant.dialog; +/* + * libcommon + * utility/helper classes for myself + * + * Copyright (c) 2014-2018 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. +*/ + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.serenegiant.utils.BuildCheck; + +public class MessageDialogFragmentV4 extends DialogFragmentEx { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageDialogFragmentV4.class.getSimpleName(); + + private static final String ARGS_KEY_PERMISSIONS = "permissions"; + + /** + * ダイアログの表示結果を受け取るためのコールバックリスナー + */ + public static interface MessageDialogListener { + public void onMessageDialogResult( + @NonNull final MessageDialogFragmentV4 dialog, final int requestCode, + @NonNull final String[] permissions, final boolean result); + } + + /** + * ダイアログ表示のためのヘルパーメソッド + * @param parent + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + * @throws IllegalStateException + */ + public static MessageDialogFragmentV4 showDialog( + @NonNull final FragmentActivity parent, final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) throws IllegalStateException { + + final MessageDialogFragmentV4 dialog + = newInstance(requestCode, id_title, id_message, permissions); + dialog.show(parent.getSupportFragmentManager(), TAG); + return dialog; + } + + /** + * ダイアログ表示のためのヘルパーメソッド + * @param parent + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + * @throws IllegalStateException + */ + public static MessageDialogFragmentV4 showDialog( + @NonNull final Fragment parent, final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) throws IllegalStateException { + + final MessageDialogFragmentV4 dialog + = newInstance(requestCode, id_title, id_message, permissions); + dialog.setTargetFragment(parent, parent.getId()); + dialog.show(parent.requireFragmentManager(), TAG); + return dialog; + } + + /** + * ダイアログ生成のためのヘルパーメソッド + * ダイアログ自体を直接生成せずにこのメソッドを呼び出すこと + * @param requestCode + * @param id_title + * @param id_message + * @param permissions + * @return + */ + public static MessageDialogFragmentV4 newInstance( + final int requestCode, + @StringRes final int id_title, @StringRes final int id_message, + @NonNull final String[] permissions) { + + final MessageDialogFragmentV4 fragment = new MessageDialogFragmentV4(); + final Bundle args = new Bundle(); + // ここでパラメータをセットする + args.putInt(ARGS_KEY_REQUEST_CODE, requestCode); + args.putInt(ARGS_KEY_ID_TITLE, id_title); + args.putInt(ARGS_KEY_ID_MESSAGE, id_message); + args.putStringArray(ARGS_KEY_PERMISSIONS, permissions); + fragment.setArguments(args); + return fragment; + } + + private MessageDialogListener mDialogListener; + + /** + * コンストラクタ, 直接生成せずに#newInstanceを使うこと + */ + public MessageDialogFragmentV4() { + super(); + // デフォルトコンストラクタが必要 + } + + @Override + public void onAttach(final Context context) { + super.onAttach(context); + // コールバックインターフェースを取得 + if (context instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)context; + } + if (mDialogListener == null) { + final Fragment fragment = getTargetFragment(); + if (fragment instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)fragment; + } + } + if (mDialogListener == null) { + if (BuildCheck.isAndroid4_2()) { + final Fragment target = getParentFragment(); + if (target instanceof MessageDialogListener) { + mDialogListener = (MessageDialogListener)target; + } + } + } + if (mDialogListener == null) { +// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener"); + throw new ClassCastException(context.toString()); + } + } + +// @Override +// public void onCreate(final Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); +// } + + @NonNull + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + final Bundle args = savedInstanceState != null ? savedInstanceState : requireArguments(); + final int id_title = args.getInt(ARGS_KEY_ID_TITLE); + final int id_message = args.getInt(ARGS_KEY_ID_MESSAGE); + + final Activity activity = requireActivity(); + return new AlertDialog.Builder(activity) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(id_title) + .setMessage(id_message) + .setPositiveButton(android.R.string.ok, mOnClickListener) + .setNegativeButton(android.R.string.cancel, mOnClickListener) + .create(); + } + + private final DialogInterface.OnClickListener mOnClickListener + = new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + // 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので + // 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ + callOnMessageDialogResult(which == DialogInterface.BUTTON_POSITIVE); + } + }; + + @Override + public void onCancel(final DialogInterface dialog) { + super.onCancel(dialog); + callOnMessageDialogResult(false); + } + + /** + * コールバックリスナー呼び出しのためのヘルパーメソッド + * @param result + */ + private void callOnMessageDialogResult(final boolean result) + throws IllegalStateException { + + final Bundle args = requireArguments(); + final int requestCode = args.getInt(ARGS_KEY_REQUEST_CODE); + final String[] permissions = args.getStringArray(ARGS_KEY_PERMISSIONS); + try { + mDialogListener.onMessageDialogResult( + MessageDialogFragmentV4.this, + requestCode, permissions, result); + } catch (final Exception e) { + Log.w(TAG, e); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java index 53269a658f..dfad92686f 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java @@ -1,3 +1,26 @@ +/* + * 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; import android.app.Activity; @@ -24,199 +47,192 @@ import java.util.ArrayList; import java.util.List; public class CameraDialog extends DialogFragment { - private static final String TAG = CameraDialog.class.getSimpleName(); + private static final String TAG = CameraDialog.class.getSimpleName(); - public interface CameraDialogParent { - public USBMonitor getUSBMonitor(); + public interface CameraDialogParent { + public USBMonitor getUSBMonitor(); + public void onDialogResult(boolean canceled); + } + + /** + * Helper method + * @param parent FragmentActivity + * @return + */ + public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) { + CameraDialog dialog = newInstance(/* add parameters here if you need */); + try { + dialog.show(parent.getFragmentManager(), TAG); + } catch (final IllegalStateException e) { + dialog = null; + } + return dialog; + } - public void onDialogResult(boolean canceled); - } + public static CameraDialog newInstance(/* add parameters here if you need */) { + final CameraDialog dialog = new CameraDialog(); + final Bundle args = new Bundle(); + // add parameters here if you need + dialog.setArguments(args); + return dialog; + } - /** - * Helper method - * - * @param parent FragmentActivity - * @return - */ - public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) { - CameraDialog dialog = newInstance(/* add parameters here if you need */); + protected USBMonitor mUSBMonitor; + private Spinner mSpinner; + private DeviceListAdapter mDeviceListAdapter; + + public CameraDialog(/* no arguments */) { + // Fragment need default constructor + } + + @SuppressWarnings("deprecation") + @Override + public void onAttach(final Activity activity) { + super.onAttach(activity); + if (mUSBMonitor == null) try { - dialog.show(parent.getFragmentManager(), TAG); - } catch (final IllegalStateException e) { - dialog = null; + mUSBMonitor = ((CameraDialogParent)activity).getUSBMonitor(); + } catch (final ClassCastException e) { + } catch (final NullPointerException e) { } - return dialog; - } + if (mUSBMonitor == null) { + throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); + } + } - public static CameraDialog newInstance(/* add parameters here if you need */) { - final CameraDialog dialog = new CameraDialog(); - final Bundle args = new Bundle(); - // add parameters here if you need - dialog.setArguments(args); - return dialog; - } - - protected USBMonitor mUSBMonitor; - private Spinner mSpinner; - private DeviceListAdapter mDeviceListAdapter; - - public CameraDialog(/* no arguments */) { - // Fragment need default constructor - } - - @SuppressWarnings("deprecation") - @Override - public void onAttach(final Activity activity) { - super.onAttach(activity); - if (mUSBMonitor == null) { - try { - mUSBMonitor = ((CameraDialogParent) activity).getUSBMonitor(); - } catch (final NullPointerException | ClassCastException e) { - e.printStackTrace(); - } - } - if (mUSBMonitor == null) { - throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); - } - } - - @Override + @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState == null) { - savedInstanceState = getArguments(); - } - } + super.onCreate(savedInstanceState); + if (savedInstanceState == null) + savedInstanceState = getArguments(); + } - @Override - public void onSaveInstanceState(final Bundle saveInstanceState) { - final Bundle args = getArguments(); - if (args != null) { - saveInstanceState.putAll(args); - } - super.onSaveInstanceState(saveInstanceState); - } + @Override + public void onSaveInstanceState(final Bundle saveInstanceState) { + final Bundle args = getArguments(); + if (args != null) + saveInstanceState.putAll(args); + super.onSaveInstanceState(saveInstanceState); + } - @Override + @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setView(initView()); - builder.setTitle(R.string.select); - builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener); - builder.setNegativeButton(android.R.string.cancel, mOnDialogClickListener); - builder.setNeutralButton(R.string.refresh, null); - final Dialog dialog = builder.create(); - dialog.setCancelable(true); - dialog.setCanceledOnTouchOutside(true); + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(initView()); + builder.setTitle(R.string.select); + builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener); + builder.setNegativeButton(android.R.string.cancel , mOnDialogClickListener); + builder.setNeutralButton(R.string.refresh, null); + final Dialog dialog = builder.create(); + dialog.setCancelable(true); + dialog.setCanceledOnTouchOutside(true); return dialog; - } + } - /** - * create view that this fragment shows - * - * @return - */ - private final View initView() { - final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null); - mSpinner = (Spinner) rootView.findViewById(R.id.spinner1); - final View empty = rootView.findViewById(android.R.id.empty); - mSpinner.setEmptyView(empty); - return rootView; - } + /** + * create view that this fragment shows + * @return + */ + private final View initView() { + final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null); + mSpinner = (Spinner)rootView.findViewById(R.id.spinner1); + final View empty = rootView.findViewById(android.R.id.empty); + mSpinner.setEmptyView(empty); + return rootView; + } - @Override - public void onResume() { - super.onResume(); - updateDevices(); - final Button button = (Button) getDialog().findViewById(android.R.id.button3); - if (button != null) { - button.setOnClickListener(mOnClickListener); - } - } + @Override + public void onResume() { + super.onResume(); + updateDevices(); + final Button button = (Button)getDialog().findViewById(android.R.id.button3); + if (button != null) { + button.setOnClickListener(mOnClickListener); + } + } - private final OnClickListener mOnClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - switch (v.getId()) { - case android.R.id.button3: - updateDevices(); - break; - } - } - }; + private final OnClickListener mOnClickListener = new OnClickListener() { + @Override + public void onClick(final View v) { + switch (v.getId()) { + case android.R.id.button3: + updateDevices(); + break; + } + } + }; - private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final Object item = mSpinner.getSelectedItem(); - if (item instanceof UsbDevice) { - mUSBMonitor.requestPermission((UsbDevice) item); - ((CameraDialogParent) getActivity()).onDialogResult(false); - } - break; - case DialogInterface.BUTTON_NEGATIVE: - ((CameraDialogParent) getActivity()).onDialogResult(true); - break; - } - } - }; + private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + final Object item = mSpinner.getSelectedItem(); + if (item instanceof UsbDevice) { + mUSBMonitor.requestPermission((UsbDevice)item); + ((CameraDialogParent)getActivity()).onDialogResult(false); + } + break; + case DialogInterface.BUTTON_NEGATIVE: + ((CameraDialogParent)getActivity()).onDialogResult(true); + break; + } + } + }; - @Override - public void onCancel(final DialogInterface dialog) { - ((CameraDialogParent) getActivity()).onDialogResult(true); - super.onCancel(dialog); - } + @Override + public void onCancel(final DialogInterface dialog) { + ((CameraDialogParent)getActivity()).onDialogResult(true); + super.onCancel(dialog); + } - public void updateDevices() { + public void updateDevices() { // mUSBMonitor.dumpDevices(); - final List filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter); - mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0))); - mSpinner.setAdapter(mDeviceListAdapter); - } + final List filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter); + mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0))); + mSpinner.setAdapter(mDeviceListAdapter); + } - private static final class DeviceListAdapter extends BaseAdapter { + private static final class DeviceListAdapter extends BaseAdapter { - private final LayoutInflater mInflater; - private final List mList; + private final LayoutInflater mInflater; + private final List mList; - public DeviceListAdapter(final Context context, final List list) { - mInflater = LayoutInflater.from(context); - mList = list != null ? list : new ArrayList(); - } + public DeviceListAdapter(final Context context, final Listlist) { + mInflater = LayoutInflater.from(context); + mList = list != null ? list : new ArrayList(); + } - @Override - public int getCount() { - return mList.size(); - } + @Override + public int getCount() { + return mList.size(); + } - @Override - public UsbDevice getItem(final int position) { - if ((position >= 0) && (position < mList.size())) { - return mList.get(position); - } else { - return null; - } - } + @Override + public UsbDevice getItem(final int position) { + if ((position >= 0) && (position < mList.size())) + return mList.get(position); + else + return null; + } - @Override - public long getItemId(final int position) { - return position; - } + @Override + public long getItemId(final int position) { + return position; + } - @Override - public View getView(final int position, View convertView, final ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(R.layout.listitem_device, parent, false); - } - if (convertView instanceof CheckedTextView) { - final UsbDevice device = getItem(position); - ((CheckedTextView) convertView).setText( - String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName())); - } - return convertView; - } - } + @Override + public View getView(final int position, View convertView, final ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.listitem_device, parent, false); + } + if (convertView instanceof CheckedTextView) { + final UsbDevice device = getItem(position); + ((CheckedTextView)convertView).setText( + String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName())); + } + return convertView; + } + } } diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java index 61b9c09d61..2e94263c92 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java @@ -1,3 +1,26 @@ +/* + * 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; import android.content.Context; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java index 3d00d77d29..f6aee755e0 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java @@ -1,3 +1,26 @@ +/* + * 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; import java.nio.ByteBuffer; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java index 55bf0b19b7..963a805398 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java @@ -1,3 +1,26 @@ +/* + * 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; import android.os.Parcel; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java index 1eea770006..b46ee9f4a6 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java @@ -1,3 +1,26 @@ +/* + * 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; import android.annotation.SuppressLint; @@ -97,20 +120,15 @@ public final class USBMonitor { } public USBMonitor(final Context context, final OnDeviceConnectListener listener) { - if (DEBUG) { - Log.v(TAG, "USBMonitor:Constructor"); - } - if (listener == null) { + if (DEBUG) Log.v(TAG, "USBMonitor:Constructor"); + if (listener == null) throw new IllegalArgumentException("OnDeviceConnectListener should not null."); - } mWeakContext = new WeakReference(context); mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); mOnDeviceConnectListener = listener; mAsyncHandler = HandlerThreadHandler.createHandler(TAG); destroyed = false; - if (DEBUG) { - Log.v(TAG, "USBMonitor:mUsbManager=" + mUsbManager); - } + if (DEBUG) Log.v(TAG, "USBMonitor:mUsbManager=" + mUsbManager); } /** @@ -118,9 +136,7 @@ public final class USBMonitor { * never reuse again */ public void destroy() { - if (DEBUG) { - Log.i(TAG, "destroy:"); - } + if (DEBUG) Log.i(TAG, "destroy:"); unregister(); if (!destroyed) { destroyed = true; @@ -153,13 +169,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public synchronized void register() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); if (mPermissionIntent == null) { - if (DEBUG) { - Log.i(TAG, "register:"); - } + if (DEBUG) Log.i(TAG, "register:"); final Context context = mWeakContext.get(); if (context != null) { mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0); @@ -209,9 +221,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.add(filter); } @@ -222,9 +232,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.add(filter); } @@ -234,9 +242,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.remove(filter); } @@ -246,9 +252,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.addAll(filters); } @@ -259,9 +263,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.addAll(filters); } @@ -270,9 +272,7 @@ public final class USBMonitor { * @param filters */ public void removeDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.removeAll(filters); } @@ -282,9 +282,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public int getDeviceCount() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList().size(); } @@ -294,9 +292,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList() throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList(mDeviceFilters); } @@ -307,9 +303,7 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList(final List filters) throws IllegalStateException { - if (destroyed) { - throw new IllegalStateException("already destroyed"); - } + if (destroyed) throw new IllegalStateException("already destroyed"); // get detected devices final HashMap deviceList = mUsbManager.getDeviceList(); // store those devices info before matching filter xml file @@ -353,8 +347,8 @@ public final class USBMonitor { break; } else { // collection failed dev's class and subclass - String devModel = android.os.Build.MODEL; - String devSystemVersion = android.os.Build.VERSION.RELEASE; + String devModel = Build.MODEL; + String devSystemVersion = Build.VERSION.RELEASE; String devClass = String.valueOf(device.getDeviceClass()); String subClass = String.valueOf(device.getDeviceSubclass()); try{ diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java index f2577935a6..d354b66ca7 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java @@ -1,3 +1,26 @@ +/* + * 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; import android.util.SparseArray; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java index b73940d1b6..15a4180b75 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java @@ -1,3 +1,26 @@ +/* + * 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; import android.graphics.SurfaceTexture; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java index 00aa36d45a..992d1cf562 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java @@ -2,9 +2,7 @@ package com.serenegiant.usb.common; import android.annotation.TargetApi; import android.app.Activity; -import android.content.Context; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; @@ -22,7 +20,6 @@ import android.view.Surface; import android.view.SurfaceHolder; import com.serenegiant.usb.IFrameCallback; -import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.Size; import com.serenegiant.usb.USBMonitor; import com.serenegiant.usb.UVCCamera; @@ -52,7 +49,6 @@ import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -68,19 +64,19 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 对外回调接口 public interface CameraCallback { - void onOpen(); + public void onOpen(); - void onClose(); + public void onClose(); - void onStartPreview(); + public void onStartPreview(); - void onStopPreview(); + public void onStopPreview(); - void onStartRecording(); + public void onStartRecording(); - void onStopRecording(); + public void onStopRecording(); - void onError(final Exception e); + public void onError(final Exception e); } public static OnEncodeResultListener mListener; @@ -180,16 +176,12 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void close() { - if (DEBUG) { - Log.v(TAG, "close:"); - } + if (DEBUG) Log.v(TAG, "close:"); if (isOpened()) { stopPreview(); sendEmptyMessage(MSG_CLOSE); } - if (DEBUG) { - Log.v(TAG, "close:finished"); - } + if (DEBUG) Log.v(TAG, "close:finished"); } // 切换分辨率 @@ -214,18 +206,14 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 关闭Camera预览 public void stopPreview() { - if (DEBUG) { - Log.v(TAG, "stopPreview:"); - } + if (DEBUG) Log.v(TAG, "stopPreview:"); removeMessages(MSG_PREVIEW_START); if (isRecording()) { stopRecording(); } if (isPreviewing()) { final CameraThread thread = mWeakThread.get(); - if (thread == null) { - return; - } + if (thread == null) return; synchronized (thread.mSync) { sendEmptyMessage(MSG_PREVIEW_STOP); if (!isCameraThread()) { @@ -239,12 +227,10 @@ public abstract class AbstractUVCCameraHandler extends Handler { } } } - if (DEBUG) { - Log.v(TAG, "stopPreview:finished"); - } + if (DEBUG) Log.v(TAG, "stopPreview:finished"); } - public void captureStill(final String path, AbstractUVCCameraHandler.OnCaptureListener listener) { + public void captureStill(final String path, OnCaptureListener listener) { AbstractUVCCameraHandler.mCaptureListener = listener; checkReleased(); sendMessage(obtainMessage(MSG_CAPTURE_STILL, path)); @@ -366,9 +352,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { @Override public void handleMessage(final Message msg) { final CameraThread thread = mWeakThread.get(); - if (thread == null) { - return; - } + if (thread == null) return; switch (msg.what) { case MSG_OPEN: thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj); @@ -472,17 +456,13 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public AbstractUVCCameraHandler getHandler() { - if (DEBUG) { - Log.v(TAG_THREAD, "getHandler:"); - } + if (DEBUG) Log.v(TAG_THREAD, "getHandler:"); synchronized (mSync) { - if (mHandler == null) { + if (mHandler == null) try { mSync.wait(); } catch (final InterruptedException e) { - e.printStackTrace(); } - } } return mHandler; } @@ -528,9 +508,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleOpen:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleOpen:"); handleClose(); try { final UVCCamera camera = new UVCCamera(); @@ -542,15 +520,12 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { callOnError(e); } - if (DEBUG) { + if (DEBUG) Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null)); - } } public void handleClose() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleClose:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleClose:"); handleStopPusher(); final UVCCamera camera; synchronized (mSync) { @@ -565,12 +540,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStartPreview(final Object surface) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStartPreview:"); - } - if ((mUVCCamera == null) || mIsPreviewing) { - return; - } + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || mIsPreviewing) return; try { mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor); // 获取USB Camera预览数据,使用NV21颜色会失真 @@ -603,9 +574,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStopPreview() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStopPreview:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:"); if (mIsPreviewing) { if (mUVCCamera != null) { mUVCCamera.stopPreview(); @@ -617,20 +586,14 @@ public abstract class AbstractUVCCameraHandler extends Handler { } callOnStopPreview(); } - if (DEBUG) { - Log.v(TAG_THREAD, "handleStopPreview:finished"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished"); } // 捕获静态图片 public void handleCaptureStill(final String path) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleCaptureStill:"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:"); final Activity parent = mWeakParent.get(); - if (parent == null) { - return; - } + 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(mWidth, mHeight); @@ -647,7 +610,6 @@ public abstract class AbstractUVCCameraHandler extends Handler { os.flush(); mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath())); } catch (final IOException e) { - e.printStackTrace(); } } finally { os.close(); @@ -706,9 +668,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { private H264EncodeConsumer mH264Consumer; public void handleStartPusher(RecordParams params) { - if ((mUVCCamera == null) || (mH264Consumer != null)) { + if ((mUVCCamera == null) || (mH264Consumer != null)) return; - } // // 获取USB Camera预览数据 // mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); @@ -747,9 +708,8 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 停止音视频编码线程 stopAudioRecord(); stopVideoRecord(); - if(isSupportOverlay) { + if(isSupportOverlay) TxtOverlay.getInstance().release(); - } // // 停止捕获视频数据 // if (mUVCCamera != null) { // mUVCCamera.stopCapture(); @@ -915,23 +875,18 @@ public abstract class AbstractUVCCameraHandler extends Handler { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void handleUpdateMedia(final String path) { - if (DEBUG) { - Log.v(TAG_THREAD, "handleUpdateMedia:path=" + 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"); - } + 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()) { + if (released || parent.isDestroyed()) handleRelease(); - } } else { Log.w(TAG, "MainActivity already destroyed"); // give up to add this movie to MediaStore now. @@ -941,71 +896,57 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleRelease() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); - } + 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"); - } + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished"); } // 自动对焦 public void handleCameraFoucs() { - if (DEBUG) { - Log.v(TAG_THREAD, "handleStartPreview:"); - } - if ((mUVCCamera == null) || !mIsPreviewing) { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || !mIsPreviewing) return; - } mUVCCamera.setAutoFocus(true); } // 获取支持的分辨率 public List getSupportedSizes() { - if ((mUVCCamera == null) || !mIsPreviewing) { + if ((mUVCCamera == null) || !mIsPreviewing) return null; - } return mUVCCamera.getSupportedSizeList(); } private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() { @Override public void onPrepared(final MediaEncoder encoder) { - if (DEBUG) { - Log.v(TAG, "onPrepared:encoder=" + encoder); - } + if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder); mIsRecording = true; - if (encoder instanceof MediaVideoEncoder) { + if (encoder instanceof MediaVideoEncoder) try { mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } - if (encoder instanceof MediaSurfaceEncoder) { + if (encoder instanceof MediaSurfaceEncoder) try { mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder); mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface()); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void onStopped(final MediaEncoder encoder) { - if (DEBUG) { - Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); - } + if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); if ((encoder instanceof MediaVideoEncoder) - || (encoder instanceof MediaSurfaceEncoder)) { + || (encoder instanceof MediaSurfaceEncoder)) try { mIsRecording = false; final Activity parent = mWeakParent.get(); @@ -1027,7 +968,6 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - } } @Override diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java new file mode 100644 index 0000000000..8a446cd2a8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseActivity.java @@ -0,0 +1,321 @@ +package com.serenegiant.usb.common; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; + +import com.mogo.usbcamera.R; +import com.serenegiant.dialog.MessageDialogFragmentV4; +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; +import com.serenegiant.utils.PermissionCheck; + +/** + * Created by saki on 2016/11/18. + * + */ +public class BaseActivity extends AppCompatActivity + implements MessageDialogFragmentV4.MessageDialogListener { + + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseActivity.class.getSimpleName(); + + /** UI操作のためのHandler */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** ワーカースレッド上で処理するためのHandler */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + protected void onPause() { + clearToast(); + super.onPause(); + } + + @Override + protected synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } + +//================================================================================ + private Toast mToast; + /** + * Toastでメッセージを表示 + * @param msg + */ + protected void showToast(@StringRes final int msg, final Object... args) { + removeFromUiThread(mShowToastTask); + mShowToastTask = new ShowToastTask(msg, args); + runOnUiThread(mShowToastTask, 0); + } + + /** + * Toastが表示されていればキャンセルする + */ + protected void clearToast() { + removeFromUiThread(mShowToastTask); + mShowToastTask = null; + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + } catch (final Exception e) { + // ignore + } + } + + private ShowToastTask mShowToastTask; + private final class ShowToastTask implements Runnable { + final int msg; + final Object args; + private ShowToastTask(@StringRes final int msg, final Object... args) { + this.msg = msg; + this.args = args; + } + + @Override + public void run() { + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + final String _msg = (args != null) ? getString(msg, args) : getString(msg); + mToast = Toast.makeText(BaseActivity.this, _msg, Toast.LENGTH_SHORT); + mToast.show(); + } catch (final Exception e) { + // ignore + } + } + } + +//================================================================================ + /** + * MessageDialogFragmentメッセージダイアログからのコールバックリスナー + * @param dialog + * @param requestCode + * @param permissions + * @param result + */ + @SuppressLint("NewApi") + @Override + public void onMessageDialogResult(final MessageDialogFragmentV4 dialog, final int requestCode, final String[] permissions, final boolean result) { + if (result) { + // メッセージダイアログでOKを押された時はパーミッション要求する + if (BuildCheck.isMarshmallow()) { + requestPermissions(permissions, requestCode); + return; + } + } + // メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す + for (final String permission: permissions) { + checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(this, permission)); + } + } + + /** + * パーミッション要求結果を受け取るためのメソッド + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく + final int n = Math.min(permissions.length, grantResults.length); + for (int i = 0; i < n; i++) { + checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } + } + + /** + * パーミッション要求の結果をチェック + * ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ + * @param requestCode + * @param permission + * @param result + */ + protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) { + // パーミッションがないときにはメッセージを表示する + if (!result && (permission != null)) { + if (Manifest.permission.RECORD_AUDIO.equals(permission)) { + showToast(R.string.permission_audio); + } + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { + showToast(R.string.permission_ext_storage); + } + if (Manifest.permission.INTERNET.equals(permission)) { + showToast(R.string.permission_network); + } + } + } + + // 動的パーミッション要求時の要求コード + protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345; + protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567; + protected static final int REQUEST_PERMISSION_NETWORK = 0x345678; + protected static final int REQUEST_PERMISSION_CAMERA = 0x537642; + + /** + * 外部ストレージへの書き込みパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true 外部ストレージへの書き込みパーミッションが有る + */ + protected boolean checkPermissionWriteExternalStorage() { + if (!PermissionCheck.hasWriteExternalStorage(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE, + R.string.permission_title, R.string.permission_ext_storage_request, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}); + return false; + } + return true; + } + + /** + * 録音のパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true 録音のパーミッションが有る + */ + protected boolean checkPermissionAudio() { + if (!PermissionCheck.hasAudio(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING, + R.string.permission_title, R.string.permission_audio_recording_request, + new String[]{Manifest.permission.RECORD_AUDIO}); + return false; + } + return true; + } + + /** + * ネットワークアクセスのパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * @return true ネットワークアクセスのパーミッションが有る + */ + protected boolean checkPermissionNetwork() { + if (!PermissionCheck.hasNetwork(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_NETWORK, + R.string.permission_title, R.string.permission_network_request, + new String[]{Manifest.permission.INTERNET}); + return false; + } + return true; + } + + /** + * カメラアクセスのパーミッションがあるかどうかをチェック + * なければ説明ダイアログを表示する + * @return true カメラアクセスのパーミッションが有る + */ + protected boolean checkPermissionCamera() { + if (!PermissionCheck.hasCamera(this)) { + MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_CAMERA, + R.string.permission_title, R.string.permission_camera_request, + new String[]{Manifest.permission.CAMERA}); + return false; + } + return true; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java new file mode 100644 index 0000000000..4e883ce185 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseFragment.java @@ -0,0 +1,348 @@ +package com.serenegiant.usb.common; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Fragment; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import com.mogo.usbcamera.R; +import com.serenegiant.dialog.MessageDialogFragment; +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; +import com.serenegiant.utils.PermissionCheck; + +/** + * Created by saki on 2016/11/19. + */ +public class BaseFragment extends Fragment + implements MessageDialogFragment.MessageDialogListener { + + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseFragment.class.getSimpleName(); + + /** + * UI操作のためのHandler + */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** + * ワーカースレッド上で処理するためのHandler + */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + public BaseFragment() { + super(); + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + public void onPause() { + clearToast(); + super.onPause(); + } + + @Override + public synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) { + return; + } + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) { + return; + } + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } + + //================================================================================ + private Toast mToast; + + /** + * Toastでメッセージを表示 + * + * @param msg + */ + protected void showToast(@StringRes final int msg, final Object... args) { + removeFromUiThread(mShowToastTask); + mShowToastTask = new ShowToastTask(msg, args); + runOnUiThread(mShowToastTask, 0); + } + + /** + * Toastが表示されていればキャンセルする + */ + protected void clearToast() { + removeFromUiThread(mShowToastTask); + mShowToastTask = null; + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + } catch (final Exception e) { + // ignore + } + } + + private ShowToastTask mShowToastTask; + + private final class ShowToastTask implements Runnable { + final int msg; + final Object args; + + private ShowToastTask(@StringRes final int msg, final Object... args) { + this.msg = msg; + this.args = args; + } + + @Override + public void run() { + try { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + if (args != null) { + final String _msg = getString(msg, args); + mToast = Toast.makeText(getActivity(), _msg, Toast.LENGTH_SHORT); + } else { + mToast = Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT); + } + mToast.show(); + } catch (final Exception e) { + // ignore + } + } + } + +//================================================================================ + + /** + * MessageDialogFragmentメッセージダイアログからのコールバックリスナー + * + * @param dialog + * @param requestCode + * @param permissions + * @param result + */ + @SuppressLint("NewApi") + @Override + public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result) { + if (result) { + // メッセージダイアログでOKを押された時はパーミッション要求する + if (BuildCheck.isMarshmallow()) { + requestPermissions(permissions, requestCode); + return; + } + } + // メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す + for (final String permission : permissions) { + checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(getActivity(), permission)); + } + } + + /** + * パーミッション要求結果を受け取るためのメソッド + * + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく + final int n = Math.min(permissions.length, grantResults.length); + for (int i = 0; i < n; i++) { + checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED); + } + } + + /** + * パーミッション要求の結果をチェック + * ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ + * + * @param requestCode + * @param permission + * @param result + */ + protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) { + // パーミッションがないときにはメッセージを表示する + if (!result && (permission != null)) { + if (Manifest.permission.RECORD_AUDIO.equals(permission)) { + showToast(R.string.permission_audio); + } + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) { + showToast(R.string.permission_ext_storage); + } + if (Manifest.permission.INTERNET.equals(permission)) { + showToast(R.string.permission_network); + } + } + } + + // 動的パーミッション要求時の要求コード + protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345; + protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567; + protected static final int REQUEST_PERMISSION_NETWORK = 0x345678; + protected static final int REQUEST_PERMISSION_CAMERA = 0x537642; + + /** + * 外部ストレージへの書き込みパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true 外部ストレージへの書き込みパーミッションが有る + */ + protected boolean checkPermissionWriteExternalStorage() { + if (!PermissionCheck.hasWriteExternalStorage(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE, + R.string.permission_title, R.string.permission_ext_storage_request, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}); + return false; + } + return true; + } + + /** + * 録音のパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true 録音のパーミッションが有る + */ + protected boolean checkPermissionAudio() { + if (!PermissionCheck.hasAudio(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING, + R.string.permission_title, R.string.permission_audio_recording_request, + new String[]{Manifest.permission.RECORD_AUDIO}); + return false; + } + return true; + } + + /** + * ネットワークアクセスのパーミッションが有るかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true ネットワークアクセスのパーミッションが有る + */ + protected boolean checkPermissionNetwork() { + if (!PermissionCheck.hasNetwork(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_NETWORK, + R.string.permission_title, R.string.permission_network_request, + new String[]{Manifest.permission.INTERNET}); + return false; + } + return true; + } + + /** + * カメラアクセスのパーミッションがあるかどうかをチェック + * なければ説明ダイアログを表示する + * + * @return true カメラアクセスのパーミッションが有る + */ + protected boolean checkPermissionCamera() { + if (!PermissionCheck.hasCamera(getActivity())) { + MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_CAMERA, + R.string.permission_title, R.string.permission_camera_request, + new String[]{Manifest.permission.CAMERA}); + return false; + } + return true; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java new file mode 100644 index 0000000000..78a2307af6 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/BaseService.java @@ -0,0 +1,108 @@ +package com.serenegiant.usb.common; + +import android.app.Service; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.serenegiant.utils.HandlerThreadHandler; + +public abstract class BaseService extends Service { + private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること + private static final String TAG = BaseService.class.getSimpleName(); + + /** UI操作のためのHandler */ + private final Handler mUIHandler = new Handler(Looper.getMainLooper()); + private final Thread mUiThread = mUIHandler.getLooper().getThread(); + /** ワーカースレッド上で処理するためのHandler */ + private Handler mWorkerHandler; + private long mWorkerThreadID = -1; + + @Override + public void onCreate() { + super.onCreate(); + // ワーカースレッドを生成 + if (mWorkerHandler == null) { + mWorkerHandler = HandlerThreadHandler.createHandler(TAG); + mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId(); + } + } + + @Override + public synchronized void onDestroy() { + // ワーカースレッドを破棄 + if (mWorkerHandler != null) { + try { + mWorkerHandler.getLooper().quit(); + } catch (final Exception e) { + // + } + mWorkerHandler = null; + } + super.onDestroy(); + } + +//================================================================================ + /** + * UIスレッドでRunnableを実行するためのヘルパーメソッド + * @param task + * @param duration + */ + public final void runOnUiThread(final Runnable task, final long duration) { + if (task == null) return; + mUIHandler.removeCallbacks(task); + if ((duration > 0) || Thread.currentThread() != mUiThread) { + mUIHandler.postDelayed(task, duration); + } else { + try { + task.run(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + /** + * UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する + * @param task + */ + public final void removeFromUiThread(final Runnable task) { + if (task == null) return; + mUIHandler.removeCallbacks(task); + } + + /** + * ワーカースレッド上で指定したRunnableを実行する + * 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される) + * @param task + * @param delayMillis + */ + protected final synchronized void queueEvent(final Runnable task, final long delayMillis) { + if ((task == null) || (mWorkerHandler == null)) return; + try { + mWorkerHandler.removeCallbacks(task); + if (delayMillis > 0) { + mWorkerHandler.postDelayed(task, delayMillis); + } else if (mWorkerThreadID == Thread.currentThread().getId()) { + task.run(); + } else { + mWorkerHandler.post(task); + } + } catch (final Exception e) { + // ignore + } + } + + /** + * 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする + * @param task + */ + protected final synchronized void removeEvent(final Runnable task) { + if (task == null) return; + try { + mWorkerHandler.removeCallbacks(task); + } catch (final Exception e) { + // ignore + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java index b102139d6d..2d01f1cd25 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java @@ -1,9 +1,30 @@ +/* + * 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.content.Context; -import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java index 7619d39df4..d8df6c26d1 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java @@ -1,10 +1,32 @@ +/* + * 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.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; @@ -101,7 +123,6 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null); } - @Override public synchronized void release() { if (mRendererHolder != null) { mRendererHolder.release(); @@ -110,8 +131,7 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { super.release(); } - @Override - public synchronized void resize(final int width, final int height) { + public synchronized void resize(final int width, final int height) { super.resize(width, height); if (mRendererHolder != null) { mRendererHolder.resize(width, height); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java index eef9858a73..9a0c0bdb7e 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; public interface IAudioEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java index 24370c80ff..7b8429d262 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; public interface IVideoEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java index 761419aa50..3246364cff 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.AudioFormat; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java index fc3e0a38ce..47eb607e08 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java index 30d3242bb9..40d26bfaa0 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java @@ -1,3 +1,26 @@ +/* + * 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.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java index 159218211a..0265cac1ea 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java @@ -1,8 +1,10 @@ package com.serenegiant.usb.encoder; -/** - * 录制参数 +/** 录制参数 + * + * Created by jiangdongguo on 2017/10/19. */ + public class RecordParams { private String recordPath; private int recordDuration; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java index 0cbf441869..69319e9b7d 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java @@ -19,10 +19,12 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; -/** - * 将PCM编码为AAC +/**将PCM编码为AAC + * + * Created by jianddongguo on 2017/7/21. */ -public class AACEncodeConsumer extends Thread { + +public class AACEncodeConsumer extends Thread{ private static final boolean DEBUG = false; private static final String TAG = "TMPU"; private static final String MIME_TYPE = "audio/mp4a-latm"; @@ -38,7 +40,7 @@ public class AACEncodeConsumer extends Thread { private AudioRecord mAudioRecord; // 音频采集 private MediaCodec mAudioEncoder; // 音频编码 private OnAACEncodeResultListener listener; - private int mSamplingRateIndex = 0;//ADTS + private int mSamplingRateIndex = 0;//ADTS private boolean isEncoderStart = false; private boolean isRecMp3 = false; private boolean isExit = false; @@ -46,7 +48,7 @@ public class AACEncodeConsumer extends Thread { private WeakReference mMuxerRef; private MediaFormat newFormat; - private static final int[] AUDIO_SOURCES = new int[]{ + private static final int[] AUDIO_SOURCES = new int[] { MediaRecorder.AudioSource.DEFAULT, MediaRecorder.AudioSource.MIC, MediaRecorder.AudioSource.CAMCORDER, @@ -54,7 +56,7 @@ public class AACEncodeConsumer extends Thread { /** * There are 13 supported frequencies by ADTS. **/ - public static final int[] AUDIO_SAMPLING_RATES = {96000, // 0 + public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 88200, // 1 64000, // 2 48000, // 3 @@ -75,13 +77,13 @@ public class AACEncodeConsumer extends Thread { private FileOutputStream fops; // 编码流结果回调接口 - public interface OnAACEncodeResultListener { + public interface OnAACEncodeResultListener{ void onEncodeResult(byte[] data, int offset, int length, long timestamp); } - public AACEncodeConsumer() { - for (int i = 0; i < AUDIO_SAMPLING_RATES.length; i++) { + public AACEncodeConsumer(){ + for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) { mSamplingRateIndex = i; break; @@ -89,16 +91,16 @@ public class AACEncodeConsumer extends Thread { } } - public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener) { + public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){ this.listener = listener; } - public void exit() { + public void exit(){ isExit = true; } - public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer) { - this.mMuxerRef = new WeakReference<>(mMuxer); + public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){ + this.mMuxerRef = new WeakReference<>(mMuxer); Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null && newFormat != null) { muxer.addTrack(newFormat, false); @@ -108,7 +110,7 @@ public class AACEncodeConsumer extends Thread { @Override public void run() { // 开启音频采集、编码 - if (!isEncoderStart) { + if(! isEncoderStart){ initAudioRecord(); initMediaCodec(); } @@ -116,16 +118,16 @@ public class AACEncodeConsumer extends Thread { byte[] mp3Buffer = new byte[1024]; // 这里有问题,当本地录制结束后,没有写入 - while (!isExit) { + while(! isExit){ byte[] audioBuffer = new byte[2048]; // 采集音频 - int readBytes = mAudioRecord.read(audioBuffer, 0, BUFFER_SIZE); + int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE); - if (DEBUG) - Log.i(TAG, "采集音频readBytes = " + readBytes); + if(DEBUG) + Log.i(TAG,"采集音频readBytes = "+readBytes); // 编码音频 - if (readBytes > 0) { - encodeBytes(audioBuffer, readBytes); + if(readBytes > 0){ + encodeBytes(audioBuffer,readBytes); } } // 停止音频采集、编码 @@ -140,81 +142,81 @@ public class AACEncodeConsumer extends Thread { ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers(); //返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区 int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT); - if (inputBufferIndex >= 0) { + if(inputBufferIndex >= 0){ // 绑定一个被空的、可写的输入缓存区inputBuffer到客户端 - ByteBuffer inputBuffer = null; - if (!isLollipop()) { + ByteBuffer inputBuffer = null; + if(!isLollipop()){ inputBuffer = inputBuffers[inputBufferIndex]; - } else { + }else{ inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex); } // 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理 - if (audioBuf == null || readBytes <= 0) { - mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, 0, getPTSUs(), MediaCodec.BUFFER_FLAG_END_OF_STREAM); - } else { + if(audioBuf==null || readBytes<=0){ + mAudioEncoder.queueInputBuffer(inputBufferIndex,0,0,getPTSUs(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); + }else{ inputBuffer.clear(); inputBuffer.put(audioBuf); - mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, readBytes, getPTSUs(), 0); + mAudioEncoder.queueInputBuffer(inputBufferIndex,0,readBytes,getPTSUs(),0); } } // 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区 // mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间 - MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = -1; - do { - outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo, TIMES_OUT); - if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { - if (DEBUG) - Log.i(TAG, "获得编码器输出缓存区超时"); - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + do{ + outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo,TIMES_OUT); + if(outputBufferIndex == MediaCodec. INFO_TRY_AGAIN_LATER){ + if(DEBUG) + Log.i(TAG,"获得编码器输出缓存区超时"); + }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ // 如果API小于21,APP需要重新绑定编码器的输入缓存区; // 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED - if (!isLollipop()) { + if(!isLollipop()){ outputBuffers = mAudioEncoder.getOutputBuffers(); } - } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ // 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次 // 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步) - if (DEBUG) - Log.i(TAG, "编码器输出缓存区格式改变,添加视频轨道到混合器"); + if(DEBUG) + Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器"); synchronized (AACEncodeConsumer.this) { newFormat = mAudioEncoder.getOutputFormat(); - if (mMuxerRef != null) { + if(mMuxerRef != null){ Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.addTrack(newFormat, false); } } } - } else { + }else{ // 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了 - if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - if (DEBUG) - Log.i(TAG, "编码数据被消费,BufferInfo的size属性置0"); + if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ + if(DEBUG) + Log.i(TAG,"编码数据被消费,BufferInfo的size属性置0"); mBufferInfo.size = 0; } // 数据流结束标志,结束本次循环 - if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - if (DEBUG) - Log.i(TAG, "数据流结束,退出循环"); + if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){ + if(DEBUG) + Log.i(TAG,"数据流结束,退出循环"); break; } // 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据 ByteBuffer mBuffer = ByteBuffer.allocate(10240); ByteBuffer outputBuffer = null; - if (!isLollipop()) { - outputBuffer = outputBuffers[outputBufferIndex]; - } else { - outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); + if(!isLollipop()){ + outputBuffer = outputBuffers[outputBufferIndex]; + }else{ + outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); } - if (mBufferInfo.size != 0) { + if(mBufferInfo.size != 0){ // 获取输出缓存区失败,抛出异常 - if (outputBuffer == null) { - throw new RuntimeException("encodecOutputBuffer" + outputBufferIndex + "was null"); + if(outputBuffer == null){ + throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null"); } // 添加视频流到混合器 - if (mMuxerRef != null) { + if(mMuxerRef != null){ Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.pumpStream(outputBuffer, mBufferInfo, false); @@ -228,25 +230,25 @@ public class AACEncodeConsumer extends Thread { addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); mBuffer.flip(); // 将AAC回调给MainModelImpl进行push - if (listener != null) { - Log.i(TAG, "----->得到aac数据流<-----"); - listener.onEncodeResult(mBuffer.array(), 0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); + if(listener != null){ + Log.i(TAG,"----->得到aac数据流<-----"); + listener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); } } // 处理结束,释放输出缓存区资源 - mAudioEncoder.releaseOutputBuffer(outputBufferIndex, false); + mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false); } - } while (outputBufferIndex >= 0); + }while (outputBufferIndex >= 0); } - private void initAudioRecord() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->开始采集音频"); + private void initAudioRecord(){ + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->开始采集音频"); // 设置进程优先级 Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); - for (final int src : AUDIO_SOURCES) { + for (final int src: AUDIO_SOURCES) { try { mAudioRecord = new AudioRecord(src, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); @@ -266,18 +268,18 @@ public class AACEncodeConsumer extends Thread { mAudioRecord.startRecording(); } - private void initMediaCodec() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->开始编码音频"); + private void initMediaCodec(){ + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->开始编码音频"); MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE); - if (mCodecInfo == null) { - Log.e(TAG, "编码器不支持" + MIME_TYPE + "类型"); + if(mCodecInfo == null){ + Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型"); return; } - try { + try{ mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName()); - } catch (IOException e) { - Log.e(TAG, "创建编码器失败" + e.getMessage()); + }catch(IOException e){ + Log.e(TAG,"创建编码器失败"+e.getMessage()); e.printStackTrace(); } MediaFormat format = new MediaFormat(); @@ -293,9 +295,9 @@ public class AACEncodeConsumer extends Thread { } private void stopAudioRecord() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->停止采集音频"); - if (mAudioRecord != null) { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止采集音频"); + if(mAudioRecord != null){ mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; @@ -303,9 +305,9 @@ public class AACEncodeConsumer extends Thread { } private void stopMediaCodec() { - if (DEBUG) - Log.d(TAG, "AACEncodeConsumer-->停止编码音频"); - if (mAudioEncoder != null) { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止编码音频"); + if(mAudioEncoder != null){ mAudioEncoder.stop(); mAudioEncoder.release(); mAudioEncoder = null; @@ -314,28 +316,28 @@ public class AACEncodeConsumer extends Thread { } // API>=21 - private boolean isLollipop() { + private boolean isLollipop(){ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } // API<=19 - private boolean isKITKAT() { + private boolean isKITKAT(){ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; } - private long getPTSUs() { - long result = System.nanoTime() / 1000; - if (result < prevPresentationTimes) { - result = (prevPresentationTimes - result) + result; + private long getPTSUs(){ + long result = System.nanoTime()/1000; + if(result < prevPresentationTimes){ + result = (prevPresentationTimes - result ) + result; } return result; } /** * 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器 - * 判断是否有支持指定mime类型的编码器 - */ - private MediaCodecInfo selectSupportCodec(String mimeType) { + * 判断是否有支持指定mime类型的编码器 + * */ + private MediaCodecInfo selectSupportCodec(String mimeType){ int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); @@ -365,7 +367,7 @@ public class AACEncodeConsumer extends Thread { } - private short[] transferByte2Short(byte[] data, int readBytes) { + private short[] transferByte2Short(byte[] data,int readBytes){ // byte[] 转 short[],数组长度缩减一半 int shortLen = readBytes / 2; // 将byte[]数组装如ByteBuffer缓冲区 diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java index 8ccd36026a..9310ce1ae4 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java @@ -17,7 +17,9 @@ import java.nio.ByteBuffer; /** * 对YUV视频流进行编码 + * Created by jiangdongguo on 2017/5/6. */ + @SuppressWarnings("deprecation") public class H264EncodeConsumer extends Thread { private static final boolean DEBUG = false; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java index 1b2fc40c5b..64307af008 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java @@ -10,9 +10,11 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -/** - * Mp4封装混合器 +/**Mp4封装混合器 + * + * Created by jianddongguo on 2017/7/28. */ + public class Mp4MediaMuxer { private static final boolean VERBOSE = false; private static final String TAG = Mp4MediaMuxer.class.getSimpleName(); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java new file mode 100644 index 0000000000..f3409fb84e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/PermissionCheck.java @@ -0,0 +1,186 @@ +package com.serenegiant.utils; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.net.Uri; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class PermissionCheck { + + public static final void dumpPermissions(@Nullable final Context context) { + if (context == null) return; + try { + final PackageManager pm = context.getPackageManager(); + final List list = pm.getAllPermissionGroups(PackageManager.GET_META_DATA); + for (final PermissionGroupInfo info : list) { + Log.d("PermissionCheck", info.name); + } + } catch (final Exception e) { + Log.w("", e); + } + } + + /** + * パーミッションを確認 + * @param context + * @param permissionName + * @return 指定したパーミッションがあればtrue + */ + @SuppressLint("NewApi") + public static boolean hasPermission(@Nullable final Context context, final String permissionName) { + if (context == null) return false; + boolean result = false; + try { + final int check; + if (BuildCheck.isMarshmallow()) { + check = context.checkSelfPermission(permissionName); + } else { + final PackageManager pm = context.getPackageManager(); + check = pm.checkPermission(permissionName, context.getPackageName()); + } + switch (check) { + case PackageManager.PERMISSION_DENIED: + break; + case PackageManager.PERMISSION_GRANTED: + result = true; + break; + } + } catch (final Exception e) { + Log.w("", e); + } + return result; + } + + /** + * 録音のミッションがあるかどうかを確認 + * @param context + * @return 録音のパーミッションがあればtrue + */ + public static boolean hasAudio(@Nullable final Context context) { + return hasPermission(context, permission.RECORD_AUDIO); + } + + /** + * ネットワークへのアクセスパーミッションがあるかどうかを確認 + * @param context + * @return ネットワークへのアクセスパーミッションがあればtrue + */ + public static boolean hasNetwork(@Nullable final Context context) { + return hasPermission(context, permission.INTERNET); + } + + /** + * 外部ストレージへの書き込みパーミッションがあるかどうかを確認 + * @param context + * @return 外部ストレージへの書き込みパーミッションがあればtrue + */ + public static boolean hasWriteExternalStorage(@Nullable final Context context) { + return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE); + } + + /** + * 外部ストレージからの読み込みパーミッションがあるかどうかを確認 + * @param context + * @return 外部ストレージへの読み込みパーミッションがあればtrue + */ + @SuppressLint("InlinedApi") + public static boolean hasReadExternalStorage(@Nullable final Context context) { + if (BuildCheck.isAndroid4()) + return hasPermission(context, permission.READ_EXTERNAL_STORAGE); + else + return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE); + } + + /** + * 位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_COARSE_LOCATION) + && hasPermission(context, permission.ACCESS_FINE_LOCATION); + } + + /** + * 低精度位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessCoarseLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_COARSE_LOCATION); + } + + /** + * 高精度位置情報アクセスのパーミッションが有るかどうかを確認 + * @param context + * @return + */ + public static boolean hasAccessFineLocation(@Nullable final Context context) { + return hasPermission(context, permission.ACCESS_FINE_LOCATION); + } + + /** + * カメラへアクセス可能かどうか + * @param context + * @return + */ + public static boolean hasCamera(@Nullable final Context context) { + return hasPermission(context, permission.CAMERA); + } + + /** + * アプリの詳細設定へ遷移させる(パーミッションを取得できなかった時など) + * @param context + */ + public static void openSettings(@NonNull final Context context) { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + final Uri uri = Uri.fromParts("package", context.getPackageName(), null); + intent.setData(uri); + context.startActivity(intent); + } + + /** + * AndroidManifest.xmlに設定されているはずのパーミッションをチェックする + * @param context + * @param expectations + * @return 空リストなら全てのパーミッションが入っていた, + * @throws IllegalArgumentException + * @throws PackageManager.NameNotFoundException + */ + public static List missingPermissions(@NonNull final Context context, @NonNull final String[] expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException { + return missingPermissions(context, new ArrayList(Arrays.asList(expectations))); + } + + /** + * AndroidManifest.xmlに設定されているはずのパーミッションをチェックする + * @param context + * @param expectations + * @return 空リストなら全てのパーミッションが入っていた, + * @throws IllegalArgumentException + * @throws PackageManager.NameNotFoundException + */ + public static List missingPermissions(@NonNull final Context context, @NonNull final List expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException { + final PackageManager pm = context.getPackageManager(); + final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + final String[] info = pi.requestedPermissions; + if (info != null) { + for (String i : info) { + expectations.remove(i); + } + } + return expectations; + } +} diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so index 2c9b394b634e30f6cc1f414e20c63a5692b06e6f..6b4d9fdfc458acba96d8158b19147ce693e78222 100644 GIT binary patch delta 31539 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 E0BkE4h5!Hn delta 51 zcmcc6FMOe2c*6}wk;!x4rfBo~zh8E9X`twA&U5F}-c~oiVQhcH2*ON2%)I>#Bg>Pc E0E2iKHvj+t diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so index 370d63d45a4db5b7b990c0c370e31d3873e74371..c27d59521988bdda1410f69c12da64137f77c07d 100644 GIT binary patch delta 39 xcmV+?0NDS)&IZ8F2C&!x6w}in(oQK~c39ZRTAX1_1}3#N&4cU#x9kA{RHb}A5;gz; delta 39 xcmV+?0NDS)&IZ8F2C&!x6uM|8uR_8@rh*e$5`-ePjPUrG%Y*Czx9kA{RHb?266*i} diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so index 03830b267c4c8a9df2958f62dff4a8051308ac19..1f2486a5f6983c7f57934f9c22a8133a1ccaac55 100644 GIT binary patch delta 39 xcmV+?0NDTFkOtt82C&!x6uZ@5>t&HffX-B9R`h|CC#K8{{e$cQx9kA{bcT|s6Sn{W delta 39 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 iv?WFHQAV3ZMB)p)+`&pvEiF7bD5@Ad>H0I zh=o!ql**lj+Cr{VdA5j+4L`O#&;Na%bDqENkM(k1pZhuQ^EsFA_j|t2R~9*Dkz>jv z`;9$x%Er`b%N>_F>J)29V{AyPO`mO<@Yuik3y;4lGfiw8bl~pL!X*m-qZuP~u4xG@ zkQvj~u|bC8518TreLJPOk^@?rcAIT-Udt4vB{XwD%hT5Bd|60Zv2K?k-is;W@Zz?4 zIz=pdU+0cM*qkYWQjUcjmG;;zc+_zhfRa(w(DpJgWtjA8+DTEoT$z#~^iP0XZ&O9Q6igM-q%$@_R~J}@MUe%l0AIYTVX(gf81Et4P6++lnNRAQlO$Ny9p!yco(7| z-x|!6RgOab67tCqoizpiN(-eB^2Siqj}R^g1}Vz7koAb?@+Qco6PQvZ>pK^OYSSlT z`XGQeV1~o0w1J+!gL$)=(rsdQnkY`GVY{-Wp`$_o>ep%ZZ7~$1(u&Xvns;91b}FvV-)W2zhpHWb2;Og9a{RN<7M7l*?Z;r4r=| zN+QY$l!YkIqlBO=$=$K|pAVc}{Frju7c&VoKR5s0Nc#@ocr)c&l&!6qat1{K9|_tV z^h1Ybgg&o6y^4JpWq#)1!r z&Vce5x&b4ZvI)8b$T299;D14x0e&^g67Y9M1l{n;POc59h^$pojJba_2I-s&u<-?< z@l=$Uv@Vvnhxb&JX4tyw9F)VR0ZLMBr>mBRsh}M|W0iE1qBqusfyc<#>EZj=W$;nP zngF)mq$ta;{R!F6u-^23+7(Nu&>N6?$*8WHs4Ft+vLd1`L$21ZH+{IGH|wPS49QM0 z)_5U;)5k^EAWLz5 zxQH?e{2M4=f&T*Kkt=yHwv?6A2F5&Fov?;R0jJWM_oJ!aSd$P@Tz`}*8FOWG$i$d0 zn@;vRT=?h1nFDLX+ZkD0|MnHjO)ke!oQ`?3H)x)Y^xCHpI#+q?*e9lmu9UI~Exv#OViIQCJq+fmw z(CYTXt#9l?YubwG1S)W>!YFV6`{=eEd&B2-c%<>lFxaEzuzd?T!f(ClABwSO-PmGU zV$ZzQyqB5M4cF;gxHi|cV9F6tUp-S!;aYqZWi@O$p#MRcG!!#)WYCQ^n3;i?nMDDZ z5Mjm|g%3#xreU-6+d9AXzuAQ(o9sfMZgk z!-wrT$R1o)-mE)jv9#uuxIp$(yiH1MiglJ4XalNY!>H^z=IKA$>Ma(?9~VVUe#$zN zl~}d4QnDk! zpQ0U(%(KGqX ztZ{^{jv}2{pWZ)NRcEbzZHkP?>sMI!HjIto^hJyJRApgpeC$F~2UNkEtwAYCZ zVOQy~6Z?``Xn`{u!EVthXAJkR^q7-tf?O1&N|4in92evu&1%8gvw5@{#PPq?mWJa4 znR1Vgv|zrv{uk5wKWs~OF3ipOH9+L+!VaikqYf^}=W>d4!3O8T(-mv1iIwSU0XZ7! z+D-G|)y1BR$_kSX6^Jc$p~;#uDciQxzv+|<>(=T%+~eWS*EY6d4Ew@b-li)V^cbR> zG*FL9FLEzUk}h1p1t7&($Hyc-Wud7Wc0RHHOUZf`?DIU@@|Ht#OqzJ5&-oc^de8+u zX2l+=Y{^{h`Gl<^X9Me|(_N(g2KG8jp=1M+xsx^+e9mm6%)tj%Mf$r$+w-o2jT%8SQn|D;dthhdc z7Pw4?-c&c5A_T zddq`#)xCe7aJ#U(We*9}*0IoY^qME@uIqe`=6kYs{#|*;aV55O&Hbsaxrwedr)1m= zuz#q=8*pN%UmMmFJHdx-*eh%o<+s6rZl)`3n5T0eRLB>FJ+6d(i1{$r;k~6Q$lBH_ zQ=>k)wkP;TZ0+1FJF23FhjfPSmdkpRgP*ZZ!3N899c3(*Nu3|X_^^@8op$)3y%u!V zhxKfCAAm~{JLp>VxyJ+gqq~X4-o8h~sB39EvU*_xn4b&V_b4|oDGWcOK zl>0Kj4qwCm%16nyyz3n0Y{&^u`TvLe`?mVvsTSUc%(M0>4Tt&l7>HGfY@%stB6Pgg zH$e5uDrl)Sq?p@q!kWo2zQWo<$7%P1 zIsR;ruJ>1G#r2nWit`crb=I2+%=5a18k-7!n6$N^%J$4v4VNOevbs-U9xY|2Ur;+* zkJ}baxLep+(+7~lqz@mJ_obchuX8oNao`6?zvti=D{Tp&QBlbzeZmS{7Zg$hn7!9hmn}FR15gVmu*p7Z*u+&f5WV3&^vjA0J|8Y3Eh>QdtMK z$>(;}lVQ39K0&(kVY)~QI^sUM0_x$uBWigI4C~AD;G0urQ5z;{oU=8~2Q|(e;k*TMtn_UG5Sul=1sdN@ z@LdBrTKYDJ?{^yCERAn!l_$SSiQekrm?Div%IUfF!#=~<+TN1cVtCjoEljEFicqKZXSST{s%fmfA`YKf@LnyB?6DqTb+ z^Qg{#QL9Y#hVwO|q^vaEfc>7vzh2hIQ&~%6y0f?0 z&y?RCxXS5DcXTWt4tHb#-~ID60XxBKJ9dP62cv>DqJnfP4rZUQnG_nrrt}^y<02&G zfI1`NBOsS*0KF>h98fr&3c-x%M?RtKBj!WNp_qjpKhf15tP>wCOQofuNE2&@Y?~Jk z>2WA4Kxa1e6rJh8I|GM8ZD%wAJHbEInVxvcTxG35ms&{&`mvAM3>w&rbsHK3=R8>v zU%b(>qS2D_-UR~T8<4X#2w#_W-n$6e(F@aHkU-d%f_k%VZT;bpr3vuU1hlTC(;>`* zVxMPyal>fsm;>ELSogaXmWE7C$L>J3W#T4yZO2$8z5G0G{x1@$>4XfGJ04 zzF3*>z-_H2EKU;1L53SsQ#hU7g-yM z?2r8^b|4}ybH(SZn4AaU#AOrB8iWU5Q#tN;!*H+3)|rlh4*=y)emI(KRu0(#av7+d zMt_uQ2H`1vGt}95eUyMF2|b=9njoFLss!>0!$Z|%nihuM4Ck$S)59?4$3lgr6SuUd z#e>;s<|xgBSp*wFLtnyB52g=aVxFNtpL%lRoE>$c<|oMI4%R1y|GyiqkUo4QPtt~P zjARMj4M!z~6f}edt426DAgDQ}hwT>n`zICGWIy5SnnYbNr9-~_+>={7e&_h@6Q7!l zCw!e<)N0dW$l36B*3|!%^Z_VcU7|oWOQ4!gHA7h^`$BYQEG-DfBi)!M&UGV&Z@BOs z$a~PO!#?DkD`k&D8F7bTbq6Nc{uNtZx zhJ0P5&|$0_RlLmX{13t_({8DHS(G%W;bB0GvmZxQK6 z#lu;!f8&o>`#5y9J+AP3^uM9wlwawb$oW+^QTOYQVqaPIDjUXrq_eMLX*i#hO9KxW zf1JS50JSZRdypL<-vG7K=zpidBUpR;Q&7*QX(RAZ7)9%Fz`#s&Wd!TwehG#!oZN+@ z3{aA5$3>koO{Z27%-3fcP-P(N{MMW&kRzNTk$y^JBG@7}kgi1FVE%b%y|BYLz=Nl| zH)PK96Vg{C&-Xqj94dB$>?VErO>I|=?*NT&`-F2QKgfFN%Tf4hd_$l3zF6l0!AVo8 zyEJwH4Md~Q;qb)@4j`ICXXp3wH7@gnH>0vh%r|ek5sBU52F1LF!?WX5^BP;E+j5fH zhO)MlJ%HKKvXN|@ZqZ3GE)G!`mv|Z#g>l(=T#U;w1)V zF$5`?$#_?@uLSP{=|&9RW=xbpUgZ21svgWm4XiNQ@fKdM^d{3=tfWgmKwHmYB8%&P zI&8UVJCk{I_LcWzr`cqSM?wlIC&q5N?h& z)}%}GONXfY`!M&RD=_2D(EZ7{m%C1pQ<$gwpGfJ`{k}uEnS6!S6qHS2#N49}S{hPO zw1`sZ!)bWHSa^`yPG%m4Inr!HG@DLw^M?m%J~zj4vj$Nt1-%dRn1gf`W}Q+;^WSH0 z>Y{7twJFTQz16>9@)VdH1SXH^WtN6rC|XQA%dA<_Z7Q>ZYDv;=1GTcunkL=XWmZ%z zRc_aynqOwklkSr;D=gex*-vL}3{QP&pl`@>o`CX*h+V zg;icEV3m~$SozX!gOyh*U~MZEu-0+A2G-hA0V}msz`8@pGXN{%qDa=1gU`Zxj|?AU zPQ7_h&Z+GsmWBotEv%a*0@g1j0@lycZiDqxiGX#iM8Mk5?HXA7N(8K3B?8t|a-RiQ zhjEmOG4%MR>{(dL%0#y271Ye)**bmzuVtQsu@YsHoZf7zUE z<@p^DS#p^ultB{VUfaO*$ zU_CzYEUbU1@=I(Aw+d1dd9vC7RSb$2RdkU+HL6IU8Y=BJRN+MeRlg#UtRQaJj9p-n zNS1$*NLC0%ehE~|jtf))|AMNWK;a+a<)kpgUs&}Q`hU%UD0@c|4 z0@Vm^*PwcJzd$u;zd)5qXTJg}yCYUq9wUmM&DHQ?G12=MY*@sTHEy4!VKs^t)~bC1 zR`NapYk{=eU?uJoux9NOu%>al2G-Pl0#?jE0SoT|mIBt?GLfv0i=Ks*Ks8G-v*L>6 z%=*t>OG5#Q7S^7<0@e?E1+4YbZiDsxUI8m@uYk3P+cmJh+ACmvzE{9nMW$rHx>qc+ zwR-=vuxP)?*5ZPkZ+N!S3oQ+2QM9nC3I(hag#uQwwA)}66$)56g#y-QZr8xdC={?( z7g}NI^q8utxa7p|6X`15_bjdhbT<|0+O|u0IO|uHOm-u1nHx!*#Jh z;5t(va2?`y4X%R)0#`wSNEa^7=}6c0Jp$Hmd!L16BDNX}@mxXK3ZAWsJ(dQ;Q>gS3 zmAypu2tFLO4b{Uv0@dw30#yySYfxR=BT${)BeJFB)BIKZ0-Y;hwfGlUP6F2Bg37fV z*6;b2hG5yOZ8Pn@MlP|U3W3CjspM)GPkn`uP8fHgQ@!0IjSwrusv7uo89EZ}yfrO#8a+DKRi2@4nK z^?((TWrZa!ITH%RCFf1@H{y~rv|xsjXDJ}h((oRN7S5zR0cU)kfD&YX+}Zp2$*{Jdvd+3fcrXS(zF*pYIWHKBclvfHS$kkip>$$h9=gL(#&Sn=9bV z$Q5wjlXe@NNx1^f_*?-elG`+&vvt8$mXb%fhBxDM|Uxc2T6=}Mv7d2FoDt(AD})jZ27I15P7TYlxw zbp0qUpSkM#>=uLBxgc~mAI!>~mWGy3p)*KyO(nX&;lt6}&^_8|MTcKsutGd`)pD~2 zT+L1^IChTWVAk2Kpw9}~oA}*RWg#Bn-=e#P_({qHa^A~E>W1gY(Yvw3($H15W7{^n z?6hVHMv&`!&zU>GEfrdAk+zfpr0#v>Y4f~QV^EW*RzBx+TJhrd#q0m*$q_OtUz zrFBJ^PYKxqk^6I~Zy2vrvtzzOsRvN!0NSu0htnaGnVlg-y4aEvOn13U0C({;cs&Km zQ-H!XgM+$E8%i+C`!2K2CYDKtQVh|SUDhGe(Jq;r4q29l$tYSR?`4VHOrTK*k(<|~ z*@hxAOAOIVl*7%>bF*fMdS{6t>Pm(~z%iP-971xkX#pgUGdrJ6&Z(UO(}A5bJ41fJ zn`#s-rY~v35j@?_{=qt_4e`=!!}I~MqcF$*V4c&3(cG-T^ajOo^ROSplrE>7qsUF? zh152Yd6NGzWai!ufx@&yW+raCrC}qA7R83`A~P!~?gYBNRGMulmTVW9nNMZh{4qCc zP|VmaGBcU{Pa-qn)bAuRW296r`3M-^5{8q6;jV7O`!N(Pj1tNzM|bkJiDYC-vkk^p zGMs{W?KY8&6mHhQSWYvzIcb|nMiG^rLNdJOTPKS3%F14%ey5qY?!_!>u@}E+3CfZq zLz#FHi=qW{o<>!my7Ei`rc|13V1SjI^D+gPOm5bI0oF>6HB*3TLvfV=Gy5|!Yu}^F zN{r3BKZvmz{e#4jztz(407Z-AKE<8K;{N4Z%8 zzvbe9@6duvuwLg@4Wc^ouZFdn{4ZfUh}zEV*)00F8W)JEpNQn-QcX3Ile0}A*|<$k z+`F4C4LxLAw(Tx-iTY~yfC3guFE-8FR^y4m?EcsIC zFK}@a&>CsMFPJ^!;;jktVE=;6vQ=QavsGp*D8te)9Yu@n!wiuvyyCCLWO-AXZMeo{ zh`IDCrE>F&+^oSgFhk6x9&{IGy#IErL%u2~t`1UXy88djAq8_9*mKNr_FFjaN0~}XZsCae zA?4g+XPSjW<76$g^fqf3TEx?j*CNH7hNR<%R<8VGh}ISO>E(W@bC5ds7D=^x>B?>3 z*hBYk<2S~c)bDp3X0M?+zq94+GjjX`KVW!=ru~86hQ3*l{0CmQdNe^T7G&4SF9olu z_;EIVm!Cnu-(ef^%f!^XI2u1t;PWTaiK}<$J-j<^PaEzbzc*;fefA^UOJg43{Xta0 zg$L-Je-uxUGQJM)a#|a{OL1HBfC25y0JK>9^y zwI0-Iv01$Z>aav~Kv`;5??Ju?`VjQi*P=nEWdcwVDEFV6Ec9zZ6O+_6iEiJB2Aq}) zLm(*cK?W!Xv^GWP3qiR)BvtgF^S44D4$AdKphfV{OB4DEP_Bk0awGp&F%d9H@#nA0At9GE*cbZiP(28AV)d}=?wprD4nqyYoK=XD3fESpoT+!fh zP>yt1o(Krb7XjgWL_kP^+3Mf9(5$vWT;N`_>Idqz&#VT3>i3I!0zr8_S)jcBj3S{g z1?Bp%Vo`tJ1DK5*(SQ=sK<82s;8!LZ@H!|Ousb9gXawyHf8~f-4FtUf8UlL#s9Eg= zT7S%}_64mvZdM0?mY*=IVW1T!&1yJk(T`?z7-(L(092uZ;fSJ6iGX3JML;zu*N2}G z`T-R}A66+U3j9e_wCrcI8i5L9&zjXJ(5iE0bu?)Cd9ykWw4&OqMuQezFsrekc^A#< zB+$Z3W_2p4nsM2z#)DaV#jGZP8n2qwnV`#lF{`sdldhT7xuA<{00?wut=JFZLAgG< zPUr)!o7E)9b|$mB7_=|wGEn`mW;F%09F*@5$_=5<1BL!|Re#g0ro+MSmRViJ9e+2g zYeAjvm{lXF!(FqQ0U8dP30nQ9S z59mmgM(}e$Q&7U=n6d?QKgufbM?raPJ@^ZfZv@`}zKO3+RrK#LrLC_{>5VcP{M(=~ zC>h{C1x-e&2mh_)W8cLG(BOHc)!>hUzl_pr3V=#|1o(%Nj|K1HCwz+D$0zXMd2AK< zm;6+T4#Fhq6h56P^QBWX_*Ifm0KZ%Ml+R$wVX40k{+#3=g8xhUjGl#Wp#4St@!)+V zp9sD$_z@_V)!F#yQ5qV-Pm;Xf9HxBE9ng!pbMb+s)Te`AE%{9Fxzfij5uXK0eH-xS zB_9I*w)9!{B~$*9`V8=n?R1LDQ&9+^9R%K(+ajj)1kd$>;KL;!27bKsS-X@elchcn z{3nt>4t|OB3HXL7B=rNpZa_x&>`HwEc$fA9 zTrBv`;5q1`RZQt8^;O_sm3%$;ccoAGT6|$H_0ixLT6v6r0)#CP_;{4BXG)25x&^*k z@_Hjv%+e=*6FyxJ5FnO;?=Sgm@MFPqKu%ln@w3zifL|c_zTh`WpY-iaDORN+3&MHH zmx6yFo!abVigO3iSTFDak{<>>3_NcvBZn!YrM?LKRLNI>|3dl%=HtIYkosZZw@6-% zhHw}H?|oq*zQUD;YVda?-w3{WN72~0A}m+%e10T=50!ip_*bOQwGyU`k@|<=-<7;m zDN{a^K2r~2u5`ru=M^PFSS6j(!RJY*J4f-=zSKJ$!*}G8Zv+0W^qGAULnHMm;G1_6 z;4;7mfae`kPBW#q)cb*dS@PkhRi?ZTfv3?}iSLf3p%DB=$(MsKls=u$V#AU8Fz{89 z9}WJt^vSLUAgMnN-lekubQydP@Vu?Qm+?(G*Q@A#6ojeLX%hGp=~Vg)Q?jML8hnN1 zZ-KureTLOBrDYev#qbm>!tQBHUcS*j~U0!c0KS LyuE;t#UvE~z@`*H diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so index f0df5b052b4b5d77a2d0dcf4c1605757ea2073e0..4f2603950e60dcf6b6e8e0c14aef17ef728e9d29 100644 GIT binary patch delta 44 zcmV+{0Mq}V>;|Ch2Czf{6{y&!nZuvK7=ABIlytGhQVq|d!~m0VDiecj0k>=c0kpK- Cu@mzE delta 44 zcmV+{0Mq}V>;|Ch2Czf{6_~8A2RiKjmVyq2a+y4Wh16Ul)c})mDiecj0k>=c0kpKo C-4iST diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so index 564d93231375ac26189257c7a0dfbfddcf9afb2d..ba68f84d056dbff2079d5fa811e213874fc9a700 100644 GIT binary patch delta 39 xcmV+?0NDTVtp@O|2Czf{6hWSb!Y^x086JthC0)lc<|6S-&4XA0w^#uIn3Yu25Q6{! delta 39 xcmV+?0NDTVtp@O|2Czf{6ctv9K%dV`d}g2!=<>!aQn3*kwS!mzw^#uIn3Y>P5SIV| diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so index 7cb48799f98104ff2829db930458dff85afee57e..df770e4e3a956bdeceb3dcbdb3df499f94e7c234 100644 GIT binary patch delta 28715 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 delta 28710 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 PVpbq#+g`%R{^1A!e$yC_ delta 67 zcmaFTBmbmFeuE36Nc>7wl^y2-BpvRTOU<#L+wWy-$vwHoUZ%N(vAu*5gqeVt8Hibc Pm=%cGwwExne>egFP{9{4 diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so index 6b771d31ae6cdbf607f65ad16cb669949ca61b0b..59d8f256cb42eaeee7c20cfa97584c0646649257 100644 GIT binary patch delta 39 vcmX@n&32-jZG#J=NPnB&?pnh=UOOkr1@CU&ZQ;}Y?nZM2kK diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so index 33057578d1eef8a68a324e4143ba3d67b4eb7157..4c1ea8ab158410849def17993f9f89b0cb8874e8 100644 GIT binary patch delta 27835 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 BEZYD8 delta 27938 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 z
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 Q3lOscG230NJ1z(*OVf delta 67 zcmaE`QSQM;xeWqLB4+m@AD6GP+?RA?-p^78m$Y-%w-qNZWRz)kU}|?@VgzCk%?!jW QK+FonY}*}}*cVI#0JvEf=>Px# diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so index 05431ef82de3e49ed0fb6d2e5c891a871f16e198..0c7cf3a9ae789b11a77a3bcf720cfc9886af1aa7 100644 GIT binary patch delta 42 zcmV+_0M-A%um-@e2CxtU6mDa6QPpv!g2fO&fo-8+3}0#gr<0)p9D_gtr$7P$mVh!4 A+5i9m delta 42 zcmV+_0M-A%um-@e2CxtU6c#t2R$G}yf=$%PGKr=sFR@q9D_gtw?F~`FsGep B5kvq0 delta 43 zcmV+`0M!5R(gyI-2CxtU6zTW{$cjQuN{&6J5~-Hr7m~ + + Regarding permission + + No audio recording permission. \nAll audio function is disabled + Audio recording permission is necessary for movie capture with audio. + Audio recording permission is necessary for movie capture with audio. + Audio recording permission is necessary for streaming with audio. + Audio recording permission is necessary for streaming with audio. + + No permission of writing external storage. \nMovie/still image capturing are disabled. + Permission of writing external storage is necessary for movie/still image capturing. + Permission of writing external storage is necessary for movie/still image capturing. + No writing external storage permission. \nApp will finish soon + + No network access permission. + Network access permission is necessary for streaming. + Network access permission is necessary for streaming. + No network access permission. \nApp will finish soon + + No location access permission + Location access permission is necessary for %s + Location access permission is necessary for %s + No location access permission. \nApp will finish soon + + No camera access permission + No camera access permission. \nApp will finish soon + Camera access permission is necessary for fallback to built in camera. + Camera access permission is necessary for fallback to built in camera. + + Permission to access phone state/hardware id necessary for calling. + \ No newline at end of file diff --git a/modules/mogo-module-main/build.gradle b/modules/mogo-module-main/build.gradle index 0e7680566d..b209b14507 100644 --- a/modules/mogo-module-main/build.gradle +++ b/modules/mogo-module-main/build.gradle @@ -62,6 +62,7 @@ dependencies { implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_function_call + implementation rootProject.ext.dependencies.mogo_core_function_carcorder } else { api project(":foudations:mogo-commons") api project(':modules:mogo-module-common') @@ -72,6 +73,8 @@ dependencies { implementation project(':core:mogo-core-utils') implementation project(':core:mogo-core-data') implementation project(':core:mogo-core-function-call') + implementation project(':core:function-impl:mogo-core-function-carcorder') + } } diff --git a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java index 670e5f7676..28b27876db 100644 --- a/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java +++ b/modules/mogo-module-main/src/main/java/com/mogo/module/main/MainActivity.java @@ -1,5 +1,7 @@ package com.mogo.module.main; +import static com.mogo.module.main.MainPresenter.MOGO_PERMISSION_REQUEST_CODE; + import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; @@ -22,6 +24,7 @@ import com.mogo.commons.mvp.MvpActivity; import com.mogo.commons.mvp.MvpFragment; import com.mogo.eagle.core.data.constants.MoGoFragmentPaths; import com.mogo.eagle.core.data.map.MogoLocation; +import com.mogo.eagle.core.function.carcorder.service.CarcorderService; import com.mogo.eagle.core.utilcode.mogo.AppLaunchTimeUtils; import com.mogo.eagle.core.utilcode.mogo.logger.Logger; import com.mogo.eagle.core.utilcode.mogo.permissions.PermissionsDialogUtils; @@ -50,8 +53,6 @@ import com.zhidao.autopilot.support.api.AutopilotServiceManage; import java.util.HashMap; import java.util.Map; -import static com.mogo.module.main.MainPresenter.MOGO_PERMISSION_REQUEST_CODE; - /** * @author congtaowang * @since 2019-12-23 @@ -255,8 +256,12 @@ public class MainActivity extends MvpActivity implement } private void startBaseService() { - Intent intent = new Intent(this, MogoMainService.class); - startService(intent); + Intent intentMainServicee = new Intent(this, MogoMainService.class); + startService(intentMainServicee); + + // USB 摄像头行车记录仪进程 + Intent intentCarcorderService = new Intent(this, CarcorderService.class); + startService(intentCarcorderService); } protected void loadContainerModules() { @@ -272,7 +277,7 @@ public class MainActivity extends MvpActivity implement @Override public void loadFunctionFragment() { - Logger.d(TAG,"loadFunctionFragment……"); + Logger.d(TAG, "loadFunctionFragment……"); // 加载 HMI 图层 BaseFragment fragmentHdMap = (BaseFragment) ARouter.getInstance().build(MoGoFragmentPaths.PATH_FRAGMENT_HMI).navigation(); addFragment(fragmentHdMap, fragmentHdMap.getTagName(), R.id.module_main_id_waring_fragment); diff --git a/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java b/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java index 8446c6d2e7..b2e9b3944a 100644 --- a/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java +++ b/test/crashreport-apmbyte/src/main/java/com/mogo/test/crashreport/apm/ApmCrashReportProvider.java @@ -66,17 +66,17 @@ public class ApmCrashReportProvider implements ITestCrashReportProvider { //设置分配的appid builder.aid(BYTEAMP_APPID); //是否开启卡顿功能 - builder.blockDetect(true); + builder.blockDetect(false); //是否开启严重卡顿功能 - builder.seriousBlockDetect(true); + builder.seriousBlockDetect(false); //是否开启流畅性和丢帧 - builder.fpsMonitor(true); + builder.fpsMonitor(false); //控制是否打开WebVeiw监控 - builder.enableWebViewMonitor(true); + builder.enableWebViewMonitor(false); //控制是否打开内存监控 - builder.memoryMonitor(true); + builder.memoryMonitor(false); //控制是否打开电量监控 - builder.batteryMonitor(true); + builder.batteryMonitor(false); //是否打印日志,注:线上release版本要配置为false builder.debugMode(true); //支持用户自定义user_id把平台数据和自己用户关联起来,可以不配置