diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/model/MogoOCHTaxiModelNew.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/model/MogoOCHTaxiModelNew.java index b3470d6fe0..5f4c847dd1 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/model/MogoOCHTaxiModelNew.java +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/model/MogoOCHTaxiModelNew.java @@ -24,6 +24,7 @@ import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener; import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager; import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager; import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotPlanningListenerManager; +import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils; import com.mogo.eagle.core.utilcode.mogo.logger.Logger; import com.mogo.eagle.core.utilcode.mogo.storage.SharedPrefsMgr; import com.mogo.eagle.core.utilcode.mogo.toast.TipToast; @@ -134,7 +135,7 @@ public class MogoOCHTaxiModelNew { if (NetworkUtils.isConnected(mContext)) { // startOrStopOrderLoop(mOCHCarStatus == 1); - if (FunctionBuildConfig.appIdentityMode == 0x00) { + if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)) { queryCarStatus(); } } @@ -821,7 +822,7 @@ public class MogoOCHTaxiModelNew { Logger.d( TAG, "onIntentReceived = %s", intentStr ); if ( ConnectivityManager.CONNECTIVITY_ACTION.equals( intentStr ) ) { if ( NetworkUtils.isConnected( mContext ) ) { - if (FunctionBuildConfig.appIdentityMode == 0x00) { + if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)) { startOrStopOrderLoop(mOCHCarStatus == 1); queryCarStatus(); } @@ -941,7 +942,7 @@ public class MogoOCHTaxiModelNew { mPrevAPStatus = state; if (FunctionBuildConfig.isDemoMode - && FunctionBuildConfig.appIdentityMode == 0x01) { + && AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode)) { // 当美化模式(演示模式)开启时:且是乘客app、且未到终点时,维持自动驾驶icon开启状态 if (!arriveAtEnd) { return; @@ -955,7 +956,7 @@ public class MogoOCHTaxiModelNew { mPrevAPStatus = state; if (FunctionBuildConfig.isDemoMode - && FunctionBuildConfig.appIdentityMode == 0x01) { + && AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode)) { // 当美化模式(演示模式)开启时:且是乘客app、且未到终点时,维持自动驾驶icon开启状态 if (!arriveAtEnd) { return; @@ -981,7 +982,7 @@ public class MogoOCHTaxiModelNew { @Override public void onAutopilotArriveAtStation(@Nullable AutopilotStationInfo data) { if (FunctionBuildConfig.isDemoMode - && FunctionBuildConfig.appIdentityMode == 0x01) { + && AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode)) { arriveAtEnd = true; } diff --git a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/BaseOchTaxiTabFragment.java b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/BaseOchTaxiTabFragment.java index 413c1ee545..5abbbf58e4 100644 --- a/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/BaseOchTaxiTabFragment.java +++ b/OCH/mogo-och-taxi-passenger/src/main/java/com/mogo/och/taxi/passenger/ui/BaseOchTaxiTabFragment.java @@ -30,6 +30,7 @@ import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager; import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager; import com.mogo.eagle.core.function.call.hmi.CallerHmiManager; import com.mogo.eagle.core.function.call.map.CallerSmpManager; +import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils; import com.mogo.eagle.core.utilcode.mogo.logger.Logger; import com.mogo.map.listener.IMogoMapListener; import com.mogo.map.uicontroller.VisualAngleMode; @@ -133,7 +134,7 @@ public abstract class BaseOchTaxiTabFragment [AppIdentityModeUtils.java][AppIdentityModeUtils.java] -> [Demo][AppIdentityModeUtils.java] +``` + +// 判断是否是 司机身份 +AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode) + +// 判断是否是 乘客身份 +AppIdentityModeUtils.isPassenger(FunctionBuildConfig.appIdentityMode) + + +// 判断是否是 小巴车 +AppIdentityModeUtils.isBus(FunctionBuildConfig.appIdentityMode) + +// 判断是否是 出租车 +AppIdentityModeUtils.isTaxi(FunctionBuildConfig.appIdentityMode) + +``` + + diff --git a/app/build.gradle b/app/build.gradle index eabcafae8f..a0c72dcba9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,9 +5,6 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'android-aspectjx' apply plugin: 'bugly' -if (!isAndroidTestBuild()) { - apply plugin: 'apm-plugin' -} //apply ByteX宿主 if (!isAndroidTestBuild()) { @@ -18,73 +15,78 @@ if (!isAndroidTestBuild()) { logLevel "DEBUG" } } - if (!isAndroidTestBuild()) { - apply plugin: 'bytex.threadOpt' - thread_opt { - enable true - enableInDebug true - rxJavaIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' - rxJavaComputationReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' - coroutineIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' - coroutineDefaultReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' - } - - /** - * 方便使用systrace工具,在工程侧打点,便于分析工程侧性能问题 - */ - apply plugin: 'bytex.systrace' - systrace { - /** - * 交付时要关闭,会有性能损耗 - */ - enable false - enableInDebug false - /** - * - 是否使用[Trace.beginAsyncSection(String, int)/Trace.endAsyncSection(String, int)]进行打点 - * - 默认使用[Trace.beginSection(String)/Trace.endSection()]进行打点 - */ - isTraceAsync false - /** - * - 是否在运行时只针对主线程打点,其它线程不打 - */ - isOnlyMainThread false - - /** - * - 是否忽略对类的静态构造方法打点 - * - 默认不忽略 - */ - isIgnoreClinitMethod false - - /** - * - 是否忽略对类中的简单方法打点 - * 简单方法定义: - * - 空方法 - * - get/set 方法 - * - 单独的方法,方法体内没有调用其它方法 - * - 默认不忽略 - */ - isIgnoreSampleMethod false - - /** - * - 针对特定类集合,配置打点白名单,在此集合中的类中的所有方法不打点 - * - 支持正则表达式 - */ - whiteListForClass = [] - - /** - * - 针对特定包名集合,配置打点白名单,所有类以此包名为前缀的类不打点 - * - 支持正则表达式 - */ - whiteListForPackage = [] + apply plugin: 'chain.log.hook' + hooklog{ + enableLoggerToServer true } } +//if (!isAndroidTestBuild()) { +// apply plugin: 'apm-plugin' +//} + +//if (!isAndroidTestBuild()) { +// apply plugin: 'bytex.threadOpt' +// thread_opt { +// enable true +// enableInDebug true +// rxJavaIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' +// rxJavaComputationReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' +// coroutineIoReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getIoPool@@()Ljava/util/concurrent/ExecutorService;' +// coroutineDefaultReplacer 'com/mogo/eagle/core/utilcode/util/ThreadUtils@@getCpuPool@@()Ljava/util/concurrent/ExecutorService;' +// } +// +// /** +// * 方便使用systrace工具,在工程侧打点,便于分析工程侧性能问题 +// */ +// apply plugin: 'bytex.systrace' +// systrace { +// /** +// * 交付时要关闭,会有性能损耗 +// */ +// enable false +// enableInDebug false +// /** +// * - 是否使用[Trace.beginAsyncSection(String, int)/Trace.endAsyncSection(String, int)]进行打点 +// * - 默认使用[Trace.beginSection(String)/Trace.endSection()]进行打点 +// */ +// isTraceAsync false +// /** +// * - 是否在运行时只针对主线程打点,其它线程不打 +// */ +// isOnlyMainThread false +// +// /** +// * - 是否忽略对类的静态构造方法打点 +// * - 默认不忽略 +// */ +// isIgnoreClinitMethod false +// +// /** +// * - 是否忽略对类中的简单方法打点 +// * 简单方法定义: +// * - 空方法 +// * - get/set 方法 +// * - 单独的方法,方法体内没有调用其它方法 +// * - 默认不忽略 +// */ +// isIgnoreSampleMethod false +// +// /** +// * - 针对特定类集合,配置打点白名单,在此集合中的类中的所有方法不打点 +// * - 支持正则表达式 +// */ +// whiteListForClass = [] +// +// /** +// * - 针对特定包名集合,配置打点白名单,所有类以此包名为前缀的类不打点 +// * - 支持正则表达式 +// */ +// whiteListForPackage = [] +// } +//} -/*apply plugin: 'chain.log.hook' -hooklog{ - enableLoggerToServer true -}*/ bugly { appId = 'ac71228f85' // 注册时分配的App ID appKey = '3c736249-d6be-4066-b577-b7a6dc975cf7' // 注册时分配的App Key @@ -254,31 +256,31 @@ dependencies { androidTestImplementation rootProject.ext.dependencies.androidx_espresso_core } -if (!isAndroidTestBuild()) { - ApmPlugin { - // 是否进行插桩 - enable true - // 是否在Debug包插桩,默认不插桩 - enableInDebug true - // DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"); - // INFO 级别Log会汇总所有被插桩处理的类供查看,路径 app/build/ByteX/ApmPlugin - logLevel "DEBUG" - // 启动分析开关:监控App启动耗时,需要同时开启pageLoadSwitch - startSwitch = true - // 页面响应开关:监控Activity的生命周期耗时 - pageLoadSwitch = true - // 网络监控开关:监控okhttp3的网络请求 - okHttp3Switch = true - // 白名单下的包进行插桩,需要填写要插装类所在的包名,支持前缀配置 - whiteList = [ - "com.mogo" - ] - // 黑名单包下类不进行插桩,可以配置包名和类名,没有可以填空 - blackList = [ - - ] - } -} +//if (!isAndroidTestBuild()) { +// ApmPlugin { +// // 是否进行插桩 +// enable true +// // 是否在Debug包插桩,默认不插桩 +// enableInDebug true +// // DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"); +// // INFO 级别Log会汇总所有被插桩处理的类供查看,路径 app/build/ByteX/ApmPlugin +// logLevel "DEBUG" +// // 启动分析开关:监控App启动耗时,需要同时开启pageLoadSwitch +// startSwitch = true +// // 页面响应开关:监控Activity的生命周期耗时 +// pageLoadSwitch = true +// // 网络监控开关:监控okhttp3的网络请求 +// okHttp3Switch = true +// // 白名单下的包进行插桩,需要填写要插装类所在的包名,支持前缀配置 +// whiteList = [ +// "com.mogo" +// ] +// // 黑名单包下类不进行插桩,可以配置包名和类名,没有可以填空 +// blackList = [ +// +// ] +// } +//} android.applicationVariants.all { variant -> def buildTime = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08:00")) diff --git a/app/productFlavors/fPadLenovo.gradle b/app/productFlavors/fPadLenovo.gradle index cd72bc87d6..31335d67d8 100644 --- a/app/productFlavors/fPadLenovo.gradle +++ b/app/productFlavors/fPadLenovo.gradle @@ -29,7 +29,7 @@ project.android.productFlavors { // 构建的应用身份类型,司机|乘客 buildConfigField 'int', 'APP_IDENTITY_MODE', "0x02" // 连接的工控机IP地址 - buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.1.102\"" + buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.1.104\"" // 构建的是否是演示(美化)模式 buildConfigField 'boolean', 'IS_DEMO_MODE', 'true' } diff --git a/app/productFlavors/fPadLenovoOchBus.gradle b/app/productFlavors/fPadLenovoOchBus.gradle index 0e65bd1c47..481187896d 100644 --- a/app/productFlavors/fPadLenovoOchBus.gradle +++ b/app/productFlavors/fPadLenovoOchBus.gradle @@ -28,8 +28,8 @@ project.android.productFlavors { // GPS数据提供源: 0-Android系统,1-工控机,2-OBU buildConfigField 'int', 'GPS_PROVIDER', "1" - // 构建的应用身份类型,司机|乘客 - buildConfigField 'int', 'APP_IDENTITY_MODE', "0x00" + // 构建的应用身份类型,出租车0|小巴A-司机|乘客 + buildConfigField 'int', 'APP_IDENTITY_MODE', "0xA0" // 连接的工控机IP地址 buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.8.102\"" // 构建的是否是演示(美化)模式 diff --git a/app/productFlavors/fPadLenovoOchBusPassenger.gradle b/app/productFlavors/fPadLenovoOchBusPassenger.gradle index 4b7b299e78..74487113d3 100644 --- a/app/productFlavors/fPadLenovoOchBusPassenger.gradle +++ b/app/productFlavors/fPadLenovoOchBusPassenger.gradle @@ -26,8 +26,8 @@ project.android.productFlavors { // GPS数据提供源: 0-Android系统,1-工控机,2-OBU buildConfigField 'int', 'GPS_PROVIDER', "1" - // 构建的应用身份类型,司机0|乘客1 - buildConfigField 'int', 'APP_IDENTITY_MODE', "0x01" + // 构建的应用身份类型,出租车0|小巴A-司机0|乘客1 + buildConfigField 'int', 'APP_IDENTITY_MODE', "0xA1" // 连接的工控机IP地址 buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.8.103\"" // 构建的是否是演示(美化)模式 diff --git a/app/productFlavors/fPadLenovoOchTaxi.gradle b/app/productFlavors/fPadLenovoOchTaxi.gradle index 4d92c20f68..7a583209e6 100644 --- a/app/productFlavors/fPadLenovoOchTaxi.gradle +++ b/app/productFlavors/fPadLenovoOchTaxi.gradle @@ -29,7 +29,7 @@ project.android.productFlavors { // GPS数据提供源: 0-Android系统,1-工控机,2-OBU buildConfigField 'int', 'GPS_PROVIDER', "1" - // 构建的应用身份类型,司机|乘客 + // 构建的应用身份类型,出租车0|小巴A-司机|乘客 buildConfigField 'int', 'APP_IDENTITY_MODE', "0x00" // 连接的工控机IP地址 buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.1.102\"" diff --git a/app/productFlavors/fPadLenovoOchTaxiPassenger.gradle b/app/productFlavors/fPadLenovoOchTaxiPassenger.gradle index b69be39466..2c4ad036b9 100644 --- a/app/productFlavors/fPadLenovoOchTaxiPassenger.gradle +++ b/app/productFlavors/fPadLenovoOchTaxiPassenger.gradle @@ -28,7 +28,7 @@ project.android.productFlavors { // GPS数据提供源: 0-Android系统,1-工控机,2-OBU buildConfigField 'int', 'GPS_PROVIDER', "1" - // 构建的应用身份类型,司机0|乘客1 + // 构建的应用身份类型,出租车0|小巴A-司机0|乘客1 buildConfigField 'int', 'APP_IDENTITY_MODE', "0x01" // 连接的工控机IP地址 buildConfigField 'String', 'ADAS_CONNECT_IP', "\"192.168.1.103\"" diff --git a/app/src/androidTest/java/com/mogo/functions/test/TipToastLeakTest.kt b/app/src/androidTest/java/com/mogo/functions/test/TipToastLeakTest.kt new file mode 100644 index 0000000000..49b38265e5 --- /dev/null +++ b/app/src/androidTest/java/com/mogo/functions/test/TipToastLeakTest.kt @@ -0,0 +1,71 @@ +package com.mogo.functions.test + +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.mogo.eagle.core.function.hmi.ui.MoGoHmiFragment +import com.mogo.eagle.core.function.main.MainLauncherActivity +import com.mogo.eagle.core.utilcode.mogo.toast.TipToast +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.suspendCancellableCoroutine +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.MILLISECONDS + +@RunWith(AndroidJUnit4::class) +@LargeTest +class TipToastLeakTest { + + + lateinit var launch: ActivityScenario + + @Before + fun before() { + launch = ActivityScenario.launch(MainLauncherActivity::class.java) + + } + + @Test + fun test() = runBlocking(Dispatchers.Main) { + val f = ensureMoGoHmiFragmentShow() + var index = 0 + while (index < 50) { + delay(TimeUnit.SECONDS.toMillis(4)) + TipToast.shortTip("toast-> $index" ) + index ++ + } + delay(TimeUnit.SECONDS.toMillis(1)) + f.activity?.finish() + delay(TimeUnit.SECONDS.toMillis(2)) + } + + private suspend fun ensureMoGoHmiFragmentShow(): MoGoHmiFragment = suspendCancellableCoroutine { + launch.onActivity { itx -> + val executor = Executors.newSingleThreadScheduledExecutor() + executor.scheduleAtFixedRate({ + var find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + while (find == null) { + find = + itx.supportFragmentManager.fragments.find { it is MoGoHmiFragment } as? MoGoHmiFragment + + } + while (!find.isResumed) { + Thread.sleep(500) + } + it.resumeWith(Result.success(find)) + try { + Thread.sleep(500) + executor.shutdownNow() + } catch (e: Throwable) { + e.printStackTrace() + } + }, 50, 500, MILLISECONDS) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mogo/launcher/MogoApplication.java b/app/src/main/java/com/mogo/launcher/MogoApplication.java index 46fb18b8dc..f7700b1668 100644 --- a/app/src/main/java/com/mogo/launcher/MogoApplication.java +++ b/app/src/main/java/com/mogo/launcher/MogoApplication.java @@ -89,6 +89,7 @@ public class MogoApplication extends MainMoGoApplication { HmiBuildConfig.isShowPerspectiveSwitchView = false; HmiBuildConfig.isShowToolsView = false; HmiBuildConfig.isShowBadCaseView = false; + HmiBuildConfig.isShowUpgradeTipsView = false; //业务端可以根据需要控制是否展示刹车和转向灯的ui // HmiBuildConfig.isShowBrakeLightView = false; // HmiBuildConfig.isShowTurnLightView = false; diff --git a/build.gradle b/build.gradle index aa98125141..1615fa6dd6 100644 --- a/build.gradle +++ b/build.gradle @@ -35,9 +35,7 @@ buildscript { classpath 'com.volcengine:apm_insight_plugin:1.4.1' classpath 'com.mogo.cloud:thread_opt:1.0.1' classpath 'com.mogo.cloud:systrace:1.0.1' - -// classpath "com.bytedance.android.byteX:base-plugin:0.3.0" -// classpath "com.mogo.cloud:hook:${HOOK_LOG_VERSION}" + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8' // classpath ("com.tencent.matrix:matrix-gradle-plugin:0.6.6") { changing = true } } diff --git a/config.gradle b/config.gradle index 858fcd1c53..3ace774d82 100644 --- a/config.gradle +++ b/config.gradle @@ -222,6 +222,7 @@ ext { mogoaicloudtanlu : "com.mogo.cloud:tanlu:${MOGO_TANLU_VERSION}", mogoaicloudtrafficlive : "com.mogo.cloud:trafficlive:${MOGO_TRAFFICLIVE_VERSION}", mogoaicloudlocation : "com.mogo.cloud:location:${MOGO_LOCATION_VERSION}", + mogoaicloudtelematic : "com.mogo.cloud:telematic:${MOGO_TELEMATIC_VERSION}", //========================= 新架构的 Maven 版本管理 ========================= mogo_core_function_autopilot : "com.mogo.eagle.core.function.impl:autopilot:${MOGO_CORE_FUNCTION_AUTOPILOT_VERSION}", @@ -237,13 +238,14 @@ 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}", mogo_core_network : "com.mogo.eagle.core:network:${MOGO_CORE_NETWORK_VERSION}", //========================= V2X SDK ========================= - mogo_v2x : "com.mogo.v2x:v2x:${MOGO_V2X_SDK_VERSION}", + mogo_v2x : "com.mogo.v2x:v2x:${MOGO_V2X_SDK_VERSION}", life_cycle_scope : "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0", view_model_scope : "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0", diff --git a/core/README.md b/core/README.md index ba2ca3c14f..180e9b16db 100644 --- a/core/README.md +++ b/core/README.md @@ -2,25 +2,25 @@ 本模块用来编写鹰眼核型功能 - function-impl 目录下编写的都是对mogo-core-function-api定的功能实现, - - mogo-core-function-check 程序及车辆检测模块 - - mogo-core-function-devatools 开发工具模块 - - mogo-core-function-hmi UI呈现及交互模块 - - mogo-core-function-main 主入口 - - mogo-core-function-map 地图相关的模块 - - mogo-core-function-monitoring 远距离监控查看,路测摄像头、前车直播 - - mogo-core-function-notice 云端公告、调度相关模块 - - mogo-core-function-obu-mogo 自研OBU预警模块 - - mogo-core-function-smp 小地图模块 - - mogo-core-function-v2x 自车+云端预警模块 + - check 程序及车辆检测模块 + - devatools 开发工具模块:日志采集、BadCase、 + - hmi UI呈现及交互模块 + - main 主入口(模块加载、后台服务启动、多进程启动等) + - map 地图相关的模块 + - monitoring 行车超视距模块,路测摄像头、前车直播 + - notice 云端公告、调度相关模块 + - obu-mogo 自研OBU预警模块 + - smp 小地图模块 + - v2x 自车+云端预警模块 - mogo-core-data:定义基础业务所需要的数据结构 - -- mogo-core-function-res:这里只存放公共资源,图片,布局,动画等 + +- mogo-core-network:公共网络请求 - mogo-core-function-api:定义基础业务功能的接口 - mogo-core-function-call:定义基础业务暴露给外部调用的接口,对function-impl的二次封装,只将能对外调用的功能进行封装 -- mogo-core-res:程序中涉及到的图片及布局资源,同一管理,并通过设置不同的目录指定是那个模块的资源 +- mogo-core-res:程序中涉及到的图片及布局资源,同一管理,并通过设置不同的目录指定是那个模块的资源,这里只存放公共资源,图片,布局,动画等 - mogo-core-utils:基于成熟的工具类开源框架下沉的,这里可以增添针对我们业务上的一些工具类 diff --git a/core/function-impl/mogo-core-function-autopilot/build.gradle b/core/function-impl/mogo-core-function-autopilot/build.gradle index 0dd305e15c..7bab1ff8fd 100644 --- a/core/function-impl/mogo-core-function-autopilot/build.gradle +++ b/core/function-impl/mogo-core-function-autopilot/build.gradle @@ -51,18 +51,20 @@ dependencies { kapt rootProject.ext.dependencies.aroutercompiler - implementation rootProject.ext.dependencies.adasHigh + implementation rootProject.ext.dependencies.mogochainbase implementation rootProject.ext.dependencies.mogoami + implementation rootProject.ext.dependencies.mogoaicloudtelematic if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { + implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.modulecommon implementation rootProject.ext.dependencies.moduleservice - implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_network implementation rootProject.ext.dependencies.mogo_core_function_api implementation rootProject.ext.dependencies.mogo_core_function_call + implementation rootProject.ext.dependencies.adasHigh } else { implementation project(':modules:mogo-module-common') implementation project(':modules:mogo-module-service') @@ -72,6 +74,7 @@ dependencies { implementation project(':core:mogo-core-network') implementation project(':core:mogo-core-function-api') implementation project(':core:mogo-core-function-call') + implementation project(':libraries:mogo-adas') } } diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt index 320f7a2b24..a120c60187 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/MoGoAutopilotProvider.kt @@ -4,6 +4,7 @@ import android.Manifest.permission import android.content.Context import androidx.annotation.RequiresPermission import com.alibaba.android.arouter.facade.annotation.Route +import com.mogo.cloud.passport.MoGoAiCloudClientConfig import com.mogo.eagle.core.data.autopilot.AutopilotControlCmdParameter import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters import com.mogo.eagle.core.data.config.FunctionBuildConfig @@ -23,9 +24,11 @@ import com.mogo.eagle.core.utilcode.util.ThreadUtils import com.zhidao.support.adas.high.AdasManager import com.zhidao.support.adas.high.AdasOptions import com.zhidao.support.adas.high.bean.IPCUpgradeInfo +import com.zhidao.support.adas.high.common.Constants import com.zhidao.support.adas.high.common.CupidLogUtils import java.util.concurrent.TimeUnit + /** * @author xiaoyuzhou * @date 2021/9/22 8:43 下午 @@ -33,7 +36,7 @@ import java.util.concurrent.TimeUnit */ @Route(path = MogoServicePaths.PATH_AUTO_PILOT) class MoGoAutopilotProvider : - IMoGoAutopilotProvider, IMoGoMapDataCollectProvider.OnMapCollectCmdListener { + IMoGoAutopilotProvider, IMoGoMapDataCollectProvider.OnMapCollectCmdListener { private val TAG = "MoGoAutoPilotProvider" private var mContext: Context? = null @@ -51,40 +54,123 @@ class MoGoAutopilotProvider : when (FunctionBuildConfig.appIdentityMode) { 0x00 -> // 司机 { + // 注册地图采集功能 CallerMapDataCollectorManager.registerOnMapCollectTaskListener(this) // "192.168.1.102" val options = AdasOptions.Builder() - .setIPCIp(FunctionBuildConfig.adasConnectIP) - .setClient(false) - .build() + .setIpcConnectionMode(AdasOptions.IPC_CONNECTION_MODE.FIXATION) + .setIpcFixationIP(AdasManager.getInstance().getIPCFixationIPList(mContext)) + .setClient(false) + .build() + AdasManager.getInstance().create(context, options) +// NSDNettyManager.getInstance().startNSDNettyServer(context, object : NettyServerListener { +// override fun onMessageResponseServer(msg: MogoProtocolMsg?, channel: Channel?) { +// Logger.d(TAG, "Receive client data is:${msg?.toString()}") +// } +// +// override fun onStartServer() { +// ToastUtils.showShort("司机端服务启动成功!") +// Logger.d(TAG, "onStartServer") +// } +// +// override fun onStopServer() { +// ToastUtils.showLong("司机端服务停止!") +// Logger.d(TAG, "onStopServer") +// } +// +// override fun onChannelConnect(channel: Channel?) { +// NSDNettyManager.getInstance().selectChannel(channel) +// val socketAddress = channel?.remoteAddress().toString() +// Logger.d(TAG, "Client ip is:${socketAddress}") +// } +// +// override fun onChannelDisConnect(channel: Channel?) { +// Logger.d(TAG, "onChannelDisConnect") +// } +// }) } 0x01 -> // 乘客 { // 乘客端默认接收绘制全局路径+引导线 //FunctionBuildConfig.isDemoMode = true //FunctionBuildConfig.isIgnoreConditionsDrawAutopilotTrajectoryData = true - // "192.168.1.103" - val options = AdasOptions.Builder() - .setIPCIp(FunctionBuildConfig.adasConnectIP) - .setClient(false) - .build() + // "192.168.1.102" + val options = AdasOptions + .Builder() + .setClient(true) + .build() AdasManager.getInstance().create(context, options) +// NSDNettyManager.getInstance().searchAndConnectServer(context, MoGoAiCloudClientConfig.getInstance().sn, object : NettyClientListener { +// override fun onMessageResponseClient(msg: MogoProtocolMsg?, sign: String?) { +// Logger.d(TAG, "收到司机端的数据!") +// // 乘客端收到adas数据直接解析,后续分发解析后的数据流程同司机端 +// msg?.let { +// AdasManager.getInstance().parseIPCData(it.body) +// Logger.d(TAG, "解析司机端数据完毕!") +// } +// } +// +// override fun onClientStatusConnectChanged(statusCode: Int, sign: String?) { +// when (statusCode) { +// ConnectState.STATUS_CONNECT_SUCCESS -> Logger.d(TAG, "乘客端连接司机端服务成功! sign is:${sign}") +// else -> { +// ToastUtils.showLong("和司机端连接异常!") +// Logger.d(TAG, "client statusCode is:${statusCode}") +// } +// } +// } +// }) } + // else -> // 默认采用UDP寻址方式 { val options = AdasOptions.Builder() - .setIPCIp(FunctionBuildConfig.adasConnectIP) - .setClient(false) - .build() + .setIpcConnectionMode(AdasOptions.IPC_CONNECTION_MODE.FIXATION) + .setIpcFixationIP(AdasManager.getInstance().getIPCFixationIPList(mContext)) + .setClient(false) + .build() AdasManager.getInstance().create(context, options) } } //////////////////////////////////注意先后顺序,AdasManager.getInstance().create后才可以设置监听///////////////////////////////////////////// // 监听 adas 连接状态 - AdasManager.getInstance().setOnAdasConnectStatusListener(MoGoAdasMsgConnectStatusListenerImpl()) - // 监听ADAS-SDK获取到的工控机数据 + AdasManager.getInstance() + .setOnAdasConnectStatusListener(MoGoAdasMsgConnectStatusListenerImpl()) + // 监听ADAS-SDK获取到的工控机数据(乘客也需注册) AdasManager.getInstance().setOnAdasListener(MoGoAdasListenerImpl()) +// // 司机端监听 +// if (FunctionBuildConfig.appIdentityMode == 0) { +// AdasManager.getInstance().setOnMultiDeviceListener { bytes -> +// Logger.d( +// TAG, +// "司机端接收到工控机吐出来的数据为:${Arrays.toString(bytes)}" +// ) +// // 发送数据给乘客端 +// if (NSDNettyManager.getInstance().isServerStart) { +// Logger.d( +// TAG, +// "司机端透传数据给乘客端!" +// ) +// NSDNettyManager.getInstance().sendMsgToAllClients(MogoProtocolMsg(NORMAL_DATA, bytes.size, bytes)) +//// NSDNettyManager.getInstance().sendMogoProtocolMsgToClient(MogoProtocolMsg(NORMAL_DATA, bytes.size, bytes)) { channelFuture: ChannelFuture -> +//// if (channelFuture.isSuccess) { +//// Logger.d( +//// TAG, +//// "Send data to client is success." +//// ) +//// } else { +//// Logger.d( +//// TAG, +//// "Send data to client is failure." +//// ) +//// } +//// } +// } else { +// Logger.d(TAG, "司机端Server未启动!") +// } +// } +// } // 同步数据给工控机的服务 AsyncDataToAutopilotServer.INSTANCE.initServer() // 同步是否开启美化模式 @@ -98,17 +184,20 @@ class MoGoAutopilotProvider : */ override fun resetIpAddress(autoPilotIp: String) { // 关闭通信 - AdasManager.getInstance().closeAllMsg() + AdasManager.getInstance().disconnect() // 延时执行连接指定IP地址 ThreadUtils.executeBySingleWithDelay(object : ThreadUtils.SimpleTask() { @RequiresPermission(permission.INTERNET) override fun doInBackground(): String { // 保存本地 AutoPilot IP地址 - mContext?.let { SharedPrefsMgr.getInstance(it).putString(MoGoConfig.AUTOPILOT_IP, autoPilotIp) } + mContext?.let { + SharedPrefsMgr.getInstance(it).putString(MoGoConfig.AUTOPILOT_IP, autoPilotIp) + } // 设置IP地址 - AdasManager.getInstance().setIPCIp(autoPilotIp) + AdasManager.getInstance().adasOptions.setIpcConnectionMode(AdasOptions.IPC_CONNECTION_MODE.ASSIGN) + AdasManager.getInstance().adasOptions.setIpcAssignIP(autoPilotIp) // 打开通讯连接 - AdasManager.getInstance().startAllMsg() + AdasManager.getInstance().connect() return "" } @@ -129,7 +218,7 @@ class MoGoAutopilotProvider : } override fun startAutoPilot(result: AutopilotControlParameters) { - if (AdasManager.getInstance().isSocketConnect) { + if (AdasManager.getInstance().ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.CONNECTED) { val parameter = AutopilotControlCmdParameter("aiCloudToStartAutopilot", result) AdasManager.getInstance().aiCloudToAdasData(GsonUtils.toJson(parameter)) } else { @@ -142,7 +231,7 @@ class MoGoAutopilotProvider : } override fun cancelAutoPilot() { - if (AdasManager.getInstance().isSocketConnect) { + if (AdasManager.getInstance().ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.CONNECTED) { AdasManager.getInstance().controlAutopilotCarHead() } else { Logger.e(TAG, "车机与工控机链接失败,无法断开自动驾驶") @@ -151,7 +240,7 @@ class MoGoAutopilotProvider : override fun recordPackage(): Boolean { return AdasManager.getInstance() - .recordPackage(1, (System.currentTimeMillis() / 1000).toInt()) + .recordPackage(1, (System.currentTimeMillis() / 1000).toInt()) } override fun recordPackage(type: Int, id: Int): Boolean { 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 09ac36f488..6e36a13d5d 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 @@ -1,5 +1,17 @@ package com.mogo.eagle.core.function.autopilot.adapter; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ARRIVE; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_GUARDIAN; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_RECORD; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ROUTE; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_STATUS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_TRAJECTORY; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_WARN; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_CAR_STATE; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_ADAS_MESSAGE_RECT_DATA; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_ADAS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_LOG_WEB_SOCKET_DATA; + import android.util.Log; import androidx.annotation.Nullable; @@ -42,6 +54,8 @@ import com.zhidao.support.adas.high.bean.WarnMessageInfo; import com.zhidao.support.adas.high.bean.guardian.AutopilotGuardianInfo; import com.zhidao.support.adas.high.bean.record.AutopilotRecordResult; import com.zhidao.support.obu.ami.AmiClientManager; +import com.zhjt.service.chain.ChainLog; +import com.zhjt.service.chain.TracingConstants; import org.json.JSONObject; @@ -57,7 +71,12 @@ import java.util.List; public class MoGoAdasListenerImpl implements OnAdasListener { private final String TAG = "OnAdasListenerAdapter"; - +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_RECT_DATA, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onRectData(RectInfo rectInfo) { if (HdMapBuildConfig.isMapLoaded) { @@ -66,6 +85,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_CAR_STATE, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onCarStateData(CarStateInfo carStateInfo) { if (HdMapBuildConfig.isMapLoaded) { @@ -145,6 +170,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_STATUS, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void autopilotStatus(AutopilotStatus autopilotStatus) { if (HdMapBuildConfig.isMapLoaded) { @@ -177,6 +208,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ARRIVE, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void autopilotArrive(AutopilotWayArrive autopilotWayArrive) { if (HdMapBuildConfig.isMapLoaded) { @@ -195,6 +232,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ROUTE, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onAutopilotRoute(AutopilotRoute route) { if (HdMapBuildConfig.isMapLoaded) { @@ -204,6 +247,11 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_TRAJECTORY, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onAutopilotTrajectory(List trajectoryList) { if (HdMapBuildConfig.isMapLoaded) { @@ -234,6 +282,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { CallerAutoPilotStatusListenerManager.INSTANCE.invokeAutopilotSNRequest(); } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_GUARDIAN, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onAutopilotGuardian(AutopilotGuardianInfo guardianInfo) { if (HdMapBuildConfig.isMapLoaded) { @@ -242,6 +296,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } } +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_RECORD, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onAutopilotRecord(AutopilotRecordResult result) { if (result != null) { @@ -276,7 +336,12 @@ public class MoGoAdasListenerImpl implements OnAdasListener { } - +// @ChainLog(linkCode = CHAIN_LINK_ADAS, +// linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_WARN, +// paramIndexes = {0}, +// clientPkFileName = "sn") @Override public void onWarnMessage(WarnMessageInfo warnMessageInfo) { final AutopilotWarnMessage warnMessage = AdasObjectConvertUtils.INSTANCE.fromAdasObject(warnMessageInfo); diff --git a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasMsgConnectStatusListenerImpl.kt b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasMsgConnectStatusListenerImpl.kt index 91731111c7..b4fa7f381d 100644 --- a/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasMsgConnectStatusListenerImpl.kt +++ b/core/function-impl/mogo-core-function-autopilot/src/main/java/com/mogo/eagle/core/function/autopilot/adapter/MoGoAdasMsgConnectStatusListenerImpl.kt @@ -2,17 +2,16 @@ package com.mogo.eagle.core.function.autopilot.adapter import com.mogo.cloud.passport.MoGoAiCloudClientConfig import com.mogo.commons.debug.DebugConfig -import com.mogo.eagle.core.data.app.AppConfigInfo import com.mogo.eagle.core.data.autopilot.AutopilotStationInfo import com.mogo.eagle.core.data.autopilot.AutopilotStatusInfo -import com.mogo.eagle.core.data.constants.MoGoConfig import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener import com.mogo.eagle.core.function.autopilot.network.AdasServiceModel import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager import com.mogo.eagle.core.utilcode.mogo.logger.Logger import com.zhidao.support.adas.high.AdasManager -import com.zhidao.support.adas.high.OnAdasMsgConnectStatusListener +import com.zhidao.support.adas.high.OnAdasConnectStatusListener import com.zhidao.support.adas.high.bean.BasicInfo +import com.zhidao.support.adas.high.common.Constants import io.reactivex.Flowable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -23,7 +22,8 @@ import java.util.concurrent.TimeUnit * * @author donghongyu */ -class MoGoAdasMsgConnectStatusListenerImpl : OnAdasMsgConnectStatusListener, IMoGoAutopilotStatusListener { +class MoGoAdasMsgConnectStatusListenerImpl : OnAdasConnectStatusListener, + IMoGoAutopilotStatusListener { private val TAG = "MoGoAdasMsgConnectStatusListenerImpl" //自动驾驶状态 @@ -32,26 +32,36 @@ class MoGoAdasMsgConnectStatusListenerImpl : OnAdasMsgConnectStatusListener, IMo //自动驾驶车速度 private var mCurrentAutopilotSpeed = 0f - override fun onWebSocketConnectSuccess() { - Logger.d(TAG, "webSocket 连接成功") - // 初始化自动驾驶状态信息 - CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectIP = AdasManager.getInstance().adasConfig.address - CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectStatus = true - CallerAutoPilotStatusListenerManager.invokeAutoPilotStatus() - // 同步SN给工控机 - syncBasicInfoToAutopilot() + override fun onConnectionIPCStatus(ipcConnectionStatus: Int, reason: String?) { + if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.CONNECTED) { + Logger.d(TAG, "webSocket 连接成功") + // 初始化自动驾驶状态信息 + CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectIP = + AdasManager.getInstance().adasConfig.address + CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectStatus = true + CallerAutoPilotStatusListenerManager.invokeAutoPilotStatus() - // 开启轮训上传自动驾驶状态 - updateDriveStatusTask() + // 同步SN给工控机 + syncBasicInfoToAutopilot() + + // 开启轮训上传自动驾驶状态 + updateDriveStatusTask() + } else if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.DISCONNECTED) { + Logger.d(TAG, "webSocket 连接失败 reason:$reason") + CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectIP = + AdasManager.getInstance().adasConfig.address + CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectStatus = false + CallerAutoPilotStatusListenerManager.invokeAutoPilotStatus() + } else if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.CONNECTING) { + Logger.d(TAG, "webSocket 正在连接") + } else if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.SEARCH_ADDRESS) { + Logger.d(TAG, "webSocket 正在搜索工控机IP") + } else if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.NOT_FOUND_ADDRESS) { + Logger.d(TAG, "webSocket 找不到可用IP 传入的IP不可用或固定IP列表中所有IP不可用") + } } - override fun onWebSocketConnectFailed(reason: String) { - Logger.d(TAG, "webSocket 连接失败 reason:$reason") - CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectIP = AdasManager.getInstance().adasConfig.address - CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfo().connectStatus = false - CallerAutoPilotStatusListenerManager.invokeAutoPilotStatus() - } /** * 工控机获取SN @@ -94,12 +104,13 @@ class MoGoAdasMsgConnectStatusListenerImpl : OnAdasMsgConnectStatusListener, IMo private fun updateDriveStatusTask() { Logger.d(TAG, "updateDriveStatusTask") Flowable.interval(0, 5, TimeUnit.SECONDS) - .subscribeOn(Schedulers.io()) - .unsubscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { aLong: Long? -> - AdasServiceModel.getInstance().updateDriveStatus(mCurrentAutopilotStatus, mCurrentAutopilotSpeed) - } + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { aLong: Long? -> + AdasServiceModel.getInstance() + .updateDriveStatus(mCurrentAutopilotStatus, mCurrentAutopilotSpeed) + } } diff --git a/core/function-impl/mogo-core-function-carcorder/.gitignore b/core/function-impl/mogo-core-function-carcorder/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-carcorder/build.gradle b/core/function-impl/mogo-core-function-carcorder/build.gradle new file mode 100644 index 0000000000..d612fd342f --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/build.gradle @@ -0,0 +1,75 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' + id 'com.alibaba.arouter' +} + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + // buildToolsVersion rootProject.ext.android.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode Integer.valueOf(VERSION_CODE) + versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION") + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + + //ARouter apt 参数 + kapt { + useBuildCache = false + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation rootProject.ext.dependencies.kotlinstdlibjdk7 + implementation rootProject.ext.dependencies.coroutinescore + implementation rootProject.ext.dependencies.arouter + kapt rootProject.ext.dependencies.aroutercompiler + implementation rootProject.ext.dependencies.mogologlib + + 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 + implementation rootProject.ext.dependencies.mogo_core_function_call + implementation rootProject.ext.dependencies.mogo_core_data + }else { + 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') + implementation project(':core:mogo-core-data') + } +} + +apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString() \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-carcorder/gradle.properties b/core/function-impl/mogo-core-function-carcorder/gradle.properties new file mode 100644 index 0000000000..ba1462e8ad --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/gradle.properties @@ -0,0 +1,3 @@ +GROUP=com.mogo.eagle.core.function.impl +POM_ARTIFACT_ID=carcorder +VERSION_CODE=1 diff --git a/core/function-impl/mogo-core-function-carcorder/proguard-rules.pro b/core/function-impl/mogo-core-function-carcorder/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file 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 new file mode 100644 index 0000000000..6c1ea8112e --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/src/main/AndroidManifest.xml @@ -0,0 +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-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/LivePushService.kt b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/LivePushService.kt new file mode 100644 index 0000000000..3940a4c9ee --- /dev/null +++ b/core/function-impl/mogo-core-function-carcorder/src/main/java/com/mogo/eagle/core/function/carcorder/service/LivePushService.kt @@ -0,0 +1,53 @@ +package com.mogo.eagle.core.function.carcorder.service + +import android.content.Intent +import android.os.IBinder +import android.util.Log +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.serenegiant.usb.common.BaseService + +/** + * 行车记录仪服务 + * @author donghongyu + */ +class LivePushService : BaseService() { + private val DEBUG = true + val TAG = LivePushService::class.java.name + + override fun onCreate() { + super.onCreate() + if (DEBUG) { + Logger.d(TAG, "onCreate……") + } + + } + + override fun onDestroy() { + super.onDestroy() + if (DEBUG) Log.d(TAG, "onDestroy:") + + } + + 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 + } + + +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/build.gradle b/core/function-impl/mogo-core-function-devatools/build.gradle index f909f820b5..56c8d2e062 100644 --- a/core/function-impl/mogo-core-function-devatools/build.gradle +++ b/core/function-impl/mogo-core-function-devatools/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation rootProject.ext.dependencies.arouter kapt rootProject.ext.dependencies.aroutercompiler implementation rootProject.ext.dependencies.mogologlib + implementation rootProject.ext.dependencies.mogochainbase if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { implementation rootProject.ext.dependencies.mogoserviceapi diff --git a/core/function-impl/mogo-core-function-devatools/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-devatools/src/main/AndroidManifest.xml index 4ba99103af..7a02e164bc 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/AndroidManifest.xml +++ b/core/function-impl/mogo-core-function-devatools/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt index b8ddf3e5f4..ca79afe0be 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/DevaToolsProvider.kt @@ -1,29 +1,105 @@ package com.zhjt.mogo_core_function_devatools +import android.annotation.SuppressLint import android.content.Context import com.alibaba.android.arouter.facade.annotation.Route +import com.mogo.cloud.passport.MoGoAiCloudClientConfig +import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_ADAS_INIT +import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_ADAS_MSG +import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_CONNECT_STATUS +import com.mogo.eagle.core.data.chain.ChainConstant.Companion.CHAIN_LINK_LOG_WEB_SOCKET_DATA +import com.mogo.eagle.core.data.chain.ChainLogParam +import com.mogo.eagle.core.data.constants.MoGoConfig import com.mogo.eagle.core.data.constants.MogoServicePaths import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.eagle.core.utilcode.mogo.storage.SharedPrefsMgr +import com.mogo.eagle.core.utilcode.util.DeviceUtils +import com.mogo.eagle.core.utilcode.util.Utils +import com.zhidao.loglib.fw.FileWriteManager +import com.zhidao.loglib.fw.FwBuild import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchManager +import com.zhjt.service.chain.core.ChainTraceStarter @Route(path = MogoServicePaths.PATH_DEVA_TOOLS) class DevaToolsProvider : IDevaToolsProvider { + private val traceInfoCache = hashMapOf() + private val fwBuildMap: MutableMap = HashMap() + override val functionName: String get() = "DevaToolsProvider" override fun init(context: Context) { MogoLogCatchManager.init(context) + logCheck(context) + initTrace(context) + } + + private fun logCheck(context: Context) { + val logger = SharedPrefsMgr.getInstance(context).getBoolean(MoGoConfig.CATCH_LOG, false) + val loggerTime = SharedPrefsMgr.getInstance(context).getLong(MoGoConfig.CATCH_LOG_TIME, 0) + val logCatchDuration = (System.currentTimeMillis() - loggerTime) / 1000 / 60 + if (logger && loggerTime > 0) { + val logTime: Int = if (10 - logCatchDuration < 1) { + 1 + } else { + 10 - logCatchDuration.toInt() + } + MogoLogCatchManager.startCatchLog(logTime) + } else { + Logger.d( + functionName, + "logCheck logger : $logger , logCatchDuration : $logCatchDuration" + ) + } } override fun startLogCatch() { MogoLogCatchManager.startCatchLog() } + override fun startLogCatch(duration: Int) { + MogoLogCatchManager.startCatchLog(duration) + } + override fun stopLogCatch() { MogoLogCatchManager.stopCatchLog() } + private fun initTrace(context: Context) { + // 初始化Trace抓取服务 + val pkgName = Utils.getApp().packageName + ChainTraceStarter.start(pkgName, DeviceUtils.getMacAddress(), BuildConfig.DEBUG) + + // Trace过程中进行日志抓取,对日志进行配置 + fwBuildMap[CHAIN_LINK_LOG_CONNECT_STATUS] = + FwBuild(true, pkgName + CHAIN_LINK_LOG_ADAS_INIT, 5_000) + fwBuildMap[CHAIN_LINK_LOG_WEB_SOCKET_DATA] = + FwBuild(false, pkgName + CHAIN_LINK_LOG_ADAS_MSG, 500) + + traceInfoCache[CHAIN_LINK_LOG_CONNECT_STATUS] = ChainLogParam(true, "ADAS连接状态") + traceInfoCache[CHAIN_LINK_LOG_WEB_SOCKET_DATA] = ChainLogParam(false, "ADAS长链数据") + FileWriteManager.getInstance() + .init(context, MoGoAiCloudClientConfig.getInstance().sn, pkgName, fwBuildMap) + } + + override fun getTraceInfo(): HashMap { + return traceInfoCache + } + + @SuppressLint("NewApi") + override fun refreshTraceInfo(map: HashMap) { + map.forEach { (type, param) -> + val fwBuild = this.fwBuildMap[type] + fwBuild?.let { + Logger.d(functionName, "param : ${param.des} , record : ${param.record}") + it.isRecord = param.record + } + } + FileWriteManager.getInstance().operateChainMap(fwBuildMap) + } + override fun onDestroy() { MogoLogCatchManager.onDestroy() } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcatch/MogoLogCatchManager.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcatch/MogoLogCatchManager.kt index cdff9714ed..3e6c352fd1 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcatch/MogoLogCatchManager.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcatch/MogoLogCatchManager.kt @@ -7,13 +7,14 @@ import android.os.Message import com.mogo.cloud.passport.MoGoAiCloudClientConfig import com.mogo.commons.AbsMogoApplication import com.mogo.commons.debug.DebugConfig +import com.mogo.eagle.core.data.constants.MoGoConfig import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsListenerManager import com.mogo.eagle.core.network.NetConfig import com.mogo.eagle.core.utilcode.mogo.logger.LogLevel import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.eagle.core.utilcode.mogo.storage.SharedPrefsMgr import com.mogo.eagle.core.utilcode.mogo.toast.TipToast import com.mogo.eagle.core.utilcode.util.ThreadUtils -import com.mogo.eagle.core.utilcode.util.TimeUtils import com.mogo.module.common.MogoApisHandler import com.mogo.service.cloud.socket.IMogoOnMessageListener import com.zhidao.loglib.bean.RemoteLogPushContent @@ -25,7 +26,6 @@ import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchConst.Companio import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchConst.Companion.LOG_PUSH_TYPE import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchConst.Companion.START_CATCH_LOG import com.zhjt.mogo_core_function_devatools.logcatch.MogoLogCatchConst.Companion.STOP_CATCH_LOG -import java.io.File @SuppressLint("StaticFieldLeak") object MogoLogCatchManager : IMogoOnMessageListener, Handler.Callback, @@ -47,7 +47,6 @@ object MogoLogCatchManager : IMogoOnMessageListener, Handl MogoApisHandler.getInstance().apis .getSocketManagerApi(AbsMogoApplication.getApp().applicationContext) .registerOnMessageListener(LOG_PUSH_TYPE, this) - manualContent.duration = 10 manualContent.pkgName = context.packageName } @@ -80,12 +79,13 @@ object MogoLogCatchManager : IMogoOnMessageListener, Handl return false } - fun startCatchLog() { + fun startCatchLog(duration:Int = 10) { if (catchingList.contains(MANUAL_CATCH_PKG_NAME)) { TipToast.shortTip("已经在抓取日志了,请稍后再试") } else { - Logger.d(TAG, "开始抓取日志====") + Logger.d(TAG, "开始抓取日志==== duration : $duration") manualContent.type = START_CATCH_LOG + manualContent.duration = duration startCatchLog(manualContent) } } @@ -101,20 +101,28 @@ object MogoLogCatchManager : IMogoOnMessageListener, Handl var delay = (content.duration).toLong() handler.removeMessages(MSG_TRY_CLOSE_LOG) if (delay <= 0) { - // 如果push 下来的delay小于等于0,那就给个默认最大值一小时 + // 如果push 下来的delay小于等于0,那就给个默认值 delay = 10 } handler.sendEmptyMessageDelayed(MSG_TRY_CLOSE_LOG, delay * 1000L * 60) + openLoggerLevel() + logInfoManager = LogInfoManagerFactory.createPushLogInfoManager( mContext, - MoGoAiCloudClientConfig.getInstance().sn + File.separator + TimeUtils.formatYMD(System.currentTimeMillis()), - content, this + MoGoAiCloudClientConfig.getInstance().sn, + content, + this ) + logInfoManager?.start() logInfoManager?.registerLogOutListener { lineLog -> CallerDevaToolsListenerManager.invokeDevaToolsLogCatchLines(lineLog) } + + SharedPrefsMgr.getInstance(mContext!!).putBoolean(MoGoConfig.CATCH_LOG, true) + SharedPrefsMgr.getInstance(mContext!!) + .putLong(MoGoConfig.CATCH_LOG_TIME, System.currentTimeMillis()) } private fun stopCatchLog(content: RemoteLogPushContent) { @@ -125,6 +133,9 @@ object MogoLogCatchManager : IMogoOnMessageListener, Handl logInfoManager?.stop() logInfoManager = null closeLoggerLevel() + + SharedPrefsMgr.getInstance(mContext!!).putBoolean(MoGoConfig.CATCH_LOG, false) + SharedPrefsMgr.getInstance(mContext!!).putLong(MoGoConfig.CATCH_LOG_TIME, 0) } /** @@ -152,6 +163,10 @@ object MogoLogCatchManager : IMogoOnMessageListener, Handl override fun onClose(pkgName: String?) { ThreadUtils.runOnUiThread { + + SharedPrefsMgr.getInstance(mContext!!).putBoolean(MoGoConfig.CATCH_LOG, false) + SharedPrefsMgr.getInstance(mContext!!).putLong(MoGoConfig.CATCH_LOG_TIME, 0) + CallerDevaToolsListenerManager.invokeDevaToolsLogCatchClose() TipToast.shortTip("日志抓取默认计时结束") } diff --git a/core/function-impl/mogo-core-function-hmi/build.gradle b/core/function-impl/mogo-core-function-hmi/build.gradle index 37a0f41171..eacd9256cd 100644 --- a/core/function-impl/mogo-core-function-hmi/build.gradle +++ b/core/function-impl/mogo-core-function-hmi/build.gradle @@ -59,26 +59,26 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { - implementation rootProject.ext.dependencies.androidxrecyclerview implementation rootProject.ext.dependencies.modulecommon + compileOnly rootProject.ext.dependencies.mogoserviceapi + implementation project(':libraries:map-usbcamera') implementation rootProject.ext.dependencies.mogo_core_res implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_network implementation rootProject.ext.dependencies.mogo_core_function_call -// implementation rootProject.ext.dependencies.mogo_core_res } else { implementation project(':modules:mogo-module-common') implementation project(':services:mogo-service-api') + implementation project(':libraries:map-usbcamera') implementation project(':core:mogo-core-res') implementation project(':core:mogo-core-data') implementation project(':core:mogo-core-utils') - implementation project(':core:mogo-core-res') + implementation project(':core:mogo-core-network') implementation project(':core:mogo-core-function-api') implementation project(':core:mogo-core-function-call') -// implementation project(':core:mogo-core-res') } } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml index 27c8e3db04..a606583b11 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml +++ b/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt index 077b88887f..bcd0564a1d 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt @@ -5,12 +5,16 @@ import android.os.Bundle import android.os.Handler import android.text.TextUtils import android.util.Log -import android.view.* +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager import android.view.animation.OvershootInterpolator import androidx.lifecycle.Lifecycle.Event.ON_DESTROY import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.lifecycleScope -import androidx.transition.* +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager import com.alibaba.android.arouter.facade.annotation.Route import com.mogo.cloud.passport.MoGoAiCloudClientConfig import com.mogo.commons.mvp.MvpFragment @@ -98,6 +102,8 @@ class MoGoHmiFragment : MvpFragment private var onBadCaseShow: (() -> View)? = null private var onBadCaseHide: (() -> Unit)? = null + private var upgradeTipsView: (() -> View)? = null + companion object { private const val MSG_WHAT_DISMISS_BAD_CASE_ENTRY = 0x1010 private val CASE_EXPIRE_DURATION = TimeUnit.HOURS.toMillis(4) @@ -116,7 +122,8 @@ class MoGoHmiFragment : MvpFragment Log.d("QQQ", "-- step -- 1 --") var oldT = try { old?.timestamp?.takeIf { it.isNotBlank() }?.let { - SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time ?: 0L + SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time + ?: 0L } ?: 0L } catch (t: Throwable) { t.printStackTrace() @@ -126,30 +133,31 @@ class MoGoHmiFragment : MvpFragment var newT = try { it.receive()?.also { record = it }?.timestamp?.takeIf { it.isNotBlank() }?.let { SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time - ?: 0L + ?: 0L } ?: 0L } catch (t: Throwable) { t.printStackTrace() 0L } - if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < CASE_EXPIRE_DURATION)) { + if (oldT == 0L || (newT > 0L && (newT - oldT > 0L) && (newT - oldT) < CASE_EXPIRE_DURATION)) { Log.d("QQQ", "-- step -- 2 --") record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also { - Log.d("QQQ", "record: [$record] is displaying and consuming ~~~" ) + Log.d("QQQ", "record: [$record] is displaying and consuming ~~~") showBadCaseEntrance(it) } continue } while (oldT != 0L && newT != 0L && (newT - oldT) >= CASE_EXPIRE_DURATION) { - Log.d("QQQ", "record: [$record] has been discarded, because it has been timeout." ) + Log.d("QQQ", "record: [$record] has been discarded, because it has been timeout.") oldT = newT newT = try { it.receive()?.also { record = it }?.timestamp?.takeIf { it.isNotBlank() }?.let { - SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time ?: 0L + SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).parse(it)?.time + ?: 0L } ?: 0L } catch (t: Throwable) { t.printStackTrace() @@ -157,7 +165,7 @@ class MoGoHmiFragment : MvpFragment } } record?.takeIf { it.key != old?.key && it.timestamp != old?.timestamp }?.also { - Log.d("QQQ", "record: [$record] is displaying for rest ..." ) + Log.d("QQQ", "record: [$record] is displaying for rest ...") showBadCaseEntrance(it) } } else { @@ -169,7 +177,7 @@ class MoGoHmiFragment : MvpFragment it.visibility = View.VISIBLE } } - Log.d("QQQ", "record: [$old] hasn't been consumed~~~~" ) + Log.d("QQQ", "record: [$old] hasn't been consumed~~~~") } } finally { delay(1000) @@ -231,6 +239,11 @@ class MoGoHmiFragment : MvpFragment } } + /*ivCameraIcon?.setOnLongClickListener { + activity?.let { it1 -> CarcorderPreviewView.show(it1) } + true + }*/ + ivToolsIcon?.setOnClickListener { if (toolsViewFloat == null) { showToolsFloat() @@ -259,6 +272,11 @@ class MoGoHmiFragment : MvpFragment } } } + + /*// TODO 这里后面需要改成独立进程通讯后台获取YUV + view.postDelayed({ + activity?.let { CarcorderPreviewView.show(it) } + }, 1000)*/ } @OptIn(ExperimentalCoroutinesApi::class) @@ -346,21 +364,29 @@ class MoGoHmiFragment : MvpFragment onBadCaseHide = onHide } + /** + *注册工控机升级提示圆点View的回调 + * @param 提示圆点View + */ + override fun registerUpgradeTipsCallback(tipsView: () -> View) { + upgradeTipsView = tipsView + } + /** * 工控机重启返回结果 * @param code * @param msg */ override fun showDockerRebootResult(code: Int, msg: String) { - ThreadUtils.runOnUiThread{ - if(code>=-1){ + ThreadUtils.runOnUiThread { + if (code >= -1) { //重启成功 ToastUtils.showShort("重启成功") - }else{ + } else { //重启失败 msg?.let { ToastUtils.showShort(it) - } + } } } } @@ -372,7 +398,7 @@ class MoGoHmiFragment : MvpFragment if (autoPilotBadCaseView == null) { autoPilotBadCaseView = AutoPilotBadCaseView(it).also { itx -> val record = - autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult + autoPilotBadCaseEntrance?.getTag(R.id.autopilot_badcase_record) as? AutoPilotRecordResult itx.tag = record itx.onDismiss { dismissBadCaseFloatView() @@ -383,12 +409,13 @@ class MoGoHmiFragment : MvpFragment val params = mutableMapOf() autoPilotBadCaseEntrance?.apply { params["carLicense"] = - MoGoAiCloudClientConfig.getInstance().sn + MoGoAiCloudClientConfig.getInstance().sn params["filename"] = record?.fileName ?: "" params["filesize"] = record?.total.toString() params["key"] = record?.key ?: "" params["reason"] = it.reason ?: "" - params["duration"] = record?.duration?.toInt()?.toString() ?: "" + params["duration"] = record?.duration?.toInt()?.toString() + ?: "" params["timestamp"] = record?.timestamp ?: "" } val response = post(params) @@ -403,9 +430,9 @@ class MoGoHmiFragment : MvpFragment dismissBadCaseFloatView() dismiss?.invoke() CallerAutoPilotManager.recordCause( - record?.key, - record?.fileName, - it.id, it.reason) + record?.key, + record?.fileName, + it.id, it.reason) ToastUtils.showShort("接管反馈成功~") record?.also { it.consumed = true @@ -431,39 +458,39 @@ class MoGoHmiFragment : MvpFragment } } autoPilotToolsFloat = WarningFloat.with(it) - .setTag("BadCaseCollectFloat") - .setLayout(autoPilotBadCaseView!!) - .setSidePattern(SidePattern.LEFT) - .setGravity(Gravity.LEFT, offsetY = 72) - .setImmersionStatusBar(true) - .setAnimator(object : DefaultAnimator() { - override fun enterAnim( - view: View, - params: WindowManager.LayoutParams, - windowManager: WindowManager, - sidePattern: SidePattern - ): Animator? = - super.enterAnim(view, params, windowManager, sidePattern) - ?.apply { - interpolator = OvershootInterpolator() - } + .setTag("BadCaseCollectFloat") + .setLayout(autoPilotBadCaseView!!) + .setSidePattern(SidePattern.LEFT) + .setGravity(Gravity.LEFT, offsetY = 72) + .setImmersionStatusBar(true) + .setAnimator(object : DefaultAnimator() { + override fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.enterAnim(view, params, windowManager, sidePattern) + ?.apply { + interpolator = OvershootInterpolator() + } - override fun exitAnim( - view: View, - params: WindowManager.LayoutParams, - windowManager: WindowManager, - sidePattern: SidePattern - ): Animator? = - super.exitAnim(view, params, windowManager, sidePattern) - ?.setDuration(200) - }) - .addWarningStatusListener(object : IMoGoWarningStatusListener { - override fun onDismiss() { - autoPilotToolsFloat = null - autoPilotBadCaseView = null - } - }) - .show() + override fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.exitAnim(view, params, windowManager, sidePattern) + ?.setDuration(200) + }) + .addWarningStatusListener(object : IMoGoWarningStatusListener { + override fun onDismiss() { + autoPilotToolsFloat = null + autoPilotBadCaseView = null + } + }) + .show() } else { autoPilotToolsFloat?.show() } @@ -663,7 +690,8 @@ class MoGoHmiFragment : MvpFragment if (floatWindow == null || TextUtils.isEmpty(showTag) || !floatWindow.isShow() || floatWindow.config.floatTag != tag) { val notificationView = V2XNotificationView(it) notificationView.setWarningIcon(EventTypeEnum.getWarningIcon(v2xType.toString())) - val warningContent = alertContent ?: EventTypeEnum.getWarningContent(v2xType.toString()) + val warningContent = alertContent + ?: EventTypeEnum.getWarningContent(v2xType.toString()) if (warningContent.isEmpty()) { Logger.e(TAG, "Show warningContent is null or empty!") return@launchWhenResumed @@ -674,48 +702,48 @@ class MoGoHmiFragment : MvpFragment WarningFloat.dismiss(floatWindow.config.floatTag, true) } mWarningFloat = WarningFloat.with(it) - .setTag(tag) - .setLayout(notificationView) - .setSidePattern(SidePattern.RESULT_TOP) - .setCountDownTime(expireTime) - .setGravity(Gravity.CENTER_HORIZONTAL, offsetY = 110) - .setImmersionStatusBar(true) - .isEnqueue(true) - .addWarningStatusListener(listenerIMoGo) - .addWarningStatusListener(object : IMoGoWarningStatusListener { - override fun onShow() { - // 创建弹窗成功才进行TTS播报 - Logger.d( - "MoGoWarningFragment", - "mWarningFloat = $mWarningFloat---ttsContent = $ttsContent" - ) - if (mWarningFloat != null && !TextUtils.isEmpty(ttsContent) && playTts) { - Logger.d("MoGoWarningFragment", "---> ttsContent = $ttsContent") - AIAssist.getInstance(activity) - .speakTTSVoice(ttsContent) - } - } - }) - .setAnimator(object : DefaultAnimator() { - override fun enterAnim( - view: View, - params: WindowManager.LayoutParams, - windowManager: WindowManager, - sidePattern: SidePattern - ): Animator? = - super.enterAnim(view, params, windowManager, sidePattern)?.apply { - interpolator = OvershootInterpolator() + .setTag(tag) + .setLayout(notificationView) + .setSidePattern(SidePattern.RESULT_TOP) + .setCountDownTime(expireTime) + .setGravity(Gravity.CENTER_HORIZONTAL, offsetY = 110) + .setImmersionStatusBar(true) + .isEnqueue(true) + .addWarningStatusListener(listenerIMoGo) + .addWarningStatusListener(object : IMoGoWarningStatusListener { + override fun onShow() { + // 创建弹窗成功才进行TTS播报 + Logger.d( + "MoGoWarningFragment", + "mWarningFloat = $mWarningFloat---ttsContent = $ttsContent" + ) + if (mWarningFloat != null && !TextUtils.isEmpty(ttsContent) && playTts) { + Logger.d("MoGoWarningFragment", "---> ttsContent = $ttsContent") + AIAssist.getInstance(activity) + .speakTTSVoice(ttsContent) + } } + }) + .setAnimator(object : DefaultAnimator() { + override fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.enterAnim(view, params, windowManager, sidePattern)?.apply { + interpolator = OvershootInterpolator() + } - override fun exitAnim( - view: View, - params: WindowManager.LayoutParams, - windowManager: WindowManager, - sidePattern: SidePattern - ): Animator? = - super.exitAnim(view, params, windowManager, sidePattern)?.setDuration(200) - }) - .show() + override fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.exitAnim(view, params, windowManager, sidePattern)?.setDuration(200) + }) + .show() } else { val notification = floatWindow.config.layoutView as? V2XNotificationView if (alertContent?.isNotEmpty() == true) { @@ -1067,22 +1095,32 @@ class MoGoHmiFragment : MvpFragment * @param upgradeStatus 升级状态 */ override fun showAdUpgradeStatus( - upgradeMode: Int, - downloadStatus: Int, - currentProgress: Int, - totalProgress: Int, - downloadVersion: String, - upgradeStatus: Int + upgradeMode: Int, + downloadStatus: Int, + currentProgress: Int, + totalProgress: Int, + downloadVersion: String, + upgradeStatus: Int ) { - ThreadUtils.runOnUiThread{ + ThreadUtils.runOnUiThread { + val tipsView = upgradeTipsView?.invoke() //如果工控机处于“下载中”、“可升级(下载完成)”、“升级中”、“升级失败”状态时,工具箱入口显示红色角标 - if(AdUpgradeStateHelper.showUpgradeTips(downloadStatus, upgradeStatus)){ - viewUpgradeTips.visibility = View.VISIBLE - }else{ - viewUpgradeTips.visibility = View.GONE + if (AdUpgradeStateHelper.showUpgradeTips(downloadStatus, upgradeStatus)) { + if (HmiBuildConfig.isShowUpgradeTipsView){ + viewUpgradeTips?.visibility = View.VISIBLE + }else{ + tipsView?.let { + it.visibility = View.VISIBLE + } + } + } else { + viewUpgradeTips?.visibility = View.GONE + tipsView?.let { + it.visibility = View.GONE + } } //将状态同步到工具箱 - toolsView?.showAdUpgradeStatus(upgradeMode,downloadStatus, currentProgress, totalProgress, downloadVersion, upgradeStatus) + toolsView?.showAdUpgradeStatus(upgradeMode, downloadStatus, currentProgress, totalProgress, downloadVersion, upgradeStatus) } } 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 new file mode 100644 index 0000000000..b67917db9c --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt @@ -0,0 +1,252 @@ +package com.mogo.eagle.core.function.hmi.ui.carcorder + +import android.animation.Animator +import android.app.Activity +import android.content.Context +import android.hardware.usb.UsbDevice +import android.os.Looper +import android.util.Log +import android.view.* +import android.view.animation.OvershootInterpolator +import com.mogo.cloud.live.manager.ILiveStreamManager +import com.mogo.cloud.live.manager.LiveStreamManagerImpl +import com.mogo.cloud.passport.MoGoAiCloudClientConfig +import com.mogo.eagle.core.function.hmi.R +import com.mogo.eagle.core.function.hmi.notification.WarningFloat +import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator +import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.usbcamera.UVCCameraHelper +import com.serenegiant.usb.ParentPreviewConstraintLayout +import com.serenegiant.usb.widget.CameraViewInterface +import kotlinx.android.synthetic.main.view_carcorder_preview.view.* + +/** + * @author donghongyu + * @date 2021/9/30 8:46 下午 + * USB-Camera 摄像头预览 + * + * TODO 临时方案,后面考虑封装将摄像头数据流通过 Tensorflow-lite 处理后展示 + */ +class CarcorderPreviewView private constructor( + context: Context +) : ParentPreviewConstraintLayout(context), + CameraViewInterface.Callback { + + private val TAG = "CarcorderPreviewView" + + + private var mCameraHelper: UVCCameraHelper? = null + + private var isRequest = false + private var isPreview = false + + private var liveStreamManager: ILiveStreamManager? = null + + init { + LayoutInflater.from(context).inflate(R.layout.view_carcorder_preview, this, true) + } + + companion object { + private var mCarcorderPreviewViewFloat: WarningFloat.Builder? = null + + @Volatile + var instance: CarcorderPreviewView? = null + + fun getInstance(context: Activity): CarcorderPreviewView { + if (instance == null) { + synchronized(CarcorderPreviewView::class) { + if (instance == null) { + instance = CarcorderPreviewView(context) + } + } + } + return instance!! + } + + /** + * 展示窗口 + */ + fun show(context: Activity) { + if (mCarcorderPreviewViewFloat == null) { + val carcorderPreviewVie = getInstance(context) + mCarcorderPreviewViewFloat = WarningFloat.with(context) + .setTag("CarcorderPreviewView") + .setLayout(carcorderPreviewVie) + .setSidePattern(SidePattern.RIGHT) + .setGravity(Gravity.RIGHT, offsetY = 250) + .setImmersionStatusBar(true) + .setAnimator(object : DefaultAnimator() { + override fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.enterAnim(view, params, windowManager, sidePattern) + ?.apply { + interpolator = OvershootInterpolator() + } + + override fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.exitAnim(view, params, windowManager, sidePattern) + ?.setDuration(200) + }) + .show() + } else { + dismiss() + } + } + + /** + * 隐藏窗口 + */ + fun dismiss() { + if (mCarcorderPreviewViewFloat != null) { + WarningFloat.dismiss(mCarcorderPreviewViewFloat!!.config.floatTag, false) + mCarcorderPreviewViewFloat = null + } + } + } + + private val listener: UVCCameraHelper.OnMyDevConnectListener = object : UVCCameraHelper.OnMyDevConnectListener { + override fun onAttachDev(device: UsbDevice?) { + Log.d(TAG, "onAttachDev") + // request open permission + if (!isRequest) { + isRequest = true + mCameraHelper?.requestPermission(0) + } + } + + override fun onDettachDev(device: UsbDevice) { + Log.d(TAG, "onDettachDev") + // close camera + if (isRequest) { + isRequest = false + mCameraHelper?.closeCamera() + showShortMsg(device.deviceName + " is out") + } + } + + override fun onConnectDev(device: UsbDevice?, isConnected: Boolean) { + Log.d(TAG, "onConnectDev:isConnected=$isConnected") + if (!isConnected) { + showShortMsg("fail to connect,please check resolution params") + isPreview = false + } else { + isPreview = true + showShortMsg("相机连接中") + // initialize seekbar + // need to wait UVCCamera initialize over + Thread { + try { + Thread.sleep(2500) + } catch (e: InterruptedException) { + e.printStackTrace() + } + Looper.prepare() + if (mCameraHelper != null && mCameraHelper!!.isCameraOpened) { + Logger.d(TAG, "亮度(brightness):${mCameraHelper!!.getModelValue(UVCCameraHelper.MODE_BRIGHTNESS)}") + Logger.d(TAG, "对比度(contrast):${mCameraHelper!!.getModelValue(UVCCameraHelper.MODE_CONTRAST)}") + } + Looper.loop() + }.start() + } + } + + override fun onDisConnectDev(device: UsbDevice?) { + Log.d(TAG, "onDisConnectDev") + showShortMsg("相机断开连接") + } + + override fun onCancelDev(device: UsbDevice?) { + Log.d(TAG, "onCancelDev:" + device?.deviceName) + } + } + + + private fun showShortMsg(msg: String) { + //Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + } + + private fun initView() { + // step.1 initialize UVCCameraHelper + carcorderPreview.setCallback(this) + mCameraHelper = UVCCameraHelper.getInstance() + mCameraHelper?.setDefaultFrameFormat(UVCCameraHelper.FRAME_FORMAT_MJPEG) + mCameraHelper?.initUSBMonitor(context as Activity, carcorderPreview, listener) + + mCameraHelper?.setOnPreviewFrameListener { nv21Yuv -> + Log.d(TAG, "onPreviewResult: " + nv21Yuv.size) + //Log.i(TAG, "onVideoFrame byte length: " + bytesLength); + if (liveStreamManager != null) { + // 将摄像头采集的YUV数据推送到ZEGO + liveStreamManager!!.notifyYUVData(nv21Yuv, 640, 480, 2) + } + } + } + + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + initView() + // step.2 register USB event broadcast + if (mCameraHelper != null) { + mCameraHelper!!.registerUSB() + } + // 初始化直播流管理 + // 初始化直播流管理 + liveStreamManager = LiveStreamManagerImpl.getInstance((context as Activity).application, + MoGoAiCloudClientConfig.getInstance().sn, true) + + // 设置状态回调 + liveStreamManager!!.setLiveStatusChangeCallback { status -> + if (status == 0) { + Logger.d(TAG, "直播中……") + } else { + Logger.d(TAG, "直播结束……") + } + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + // step.3 unregister USB event broadcast + if (mCameraHelper != null) { + mCameraHelper!!.unregisterUSB() + } + + if (liveStreamManager != null) { + // 停止 + liveStreamManager!!.stopLiveStream() + // 释放资源 + liveStreamManager!!.release() + } + } + + override fun onSurfaceCreated(view: CameraViewInterface?, surface: Surface?) { + if (!isPreview && mCameraHelper!!.isCameraOpened) { + mCameraHelper!!.startPreview(carcorderPreview) + isPreview = true + } + } + + override fun onSurfaceChanged(view: CameraViewInterface?, surface: Surface?, width: Int, height: Int) { + + } + + override fun onSurfaceDestroy(view: CameraViewInterface?, surface: Surface?) { + if (isPreview && mCameraHelper!!.isCameraOpened) { + mCameraHelper!!.stopPreview() + isPreview = false + } + } + + +} \ 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/logcatch/AbsLogView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/AbsLogView.kt index 8af3873f7b..b83074ecb1 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/AbsLogView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/AbsLogView.kt @@ -68,7 +68,6 @@ abstract class AbsLogView : ILogView, TouchProxy.OnTouchEventListener { fun show(context: Context) { if (isShow) { - Log.d("EmArrow", "isShow : $isShow") return } performCreate(context) diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/LogInfoView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/LogInfoView.kt index 6a8e8bf27c..30495732c2 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/LogInfoView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/logcatch/LogInfoView.kt @@ -244,7 +244,6 @@ class LogInfoView : AbsLogView() { mLogHint!!.visibility = View.VISIBLE mLogRvWrap!!.visibility = View.GONE val layoutParams = systemLayoutParams ?: return - Log.d("EmArrow", "minimize , layoutParams is not null") layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/setting/DebugSettingView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/setting/DebugSettingView.kt index 7a428474e1..7cce805b60 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/setting/DebugSettingView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/setting/DebugSettingView.kt @@ -11,6 +11,7 @@ import com.mogo.commons.AbsMogoApplication import com.mogo.commons.debug.DebugConfig import com.mogo.eagle.core.data.app.AppConfigInfo import com.mogo.eagle.core.data.autopilot.* +import com.mogo.eagle.core.data.chain.ChainConstant import com.mogo.eagle.core.data.config.FunctionBuildConfig import com.mogo.eagle.core.data.constants.MoGoConfig import com.mogo.eagle.core.data.map.MogoLocation @@ -35,11 +36,20 @@ import com.mogo.eagle.core.function.call.obu.CallerObuListenerManager import com.mogo.eagle.core.function.hmi.R import com.mogo.eagle.core.function.hmi.ui.logcatch.ILogViewListener import com.mogo.eagle.core.function.hmi.ui.logcatch.LogInfoView +import com.mogo.eagle.core.utilcode.kotlin.onClick import com.mogo.eagle.core.utilcode.mogo.logger.LogLevel import com.mogo.eagle.core.utilcode.mogo.logger.Logger import com.mogo.eagle.core.utilcode.mogo.storage.SharedPrefsMgr import com.mogo.eagle.core.utilcode.util.* import com.mogo.map.MogoMap +import com.mogo.map.uicontroller.VisualAngleMode +import com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_300 +import com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_CROSS +import com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_TOP +import com.mogo.map.uicontroller.VisualAngleMode.MODE_CLOSE_SIGHT +import com.mogo.map.uicontroller.VisualAngleMode.MODE_LONG_SIGHT +import com.mogo.map.uicontroller.VisualAngleMode.MODE_MEDIUM_SIGHT +import com.mogo.module.common.MogoApisHandler import kotlinx.android.synthetic.main.view_debug_setting.view.* import java.util.* @@ -50,13 +60,13 @@ import java.util.* * 展示 本机、网络、工控机、OBU等状态信息,支持设置IP,等参数进行调试 */ class DebugSettingView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), IMoGoObuStatusListener, - IMoGoAutopilotStatusListener, IMoGoAutopilotCarStateListener, - IMoGoMapLocationListener, IMoGoAutopilotIdentifyListener, - IMoGoAutopilotPlanningListener { + IMoGoAutopilotStatusListener, IMoGoAutopilotCarStateListener, + IMoGoMapLocationListener, IMoGoAutopilotIdentifyListener, + IMoGoAutopilotPlanningListener { private val TAG = "DebugSettingView" @@ -75,6 +85,12 @@ class DebugSettingView @JvmOverloads constructor( // 全局路径规划点个数 private var mRouteInfoSize = 0 + private val mapUiController by lazy { + MogoApisHandler.getInstance().apis?.mapServiceApi?.mapUIController + } + + private var lastVisualAngleMode: VisualAngleMode? = null + init { LayoutInflater.from(context).inflate(R.layout.view_debug_setting, this, true) initView() @@ -204,21 +220,40 @@ class DebugSettingView @JvmOverloads constructor( } } - changesight_top_btn.setOnClickListener { - CallerHDMapManager.setMapDAngle(0); + lastVisualAngleMode = mapUiController?.currentMapVisualAngle + + changesight_top_btn.onClick { + mapUiController?.changeMapVisualAngle(MAP_STYLE_VR_ANGLE_TOP, null) } + changesight_back_btn?.onClick { + mapUiController?.changeMapVisualAngle(MAP_STYLE_VR_ANGLE_300, null) + } + + changesight_cross_btn?.onClick { + mapUiController?.changeMapVisualAngle(MAP_STYLE_VR_ANGLE_CROSS, null) + } + + changesight_far_btn?.onClick { + mapUiController?.changeMapVisualAngle(MODE_LONG_SIGHT, null) + } + + reset_changesight?.onClick { + lastVisualAngleMode?.let { + mapUiController?.changeMapVisualAngle(it, null) + } + } tvObuInfo.text = CallerObuListenerManager.getObuStatusInfoJsonString() tvAutopilotInfo.text = - CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfoJsonString() + CallerAutoPilotStatusListenerManager.getAutoPilotStatusInfoJsonString() // 绘制应用基本信息 drawAppInfo() // 初始化OBU IP信息 val ipAddress = - SharedPrefsMgr.getInstance(context).getString(MoGoConfig.OBU_IP, "192.168.1.199") + SharedPrefsMgr.getInstance(context).getString(MoGoConfig.OBU_IP, "192.168.1.199") etObuIP.setText(ipAddress) etObuIP.text?.let { etObuIP.setSelection(it.length) } @@ -255,18 +290,18 @@ class DebugSettingView @JvmOverloads constructor( // 初始化 GSP数据源 数据 rgGpsProvider.check( - when (FunctionBuildConfig.gpsProvider) { - 0 -> { - R.id.rbGpsProviderAndroid - } - 1 -> { - R.id.rbGpsProviderRTK - } - 2 -> { - R.id.rbGpsProviderOBU - } - else -> R.id.rbGpsProviderAndroid + when (FunctionBuildConfig.gpsProvider) { + 0 -> { + R.id.rbGpsProviderAndroid } + 1 -> { + R.id.rbGpsProviderRTK + } + 2 -> { + R.id.rbGpsProviderOBU + } + else -> R.id.rbGpsProviderAndroid + } ) rgGpsProvider.setOnCheckedChangeListener { group, checkedId -> when (checkedId) { @@ -350,31 +385,29 @@ class DebugSettingView @JvmOverloads constructor( } tbLogCatch.isChecked = - SharedPrefsMgr.getInstance(context).getBoolean(MoGoConfig.CATCH_LOG, false) + SharedPrefsMgr.getInstance(context).getBoolean(MoGoConfig.CATCH_LOG, false) tbLogCatch.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { CallerDevaToolsManager.startCatchLog() - SharedPrefsMgr.getInstance(context).putBoolean(MoGoConfig.CATCH_LOG, true) } else { CallerDevaToolsManager.stopCatchLog() - SharedPrefsMgr.getInstance(context).putBoolean(MoGoConfig.CATCH_LOG, false) } } CallerDevaToolsListenerManager.registerDevaToolsLogCatchListener(TAG, - object : IMoGoDevaToolsListener { - override fun onLogCatchClose() { - super.onLogCatchClose() - tbLogCatch.isChecked = false - } + object : IMoGoDevaToolsListener { + override fun onLogCatchClose() { + super.onLogCatchClose() + tbLogCatch.isChecked = false + } - override fun onLogCatch(lineLog: String) { - logInfoView?.let { - if (logViewAttach) { - it.onLogCatch(lineLog) - } + override fun onLogCatch(lineLog: String) { + logInfoView?.let { + if (logViewAttach) { + it.onLogCatch(lineLog) } } - }) + } + }) tbLogDebugView.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { logInfoView = LogInfoView() @@ -394,6 +427,15 @@ class DebugSettingView @JvmOverloads constructor( logViewDestroy() } } + tbLogTraceView.setOnCheckedChangeListener { _, isChecked -> + val traceInfoMap = CallerDevaToolsManager.getTraceInfo() + val chainLogParam = traceInfoMap[ChainConstant.CHAIN_LINK_LOG_WEB_SOCKET_DATA] + chainLogParam?.let { + it.record = isChecked + traceInfoMap[ChainConstant.CHAIN_LINK_LOG_WEB_SOCKET_DATA] = chainLogParam + CallerDevaToolsManager.refreshTraceInfo(traceInfoMap) + } + } } private fun logViewDestroy() { @@ -428,15 +470,15 @@ class DebugSettingView @JvmOverloads constructor( tvAutopilotInfo.text = GsonUtils.toJson(mAutoPilotStatusInfo) tvCarInfo.text = - "GPS时间:${mAutoPilotCarStateInfo?.values?.satelliteTime}\n" + - "自车经纬度:\n${mAutoPilotCarStateInfo?.values?.lon}\n${mAutoPilotCarStateInfo?.values?.lat}\n" + "GPS时间:${mAutoPilotCarStateInfo?.values?.satelliteTime}\n" + + "自车经纬度:\n${mAutoPilotCarStateInfo?.values?.lon}\n${mAutoPilotCarStateInfo?.values?.lat}\n" tvIdentifyInfo.text = - "感知数据个数:${mIdentifyDataSize}" + "感知数据个数:${mIdentifyDataSize}" tvTrajectoryInfoSize.text = - "引导线点个数:${mTrajectoryInfoSize}" + "引导线点个数:${mTrajectoryInfoSize}" tvRouteInfoSize.text = - "全局路径规划点个数:${mRouteInfoSize}" + "全局路径规划点个数:${mRouteInfoSize}" // 用完之后重制为0,防止节点回掉突然没数据,导致页面显示还是之前的数据情况 mIdentifyDataSize = 0 diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt index 8f7d97bdf1..587bac0de2 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/tools/AutoPilotAndCheckView.kt @@ -8,11 +8,13 @@ import android.view.View import android.widget.FrameLayout import com.mogo.eagle.core.data.autopilot.AutopilotGuardianStatusInfo import com.mogo.eagle.core.data.autopilot.AutopilotStatusInfo +import com.mogo.eagle.core.data.config.FunctionBuildConfig import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotManager import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager import com.mogo.eagle.core.function.hmi.R import com.mogo.eagle.core.function.hmi.ui.utils.KeyBoardUtil +import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils import com.mogo.eagle.core.utilcode.util.ToastUtils import kotlinx.android.synthetic.main.view_auto_pilot_check.view.* import kotlinx.android.synthetic.main.view_check_system.view.* @@ -102,6 +104,7 @@ class AutoPilotAndCheckView @JvmOverloads constructor( return@setOnTouchListener false } } + updateSpeedSettingViews() // // 比如需要设置默认速度 // val speed = "30" // etInputSpeed.setText(speed) @@ -112,6 +115,22 @@ class AutoPilotAndCheckView @JvmOverloads constructor( this.clickListener = clickListener } + /** + * Bus不可设置自动驾驶速度,而Taxi可以 + */ + private fun updateSpeedSettingViews() { + when { + AppIdentityModeUtils.isBus(FunctionBuildConfig.appIdentityMode) -> { + tvSpeedTitle.visibility = View.GONE + llSpeedPosition.visibility = View.GONE + } + else -> { + tvSpeedTitle.visibility = View.VISIBLE + llSpeedPosition.visibility = View.VISIBLE + } + } + } + /** * 展示工控机下载、升级状态信息 * @param upgradeMode 升级模式(提示升级、静默升级) diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/widget/SystemVersionView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/widget/SystemVersionView.kt index cab7baf628..52faf45bb8 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/widget/SystemVersionView.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/widget/SystemVersionView.kt @@ -71,6 +71,12 @@ class SystemVersionView @JvmOverloads constructor( if(AdUpgradeStateHelper.isDownloading(downloadStatus)){ //点击Toast提示:下载剩余时间 ToastUtils.showShort("预计"+AdUpgradeStateHelper.getRemainingTime(totalProgress,previousProgress,currentProgress)+"下载完成") + }else if(AdUpgradeStateHelper.getUpgradeStatus()){ + //工控机状态为“升级中” + ToastUtils.showShort("新版本升级中,预计5分钟升级完成") + }else if(AdUpgradeStateHelper.isUpgradeFailed(upgradeStatus)){ + //如果升级失败,则Toast提示:升级失败,请联系运维人员 + ToastUtils.showShort("升级失败,请联系运维人员") }else if(AdUpgradeStateHelper.isHintUpgradeMode(upgradeMode) && AdUpgradeStateHelper.isDownloadFinish(downloadStatus,upgradeStatus)){ //如果升级模式为“提示升级”,并且下载状态为已经下载完成,点击弹出升级确认弹窗 if(adUpgradeDialog == null){ @@ -86,6 +92,10 @@ class SystemVersionView @JvmOverloads constructor( //设置当前状态为“升级中” AdUpgradeStateHelper.setUpgradeStatus(true) CallerAutoPilotManager.setIPCUpgradeAffirm() + //将角标设为升级中 + ivAdStatus?.setImageResource(R.drawable.icon_upgrading) + adCircularProgressView?.visibility = View.GONE + ivAdVersion?.setBackgroundResource(R.drawable.version_latest_background) } } @@ -99,12 +109,6 @@ class SystemVersionView @JvmOverloads constructor( }) } adUpgradeDialog?.showUpgradeDialog() - }else if(AdUpgradeStateHelper.getUpgradeStatus()){ - //工控机状态为“升级中” - ToastUtils.showShort("新版本升级中,预计5分钟升级完成") - }else if(AdUpgradeStateHelper.isUpgradeFailed(upgradeStatus)){ - //如果升级失败,则Toast提示:升级失败,请联系运维人员 - ToastUtils.showShort("升级失败,请联系运维人员") } } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml index ddc8434d76..fbbc1af7f3 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/fragment_hmi.xml @@ -112,8 +112,8 @@ + + + + + \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_debug_setting.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_debug_setting.xml index 5b5182de1b..b6619bc811 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_debug_setting.xml +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_debug_setting.xml @@ -413,6 +413,16 @@ android:textOn="关闭日志过滤面板" android:textSize="@dimen/dp_24" /> + + diff --git a/core/function-impl/mogo-core-function-main/build.gradle b/core/function-impl/mogo-core-function-main/build.gradle index dfc2b92c2b..f24479d0cd 100644 --- a/core/function-impl/mogo-core-function-main/build.gradle +++ b/core/function-impl/mogo-core-function-main/build.gradle @@ -68,7 +68,6 @@ dependencies { api rootProject.ext.dependencies.mogocommons api rootProject.ext.dependencies.modulecommon api rootProject.ext.dependencies.mogoservice - api rootProject.ext.dependencies.moduleshare api rootProject.ext.dependencies.moduleextensions api rootProject.ext.dependencies.callchat api rootProject.ext.dependencies.callchatprovider diff --git a/core/function-impl/mogo-core-function-main/src/main/java/com/mogo/eagle/core/function/main/MainMoGoApplication.java b/core/function-impl/mogo-core-function-main/src/main/java/com/mogo/eagle/core/function/main/MainMoGoApplication.java index a56f5b701f..05e39daa4c 100644 --- a/core/function-impl/mogo-core-function-main/src/main/java/com/mogo/eagle/core/function/main/MainMoGoApplication.java +++ b/core/function-impl/mogo-core-function-main/src/main/java/com/mogo/eagle/core/function/main/MainMoGoApplication.java @@ -61,7 +61,6 @@ public abstract class MainMoGoApplication extends AbsMogoApplication { return; } start = System.currentTimeMillis(); - ChainTraceStarter.start("com.mogo.launcher.f", DeviceUtils.getMacAddress()); // Crash 日志收集 initCrashConfig(); initLogConfig(); @@ -153,7 +152,7 @@ public abstract class MainMoGoApplication extends AbsMogoApplication { // 设置是否输出日志 clientConfig.setShowDebugLog(true); // 设置是否是直播推流的主播 - clientConfig.setAnchor(false); + clientConfig.setAnchor(true); // 设置从蘑菇AI开放平台获取的APPKey switch (DebugConfig.getCarMachineType()) { // 比亚迪 @@ -178,8 +177,8 @@ public abstract class MainMoGoApplication extends AbsMogoApplication { // 设置AI云平台分配给三方应用的签名密钥,需要从AI云平台申请 // 设置车机设备的唯一标识(这些表识必须是通过后台录入的设备) clientConfig.setThirdPartyDeviceId(Utils.getDevicesId()); - // 设置循环检测间隔时间 - clientConfig.setLoopCheckDelay(60 * 60 * 24 * 1000); + // 设置循环检测间隔时间(每隔2小时loop一次httpDnsConfig) + clientConfig.setLoopCheckDelay(60 * 60 * 2 * 1000); //连接ami connectAmiIp(); @@ -302,7 +301,8 @@ public abstract class MainMoGoApplication extends AbsMogoApplication { private void initModules() { Logger.d(TAG, "initModules"); - + //mogo deva tools + MogoModulePaths.addModuleFunctionServer(new MogoModule(MogoServicePaths.PATH_DEVA_TOOLS, "IMoGoDevaToolsProvider")); // 初始化 bugly 升级 MogoModulePaths.addBaseModule(new MogoModule(UpgradeReportConstants.PATH, UpgradeReportConstants.NAME)); // 初始化 apm 日志采集 @@ -312,9 +312,8 @@ public abstract class MainMoGoApplication extends AbsMogoApplication { MogoModulePaths.addBaseModule(new MogoModule(MapApiPath.PATH, "CustomMapApiBuilder")); MogoModulePaths.addBaseModule(new MogoModule(ServiceConst.PATH_REFRESH_STRATEGY, ServiceConst.PATH_REFRESH_STRATEGY)); - // MogoModulePaths.addBaseModule(new MogoModule(V2XConst.PATH_V2X_UI, V2XConst.MODULE_NAME)); - //mogo deva tools - MogoModulePaths.addModuleFunctionServer(new MogoModule(MogoServicePaths.PATH_DEVA_TOOLS, "IMoGoDevaToolsProvider")); + // MogoModulePaths.addBaseModule(new MogoModule(V2XConst.PATH_V2X_UI, V2XConst.MODULE_NAME)); + // 域控制器模块(新) MogoModulePaths.addModuleFunctionServer(new MogoModule(MogoServicePaths.PATH_AUTO_PILOT, "IMoGoAutoPilotProvider")); // OBU 模块 diff --git a/core/function-impl/mogo-core-function-map/build.gradle b/core/function-impl/mogo-core-function-map/build.gradle index 7600662cfe..a3a65d41fa 100644 --- a/core/function-impl/mogo-core-function-map/build.gradle +++ b/core/function-impl/mogo-core-function-map/build.gradle @@ -50,7 +50,7 @@ dependencies { implementation rootProject.ext.dependencies.rxandroid kapt rootProject.ext.dependencies.aroutercompiler - implementation rootProject.ext.dependencies.adasHigh + //implementation rootProject.ext.dependencies.adasHigh implementation rootProject.ext.dependencies.mogocustommapoperational if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { implementation rootProject.ext.dependencies.mogoserviceapi @@ -74,6 +74,8 @@ dependencies { implementation project(":libraries:mogo-map") implementation project(":libraries:mogo-map-api") + + implementation project(':libraries:mogo-adas') } } diff --git a/core/function-impl/mogo-core-function-v2x/build.gradle b/core/function-impl/mogo-core-function-v2x/build.gradle index e8df059182..5ec1a4c322 100644 --- a/core/function-impl/mogo-core-function-v2x/build.gradle +++ b/core/function-impl/mogo-core-function-v2x/build.gradle @@ -53,7 +53,7 @@ dependencies { implementation rootProject.ext.dependencies.flexbox kapt rootProject.ext.dependencies.aroutercompiler - implementation rootProject.ext.dependencies.adasHigh +// implementation rootProject.ext.dependencies.adasHigh implementation rootProject.ext.dependencies.mogo_v2x implementation rootProject.ext.dependencies.mogoaicloudtrafficlive if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { @@ -75,6 +75,8 @@ dependencies { implementation project(':modules:mogo-module-carchattingprovider') implementation project(':core:mogo-core-res') + implementation project(':libraries:mogo-adas') + } } diff --git a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingEventView.java b/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingEventView.java deleted file mode 100644 index afcfdf13b2..0000000000 --- a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingEventView.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mogo.eagle.core.function.v2x.events.widgets; - -import com.mogo.commons.mvp.IView; -import com.mogo.module.common.entity.MarkerExploreWay; - -import java.util.List; - -/** - * @author lixiaopeng - * @description - * @since 2020/7/29 - */ -public interface SurroundingEventView extends IView { - - void showSurroudingData(List exploreWayList); -} diff --git a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingMarginDecoration.java b/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingMarginDecoration.java deleted file mode 100644 index 858dcf5e51..0000000000 --- a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/events/widgets/SurroundingMarginDecoration.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.mogo.eagle.core.function.v2x.events.widgets; - -import android.graphics.Rect; -import android.view.View; - -import androidx.recyclerview.widget.RecyclerView; - -/** - * @author lixiaopeng - * @description - * @since 2020/8/11 - */ -public class SurroundingMarginDecoration extends RecyclerView.ItemDecoration { - private int margin ; - private int marginLeft ; - - public SurroundingMarginDecoration(int space, int left) { - margin = space; - marginLeft = left; - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - outRect.bottom = margin; - - //由于每行都只有2个,所以第一个都是2的倍数 - if (parent.getChildLayoutPosition(view) % 2 == 0) { - outRect.left = marginLeft; - outRect.right = margin / 2; - } else { - outRect.left = margin / 2; - outRect.right = marginLeft; - } - } - -} diff --git a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/trafficlight/TrafficLightConst.kt b/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/trafficlight/TrafficLightConst.kt index bb1dc56689..b8faf87b28 100644 --- a/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/trafficlight/TrafficLightConst.kt +++ b/core/function-impl/mogo-core-function-v2x/src/main/java/com/mogo/eagle/core/function/v2x/trafficlight/TrafficLightConst.kt @@ -1,25 +1,10 @@ package com.mogo.eagle.core.function.v2x.trafficlight -import com.mogo.commons.debug.DebugConfig - class TrafficLightConst { companion object { const val MODULE_NAME = "MODULE_V2X_TRAFFIC_LIGHT" - private const val HOST_DEV = "http://dzt-test.zhidaozhixing.com" - private const val HOST_TEST = "http://dzt-test.zhidaozhixing.com" - private const val HOST_DEMO = "http://dzt-show.zhidaozhixing.com" - private const val HOST_PRODUCT = "http://dzt.zhidaozhixing.com" - - fun getNetHost(): String { - return when (DebugConfig.getNetMode()) { - DebugConfig.NET_MODE_DEV -> HOST_DEV - DebugConfig.NET_MODE_QA -> HOST_TEST - DebugConfig.NET_MODE_DEMO -> HOST_DEMO - else -> HOST_PRODUCT - } - } } } diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AdUpgradeStateHelper.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AdUpgradeStateHelper.kt index 0edea8699b..61c79e7ec3 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AdUpgradeStateHelper.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/autopilot/AdUpgradeStateHelper.kt @@ -1,8 +1,5 @@ package com.mogo.eagle.core.data.autopilot -import android.util.Log -import java.util.logging.Logger - /** * @author XuXinChao * @description 工控机升级状态实体类 @@ -32,6 +29,7 @@ class AdUpgradeStateHelper { * @param downloadStatus 下载状态 * @param upgradeStatus 升级状态 */ + @JvmStatic fun showUpgradeTips(downloadStatus: Int,upgradeStatus: Int) : Boolean{ return isDownloading(downloadStatus) || isDownloadFinish(downloadStatus,upgradeStatus) || getUpgradeStatus() || isUpgradeFailed(upgradeStatus) } @@ -41,6 +39,7 @@ class AdUpgradeStateHelper { * @param downloadStatus 下载状态 * @param upgradeStatus 升级状态 */ + @JvmStatic fun showCannotReboot(downloadStatus: Int,upgradeStatus: Int): Boolean{ return isDownloading(downloadStatus)|| isDownloadFinish(downloadStatus,upgradeStatus) || getUpgradeStatus() } @@ -49,6 +48,7 @@ class AdUpgradeStateHelper { * 工控机是否处于“下载中”状态 * @param downloadStatus 下载状态 */ + @JvmStatic fun isDownloading(downloadStatus: Int) : Boolean{ return downloadStatus == DOWNLOAD_START } @@ -58,6 +58,7 @@ class AdUpgradeStateHelper { * @param downloadStatus 下载状态 * @param upgradeStatus 升级状态 */ + @JvmStatic fun isDownloadFinish(downloadStatus: Int,upgradeStatus: Int) : Boolean{ return downloadStatus == DOWNLOAD_FINISH && upgradeStatus == USER_AFFIRM } @@ -66,6 +67,7 @@ class AdUpgradeStateHelper { * 工控机是否处于“下载失败”状态 * @param downloadStatus 下载状态 */ + @JvmStatic fun isDownloadFailed(downloadStatus: Int) : Boolean{ return downloadStatus == DOWNLOAD_FAILED } @@ -74,6 +76,7 @@ class AdUpgradeStateHelper { * 工控机是否处于“升级成功”状态 * @param upgradeStatus 升级状态 */ + @JvmStatic fun isUpgradeSuccess(upgradeStatus: Int) : Boolean{ return upgradeStatus == UPGRADE_SUCCEED } @@ -82,6 +85,7 @@ class AdUpgradeStateHelper { * 工控机是否处于“升级失败”状态 * @param upgradeStatus 升级状态 */ + @JvmStatic fun isUpgradeFailed(upgradeStatus: Int) : Boolean{ return upgradeStatus == UPGRADE_FAILED } @@ -91,6 +95,7 @@ class AdUpgradeStateHelper { * @param currentProgress 当前已下载包体大小 * @param totalProgress 包体总大小 */ + @JvmStatic fun downloadProgress(currentProgress: Int,totalProgress: Int) : Int{ return (currentProgress.toDouble()/totalProgress.toDouble()*100).toInt() } @@ -99,6 +104,7 @@ class AdUpgradeStateHelper { * 工控机升级模式是否是静默升级 * @param upgradeMode 升级模式 */ + @JvmStatic fun isQuietUpgradeMode(upgradeMode: Int) : Boolean{ return upgradeMode == UPGRADE_QUIET } @@ -107,6 +113,7 @@ class AdUpgradeStateHelper { * 工控机升级模式是否是提示升级 * @param upgradeMode 升级模式 */ + @JvmStatic fun isHintUpgradeMode(upgradeMode: Int) : Boolean{ return upgradeMode == UPGRADE_HINT } @@ -114,6 +121,7 @@ class AdUpgradeStateHelper { /** * 获取是否处于“升级中”状态 */ + @JvmStatic fun getUpgradeStatus() : Boolean{ return UPGRADING } @@ -122,6 +130,7 @@ class AdUpgradeStateHelper { * 设置是否处于“升级中”状态 * @param upgrading 是否是升级中 */ + @JvmStatic fun setUpgradeStatus(upgrading: Boolean){ UPGRADING = upgrading } @@ -129,6 +138,7 @@ class AdUpgradeStateHelper { /** * 获取工控机包体下载剩余时间 */ + @JvmStatic fun getRemainingTime(totalProgress: Int,previousProgress: Int,currentProgress: Int) : String{ //剩余包体大小 val remainingSize = totalProgress - currentProgress @@ -139,14 +149,14 @@ class AdUpgradeStateHelper { //转换为分秒格式返回 val minute = time/60 val second = time%60 - if(minute>0 && second>0){ - return minute.toString()+"分钟"+second+"秒" + return if(minute>0 && second>0){ + minute.toString()+"分钟"+second+"秒" }else if(minute>0){ - return minute.toString()+"分钟" + minute.toString()+"分钟" }else if(second>0){ - return second.toString()+"秒" + second.toString()+"秒" }else{ - return "" + "" } } diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainConstant.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainConstant.kt new file mode 100644 index 0000000000..77321383e3 --- /dev/null +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainConstant.kt @@ -0,0 +1,31 @@ +package com.mogo.eagle.core.data.chain + +class ChainConstant { + + companion object{ + + const val CHAIN_LINK_CLOUD_SHOW = 0 + const val CHAIN_LINK_ADAS = 1 + + const val CHAIN_LINK_LOG_CONNECT_STATUS = 0 + const val CHAIN_LINK_LOG_WEB_SOCKET_DATA = 1 + + const val CHAIN_LINK_LOG_ADAS_INIT = "-adasInitStatus" + const val CHAIN_LINK_LOG_ADAS_MSG = "-adasWsMsg" + + const val CHAIN_ALIAS_CODE_UDP_INIT = "PAD_ADAS_UDP_INIT" + const val CHAIN_ALIAS_CODE_UDP_CONNECT_ADDRESS = "PAD_ADAS_UDP_CONNECT_ADDRESS" + const val CHAIN_ALIAS_CODE_WEB_SOCKET_OPEN = "PAD_ADAS_WEB_SOCKET_OPEN" + const val CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_JSON = "PAD_ADAS_WEB_SOCKET_MESSAGE_JSON" + const val CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_BYTE = "PAD_ADAS_WEB_SOCKET_MESSAGE_BYTE" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_RECT_DATA = "PAD_ADAS_MESSAGE_AUTOPILOT_RECT_DATA" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_CAR_STATE = "PAD_ADAS_MESSAGE_AUTOPILOT_CAR_STATE" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_STATUS = "PAD_ADAS_MESSAGE_AUTOPILOT_STATUS" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ARRIVE = "PAD_ADAS_MESSAGE_AUTOPILOT_ARRIVE" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_ROUTE = "PAD_ADAS_MESSAGE_AUTOPILOT_ROUTE" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_TRAJECTORY = "PAD_ADAS_MESSAGE_AUTOPILOT_TRAJECTORY" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_GUARDIAN = "PAD_ADAS_MESSAGE_AUTOPILOT_GUARDIAN" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_RECORD = "PAD_ADAS_MESSAGE_AUTOPILOT_RECORD" + const val CHAIN_ALIAS_CODE_ADAS_MESSAGE_AUTOPILOT_WARN = "PAD_ADAS_MESSAGE_AUTOPILOT_WARN" + } +} \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainLogParam.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainLogParam.kt new file mode 100644 index 0000000000..a840e24497 --- /dev/null +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/chain/ChainLogParam.kt @@ -0,0 +1,13 @@ +package com.mogo.eagle.core.data.chain + +class ChainLogParam { + + var record: Boolean = false + var des: String? = null + + constructor(record: Boolean, des: String) { + this.record = record + this.des = des + } + +} \ No newline at end of file diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/HmiBuildConfig.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/HmiBuildConfig.kt index 966fd42ec1..9d0ed0520d 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/HmiBuildConfig.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/config/HmiBuildConfig.kt @@ -36,6 +36,12 @@ object HmiBuildConfig { @JvmField var isShowBadCaseView = true + /** + * 是否展示工控机升级提示UI + */ + @JvmField + var isShowUpgradeTipsView = true + /** * 是否展示转向灯ui */ diff --git a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MoGoConfig.kt b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MoGoConfig.kt index 582dd3262f..ab33038541 100644 --- a/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MoGoConfig.kt +++ b/core/mogo-core-data/src/main/java/com/mogo/eagle/core/data/constants/MoGoConfig.kt @@ -14,6 +14,8 @@ object MoGoConfig { // CMD全量日志抓取 const val CATCH_LOG = "CATCH_LOG" + // CMD全量日志抓取当时时间 + const val CATCH_LOG_TIME = "CATCH_LOG_TIME" // 是否是演示(美化)模式,会存在SP中,方便做现场恢复 const val IS_DEMO_MODE = "IS_DEMO_MODE" diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt index f2567433fc..18d9f6b400 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/IDevaToolsProvider.kt @@ -1,5 +1,6 @@ package com.mogo.eagle.core.function.api.devatools +import com.mogo.eagle.core.data.chain.ChainLogParam import com.mogo.eagle.core.function.api.base.IMoGoFunctionServerProvider /** @@ -9,5 +10,12 @@ interface IDevaToolsProvider : IMoGoFunctionServerProvider { fun startLogCatch() + fun startLogCatch(duration: Int) + fun stopLogCatch() + + fun getTraceInfo():HashMap + + fun refreshTraceInfo(map: HashMap) + } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt index a24202a0dd..3f0f3304a4 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/hmi/warning/IMoGoWaringProvider.kt @@ -193,6 +193,12 @@ interface IMoGoWaringProvider { */ fun registerBadCaseCallback(onShow:() -> View, onHide: (() -> Unit)?) + /** + *注册工控机升级提示圆点View的回调 + * @param 提示圆点View + */ + fun registerUpgradeTipsCallback(tipsView:() -> View) + /** * 工控机重启返回结果 * @param code diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt index deadeb95e3..2765d0d12c 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/devatools/CallerDevaToolsManager.kt @@ -1,8 +1,10 @@ package com.mogo.eagle.core.function.call.devatools import com.alibaba.android.arouter.launcher.ARouter +import com.mogo.eagle.core.data.chain.ChainLogParam import com.mogo.eagle.core.data.constants.MogoServicePaths.PATH_DEVA_TOOLS import com.mogo.eagle.core.function.api.devatools.IDevaToolsProvider +import com.mogo.eagle.core.utilcode.util.SnackbarUtils object CallerDevaToolsManager { @@ -17,10 +19,32 @@ object CallerDevaToolsManager { devaToolsProviderApi.startLogCatch() } + /** + * 开始抓取全量日志 + * duration 分钟数 + */ + fun startCatchLog(duration: Int){ + devaToolsProviderApi.startLogCatch(duration) + } + /** * 停止抓取全量日志 */ fun stopCatchLog() { devaToolsProviderApi.stopLogCatch() } + + /** + * 更新链路节点信息,是否写入 + */ + fun refreshTraceInfo(map: HashMap) { + devaToolsProviderApi.refreshTraceInfo(map) + } + + /** + * 获取链路节点信息 + */ + fun getTraceInfo():HashMap{ + return devaToolsProviderApi.getTraceInfo() + } } \ No newline at end of file diff --git a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt index f7a25198d8..a7f5173590 100644 --- a/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt +++ b/core/mogo-core-function-call/src/main/java/com/mogo/eagle/core/function/call/hmi/CallerHmiManager.kt @@ -273,6 +273,14 @@ object CallerHmiManager : CallerBase() { waringProviderApi?.registerBadCaseCallback(onShow, onHide) } + /** + *注册工控机升级提示圆点View的回调 + * @param 提示圆点View + */ + fun registerUpgradeTipsCallback(tipsView:() -> View){ + waringProviderApi?.registerUpgradeTipsCallback(tipsView) + } + /** * 工控机重启返回结果 * @param code diff --git a/core/mogo-core-utils/src/main/AndroidManifest.xml b/core/mogo-core-utils/src/main/AndroidManifest.xml index c953b8a6ce..11c86de1fd 100644 --- a/core/mogo-core-utils/src/main/AndroidManifest.xml +++ b/core/mogo-core-utils/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + { synchronized ( sSyncObject ) { - if ( context == null ) { return; } - - if ( sToast != null ) { - sToast.cancel(); + if ( sToast != null) { + View view = sToast.getView(); + if (view != null && ViewCompat.isAttachedToWindow(view)) { + sToast.cancel(); + } } - if ( sGenerator == null ) { sToast = Toast.makeText( context, msg, duration ); } else { sToast = new Toast( context ); final View view = sGenerator.make( context, msg, tipDrawable ); + if ( view != null ) { sToast.setView( view ); sToast.setGravity( sGenerator.gravity(), sGenerator.xOffset(), sGenerator.yOffset() ); @@ -187,6 +195,15 @@ public final class TipToast { sToast = Toast.makeText( context, msg, duration ); } } + View view = sToast.getView(); + if (view != null) { + LifecycleOwner lifecycleOwner = ExtensionsKt.getLifecycleOwner(view); + lifecycleOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> { + if (event == Lifecycle.Event.ON_DESTROY) { + sToast = null; + } + }); + } if ( sToast != null ) { sToast.show(); } diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/DeviceIdUtils.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/DeviceIdUtils.java index 2ba89bf273..8bfe7fe99e 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/DeviceIdUtils.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/DeviceIdUtils.java @@ -3,38 +3,40 @@ package com.mogo.eagle.core.utilcode.util; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; +import android.media.MediaDrm; import android.os.Build; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; -import com.elegant.utils.storage.SharedPrefsMgr; - import androidx.core.content.ContextCompat; +import com.elegant.utils.storage.SharedPrefsMgr; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.UUID; public final class DeviceIdUtils { public static final String KEY_DEVICE_ID = "deviceId"; - private DeviceIdUtils() {} + private DeviceIdUtils() { + } - private static void saveDeviceId( Context context, String deviceId){ + private static void saveDeviceId(Context context, String deviceId) { SharedPrefsMgr.getInstance(context).putString(KEY_DEVICE_ID, deviceId); } - public static String getDeviceId( Context context) { - if(context == null){ + public static String getDeviceId(Context context) { + if (context == null) { throw new NullPointerException("context must not be null."); } final Context appContext = context.getApplicationContext(); - String deviceId = SharedPrefsMgr.getInstance( context ).getString( KEY_DEVICE_ID ); + String deviceId = SharedPrefsMgr.getInstance(context).getString(KEY_DEVICE_ID); - if ( TextUtils.isEmpty( deviceId )) { + if (TextUtils.isEmpty(deviceId)) { deviceId = getDeviceIdInternal(appContext); if (TextUtils.isEmpty(deviceId)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -54,35 +56,37 @@ public final class DeviceIdUtils { } } } - saveDeviceId(appContext,deviceId); + saveDeviceId(appContext, deviceId); } return deviceId; } - private static String getDeviceIdInternal( Context context) { + private static String getDeviceIdInternal(Context context) { String id = ""; - if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) { - if ( ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { return id; } } - TelephonyManager telephonymanager = ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE); + TelephonyManager telephonymanager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (telephonymanager != null) { id = telephonymanager.getDeviceId(); - if ( TextUtils.isEmpty(id)) + if (TextUtils.isEmpty(id)) { id = ""; + } } return id; } - private static String getAndroidId( Context context) { + private static String getAndroidId(Context context) { String s = ""; s = Settings.Secure.getString(context.getContentResolver(), "android_id"); - if ( TextUtils.isEmpty(s)) + if (TextUtils.isEmpty(s)) { s = ""; + } return s; } @@ -95,16 +99,63 @@ public final class DeviceIdUtils { if (!method.isAccessible()) { method.setAccessible(true); } - serial = ( String ) method.invoke(new Build(), "ro.serialno"); - } catch ( ClassNotFoundException e) { + serial = (String) method.invoke(new Build(), "ro.serialno"); + } catch (ClassNotFoundException e) { e.printStackTrace(); - } catch ( NoSuchMethodException e) { + } catch (NoSuchMethodException e) { e.printStackTrace(); - } catch ( InvocationTargetException e) { + } catch (InvocationTargetException e) { e.printStackTrace(); - } catch ( IllegalAccessException e) { + } catch (IllegalAccessException e) { e.printStackTrace(); } return serial; } + + /** + * 获取数字版权管理设备ID + * + * @return WidevineID,可能为空 + */ + public static String getWidevineID(Context context) { + try { + //See https://stackoverflow.com/questions/16369818/how-to-get-crypto-scheme-uuid + //You can find some UUIDs in the https://github.com/google/ExoPlayer source code + final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID); + byte[] widevineId = mediaDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID); + if (widevineId == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (byte aByte : widevineId) { + sb.append(String.format("%02x", aByte)); + } + return sb.toString(); + } catch (Exception | Error e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 获取数字版权管理设备ID,进行MD5加密,获取32位的唯一标记,给后台生成SN + * + * @return WidevineID,可能为空 + */ + public static String getWidevineIDWithMd5(Context context) { + try { + String widevineId = getWidevineID(context); + if (!TextUtils.isEmpty(widevineId)) { + widevineId = EncryptUtils.encryptHmacMD5ToString(widevineId, "MoGoAuto"); + return widevineId; + } else { + return getDeviceId(context); + } + } catch (Exception | Error e) { + e.printStackTrace(); + } + return getDeviceId(context); + } + } \ No newline at end of file diff --git a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/TimeUtils.java b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/TimeUtils.java index b8bed29b54..67f5a97a88 100644 --- a/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/TimeUtils.java +++ b/core/mogo-core-utils/src/main/java/com/mogo/eagle/core/utilcode/util/TimeUtils.java @@ -55,14 +55,6 @@ public final class TimeUtils { throw new UnsupportedOperationException("u can't instantiate me..."); } - @SuppressLint("SimpleDateFormat") - public static String formatYMD(long time){ - Date date = new Date(time); - String strDateFormat = "yyyy-MM-dd"; - SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat); - return sdf.format(date); - } - /** * Milliseconds to the formatted time string. *

The pattern is {@code yyyy-MM-dd HH:mm:ss}.

@@ -1532,8 +1524,8 @@ public final class TimeUtils { return CHINESE_ZODIAC[year % 12]; } - private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22}; - private static final String[] ZODIAC = { + private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22}; + private static final String[] ZODIAC = { "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座" }; diff --git a/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java b/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java index 89ebda028a..afe552723b 100644 --- a/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java +++ b/foudations/mogo-commons/src/main/java/com/mogo/commons/AbsMogoApplication.java @@ -179,13 +179,12 @@ public abstract class AbsMogoApplication extends Application { */ protected void registerSocketHttpDnsTTL(String host) { sApis.addressChangedListener(map -> { - Logger.d("TEST-SOCKET", "ttl callBack ,ready to getCache Dns IP"); String dnsCacheIp = sApis.getCachedHttpDnsIps(host, HTTP_DNS_ADDRESS_TYPE_HTTP); if (dnsCacheIp == null) { return; } - Logger.d("TEST-SOCKET", "获取缓存Dns IP : " + dnsCacheIp + " , 原缓存 IP : " + cacheIp); if (!dnsCacheIp.equals(cacheIp)) { + Logger.d("TEST-SOCKET", "获取缓存Dns IP : " + dnsCacheIp + " , 原缓存 IP : " + cacheIp); socketTTL(); this.cacheIp = dnsCacheIp; } diff --git a/gradle.properties b/gradle.properties index df290325bc..94f8099b08 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,12 +32,12 @@ kapt.include.compile.classpath=false # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK +# Android operating system, and which are packaged with your app'protoc_platforms APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -android.jetifier.blacklist=module-service-2.1.16.8.aar +android.jetifier.blacklist=module-service-2.1.16.10.aar ## maven 配置 RELEASE_REPOSITORY_URL=http://nexus.zhidaoauto.com/repository/maven-releases/ SNAPSHOT_REPOSITORY_URL=http://nexus.zhidaoauto.com/repository/maven-snapshots/ @@ -56,28 +56,30 @@ bytex.forbidUseLenientMutationDuringGetArtifact=true bytex.verifyProguardConfigurationChanged=false bytex.ASM_API=ASM7 -HOOK_LOG_VERSION=1.4.109 -SERVICE_CHAIN_VERSION=1.0.43 +HOOK_LOG_VERSION=1.5.17 +SERVICE_CHAIN_VERSION=1.0.53 ################ 外部依赖引用 ################ # loglib -LOGLIB_VERSION=1.1.18 +LOGLIB_VERSION=1.2.8 ######## MogoAiCloudSDK Version ######## # 网络请求 -MOGO_NETWORK_VERSION=1.3.18 +MOGO_NETWORK_VERSION=1.3.30 # 鉴权 -MOGO_PASSPORT_VERSION=1.3.18 +MOGO_PASSPORT_VERSION=1.3.30 # 常链接 -MOGO_SOCKET_VERSION=1.3.18 +MOGO_SOCKET_VERSION=1.3.30 # 数据采集 -MOGO_REALTIME_VERSION=1.3.18 +MOGO_REALTIME_VERSION=1.3.30 # 探路,道路事件发布,获取 -MOGO_TANLU_VERSION=1.3.18 +MOGO_TANLU_VERSION=1.3.30 # 直播推流 -MOGO_LIVE_VERSION=1.3.18 +MOGO_LIVE_VERSION=1.3.30 # 直播拉流 -MOGO_TRAFFICLIVE_VERSION=1.3.18 +MOGO_TRAFFICLIVE_VERSION=1.3.30 # 定位服务 -MOGO_LOCATION_VERSION=1.3.18 +MOGO_LOCATION_VERSION=1.3.30 +# 远程通讯模块 +MOGO_TELEMATIC_VERSION=1.3.30 ######## MogoAiCloudSDK Version ######## # 自研地图 MAP_SDK_VERSION=2.0.5.1 @@ -92,105 +94,107 @@ versionCode=80008 versionName=2.5.2 ################# 新架构模块Maven版本管理 ################# -MOGO_CORE_FUNCTION_AUTOPILOT_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_CHECK_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_HMI_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_MAIN_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_MAP_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_MONITORING_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_NOTICE_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_OBU_MOGO_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_SMP_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_V2X_VERSION=0.0.58.8 -MOGO_CORE_DATA_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_API_VERSION=0.0.58.8 -MOGO_CORE_FUNCTION_CALL_VERSION=0.0.58.8 -MOGO_CORE_RES_VERSION=0.0.58.8 -MOGO_CORE_UTILS_VERSION=0.0.58.8 -MOGO_CORE_NETWORK_VERSION=0.0.58.8 +MOGO_CORE_FUNCTION_AUTOPILOT_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_CHECK_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_HMI_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_MAIN_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_MAP_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_MONITORING_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_NOTICE_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_OBU_MOGO_VERSION=0.0.58.10 +MOGO_CORE_FUNCTION_SMP_VERSION=0.0.58.10 +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 +MOGO_CORE_NETWORK_VERSION=0.0.58.10 ################# 旧版本架构模块版本 ################# ## 工程内模块 -MOGO_COMMONS_VERSION=2.1.16.8 -MOGO_UTILS_VERSION=2.1.16.8 -MAP_AMAP_VERSION=2.1.16.8 -MAP_AUTONAVI_VERSION=2.1.16.8 -MOGO_MAP_VERSION=2.1.16.8 -MOGO_MAP_API_VERSION=2.1.16.8 -MOGO_SERVICE_VERSION=2.1.16.8 -MOGO_SERVICE_API_VERSION=2.1.16.8 -MOGO_CONNECTION_VERSION=2.1.16.8 -MOGO_MODULE_APPS_VERSION=2.1.16.8 -MOGO_MODULE_NAVI_VERSION=2.1.16.8 -MOGO_MODULE_SHARE_VERSION=2.1.16.8 -MOGO_MODULE_COMMON_VERSION=2.1.16.8 -MOGO_MODULE_MAIN_VERSION=2.1.16.8 -MOGO_MODULE_MAP_VERSION=2.1.16.8 -MOGO_MODULE_SERVICE_VERSION=2.1.16.8 -MOGO_MODULE_EXTENSIONS_VERSION=2.1.16.8 -MOGO_MODULE_SEARCH_VERSION=2.1.16.8 -MOGO_MODULE_BACK_VERSION=2.1.16.8 -MOGO_MODULE_V2X_VERSION=2.1.16.8 +MOGO_COMMONS_VERSION=2.1.16.10 +MOGO_UTILS_VERSION=2.1.16.10 +MAP_AMAP_VERSION=2.1.16.10 +MAP_AUTONAVI_VERSION=2.1.16.10 +MOGO_MAP_VERSION=2.1.16.10 +MOGO_MAP_API_VERSION=2.1.16.10 +MOGO_SERVICE_VERSION=2.1.16.10 +MOGO_SERVICE_API_VERSION=2.1.16.10 +MOGO_CONNECTION_VERSION=2.1.16.10 +MOGO_MODULE_APPS_VERSION=2.1.16.10 +MOGO_MODULE_NAVI_VERSION=2.1.16.10 +MOGO_MODULE_SHARE_VERSION=2.1.16.10 +MOGO_MODULE_COMMON_VERSION=2.1.16.10 +MOGO_MODULE_MAIN_VERSION=2.1.16.10 +MOGO_MODULE_MAP_VERSION=2.1.16.10 +MOGO_MODULE_SERVICE_VERSION=2.1.16.10 +MOGO_MODULE_EXTENSIONS_VERSION=2.1.16.10 +MOGO_MODULE_SEARCH_VERSION=2.1.16.10 +MOGO_MODULE_BACK_VERSION=2.1.16.10 +MOGO_MODULE_V2X_VERSION=2.1.16.10 # 探路 -MOGO_MODULE_TANLU_VERSION=2.1.16.8 +MOGO_MODULE_TANLU_VERSION=2.1.16.10 # 推送 -MOGO_MODULE_PUSH_VERSION=2.1.16.8 -MOGO_MODULE_PUSH_BASE_VERSION=2.1.16.8 -MOGO_MODULE_PUSH_NOOP_VERSION=2.1.16.8 +MOGO_MODULE_PUSH_VERSION=2.1.16.10 +MOGO_MODULE_PUSH_BASE_VERSION=2.1.16.10 +MOGO_MODULE_PUSH_NOOP_VERSION=2.1.16.10 # 探路上报和分享模块 -TANLULIB_VERSION=2.1.16.8 -MOGO_TANLU_API_VERSION=2.1.16.8 +TANLULIB_VERSION=2.1.16.10 +MOGO_TANLU_API_VERSION=2.1.16.10 #左侧面板模块 -MOGO_MODULE_LEFT_PANEL_VERSION=2.1.16.8 -MOGO_MODULE_LEFT_PANEL_NOOP_VERSION=2.1.16.8 +MOGO_MODULE_LEFT_PANEL_VERSION=2.1.16.10 +MOGO_MODULE_LEFT_PANEL_NOOP_VERSION=2.1.16.10 # 小控件 -MOGO_MODULE_WIDGETS_VERSION=2.1.16.8 +MOGO_MODULE_WIDGETS_VERSION=2.1.16.10 # obu -MOGO_MODULE_OBU_VERSION=2.1.16.8 -MOGO_MODULE_OBU_MOGO_VERSION=2.1.16.8 +MOGO_MODULE_OBU_VERSION=2.1.16.10 +MOGO_MODULE_OBU_MOGO_VERSION=2.1.16.10 # monitor -MOGO_MODULE_MONITOR_VERSION=2.1.16.8 +MOGO_MODULE_MONITOR_VERSION=2.1.16.10 # bugly -CRASHREPORT_VERSION=2.1.16.8 -CRASHREPORT_BUGLY_VERSION=2.1.16.8 -CRASHREPORT_NOOP_VERSION=2.1.16.8 -CRASHREPORT_APMBYTE_VERSION=2.1.16.8 -CRASHREPORT_UPGRADE_VERSION=2.1.16.8 +CRASHREPORT_VERSION=2.1.16.10 +CRASHREPORT_BUGLY_VERSION=2.1.16.10 +CRASHREPORT_NOOP_VERSION=2.1.16.10 +CRASHREPORT_APMBYTE_VERSION=2.1.16.10 +CRASHREPORT_UPGRADE_VERSION=2.1.16.10 ## tts -TTS_BASE_VERSION=2.1.16.8 -TTS_DI_VERSION=2.1.16.8 -TTS_ZHI_VERSION=2.1.16.8 -TTS_PAD_VERSION=2.1.16.8 -TTS_NOOP_VERSION=2.1.16.8 +TTS_BASE_VERSION=2.1.16.10 +TTS_DI_VERSION=2.1.16.10 +TTS_ZHI_VERSION=2.1.16.10 +TTS_PAD_VERSION=2.1.16.10 +TTS_NOOP_VERSION=2.1.16.10 # 自研地图 -MAP_CUSTOM_VERSION=2.1.16.8 -MOGO_MODULE_ADAS_VERSION=2.1.16.8 +MAP_CUSTOM_VERSION=2.1.16.10 +MOGO_MODULE_ADAS_VERSION=2.1.16.10 # 基础服务实现:passport、socket、location -MOGO_BASE_WEBSOCKET_SDK_VERSION=2.1.16.8 -MOGO_BASE_SERVICES_APK_VERSION=2.1.16.8 -MOGO_BASE_SERVICES_SDK_VERSION=2.1.16.8 -MOGO_MODULE_CHAT_VERSION=2.1.16.8 +MOGO_BASE_WEBSOCKET_SDK_VERSION=2.1.16.10 +MOGO_BASE_SERVICES_APK_VERSION=2.1.16.10 +MOGO_BASE_SERVICES_SDK_VERSION=2.1.16.10 +MOGO_MODULE_CHAT_VERSION=2.1.16.10 # 车聊聊 -MOGO_MODULE_CARCHATTING_VERSION=2.1.16.8 +MOGO_MODULE_CARCHATTING_VERSION=2.1.16.10 # 车聊聊接口 -MOGO_MODULE_CARCHATTINGPROVIDER_VERSION=2.1.16.8 +MOGO_MODULE_CARCHATTINGPROVIDER_VERSION=2.1.16.10 # 皮肤 -MOGO_SKIN_SUPPORT_VERSION=2.1.16.8 -MOGO_SKIN_LIGHT_VERSION=2.1.16.8 -MOGO_SKIN_SUPPORT_IMPL_VERSION=2.1.16.8 -MOGO_SKIN_SUPPORT_NOOP_VERSION=2.1.16.8 -SKIN_SUPPORT_VERSION=2.1.16.8 -SKIN_SUPPORT_APPCOMPAT_VERSION=2.1.16.8 -SKIN_SUPPORT_CARDVIEW_VERSION=2.1.16.8 -SKIN_SUPPORT_CONSTRAINT_LAYOUT_VERSION=2.1.16.8 -SKIN_SUPPORT_DESIGN_VERSION=2.1.16.8 +MOGO_SKIN_SUPPORT_VERSION=2.1.16.10 +MOGO_SKIN_LIGHT_VERSION=2.1.16.10 +MOGO_SKIN_SUPPORT_IMPL_VERSION=2.1.16.10 +MOGO_SKIN_SUPPORT_NOOP_VERSION=2.1.16.10 +SKIN_SUPPORT_VERSION=2.1.16.10 +SKIN_SUPPORT_APPCOMPAT_VERSION=2.1.16.10 +SKIN_SUPPORT_CARDVIEW_VERSION=2.1.16.10 +SKIN_SUPPORT_CONSTRAINT_LAYOUT_VERSION=2.1.16.10 +SKIN_SUPPORT_DESIGN_VERSION=2.1.16.10 # OCH -MOGO_OCH_VERSION=2.1.16.8-test +MOGO_OCH_VERSION=2.1.16.10-test MOGO_OCH_BUS_VERSION=2.0.66 MOGO_OCH_NOOP_VERSION=2.0.66 MOGO_OCH_TAXI_VERSION=2.0.66 # mogoAiCloud sdk services -MOGO_AICLOUD_SERVICES_SDK_VERSION=2.1.16.8 +MOGO_AICLOUD_SERVICES_SDK_VERSION=2.1.16.10 # v2x-sdk -MOGO_V2X_SDK_VERSION=1.0.1 +MOGO_V2X_SDK_VERSION=1.3.30 ################# 旧版本架构模块版本 ################# diff --git a/libraries/map-custom/src/main/java/com/mogo/map/impl/custom/AMapViewWrapper.java b/libraries/map-custom/src/main/java/com/mogo/map/impl/custom/AMapViewWrapper.java index 760195fb3e..eb3575736e 100644 --- a/libraries/map-custom/src/main/java/com/mogo/map/impl/custom/AMapViewWrapper.java +++ b/libraries/map-custom/src/main/java/com/mogo/map/impl/custom/AMapViewWrapper.java @@ -1,5 +1,8 @@ package com.mogo.map.impl.custom; +import static com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_300; +import static com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_CROSS; +import static com.mogo.map.uicontroller.VisualAngleMode.MAP_STYLE_VR_ANGLE_TOP; import static com.mogo.map.uicontroller.VisualAngleMode.MODE_CLOSE_SIGHT; import static com.mogo.map.uicontroller.VisualAngleMode.MODE_LONG_SIGHT; import static com.mogo.map.uicontroller.VisualAngleMode.MODE_MEDIUM_SIGHT; @@ -767,6 +770,12 @@ public class AMapViewWrapper implements IMogoMapView, return MODE_MEDIUM_SIGHT; case 2: return MODE_LONG_SIGHT; + case 3: + return MAP_STYLE_VR_ANGLE_300; + case 4: + return MAP_STYLE_VR_ANGLE_TOP; + case 5: + return MAP_STYLE_VR_ANGLE_CROSS; default: throw new IllegalStateException("mode is unCorrect"); } diff --git a/libraries/map-usbcamera/.gitignore b/libraries/map-usbcamera/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libraries/map-usbcamera/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libraries/map-usbcamera/build.gradle b/libraries/map-usbcamera/build.gradle new file mode 100644 index 0000000000..2616caa0ec --- /dev/null +++ b/libraries/map-usbcamera/build.gradle @@ -0,0 +1,73 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' + id 'com.alibaba.arouter' +} + +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + // buildToolsVersion rootProject.ext.android.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode Integer.valueOf(VERSION_CODE) + versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION") + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + + //ARouter apt 参数 + kapt { + useBuildCache = false + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } + } + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + + repositories { + flatDir { + dirs 'libs' + } + } + sourceSets{ + main{ + jniLibs.srcDir(['libs']) + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + + implementation rootProject.ext.dependencies.androidxconstraintlayout + implementation rootProject.ext.dependencies.arouter + kapt rootProject.ext.dependencies.aroutercompiler + + if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { + + } else { + + } +} + +apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString() \ No newline at end of file diff --git a/libraries/map-usbcamera/consumer-rules.pro b/libraries/map-usbcamera/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/map-usbcamera/gradle.properties b/libraries/map-usbcamera/gradle.properties new file mode 100644 index 0000000000..86f5eb86de --- /dev/null +++ b/libraries/map-usbcamera/gradle.properties @@ -0,0 +1,3 @@ +GROUP=com.mogo.camera +POM_ARTIFACT_ID=map-usbcamera +VERSION_CODE=1 \ No newline at end of file diff --git a/libraries/map-usbcamera/libs/common-4.1.1.aar b/libraries/map-usbcamera/libs/common-4.1.1.aar new file mode 100644 index 0000000000..9d78514565 Binary files /dev/null and b/libraries/map-usbcamera/libs/common-4.1.1.aar differ diff --git a/libraries/map-usbcamera/proguard-rules.pro b/libraries/map-usbcamera/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/libraries/map-usbcamera/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/AndroidManifest.xml b/libraries/map-usbcamera/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..70e75d7b52 --- /dev/null +++ b/libraries/map-usbcamera/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/assets/zk/SIMYOU.ttf b/libraries/map-usbcamera/src/main/assets/zk/SIMYOU.ttf new file mode 100644 index 0000000000..933b9d369f Binary files /dev/null and b/libraries/map-usbcamera/src/main/assets/zk/SIMYOU.ttf differ 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 new file mode 100644 index 0000000000..4a14cd62f3 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java @@ -0,0 +1,373 @@ +package com.mogo.usbcamera; + +import android.app.Activity; +import android.graphics.SurfaceTexture; +import android.hardware.usb.UsbDevice; +import android.os.Environment; + +import com.serenegiant.usb.DeviceFilter; +import com.serenegiant.usb.Size; +import com.serenegiant.usb.USBMonitor; +import com.serenegiant.usb.UVCCamera; +import com.serenegiant.usb.common.AbstractUVCCameraHandler; +import com.serenegiant.usb.common.UVCCameraHandler; +import com.serenegiant.usb.encoder.RecordParams; +import com.serenegiant.usb.widget.CameraViewInterface; + +import org.easydarwin.sw.TxtOverlay; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +/** + * UVCCamera Helper class + */ +public class UVCCameraHelper { + public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + + File.separator; + public static final String SUFFIX_JPEG = ".jpg"; + public static final String SUFFIX_MP4 = ".mp4"; + private static final String TAG = "UVCCameraHelper"; + private int previewWidth = 640; + private int previewHeight = 480; + public static final int FRAME_FORMAT_YUYV = UVCCamera.FRAME_FORMAT_YUYV; + // Default using MJPEG + // if your device is connected,but have no images + // please try to change it to FRAME_FORMAT_YUYV + public static final int FRAME_FORMAT_MJPEG = UVCCamera.FRAME_FORMAT_MJPEG; + public static final int MODE_BRIGHTNESS = UVCCamera.PU_BRIGHTNESS; + public static final int MODE_CONTRAST = UVCCamera.PU_CONTRAST; + private int mFrameFormat = FRAME_FORMAT_MJPEG; + + private static UVCCameraHelper mCameraHelper; + // USB Manager + private USBMonitor mUSBMonitor; + // Camera Handler + private UVCCameraHandler mCameraHandler; + private USBMonitor.UsbControlBlock mCtrlBlock; + + private Activity mActivity; + private CameraViewInterface mCamView; + + private UVCCameraHelper() { + } + + public static UVCCameraHelper getInstance() { + if (mCameraHelper == null) { + mCameraHelper = new UVCCameraHelper(); + } + return mCameraHelper; + } + + public void closeCamera() { + if (mCameraHandler != null) { + mCameraHandler.close(); + } + } + + public interface OnMyDevConnectListener { + void onAttachDev(UsbDevice device); + + void onDettachDev(UsbDevice device); + + void onConnectDev(UsbDevice device, boolean isConnected); + + void onDisConnectDev(UsbDevice device); + + void onCancelDev(UsbDevice device); + + } + + public void initUSBMonitor(Activity activity, CameraViewInterface cameraView, final OnMyDevConnectListener listener) { + this.mActivity = activity; + this.mCamView = cameraView; + + mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() { + + // called by checking usb device + // do request device permission + @Override + public void onAttach(UsbDevice device) { + if (listener != null) { + listener.onAttachDev(device); + } + } + + // called by taking out usb device + // do close camera + @Override + public void onDettach(UsbDevice device) { + if (listener != null) { + listener.onDettachDev(device); + } + } + + // called by connect to usb camera + // do open camera,start previewing + @Override + public void onConnect(final UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) { + mCtrlBlock = ctrlBlock; + openCamera(ctrlBlock); + 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); + } + } + + // called by disconnect to usb camera + // do nothing + @Override + public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) { + if (listener != null) { + listener.onDisConnectDev(device); + } + } + + @Override + public void onCancel(UsbDevice device) { + if (listener != null) { + listener.onCancelDev(device); + } + } + }); + + createUVCCamera(); + } + + public void createUVCCamera() { + if (mCamView == null) { + throw new NullPointerException("CameraViewInterface cannot be null!"); + } + + // release resources for initializing camera handler + if (mCameraHandler != null) { + mCameraHandler.release(); + mCameraHandler = null; + } + // initialize camera handler + mCamView.setAspectRatio(previewWidth / (float) previewHeight); + mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2, + previewWidth, previewHeight, mFrameFormat); + } + + public void updateResolution(int width, int height) { + if (previewWidth == width && previewHeight == height) { + return; + } + this.previewWidth = width; + this.previewHeight = height; + if (mCameraHandler != null) { + mCameraHandler.release(); + mCameraHandler = null; + } + mCamView.setAspectRatio(previewWidth / (float) previewHeight); + mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2, + previewWidth, previewHeight, mFrameFormat); + openCamera(mCtrlBlock); + 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); + } + }).start(); + } + + public void registerUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.register(); + } + } + + public void unregisterUSB() { + if (mUSBMonitor != null) { + mUSBMonitor.unregister(); + } + } + + public boolean checkSupportFlag(final int flag) { + return mCameraHandler != null && mCameraHandler.checkSupportFlag(flag); + } + + public int getModelValue(final int flag) { + return mCameraHandler != null ? mCameraHandler.getValue(flag) : 0; + } + + public int setModelValue(final int flag, final int value) { + return mCameraHandler != null ? mCameraHandler.setValue(flag, value) : 0; + } + + public int resetModelValue(final int flag) { + return mCameraHandler != null ? mCameraHandler.resetValue(flag) : 0; + } + + public void requestPermission(int index) { + List devList = getUsbDeviceList(); + if (devList == null || devList.size() == 0) { + return; + } + int count = devList.size(); + if (index >= count) { + new IllegalArgumentException("index illegal,should be < devList.size()"); + } + if (mUSBMonitor != null) { + mUSBMonitor.requestPermission(getUsbDeviceList().get(index)); + } + } + + public int getUsbDeviceCount() { + List devList = getUsbDeviceList(); + if (devList == null || devList.size() == 0) { + return 0; + } + return devList.size(); + } + + public List getUsbDeviceList() { + List deviceFilters = DeviceFilter + .getDeviceFilters(mActivity.getApplicationContext(), R.xml.device_filter); + if (mUSBMonitor == null || deviceFilters == null) { + // throw new NullPointerException("mUSBMonitor ="+mUSBMonitor+"deviceFilters=;"+deviceFilters); + return null; + } + // matching all of filter devices + return mUSBMonitor.getDeviceList(deviceFilters); + } + + 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(); + } + mCameraHandler.captureStill(savePath, listener); + } + } + + public void startPusher(AbstractUVCCameraHandler.OnEncodeResultListener listener) { + if (mCameraHandler != null && !isPushing()) { + mCameraHandler.startRecording(null, listener); + } + } + + public void startPusher(RecordParams params, AbstractUVCCameraHandler.OnEncodeResultListener listener) { + if (mCameraHandler != null && !isPushing()) { + if (params.isSupportOverlay()) { + TxtOverlay.install(mActivity.getApplicationContext()); + } + mCameraHandler.startRecording(params, listener); + } + } + + public void stopPusher() { + if (mCameraHandler != null && isPushing()) { + mCameraHandler.stopRecording(); + } + } + + public boolean isPushing() { + if (mCameraHandler != null) { + return mCameraHandler.isRecording(); + } + return false; + } + + public boolean isCameraOpened() { + if (mCameraHandler != null) { + return mCameraHandler.isOpened(); + } + return false; + } + + public void release() { + if (mCameraHandler != null) { + mCameraHandler.release(); + mCameraHandler = null; + } + if (mUSBMonitor != null) { + mUSBMonitor.destroy(); + mUSBMonitor = null; + } + } + + public USBMonitor getUSBMonitor() { + return mUSBMonitor; + } + + public void setOnPreviewFrameListener(AbstractUVCCameraHandler.OnPreViewResultListener listener) { + if (mCameraHandler != null) { + mCameraHandler.setOnPreViewResultListener(listener); + } + } + + private void openCamera(USBMonitor.UsbControlBlock ctrlBlock) { + if (mCameraHandler != null) { + mCameraHandler.open(ctrlBlock); + } + } + + public void startPreview(CameraViewInterface cameraView) { + SurfaceTexture st = cameraView.getSurfaceTexture(); + if (mCameraHandler != null) { + mCameraHandler.startPreview(st); + } + } + + public void stopPreview() { + if (mCameraHandler != null) { + mCameraHandler.stopPreview(); + } + } + + public void startCameraFoucs() { + if (mCameraHandler != null) { + mCameraHandler.startCameraFoucs(); + } + } + + public List getSupportedPreviewSizes() { + if (mCameraHandler == null) { + return null; + } + return mCameraHandler.getSupportedPreviewSizes(); + } + + public void setDefaultPreviewSize(int defaultWidth, int defaultHeight) { + if (mUSBMonitor != null) { + throw new IllegalStateException("setDefaultPreviewSize should be call before initMonitor"); + } + this.previewWidth = defaultWidth; + this.previewHeight = defaultHeight; + } + + public void setDefaultFrameFormat(int format) { + if (mUSBMonitor != null) { + throw new IllegalStateException("setDefaultFrameFormat should be call before initMonitor"); + } + this.mFrameFormat = format; + } + + public int getPreviewWidth() { + return previewWidth; + } + + public int getPreviewHeight() { + return previewHeight; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/utils/FileUtils.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/utils/FileUtils.java new file mode 100644 index 0000000000..09a2e2037c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/utils/FileUtils.java @@ -0,0 +1,61 @@ +package com.mogo.usbcamera.utils; + +import android.os.Environment; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * + * Created by jiangdongguo on 2017/10/18. + */ +public class FileUtils { + + private static BufferedOutputStream outputStream; + public static String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator; + + public static void createfile(String path){ + File file = new File(path); + if(file.exists()){ + file.delete(); + } + try { + outputStream = new BufferedOutputStream(new FileOutputStream(file)); + } catch (Exception e){ + e.printStackTrace(); + } + } + + public static void releaseFile(){ + try { + if(outputStream != null) { + outputStream.flush(); + outputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void putFileStream(byte[] data,int offset,int length){ + if(outputStream != null) { + try { + outputStream.write(data,offset,length); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void putFileStream(byte[] data){ + if(outputStream != null) { + try { + outputStream.write(data); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} 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/glutils/AbstractRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java new file mode 100644 index 0000000000..570dc75eb4 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java @@ -0,0 +1,1524 @@ +package com.serenegiant.glutils; +/* + * 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 static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.opengl.GLES30; +import android.opengl.Matrix; +import android.os.Build; +import android.util.Log; +import android.util.SparseArray; +import android.view.Surface; +import android.view.SurfaceHolder; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.serenegiant.utils.BuildCheck; + +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public abstract class AbstractRendererHolder implements IRendererHolder { + private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = AbstractRendererHolder.class.getSimpleName(); + private static final String RENDERER_THREAD_NAME = "RendererHolder"; + private static final String CAPTURE_THREAD_NAME = "CaptureTask"; + + protected static final int REQUEST_DRAW = 1; + protected static final int REQUEST_UPDATE_SIZE = 2; + protected static final int REQUEST_ADD_SURFACE = 3; + protected static final int REQUEST_REMOVE_SURFACE = 4; + protected static final int REQUEST_REMOVE_SURFACE_ALL = 12; + protected static final int REQUEST_RECREATE_MASTER_SURFACE = 5; + protected static final int REQUEST_MIRROR = 6; + protected static final int REQUEST_ROTATE = 7; + protected static final int REQUEST_CLEAR = 8; + protected static final int REQUEST_CLEAR_ALL = 9; + protected static final int REQUEST_SET_MVP = 10; + + protected final Object mSync = new Object(); + @Nullable + private final RenderHolderCallback mCallback; + private volatile boolean isRunning; + + private OutputStream mCaptureStream; + @StillCaptureFormat + private int mCaptureFormat; + @IntRange(from = 1L,to = 99L) + private int mCaptureCompression = DEFAULT_CAPTURE_COMPRESSION; + protected final RendererTask mRendererTask; + + protected AbstractRendererHolder(final int width, final int height, + @Nullable final RenderHolderCallback callback) { + + this(width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE, + callback); + } + + protected AbstractRendererHolder(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags, + @Nullable final RenderHolderCallback callback) { + + mCallback = callback; + mRendererTask = createRendererTask(width, height, + maxClientVersion, sharedContext, flags); + new Thread(mRendererTask, RENDERER_THREAD_NAME).start(); + if (!mRendererTask.waitReady()) { + // 初期化に失敗した時 + throw new RuntimeException("failed to start renderer thread"); + } + startCaptureTask(); + } + +//-------------------------------------------------------------------------------- +// IRendererHolderの実装 + @Override + public boolean isRunning() { + return isRunning; + } + + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + mRendererTask.release(); + synchronized (mSync) { + isRunning = false; + mSync.notifyAll(); + } +// if (DEBUG) Log.v(TAG, "release:finished"); + } + + @Nullable + public EGLBase.IContext getContext() { + return mRendererTask.getContext(); + } + + /** + * マスター用の映像を受け取るためのSurfaceを取得 + * @return + */ + @Override + public Surface getSurface() { + return mRendererTask.getSurface(); + } + + /** + * マスター用の映像を受け取るためのSurfaceTextureを取得 + * @return + */ + @Override + public SurfaceTexture getSurfaceTexture() { + return mRendererTask.getSurfaceTexture(); + } + + /** + * マスター用の映像を受け取るためのマスターをチェックして無効なら再生成要求する + */ + @Override + public void reset() { + mRendererTask.checkMasterSurface(); + } + + /** + * マスター映像サイズをサイズ変更要求 + * @param width + * @param height + */ + @Override + public void resize(final int width, final int height) + throws IllegalStateException { + + mRendererTask.resize(width, height); + } + + /** + * ミラーモードをセット + * @param mirror + */ + @Override + public void setMirror(@MirrorMode final int mirror) { + mRendererTask.mirror(mirror % MIRROR_NUM); + } + + /** + * 現在のミラーモードを取得 + * @return + */ + @Override + @MirrorMode + public int getMirror() { + return mRendererTask.mirror(); + } + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通はSurface#hashCodeを使う + * @param surface + * @param isRecordable + */ + @Override + public void addSurface(final int id, + final Object surface, final boolean isRecordable) + throws IllegalStateException, IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + mRendererTask.addSurface(id, surface); + } + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通はSurface#hashCodeを使う + * @param surface + * @param isRecordable + * @param maxFps + */ + @Override + public void addSurface(final int id, + final Object surface, final boolean isRecordable, final int maxFps) + throws IllegalStateException, IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + mRendererTask.addSurface(id, surface, maxFps); + } + + /** + * 分配描画用のSurfaceを削除要求する。 + * このメソッドは指定したSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + */ + @Override + public void removeSurface(final int id) { +// if (DEBUG) Log.v(TAG, "removeSurface:id=" + id); + mRendererTask.removeSurface(id); + } + + /** + * 分配描画用のSurfaceを全て削除要求する + * このメソッドはSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + */ + @Override + public void removeSurfaceAll() { +// if (DEBUG) Log.v(TAG, "removeSurfaceAll:id=" + id); + mRendererTask.removeSurfaceAll(); + } + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param id + * @param color + */ + @Override + public void clearSurface(final int id, final int color) { + mRendererTask.clearSurface(id, color); + } + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param color + */ + public void clearSurfaceAll(final int color) { + mRendererTask.clearSurfaceAll(color); + } + + public void setMvpMatrix(final int id, + final int offset, @NonNull final float[] matrix) { + mRendererTask.setMvpMatrix(id, offset, matrix); + } + + /** + * 分配描画用のSurfaceへの描画が有効かどうかを取得 + * @param id + * @return + */ + @Override + public boolean isEnabled(final int id) { + return mRendererTask.isEnabled(id); + } + + /** + * 分配描画用のSurfaceへの描画の有効・無効を切替 + * @param id + * @param enable + */ + @Override + public void setEnabled(final int id, final boolean enable) { + mRendererTask.setEnabled(id, enable); + } + + /** + * 強制的に現在の最新のフレームを描画要求する + * 分配描画用Surface全てが更新されるので注意 + */ + @Override + public void requestFrame() { + mRendererTask.removeRequest(REQUEST_DRAW); + mRendererTask.offer(REQUEST_DRAW); + } + + /** + * 追加されている分配描画用のSurfaceの数を取得 + * @return + */ + @Override + public int getCount() { + return mRendererTask.getCount(); + } + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + */ + @Deprecated + @Override + public void captureStillAsync(@NonNull final String path) + throws FileNotFoundException, IllegalStateException { + + if (DEBUG) Log.v(TAG, "captureStillAsync:" + path); + + captureStill(new BufferedOutputStream(new FileOutputStream(path)), + getCaptureFormat(path), DEFAULT_CAPTURE_COMPRESSION, false); + } + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + * @param captureCompression + */ + @Deprecated + @Override + public void captureStillAsync(@NonNull final String path, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws FileNotFoundException, IllegalStateException { + + if (DEBUG) Log.v(TAG, "captureStillAsync:" + path + + ",captureCompression=" + captureCompression); + + captureStill(new BufferedOutputStream(new FileOutputStream(path)), + getCaptureFormat(path), captureCompression, false); + } + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + */ + @Override + public void captureStill(@NonNull final String path) + throws FileNotFoundException, IllegalStateException { + + if (DEBUG) Log.v(TAG, "captureStill:" + path); + + captureStill(new BufferedOutputStream(new FileOutputStream(path)), + getCaptureFormat(path), DEFAULT_CAPTURE_COMPRESSION, true); + } + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + */ + @Override + public void captureStill(@NonNull final String path, + @IntRange(from = 1L,to = 99L)final int captureCompression) + throws FileNotFoundException, IllegalStateException { + + if (DEBUG) Log.v(TAG, "captureStill:" + path + + ",captureCompression=" + captureCompression); + captureStill(new BufferedOutputStream(new FileOutputStream(path)), + getCaptureFormat(path), captureCompression, true); + } + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param out + * @param captureFormat + * @param captureCompression + */ + @Override + public void captureStill(@NonNull final OutputStream out, + @StillCaptureFormat final int captureFormat, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws IllegalStateException { + + captureStill(out, captureFormat, captureCompression, true); + } + + /** + * 実際の静止画撮影要求メソッド + * @param out + * @param captureFormat + * @param captureCompression + * @param needWait 撮影完了を待機するを待機するかどうか + */ + private void captureStill(@NonNull final OutputStream out, + @StillCaptureFormat final int captureFormat, + @IntRange(from = 1L,to = 99L) final int captureCompression, + final boolean needWait) throws IllegalStateException { + + synchronized (mSync) { + if (!isRunning) { + throw new IllegalStateException("already released?"); + } + if (mCaptureStream != null) { + throw new IllegalStateException("already run still capturing now"); + } + mCaptureStream = out; + mCaptureFormat = captureFormat; + mCaptureCompression = captureCompression; + mSync.notifyAll(); + if (needWait) { + // 撮影完了街をする場合 + for ( ; isRunning && (mCaptureStream != null) ; ) { + try { + if (DEBUG) Log.v(TAG, "静止画撮影待ち"); + mSync.wait(1000); + } catch (final InterruptedException e) { + // ignore + } + } + } + } + if (DEBUG) Log.v(TAG, "captureStill:終了"); + } + + /** + * パス文字列の拡張子を調べて静止画圧縮フォーマットを取得する。 + * jpeg(jpg)/png/webpのいずれでもなければIllegalArgumentExceptionを投げる + * @param path + * @return + * @throws IllegalArgumentException + */ + @StillCaptureFormat + private static int getCaptureFormat(@NonNull final String path) + throws IllegalArgumentException { + + int result; + final String _path = path.toLowerCase(); + if (path.endsWith(".jpg") || path.endsWith(".jpeg")) { + result = OUTPUT_FORMAT_JPEG; + } else if (path.endsWith(".png")) { + result = OUTPUT_FORMAT_PNG; + } else if (path.endsWith(".webp")) { + result = OUTPUT_FORMAT_WEBP; + } else { + throw new IllegalArgumentException("unknown compress format(extension)"); + } + return result; + } + + /** + * 静止画圧縮フォーマットをBitmap.CompressFormatに変換する + * @param captureFormat + * @return + */ + private static Bitmap.CompressFormat getCaptureFormat( + @StillCaptureFormat final int captureFormat) { + + Bitmap.CompressFormat result; + switch (captureFormat) { + case OUTPUT_FORMAT_JPEG: + result = Bitmap.CompressFormat.JPEG; + break; + case OUTPUT_FORMAT_PNG: + result = Bitmap.CompressFormat.PNG; + break; + case OUTPUT_FORMAT_WEBP: + result = Bitmap.CompressFormat.WEBP; + break; + default: + result = Bitmap.CompressFormat.JPEG; + break; + } + return result; + } +//-------------------------------------------------------------------------------- + @NonNull + protected abstract RendererTask createRendererTask(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags); + + protected void startCaptureTask() { + new Thread(mCaptureTask, CAPTURE_THREAD_NAME).start(); + synchronized (mSync) { + if (!isRunning) { + try { + mSync.wait(); + } catch (final InterruptedException e) { + // ignore + } + } + } + } + + protected void notifyCapture() { +// if (DEBUG) Log.v(TAG, "notifyCapture:"); + synchronized (mCaptureTask) { + // キャプチャタスクに映像が更新されたことを通知 + mCaptureTask.notify(); + } + } + +//-------------------------------------------------------------------------------- + protected void callOnCreate(Surface surface) { + if (mCallback != null) { + try { + mCallback.onCreate(surface); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + protected void callOnFrameAvailable() { + if (mCallback != null) { + try { + mCallback.onFrameAvailable(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + + protected void callOnDestroy() { + if (mCallback != null) { + try { + mCallback.onDestroy(); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + +//-------------------------------------------------------------------------------- + protected abstract static class BaseRendererTask extends EglTask { + private final SparseArray mClients + = new SparseArray(); + private final AbstractRendererHolder mParent; + private int mVideoWidth, mVideoHeight; + final float[] mTexMatrix = new float[16]; + int mTexId; + private SurfaceTexture mMasterTexture; + private Surface mMasterSurface; + @MirrorMode + private int mMirror = MIRROR_NORMAL; + private int mRotation = 0; + private volatile boolean mIsFirstFrameRendered; + + public BaseRendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height) { + + this(parent, width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE); + } + + public BaseRendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height, + final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + + super(maxClientVersion, sharedContext, flags); + mParent = parent; + mVideoWidth = width > 0 ? width : 640; + mVideoHeight = height > 0 ? height : 480; + } + /** + * ワーカースレッド開始時の処理(ここはワーカースレッド上) + */ + @Override + protected final void onStart() { +// if (DEBUG) Log.v(TAG, "onStart:"); + handleReCreateMasterSurface(); + internalOnStart(); + synchronized (mParent.mSync) { + mParent.isRunning = true; + mParent.mSync.notifyAll(); + } +// if (DEBUG) Log.v(TAG, "onStart:finished"); + } + + /** + * ワーカースレッド終了時の処理(ここはまだワーカースレッド上) + */ + @Override + protected void onStop() { +// if (DEBUG) Log.v(TAG, "onStop"); + synchronized (mParent.mSync) { + mParent.isRunning = false; + mParent.mSync.notifyAll(); + } + makeCurrent(); + internalOnStop(); + handleReleaseMasterSurface(); + handleRemoveAll(); +// if (DEBUG) Log.v(TAG, "onStop:finished"); + } + + @Override + protected boolean onError(final Exception e) { +// if (DEBUG) Log.w(TAG, e); + return false; + } + + protected abstract void internalOnStart(); + protected abstract void internalOnStop(); + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) { + + switch (request) { + case REQUEST_DRAW: + handleDraw(); + break; + case REQUEST_UPDATE_SIZE: + handleResize(arg1, arg2); + break; + case REQUEST_ADD_SURFACE: + handleAddSurface(arg1, obj, arg2); + break; + case REQUEST_REMOVE_SURFACE: + handleRemoveSurface(arg1); + break; + case REQUEST_REMOVE_SURFACE_ALL: + handleRemoveAll(); + break; + case REQUEST_RECREATE_MASTER_SURFACE: + handleReCreateMasterSurface(); + break; + case REQUEST_MIRROR: + handleMirror(arg1); + break; + case REQUEST_ROTATE: + handleRotate(arg1, arg2); + break; + case REQUEST_CLEAR: + handleClear(arg1, arg2); + break; + case REQUEST_CLEAR_ALL: + handleClearAll(arg1); + break; + case REQUEST_SET_MVP: + handleSetMvp(arg1, arg2, obj); + break; + } + return null; + } + + /** + * マスター映像取得用のSurfaceを取得 + * @return + */ + public Surface getSurface() { +// if (DEBUG) Log.v(TAG, "getSurface:" + mMasterSurface); + checkMasterSurface(); + return mMasterSurface; + } + + /** + * マスター映像受け取り用のSurfaceTextureを取得 + * @return + */ + public SurfaceTexture getSurfaceTexture() { +// if (DEBUG) Log.v(TAG, "getSurfaceTexture:" + mMasterTexture); + checkMasterSurface(); + return mMasterTexture; + } + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + * @param surface + */ + public void addSurface(final int id, final Object surface) + throws IllegalStateException, IllegalArgumentException { + + addSurface(id, surface, -1); + } + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + * @param surface + */ + public void addSurface(final int id, + final Object surface, final int maxFps) + throws IllegalStateException, IllegalArgumentException { + + checkFinished(); + if (!((surface instanceof SurfaceTexture) + || (surface instanceof Surface) + || (surface instanceof SurfaceHolder))) { + + throw new IllegalArgumentException( + "Surface should be one of Surface, SurfaceTexture or SurfaceHolder"); + } + synchronized (mClients) { + if (mClients.get(id) == null) { + for ( ; isRunning() ; ) { + if (offer(REQUEST_ADD_SURFACE, id, maxFps, surface)) { + try { + mClients.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + // キューに追加できなかった時は待機する + try { + mClients.wait(5); + } catch (final InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * 分配描画用のSurfaceを削除 + * このメソッドは指定したSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + */ + public void removeSurface(final int id) { + synchronized (mClients) { + if (mClients.get(id) != null) { + for ( ; isRunning() ; ) { + if (offer(REQUEST_REMOVE_SURFACE, id)) { + try { + mClients.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + // キューに追加できなかった時は待機する + try { + mClients.wait(5); + } catch (final InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * 分配描画用のSurfaceを全て削除する + * このメソッドはSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + */ + public void removeSurfaceAll() { + synchronized (mClients) { + for ( ; isRunning() ; ) { + if (offer(REQUEST_REMOVE_SURFACE_ALL)) { + try { + mClients.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + // キューに追加できなかった時は待機する + try { + mClients.wait(5); + } catch (final InterruptedException e) { + break; + } + } + } + } + } + + /** + * 指定したIDの分配描画用のSurfaceを指定した色で塗りつぶす + * @param id + * @param color + */ + public void clearSurface(final int id, final int color) { + checkFinished(); + offer(REQUEST_CLEAR, id, color); + } + + public void clearSurfaceAll(final int color) { + checkFinished(); + offer(REQUEST_CLEAR_ALL, color); + } + + public void setMvpMatrix(final int id, + final int offset, @NonNull final float[] matrix) { + checkFinished(); + offer(REQUEST_SET_MVP, id, offset, matrix); + } + + public boolean isEnabled(final int id) { + synchronized (mClients) { + final RendererSurfaceRec rec = mClients.get(id); + return rec != null && rec.isEnabled(); + } + } + + public void setEnabled(final int id, final boolean enable) { + synchronized (mClients) { + final RendererSurfaceRec rec = mClients.get(id); + if (rec != null) { + rec.setEnabled(enable); + } + } + } + + /** + * 分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount() { + synchronized (mClients) { + return mClients.size(); + } + } + + /** + * リサイズ + * @param width + * @param height + */ + public void resize(final int width, final int height) + throws IllegalStateException { + + checkFinished(); + if ( ((width > 0) && (height > 0)) + && ((mVideoWidth != width) || (mVideoHeight != height)) ) { + + offer(REQUEST_UPDATE_SIZE, width, height); + } + } + + protected int width() { + return mVideoWidth; + } + + protected int height() { + return mVideoHeight; + } + + public void mirror(final int mirror) { + checkFinished(); + if (mMirror != mirror) { + offer(REQUEST_MIRROR, mirror); + } + } + + @MirrorMode + public int mirror() { + return mMirror; + } + + /** + * 分配描画用のマスターSurfaceが有効かどうかをチェックして無効なら再生成する + */ + public void checkMasterSurface() { + checkFinished(); + if ((mMasterSurface == null) || (!mMasterSurface.isValid())) { + Log.d(TAG, "checkMasterSurface:invalid master surface"); + offerAndWait(REQUEST_RECREATE_MASTER_SURFACE, 0, 0, null); + } + } + + protected void checkFinished() throws IllegalStateException { + if (isFinished()) { + throw new IllegalStateException("already finished"); + } + } + + protected AbstractRendererHolder getParent() { + return mParent; + } + +//================================================================================ +// ワーカースレッド上での処理 +//================================================================================ + /** + * 実際の描画処理 + */ + protected void handleDraw() { + if ((mMasterSurface == null) || (!mMasterSurface.isValid())) { + Log.e(TAG, "checkMasterSurface:invalid master surface"); + offer(REQUEST_RECREATE_MASTER_SURFACE); + return; + } + if (mIsFirstFrameRendered) { + try { + makeCurrent(); + handleUpdateTexture(); + } catch (final Exception e) { + Log.e(TAG, "draw:thread id =" + Thread.currentThread().getId(), e); + offer(REQUEST_RECREATE_MASTER_SURFACE); + return; + } + mParent.notifyCapture(); + preprocess(); + handleDrawClients(); + mParent.callOnFrameAvailable(); + } + makeCurrent(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glFlush(); + } + + protected void handleUpdateTexture() { + mMasterTexture.updateTexImage(); + mMasterTexture.getTransformMatrix(mTexMatrix); + } + + protected abstract void preprocess(); + + /** + * 各Surfaceへ描画する + */ + protected void handleDrawClients() { + synchronized (mClients) { + final int n = mClients.size(); + RendererSurfaceRec client; + for (int i = n - 1; i >= 0; i--) { + client = mClients.valueAt(i); + if ((client != null) && client.canDraw()) { + try { + onDrawClient(client, mTexId, mTexMatrix); + } catch (final Exception e) { + // removeSurfaceが呼ばれなかったかremoveSurfaceを呼ぶ前に破棄されてしまった + mClients.removeAt(i); + client.release(); + } + } + } + } + } + + /** + * Surface1つの描画処理 + * @param client + * @param texId + * @param texMatrix + */ + protected abstract void onDrawClient( + @NonNull final RendererSurfaceRec client, + final int texId, final float[] texMatrix); + + /** + * 指定したIDの分配描画先Surfaceを追加する + * @param id + * @param surface + */ + protected void handleAddSurface(final int id, + final Object surface, final int maxFps) { + +// if (DEBUG) Log.v(TAG, "handleAddSurface:id=" + id); + checkSurface(); + synchronized (mClients) { + RendererSurfaceRec client = mClients.get(id); + if (client == null) { + try { + client = RendererSurfaceRec.newInstance(getEgl(), surface, maxFps); + setMirror(client, mMirror); + mClients.append(id, client); + } catch (final Exception e) { + Log.w(TAG, "invalid surface: surface=" + surface, e); + } + } else { + Log.w(TAG, "surface is already added: id=" + id); + } + mClients.notifyAll(); + } + } + + /** + * 指定したIDの分配描画先Surfaceを破棄する + * @param id + */ + protected void handleRemoveSurface(final int id) { + // if (DEBUG) Log.v(TAG, "handleRemoveSurface:id=" + id); + synchronized (mClients) { + final RendererSurfaceRec client = mClients.get(id); + if (client != null) { + mClients.remove(id); + if (client.isValid()) { + client.clear(0); // XXX 黒で塗りつぶし, 色指定できるようにする? + } + client.release(); + } + checkSurface(); + mClients.notifyAll(); + } + } + + /** + * 念の為に分配描画先のSurfaceを全て破棄する + */ + protected void handleRemoveAll() { + // if (DEBUG) Log.v(TAG, "handleRemoveAll:"); + synchronized (mClients) { + final int n = mClients.size(); + RendererSurfaceRec client; + for (int i = 0; i < n; i++) { + client = mClients.valueAt(i); + if (client != null) { + if (client.isValid()) { + client.clear(0); // XXX 黒で塗りつぶし, 色指定できるようにする? + } + client.release(); + } + } + mClients.clear(); + mClients.notifyAll(); + } + // if (DEBUG) Log.v(TAG, "handleRemoveAll:finished"); + } + + /** + * 分配描画先のSurfaceが有効かどうかをチェックして無効なものは削除する + */ + protected void checkSurface() { + // if (DEBUG) Log.v(TAG, "checkSurface"); + synchronized (mClients) { + final int n = mClients.size(); + for (int i = 0; i < n; i++) { + final RendererSurfaceRec client = mClients.valueAt(i); + if ((client != null) && !client.isValid()) { + final int id = mClients.keyAt(i); + // if (DEBUG) Log.i(TAG, "checkSurface:found invalid surface:id=" + id); + mClients.valueAt(i).release(); + mClients.remove(id); + } + } + } + // if (DEBUG) Log.v(TAG, "checkSurface:finished"); + } + + /** + * 指定したIDの分配描画用Surfaceを指定した色で塗りつぶす + * @param id + * @param color + */ + protected void handleClear(final int id, final int color) { + synchronized (mClients) { + final RendererSurfaceRec client = mClients.get(id); + if ((client != null) && client.isValid()) { + client.clear(color); + } + } + } + + /** + * 分配描画用Surface全てを指定した色で塗りつぶす + * @param color + */ + protected void handleClearAll(final int color) { + synchronized (mClients) { + final int n = mClients.size(); + for (int i = 0; i < n; i++) { + final RendererSurfaceRec client = mClients.valueAt(i); + if ((client != null) && client.isValid()) { + client.clear(color); + } + } + } + } + + /** + * モデルビュー変換行列を適用する + * @param id + * @param offset + * @param mvp + */ + protected void handleSetMvp(final int id, + final int offset, final Object mvp) { + + if ((mvp instanceof float[]) && (((float[]) mvp).length >= 16 + offset)) { + final float[] array = (float[])mvp; + synchronized (mClients) { + final RendererSurfaceRec client = mClients.get(id); + if ((client != null) && client.isValid()) { + System.arraycopy(array, offset, client.mMvpMatrix, 0, 16); + } + } + } + } + + /** + * マスターSurfaceを再生成する + */ + @SuppressLint("NewApi") + protected void handleReCreateMasterSurface() { + makeCurrent(); + handleReleaseMasterSurface(); + makeCurrent(); + mTexId = GLHelper.initTex(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_NEAREST); + mMasterTexture = new SurfaceTexture(mTexId); + mMasterSurface = new Surface(mMasterTexture); + if (BuildCheck.isAndroid4_1()) { + mMasterTexture.setDefaultBufferSize(mVideoWidth, mVideoHeight); + } + mMasterTexture.setOnFrameAvailableListener(mOnFrameAvailableListener); + mParent.callOnCreate(mMasterSurface); + } + + /** + * マスターSurfaceを破棄する + */ + protected void handleReleaseMasterSurface() { + if (mMasterSurface != null) { + try { + mMasterSurface.release(); + } catch (final Exception e) { + Log.w(TAG, e); + } + mMasterSurface = null; + mParent.callOnDestroy(); + } + if (mMasterTexture != null) { + try { + mMasterTexture.release(); + } catch (final Exception e) { + Log.w(TAG, e); + } + mMasterTexture = null; + } + if (mTexId != 0) { + GLHelper.deleteTex(mTexId); + mTexId = 0; + } + } + + /** + * マスター映像サイズをリサイズ + * @param width + * @param height + */ + @SuppressLint("NewApi") + protected void handleResize(final int width, final int height) { +// if (DEBUG) Log.v(TAG, String.format("handleResize:(%d,%d)", width, height)); + mVideoWidth = width; + mVideoHeight = height; + if (BuildCheck.isAndroid4_1()) { + mMasterTexture.setDefaultBufferSize(mVideoWidth, mVideoHeight); + } + } + + /** + * ミラーモードをセット + * @param mirror + */ + protected void handleMirror(final int mirror) { + mMirror = mirror; + synchronized (mClients) { + final int n = mClients.size(); + for (int i = 0; i < n; i++) { + final RendererSurfaceRec client = mClients.valueAt(i); + if (client != null) { + setMirror(client, mirror); + } + } + } + } + + /** + * handleMirrorの下請け + * @param client + * @param mirror + */ + protected void setMirror(final RendererSurfaceRec client, final int mirror) { + RendererHolder.setMirror(client.mMvpMatrix, mirror); + } + + protected void handleRotate(final int id, final int degree) { + // FIXME 未実装 + } + + /** + * TextureSurfaceで映像を受け取った際のコールバックリスナー + */ + protected final SurfaceTexture.OnFrameAvailableListener + mOnFrameAvailableListener = new SurfaceTexture.OnFrameAvailableListener() { + + @Override + public void onFrameAvailable(final SurfaceTexture surfaceTexture) { + removeRequest(REQUEST_DRAW); + mIsFirstFrameRendered = true; + offer(REQUEST_DRAW); + } + }; + + } + + /** + * ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス + */ + protected abstract static class RendererTask extends BaseRendererTask { + + protected GLDrawer2D mDrawer; + + public RendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height) { + + super(parent, width, height); + } + + public RendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height, + final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + + super(parent, width, height, maxClientVersion, sharedContext, flags); + } + + @Override + protected void internalOnStart() { + mDrawer = new GLDrawer2D(true); + } + + @Override + protected void internalOnStop() { + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + } + + @Override + protected void preprocess() { + } + + @Override + protected void onDrawClient(@NonNull final RendererSurfaceRec client, + final int texId, final float[] texMatrix) { + + client.draw(mDrawer, texId, texMatrix); + } + } + +//-------------------------------------------------------------------------------- + + protected void setupCaptureDrawer(final GLDrawer2D drawer) { + } + + /** + * 静止画を非同期でキャプチャするためのRunnable + */ + private final Runnable mCaptureTask = new Runnable() { + EGLBase eglBase; + EGLBase.IEglSurface captureSurface; + GLDrawer2D drawer; + final float[] mMvpMatrix = new float[16]; + + @Override + public void run() { +// if (DEBUG) Log.v(TAG, "captureTask start"); + synchronized (mSync) { + // 描画スレッドが実行されるまで待機 + for (; !isRunning && !mRendererTask.isFinished(); ) { + try { + mSync.wait(1000); + } catch (final InterruptedException e) { + break; + } + } + } + if (isRunning) { + init(); + try { + if ((eglBase.getGlVersion() > 2) && (BuildCheck.isAndroid4_3())) { + captureLoopGLES3(); + } else { + captureLoopGLES2(); + } + } catch (final Exception e) { + Log.w(TAG, e); + } finally { + // release resources + release(); + } + } +// if (DEBUG) Log.v(TAG, "captureTask finished"); + } + + private final void init() { + eglBase = EGLBase.createFrom(3, + mRendererTask.getContext(), false, 0, false); + captureSurface = eglBase.createOffscreen( + mRendererTask.width(), mRendererTask.height()); + Matrix.setIdentityM(mMvpMatrix, 0); + drawer = new GLDrawer2D(true); + setupCaptureDrawer(drawer); + } + + private final void captureLoopGLES2() { + int width = -1, height = -1; + ByteBuffer buf = null; + int captureCompression = DEFAULT_CAPTURE_COMPRESSION; +// if (DEBUG) Log.v(TAG, "captureTask loop"); + for (; isRunning ;) { + synchronized (mSync) { + if (mCaptureStream == null) { + try { + mSync.wait(); + } catch (final InterruptedException e) { + break; + } + if (mCaptureStream != null) { +// if (DEBUG) Log.i(TAG, "静止画撮影要求を受け取った"); + captureCompression = mCaptureCompression; + if ((captureCompression <= 0) || (captureCompression >= 100)) { + captureCompression = 90; + } + } else { + // 起床されたけどmCaptureStreamがnullだった + continue; + } + } + if (DEBUG) Log.v(TAG, "#captureLoopGLES2:start capture"); + if ((buf == null) + || (width != mRendererTask.width()) + || (height != mRendererTask.height())) { + + width = mRendererTask.width(); + height = mRendererTask.height(); + buf = ByteBuffer.allocateDirect(width * height * 4); + buf.order(ByteOrder.LITTLE_ENDIAN); + if (captureSurface != null) { + captureSurface.release(); + captureSurface = null; + } + captureSurface = eglBase.createOffscreen(width, height); + } + if (isRunning && (width > 0) && (height > 0)) { + setMirror(mMvpMatrix, mRendererTask.mirror()); + mMvpMatrix[5] *= -1.0f; // flip up-side down + drawer.setMvpMatrix(mMvpMatrix, 0); + captureSurface.makeCurrent(); + drawer.draw(mRendererTask.mTexId, mRendererTask.mTexMatrix, 0); + captureSurface.swap(); + buf.clear(); + GLES20.glReadPixels(0, 0, width, height, + GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); +// if (DEBUG) Log.v(TAG, "save pixels to file:" + captureFile); + final Bitmap.CompressFormat compressFormat + = getCaptureFormat(mCaptureFormat); + try { + try { + final Bitmap bmp = Bitmap.createBitmap( + width, height, Bitmap.Config.ARGB_8888); + buf.clear(); + bmp.copyPixelsFromBuffer(buf); + bmp.compress(compressFormat, captureCompression, mCaptureStream); + bmp.recycle(); + mCaptureStream.flush(); + } finally { + mCaptureStream.close(); + } + } catch (final IOException e) { + Log.w(TAG, "failed to save file", e); + } + } else if (isRunning) { + Log.w(TAG, "#captureLoopGLES3:unexpectedly width/height is zero"); + } + if (DEBUG) Log.i(TAG, "#captureLoopGLES2:静止画撮影終了"); + mCaptureStream = null; + mSync.notifyAll(); + } // end of synchronized (mSync) + } // end of for (; isRunning ;) + synchronized (mSync) { + mSync.notifyAll(); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + private final void captureLoopGLES3() { + int width = -1, height = -1; + ByteBuffer buf = null; + int captureCompression = 90; +// if (DEBUG) Log.v(TAG, "captureTask loop"); + for (; isRunning ;) { + synchronized (mSync) { + if (mCaptureStream == null) { + try { + mSync.wait(); + } catch (final InterruptedException e) { + break; + } + if (mCaptureStream != null) { +// if (DEBUG) Log.i(TAG, "静止画撮影要求を受け取った"); + captureCompression = mCaptureCompression; + if ((captureCompression <= 0) || (captureCompression >= 100)) { + captureCompression = 90; + } + } else { + // 起床されたけどmCaptureStreamがnullだった + continue; + } + } + if (DEBUG) Log.v(TAG, "#captureLoopGLES3:start capture"); + if ((buf == null) + || (width != mRendererTask.width()) + || (height != mRendererTask.height())) { + + width = mRendererTask.width(); + height = mRendererTask.height(); + buf = ByteBuffer.allocateDirect(width * height * 4); + buf.order(ByteOrder.LITTLE_ENDIAN); + if (captureSurface != null) { + captureSurface.release(); + captureSurface = null; + } + captureSurface = eglBase.createOffscreen(width, height); + } + if (isRunning && (width > 0) && (height > 0)) { + setMirror(mMvpMatrix, mRendererTask.mirror()); + mMvpMatrix[5] *= -1.0f; // flip up-side down + drawer.setMvpMatrix(mMvpMatrix, 0); + captureSurface.makeCurrent(); + drawer.draw(mRendererTask.mTexId, mRendererTask.mTexMatrix, 0); + captureSurface.swap(); + buf.clear(); + // FIXME これはGL|ES3のPBOとglMapBufferRange/glUnmapBufferを使うように変更する + GLES30.glReadPixels(0, 0, width, height, + GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buf); +// if (DEBUG) Log.v(TAG, "save pixels to file:" + captureFile); + final Bitmap.CompressFormat compressFormat + = getCaptureFormat(mCaptureFormat); + try { + try { + final Bitmap bmp = Bitmap.createBitmap( + width, height, Bitmap.Config.ARGB_8888); + buf.clear(); + bmp.copyPixelsFromBuffer(buf); + bmp.compress(compressFormat, captureCompression, mCaptureStream); + bmp.recycle(); + mCaptureStream.flush(); + } finally { + mCaptureStream.close(); + } + } catch (final IOException e) { + Log.w(TAG, "failed to save file", e); + } + } else if (isRunning) { + Log.w(TAG, "#captureLoopGLES3:unexpectedly width/height is zero"); + } + if (DEBUG) Log.i(TAG, "#captureLoopGLES3:静止画撮影終了"); + mCaptureStream = null; + mSync.notifyAll(); + } // end of synchronized (mSync) + } // end of for (; isRunning ;) + synchronized (mSync) { + mSync.notifyAll(); + } + } + + private final void release() { + if (captureSurface != null) { + captureSurface.makeCurrent(); + captureSurface.release(); + captureSurface = null; + } + if (drawer != null) { + drawer.release(); + drawer = null; + } + if (eglBase != null) { + eglBase.release(); + eglBase = null; + } + } + }; + +//================================================================================ + protected static void setMirror(final float[] mvp, final int mirror) { + switch (mirror) { + case MIRROR_NORMAL: + mvp[0] = Math.abs(mvp[0]); + mvp[5] = Math.abs(mvp[5]); + break; + case MIRROR_HORIZONTAL: + mvp[0] = -Math.abs(mvp[0]); // flip left-right + mvp[5] = Math.abs(mvp[5]); + break; + case MIRROR_VERTICAL: + mvp[0] = Math.abs(mvp[0]); + mvp[5] = -Math.abs(mvp[5]); // flip up-side down + break; + case MIRROR_BOTH: + mvp[0] = -Math.abs(mvp[0]); // flip left-right + mvp[5] = -Math.abs(mvp[5]); // flip up-side down + break; + } + } + + /** + * 現在のモデルビュー変換行列をxy平面で指定した角度回転させる + * @param mvp + * @param degrees + */ + protected static void rotate(final float[] mvp, final int degrees) { + if ((degrees % 180) != 0) { + Matrix.rotateM(mvp, 0, degrees, 0.0f, 0.0f, 1.0f); + } + } + + /** + * モデルビュー変換行列にxy平面で指定した角度回転させた回転行列をセットする + * @param mvp + * @param degrees + */ + protected static void setRotation(final float[] mvp, final int degrees) { + Matrix.setIdentityM(mvp, 0); + if ((degrees % 180) != 0) { + Matrix.rotateM(mvp, 0, degrees, 0.0f, 0.0f, 1.0f); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java new file mode 100644 index 0000000000..2f4f5db2f0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java @@ -0,0 +1,248 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.SurfaceTexture; +import android.util.Log; +import android.view.Surface; + +import androidx.annotation.NonNull; + +/** + * OpenGL|ESでのSurfaceへの描画処理をDelegaterを介して行うためのIRenderer + */ +public class DumbRenderer implements IRenderer { +// private static final boolean DEBUG = BuildConfig.DEBUG && false; + private static final String TAG = DumbRenderer.class.getSimpleName(); + + public interface RendererDelegater { + public void onStart(final EGLBase eglBase); + public void onStop(final EGLBase eglBase); + public void onSetSurface(final EGLBase eglBase, final Object surface); + public void onResize(final EGLBase eglBase, final int width, final int height); + /** + * 描画実行 + * @param eglBase + * @param args #requestRenderの引数 + */ + public void onDraw(final EGLBase eglBase, final Object... args); + public void onMirror(final EGLBase eglBase, final int mirror); + } + + /** レンダリングスレッドの排他制御用オブジェクト */ + private final Object mSync = new Object(); + private RendererTask mRendererTask; + @MirrorMode + private int mMirror = MIRROR_NORMAL; + + public DumbRenderer(final EGLBase.IContext sharedContext, + final int flags, final RendererDelegater delegater) { + + this(3, sharedContext, flags, delegater); + } + + public DumbRenderer(final int maxClientVersion, + final EGLBase.IContext sharedContext, + final int flags, final RendererDelegater delegater) { + + mRendererTask = new RendererTask(maxClientVersion, sharedContext, flags, delegater); + new Thread(mRendererTask, TAG).start(); + if (!mRendererTask.waitReady()) { + // 初期化に失敗した時 + throw new RuntimeException("failed to start renderer thread"); + } + } + + @Override + public void release() { + synchronized (mSync) { + if (mRendererTask != null) { + // 描画タスクを開放 + mRendererTask.release(); + mRendererTask = null; + } + } + } + + @Override + public void setSurface(final Surface surface) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_SET_SURFACE, surface); + } + } + } + + @Override + public void setSurface(final SurfaceTexture surface) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_SET_SURFACE, surface); + } + } + } + + @Override + public void setMirror(@MirrorMode final int mirror) { + synchronized (mSync) { + if (mMirror != mirror) { + mMirror = mirror; + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_MIRROR, mirror % 4); + } + } + } + } + + @Override + @MirrorMode + public int getMirror() { + return mMirror; + } + + @Override + public void resize(final int width, final int height) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_RESIZE, width, height); + } + } + } + + @Override + public void requestRender(final Object... args) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_DRAW, args); + } + } + } + + private static final int REQUEST_SET_SURFACE = 1; + private static final int REQUEST_DRAW = 2; + private static final int REQUEST_RESIZE = 3; + private static final int REQUEST_MIRROR = 4; + + private static class RendererTask extends EglTask { + private final RendererDelegater mDelegater; + /** 最後にレンダリングしたフレームサイズ, 0ならまで一度も描画されていない */ + private int frameWidth, frameHeight, frameRotation; + /** 描画先Surfaceのサイズ */ + private int surfaceWidth, surfaceHeight; + /** 映像を左右反転させるかどうか */ + private boolean mirror; + + public RendererTask(final EGLBase.IContext sharedContext, + final int flags, @NonNull final RendererDelegater delegater) { + + this(3, sharedContext, flags, delegater); + } + + public RendererTask(final int maxClientVersion, + final EGLBase.IContext sharedContext, + final int flags, @NonNull final RendererDelegater delegater) { + + super(maxClientVersion, sharedContext, flags); + mDelegater = delegater; + } + + @Override + protected void onStart() { + makeCurrent(); + try { + mDelegater.onStart(getEgl()); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + @Override + protected void onStop() { + makeCurrent(); + try { + mDelegater.onStop(getEgl()); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) throws TaskBreak { + + switch (request) { + case REQUEST_SET_SURFACE: + handleSetSurface(obj); + break; + case REQUEST_DRAW: + handleDraw(obj); + break; + case REQUEST_RESIZE: + handleResize(arg1, arg2); + break; + case REQUEST_MIRROR: + handleMirror(arg1); + break; + } + return null; + } + + private void handleSetSurface(final Object surface) { + makeCurrent(); + try { + mDelegater.onSetSurface(getEgl(), surface); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + private void handleResize(final int width, final int height) { + if ((surfaceWidth != width) || (surfaceHeight != height)) { + surfaceWidth = width; + surfaceHeight = height; + makeCurrent(); + try { + mDelegater.onResize(getEgl(), width, height); + } catch (final Exception e) { + Log.w(TAG, e); + } + handleDraw(); + } + } + + private void handleDraw(final Object... args) { + makeCurrent(); + try { + mDelegater.onDraw(getEgl(), args); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + private void handleMirror(final int mirror) { + makeCurrent(); + try { + mDelegater.onMirror(getEgl(), mirror); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java new file mode 100644 index 0000000000..1a5b9fe61e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java @@ -0,0 +1,183 @@ +package com.serenegiant.glutils; +/* + * 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.os.Build; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +public abstract class EGLBase { + public static final Object EGL_LOCK = new Object(); + + public static final int EGL_RECORDABLE_ANDROID = 0x3142; + public static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public static final int EGL_OPENGL_ES2_BIT = 4; + public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040; +// public static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * maxClientVersion=3, ステンシルバッファなし + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final IContext sharedContext, + final boolean withDepthBuffer, final boolean isRecordable) { + + return createFrom(3, sharedContext, withDepthBuffer, 0, isRecordable); + } + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * maxClientVersion=3 + * @param sharedContext + * @param withDepthBuffer + * @param stencilBits + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final IContext sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + + return createFrom(3, sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer trueなら16ビットのデプスバッファ有り, falseならデプスバッファなし + * @param stencilBits 0以下ならステンシルバッファなし + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final int maxClientVersion, + final IContext sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + + if (isEGL14Supported() && ((sharedContext == null) + || (sharedContext instanceof EGLBase14.Context))) { + + return new EGLBase14(maxClientVersion, + (EGLBase14.Context)sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } else { + return new EGLBase10(maxClientVersion, + (EGLBase10.Context)sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } + } + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static abstract class IContext { + public abstract long getNativeHandle(); + public abstract Object getEGLContext(); + } + + /** + * EGLコンフィグのホルダークラス + */ + public static abstract class IConfig { + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public interface IEglSurface { + public void makeCurrent(); + public void swap(); + + public IContext getContext(); + /** + * swap with presentation time[ns] + * only works well now when using EGLBase14 + * @param presentationTimeNs + */ + public void swap(final long presentationTimeNs); + public void release(); + public boolean isValid(); + } + + public static boolean isEGL14Supported() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * 関連するリソースを破棄する + */ + public abstract void release(); + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + public abstract String queryString(final int what); + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + public abstract int getGlVersion(); + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + public abstract IContext getContext(); + + /** + * EGLコンフィグを取得する + * @return + */ + public abstract IConfig getConfig(); + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + public abstract IEglSurface createFromSurface(final Object nativeWindow); + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + public abstract IEglSurface createOffscreen(final int width, final int height); + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + public abstract void makeDefault(); + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + public abstract void sync(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java new file mode 100644 index 0000000000..c5ae0e2dd8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java @@ -0,0 +1,724 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.opengl.GLES10; +import android.opengl.GLES20; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.serenegiant.utils.BuildCheck; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +/*package*/ class EGLBase10 extends EGLBase { +// private static final boolean DEBUG = false; // FIXME set false on release + private static final String TAG = "EGLBase10"; + + private EGL10 mEgl = null; + private EGLDisplay mEglDisplay = null; + private Config mEglConfig = null; + private int mGlVersion = 2; + + private static final Context EGL_NO_CONTEXT = new Context(EGL10.EGL_NO_CONTEXT); + @NonNull private Context mContext = EGL_NO_CONTEXT; + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static class Context extends IContext { + public final EGLContext eglContext; + + private Context(final EGLContext context) { + eglContext = context; + } + + @Override + public long getNativeHandle() { + return 0L; + } + + @Override + public Object getEGLContext() { + return eglContext; + } + } + + public static class Config extends IConfig { + public final EGLConfig eglConfig; + + private Config(final EGLConfig eglConfig) { + this.eglConfig = eglConfig; + } + } + + /** + * Android4.1.2だとSurfaceを使えない。 + * SurfaceTexture/SurfaceHolderの場合は内部でSurfaceを生成して使っているにもかかわらず。 + * SurfaceHolderはインターフェースなのでSurfaceHolderを継承したダミークラスを生成して食わす + */ + public static class MySurfaceHolder implements SurfaceHolder { + private final Surface surface; + + public MySurfaceHolder(final Surface surface) { + this.surface = surface; + } + @Override + public Surface getSurface() { + return surface; + } + // ここより下はどないでもええ + @Override + public void addCallback(final Callback callback) { + } + @Override + public void removeCallback(final Callback callback) { + } + @Override + public boolean isCreating() { + return false; + } + @Override + public void setType(final int type) { + } + @Override + public void setFixedSize(final int width, final int height) { + } + @Override + public void setSizeFromLayout() { + } + @Override + public void setFormat(final int format) { + } + @Override + public void setKeepScreenOn(final boolean screenOn) { + } + @Override + public Canvas lockCanvas() { + return null; + } + @Override + public Canvas lockCanvas(final Rect dirty) { + return null; + } + @Override + public void unlockCanvasAndPost(final Canvas canvas) { + } + @Override + public Rect getSurfaceFrame() { + return null; + } + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public static class EglSurface implements IEglSurface { + private final EGLBase10 mEglBase; + private EGLSurface mEglSurface = EGL10.EGL_NO_SURFACE; + + /** + * Surface(Surface/SurfaceTexture/SurfaceHolder)に関係付けられたEglSurface + * @param eglBase + * @param surface + */ + private EglSurface(final EGLBase10 eglBase, final Object surface) + throws IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((surface instanceof Surface) && !BuildCheck.isAndroid4_2()) { + // Android4.1.2だとSurfaceを使えない。 + // SurfaceTexture/SurfaceHolderの場合は内部で + // Surfaceを生成して使っているにもかかわらず。 + // SurfaceHolderはインターフェースなのでSurfaceHolderを + // 継承したダミークラスを生成して食わす + mEglSurface = mEglBase.createWindowSurface( + new MySurfaceHolder((Surface) surface)); + } else if ((surface instanceof Surface) + || (surface instanceof SurfaceHolder) + || (surface instanceof SurfaceTexture) + || (surface instanceof SurfaceView)) { + mEglSurface = mEglBase.createWindowSurface(surface); + } else { + throw new IllegalArgumentException("unsupported surface"); + } + } + + /** + * 指定した大きさを持つオフスクリーンEglSurface(PBuffer) + * @param eglBase + * @param width + * @param height + */ + private EglSurface(final EGLBase10 eglBase, final int width, final int height) { +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((width <= 0) || (height <= 0)) { + mEglSurface = mEglBase.createOffscreenSurface(1, 1); + } else { + mEglSurface = mEglBase.createOffscreenSurface(width, height); + } + } + + /** + * 指定したEGLSurfaceをカレントの描画Surfaceに設定する + * Surface全面に描画できるようにViewportも変更するので必要であればswapの後に変更すること + */ + @Override + public void makeCurrent() { + mEglBase.makeCurrent(mEglSurface); + if (mEglBase.getGlVersion() >= 2) { + GLES20.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), mEglBase.getSurfaceHeight(mEglSurface)); + } else { + GLES10.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), mEglBase.getSurfaceHeight(mEglSurface)); + } + } + + /** + * 描画を終了してダブルバッファを切り替える + */ + @Override + public void swap() { + mEglBase.swap(mEglSurface); + } + + @Override + public void swap(final long presentationTimeNs) { + mEglBase.swap(mEglSurface, presentationTimeNs); + } + + @Override + public IContext getContext() { + return mEglBase.getContext(); + } + + public void setPresentationTime(final long presentationTimeNs) { +// EGLExt.eglPresentationTimeANDROID(mEglBase.mEglDisplay, +// mEglSurface, presentationTimeNs); + } + + /** + * EGLSurfaceが有効かどうかを取得 + * @return + */ + @Override + public boolean isValid() { + return (mEglSurface != null) + && (mEglSurface != EGL10.EGL_NO_SURFACE) + && (mEglBase.getSurfaceWidth(mEglSurface) > 0) + && (mEglBase.getSurfaceHeight(mEglSurface) > 0); + } + + /** + * 破棄処理 + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "EglSurface:release:"); + mEglBase.makeDefault(); + mEglBase.destroyWindowSurface(mEglSurface); + mEglSurface = EGL10.EGL_NO_SURFACE; + } + } + + /** + * コンストラクタ + * @param maxClientVersion + * @param sharedContext 共有コンテキストを使用する場合に指定 + * @param withDepthBuffer + * @param isRecordable true MediaCodec等の録画用Surfaceを使用する場合に、 + * EGL_RECORDABLE_ANDROIDフラグ付きでコンフィグする + */ + public EGLBase10(final int maxClientVersion, + final Context sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "Constructor:"); + init(maxClientVersion, sharedContext, withDepthBuffer, stencilBits, isRecordable); + } + + /** + * 関連するリソースを破棄する + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + destroyContext(); + mContext = EGL_NO_CONTEXT; + if (mEgl == null) return; + mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); +// mEgl.eglReleaseThread(); // XXX これを入れるとハングアップする機種がある + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + mEglConfig = null; + mEgl = null; + } + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + @Override + public EglSurface createFromSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createFromSurface:"); + final EglSurface eglSurface = new EglSurface(this, nativeWindow); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + @Override + public EglSurface createOffscreen(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreen:"); + final EglSurface eglSurface = new EglSurface(this, width, height); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + @Override + public Context getContext() { + return mContext; + } + + /** + * EGLコンフィグを取得する + * @return + */ + @Override + public Config getConfig() { + return mEglConfig; + } + + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + @Override + public void makeDefault() { +// if (DEBUG) Log.v(TAG, "makeDefault:"); + if (!mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) { + + Log.w(TAG, "makeDefault:eglMakeCurrent:err=" + mEgl.eglGetError()); + } + } + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + @Override + public void sync() { + mEgl.eglWaitGL(); // GLES20.glFinish()と同様の効果 + mEgl.eglWaitNative(EGL10.EGL_CORE_NATIVE_ENGINE, null); + } + + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + @Override + public String queryString(final int what) { + return mEgl.eglQueryString(mEglDisplay, what); + } + + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + @Override + public int getGlVersion() { + return mGlVersion; + } + + /** + * 初期化の下請け + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + */ + private final void init(final int maxClientVersion, + @Nullable Context sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "init:"); + sharedContext = (sharedContext != null) ? sharedContext : EGL_NO_CONTEXT; + if (mEgl == null) { + mEgl = (EGL10)EGLContext.getEGL(); + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + // EGLのバージョンを取得 + final int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + mEglDisplay = null; + throw new RuntimeException("eglInitialize failed"); + } + } + EGLConfig config; + if (maxClientVersion >= 3) { + // GLES3で取得できるかどうか試してみる + config = getConfig(3, withDepthBuffer, stencilBits, isRecordable); + if (config != null) { + final EGLContext context = createContext(sharedContext, config, 3); + if ((mEgl.eglGetError()) == EGL10.EGL_SUCCESS) { + // ここは例外生成したくないのでcheckEglErrorの代わりに自前でチェック + //Log.d(TAG, "Got GLES 3 config"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 3; + } + } + } + // GLES3で取得できなかった時はGLES2を試みる + if ((maxClientVersion >= 2) + && ((mContext == null) || (mContext.eglContext == EGL10.EGL_NO_CONTEXT))) { + + config = getConfig(2, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + try { + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } catch (final Exception e) { + if (isRecordable) { + config = getConfig(2, withDepthBuffer, stencilBits, false); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } + } + } + if ((mContext == null) || (mContext.eglContext == EGL10.EGL_NO_CONTEXT)) { + config = getConfig(1, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 1); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 1; + } + // confirm whether the EGL rendering context is successfully created + final int[] values = new int[1]; + mEgl.eglQueryContext(mEglDisplay, + mContext.eglContext, EGL_CONTEXT_CLIENT_VERSION, values); + Log.d(TAG, "EGLContext created, client version " + values[0]); + makeDefault(); + } + + /** + * change context to draw this window surface + * @return + */ + private final boolean makeCurrent(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "makeCurrent:"); +/* if (mEglDisplay == null) { + if (DEBUG) Log.d(TAG, "makeCurrent:eglDisplay not initialized"); + } */ + if (surface == null || surface == EGL10.EGL_NO_SURFACE) { + final int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "makeCurrent:EGL_BAD_NATIVE_WINDOW"); + } + return false; + } + // attach EGL rendering context to specific EGL window surface + if (!mEgl.eglMakeCurrent(mEglDisplay, surface, surface, mContext.eglContext)) { + Log.w("TAG", "eglMakeCurrent" + mEgl.eglGetError()); + return false; + } + return true; + } + + private final int swap(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "swap:"); + if (!mEgl.eglSwapBuffers(mEglDisplay, surface)) { + final int err = mEgl.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL10.EGL_SUCCESS; + } + + /** + * swap rendering buffer with presentation time[ns] + * presentationTimeNs is ignored on this method + * @param surface + * @param ignored + * @return + */ + private final int swap(final EGLSurface surface, final long ignored) { +// if (DEBUG) Log.v(TAG, "swap:"); +// EGLExt.eglPresentationTimeANDROID(mEglDisplay, surface, presentationTimeNs); + if (!mEgl.eglSwapBuffers(mEglDisplay, surface)) { + final int err = mEgl.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL10.EGL_SUCCESS; + } + + private final EGLContext createContext( + @NonNull final Context sharedContext, + final EGLConfig config, final int version) { + +// if (DEBUG) Log.v(TAG, "createContext:"); + + final int[] attrib_list = { + EGL_CONTEXT_CLIENT_VERSION, version, + EGL10.EGL_NONE + }; + final EGLContext context = mEgl.eglCreateContext( + mEglDisplay, config, sharedContext.eglContext, attrib_list); +// checkEglError("eglCreateContext"); + return context; + } + + private final void destroyContext() { +// if (DEBUG) Log.v(TAG, "destroyContext:"); + + if (!mEgl.eglDestroyContext(mEglDisplay, mContext.eglContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mContext.eglContext); + Log.e(TAG, "eglDestroyContext:" + mEgl.eglGetError()); + } + mContext = EGL_NO_CONTEXT; + } + + private final int getSurfaceWidth(final EGLSurface surface) { + final int[] value = new int[1]; + final boolean ret = mEgl.eglQuerySurface(mEglDisplay, + surface, EGL10.EGL_WIDTH, value); + if (!ret) value[0] = 0; + return value[0]; + } + + private final int getSurfaceHeight(final EGLSurface surface) { + final int[] value = new int[1]; + final boolean ret = mEgl.eglQuerySurface(mEglDisplay, + surface, EGL10.EGL_HEIGHT, value); + if (!ret) value[0] = 0; + return value[0]; + } + + /** + * nativeWindow should be one of the SurfaceView, Surface, SurfaceHolder and SurfaceTexture + * @param nativeWindow + * @return + */ + private final EGLSurface createWindowSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createWindowSurface:nativeWindow=" + nativeWindow); + + final int[] surfaceAttribs = { + EGL10.EGL_NONE + }; + EGLSurface result = null; + try { + result = mEgl.eglCreateWindowSurface(mEglDisplay, + mEglConfig.eglConfig, nativeWindow, surfaceAttribs); + if (result == null || result == EGL10.EGL_NO_SURFACE) { + final int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + throw new RuntimeException("createWindowSurface failed error=" + error); + } + makeCurrent(result); + // 画面サイズ・フォーマットの取得 + } catch (final Exception e) { + Log.e(TAG, "eglCreateWindowSurface", e); + throw new IllegalArgumentException(e); + } + return result; + } + + /** + * Creates an EGL surface associated with an offscreen buffer. + */ + private final EGLSurface createOffscreenSurface(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreenSurface:"); + final int[] surfaceAttribs = { + EGL10.EGL_WIDTH, width, + EGL10.EGL_HEIGHT, height, + EGL10.EGL_NONE + }; + mEgl.eglWaitGL(); + EGLSurface result = null; + try { + result = mEgl.eglCreatePbufferSurface(mEglDisplay, + mEglConfig.eglConfig, surfaceAttribs); + checkEglError("eglCreatePbufferSurface"); + if (result == null) { + throw new RuntimeException("surface was null"); + } + } catch (final IllegalArgumentException e) { + Log.e(TAG, "createOffscreenSurface", e); + } catch (final RuntimeException e) { + Log.e(TAG, "createOffscreenSurface", e); + } + return result; + } + + private final void destroyWindowSurface(EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "destroySurface:"); + + if (surface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, surface); + } + surface = EGL10.EGL_NO_SURFACE; +// if (DEBUG) Log.v(TAG, "destroySurface:finished"); + } + + private final void checkEglError(final String msg) { + int error; + if ((error = mEgl.eglGetError()) != EGL10.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + + private final EGLConfig getConfig(final int version, + final boolean hasDepthBuffer, final int stencilBits, final boolean isRecordable) { + + int renderableType = EGL_OPENGL_ES2_BIT; + if (version >= 3) { + renderableType |= EGL_OPENGL_ES3_BIT_KHR; + } +// final int swapBehavior = dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + final int[] attribList = { + EGL10.EGL_RENDERABLE_TYPE, renderableType, + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, +// EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT | swapBehavior, + EGL10.EGL_NONE, EGL10.EGL_NONE, //EGL10.EGL_STENCIL_SIZE, 8, + // this flag need to recording of MediaCodec + EGL10.EGL_NONE, EGL10.EGL_NONE, // EGL_RECORDABLE_ANDROID, 1, + EGL10.EGL_NONE, EGL10.EGL_NONE, // with_depth_buffer ? EGL10.EGL_DEPTH_SIZE : EGL10.EGL_NONE, + // with_depth_buffer ? 16 : 0, + EGL10.EGL_NONE + }; + int offset = 10; + if (stencilBits > 0) { // ステンシルバッファ(常時未使用) + attribList[offset++] = EGL10.EGL_STENCIL_SIZE; + attribList[offset++] = 8; + } + if (hasDepthBuffer) { // デプスバッファ + attribList[offset++] = EGL10.EGL_DEPTH_SIZE; + attribList[offset++] = 16; + } + if (isRecordable && BuildCheck.isAndroid4_3()) { + // MediaCodecの入力用Surfaceの場合 + // A-1000F(Android4.1.2)はこのフラグをつけるとうまく動かない + attribList[offset++] = EGL_RECORDABLE_ANDROID; + attribList[offset++] = 1; + } + for (int i = attribList.length - 1; i >= offset; i--) { + attribList[i] = EGL10.EGL_NONE; + } + EGLConfig config = internalGetConfig(attribList); + if ((config == null) && (version == 2)) { + if (isRecordable) { + // EGL_RECORDABLE_ANDROIDをつけると失敗する機種もあるので取り除く + final int n = attribList.length; + for (int i = 10; i < n - 1; i += 2) { + if (attribList[i] == EGL_RECORDABLE_ANDROID) { + for (int j = i; j < n; j++) { + attribList[j] = EGL10.EGL_NONE; + } + break; + } + } + config = internalGetConfig(attribList); + } + } + if (config == null) { + Log.w(TAG, "try to fallback to RGB565"); + attribList[3] = 5; + attribList[5] = 6; + attribList[7] = 5; + config = internalGetConfig(attribList); + } + return config; + } + + private EGLConfig internalGetConfig(final int[] attribList) { + final EGLConfig[] configs = new EGLConfig[1]; + final int[] numConfigs = new int[1]; + if (!mEgl.eglChooseConfig(mEglDisplay, + attribList, configs, configs.length, numConfigs)) { + + return null; + } + return configs[0]; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java new file mode 100644 index 0000000000..fb119a4641 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java @@ -0,0 +1,631 @@ +package com.serenegiant.glutils; +/* + * 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.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES10; +import android.opengl.GLES20; +import android.os.Build; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.annotation.NonNull; + +import com.serenegiant.utils.BuildCheck; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +/*package*/ class EGLBase14 extends EGLBase { // API >= 17 +// private static final boolean DEBUG = false; // TODO set false on release + private static final String TAG = "EGLBase14"; + + private static final Context EGL_NO_CONTEXT = new Context(EGL14.EGL_NO_CONTEXT); + + private Config mEglConfig = null; + @NonNull private Context mContext = EGL_NO_CONTEXT; +// private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT; + private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext mDefaultContext = EGL14.EGL_NO_CONTEXT; + private int mGlVersion = 2; + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static class Context extends IContext { + public final EGLContext eglContext; + + private Context(final EGLContext context) { + eglContext = context; + } + + @Override + @SuppressLint("NewApi") + public long getNativeHandle() { + return eglContext != null ? + (BuildCheck.isLollipop() + ? eglContext.getNativeHandle() : eglContext.getHandle()) : 0L; + } + + @Override + public Object getEGLContext() { + return eglContext; + } + } + + public static class Config extends IConfig { + public final EGLConfig eglConfig; + + private Config(final EGLConfig eglConfig) { + this.eglConfig = eglConfig; + } + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public static class EglSurface implements IEglSurface { + private final EGLBase14 mEglBase; + private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE; + + private EglSurface(final EGLBase14 eglBase, final Object surface) + throws IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((surface instanceof Surface) + || (surface instanceof SurfaceHolder) + || (surface instanceof SurfaceTexture) + || (surface instanceof SurfaceView)) { + mEglSurface = mEglBase.createWindowSurface(surface); + } else { + throw new IllegalArgumentException("unsupported surface"); + } + } + + /** + * 指定した大きさを持つオフスクリーンEglSurface(PBuffer) + * @param eglBase + * @param width + * @param height + */ + private EglSurface(final EGLBase14 eglBase, + final int width, final int height) { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((width <= 0) || (height <= 0)) { + mEglSurface = mEglBase.createOffscreenSurface(1, 1); + } else { + mEglSurface = mEglBase.createOffscreenSurface(width, height); + } + } + + @Override + public void makeCurrent() { + mEglBase.makeCurrent(mEglSurface); + if (mEglBase.getGlVersion() >= 2) { + GLES20.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), + mEglBase.getSurfaceHeight(mEglSurface)); + } else { + GLES10.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), + mEglBase.getSurfaceHeight(mEglSurface)); + } + } + + @Override + public void swap() { + mEglBase.swap(mEglSurface); + } + + @Override + public void swap(final long presentationTimeNs) { + mEglBase.swap(mEglSurface, presentationTimeNs); + } + + public void setPresentationTime(final long presentationTimeNs) { + EGLExt.eglPresentationTimeANDROID(mEglBase.mEglDisplay, + mEglSurface, presentationTimeNs); + } + + @Override + public IContext getContext() { + return mEglBase.getContext(); + } + + @Override + public boolean isValid() { + return (mEglSurface != null) + && (mEglSurface != EGL14.EGL_NO_SURFACE) + && (mEglBase.getSurfaceWidth(mEglSurface) > 0) + && (mEglBase.getSurfaceHeight(mEglSurface) > 0); + } + + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "EglSurface:release:"); + mEglBase.makeDefault(); + mEglBase.destroyWindowSurface(mEglSurface); + mEglSurface = EGL14.EGL_NO_SURFACE; + } + } + + /** + * コンストラクタ + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + */ + public EGLBase14(final int maxClientVersion, + final Context sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "Constructor:"); + init(maxClientVersion, sharedContext, withDepthBuffer, stencilBits, isRecordable); + } + + /** + * 関連するリソースを破棄する + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mEglDisplay != EGL14.EGL_NO_DISPLAY) { + destroyContext(); + EGL14.eglTerminate(mEglDisplay); + EGL14.eglReleaseThread(); + } + mEglDisplay = EGL14.EGL_NO_DISPLAY; + mContext = EGL_NO_CONTEXT; + } + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + @Override + public EglSurface createFromSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createFromSurface:"); + final EglSurface eglSurface = new EglSurface(this, nativeWindow); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + @Override + public EglSurface createOffscreen(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreen:"); + final EglSurface eglSurface = new EglSurface(this, width, height); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + public String queryString(final int what) { + return EGL14.eglQueryString(mEglDisplay, what); + } + + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + @Override + public int getGlVersion() { + return mGlVersion; + } + + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + @Override + public Context getContext() { + return mContext; + } + + /** + * EGLコンフィグを取得する + * @return + */ + @Override + public Config getConfig() { + return mEglConfig; + } + + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + @Override + public void makeDefault() { +// if (DEBUG) Log.v(TAG, "makeDefault:"); + if (!EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) { + + Log.w("TAG", "makeDefault" + EGL14.eglGetError()); + } + } + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + @Override + public void sync() { + EGL14.eglWaitGL(); // GLES20.glFinish()と同様の効果 + EGL14.eglWaitNative(EGL14.EGL_CORE_NATIVE_ENGINE); + } + + private void init(final int maxClientVersion, Context sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "init:"); + if (mEglDisplay != EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("EGL already set up"); + } + + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + // EGLのバージョンを取得 + final int[] version = new int[2]; + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + mEglDisplay = null; + throw new RuntimeException("eglInitialize failed"); + } + + sharedContext = (sharedContext != null) ? sharedContext : EGL_NO_CONTEXT; + + EGLConfig config; + if (maxClientVersion >= 3) { + // GLES3で取得できるかどうか試してみる + config = getConfig(3, withDepthBuffer, stencilBits, isRecordable); + if (config != null) { + final EGLContext context = createContext(sharedContext, config, 3); + if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { + // ここは例外生成したくないのでcheckEglErrorの代わりに自前でチェック + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 3; + } + } + } + // GLES3で取得できなかった時はGLES2を試みる + if ((maxClientVersion >= 2) + && ((mContext == null) || (mContext.eglContext == EGL14.EGL_NO_CONTEXT))) { + + config = getConfig(2, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + try { + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } catch (final Exception e) { + if (isRecordable) { + config = getConfig(2, withDepthBuffer, stencilBits, false); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } + } + } + if ((mContext == null) || (mContext.eglContext == EGL14.EGL_NO_CONTEXT)) { + config = getConfig(1, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 1); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 1; + } + // confirm whether the EGL rendering context is successfully created + final int[] values = new int[1]; + EGL14.eglQueryContext(mEglDisplay, + mContext.eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0); + Log.d(TAG, "EGLContext created, client version " + values[0]); + makeDefault(); // makeCurrent(EGL14.EGL_NO_SURFACE); + } + + /** + * change context to draw this window surface + * @return + */ + private boolean makeCurrent(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "makeCurrent:"); +/* if (mEglDisplay == null) { + if (DEBUG) Log.d(TAG, "makeCurrent:eglDisplay not initialized"); + } */ + if (surface == null || surface == EGL14.EGL_NO_SURFACE) { + final int error = EGL14.eglGetError(); + if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "makeCurrent:returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + // attach EGL rendering context to specific EGL window surface + if (!EGL14.eglMakeCurrent(mEglDisplay, surface, surface, mContext.eglContext)) { + Log.w("TAG", "eglMakeCurrent" + EGL14.eglGetError()); + return false; + } + return true; + } + + private int swap(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "swap:"); + if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) { + final int err = EGL14.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL14.EGL_SUCCESS; + } + + private int swap(final EGLSurface surface, final long presentationTimeNs) { +// if (DEBUG) Log.v(TAG, "swap:"); + EGLExt.eglPresentationTimeANDROID(mEglDisplay, surface, presentationTimeNs); + if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) { + final int err = EGL14.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL14.EGL_SUCCESS; + } + + private EGLContext createContext(final Context sharedContext, + final EGLConfig config, final int version) { + +// if (DEBUG) Log.v(TAG, "createContext:"); + + final int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, version, + EGL14.EGL_NONE + }; + final EGLContext context = EGL14.eglCreateContext(mEglDisplay, + config, sharedContext.eglContext, attrib_list, 0); +// checkEglError("eglCreateContext"); + return context; + } + + private void destroyContext() { +// if (DEBUG) Log.v(TAG, "destroyContext:"); + + if (!EGL14.eglDestroyContext(mEglDisplay, mContext.eglContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mContext.eglContext); + Log.e(TAG, "eglDestroyContext:" + EGL14.eglGetError()); + } + mContext = EGL_NO_CONTEXT; + if (mDefaultContext != EGL14.EGL_NO_CONTEXT) { + if (!EGL14.eglDestroyContext(mEglDisplay, mDefaultContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mDefaultContext); + Log.e(TAG, "eglDestroyContext:" + EGL14.eglGetError()); + } + mDefaultContext = EGL14.EGL_NO_CONTEXT; + } + } + + private final int[] mSurfaceDimension = new int[2]; + private final int getSurfaceWidth(final EGLSurface surface) { + final boolean ret = EGL14.eglQuerySurface(mEglDisplay, + surface, EGL14.EGL_WIDTH, mSurfaceDimension, 0); + if (!ret) mSurfaceDimension[0] = 0; + return mSurfaceDimension[0]; + } + + private final int getSurfaceHeight(final EGLSurface surface) { + final boolean ret = EGL14.eglQuerySurface(mEglDisplay, + surface, EGL14.EGL_HEIGHT, mSurfaceDimension, 1); + if (!ret) mSurfaceDimension[1] = 0; + return mSurfaceDimension[1]; + } + + /** + * nativeWindow should be one of the Surface, SurfaceHolder and SurfaceTexture + * @param nativeWindow + * @return + */ + private final EGLSurface createWindowSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createWindowSurface:nativeWindow=" + nativeWindow); + + final int[] surfaceAttribs = { + EGL14.EGL_NONE + }; + EGLSurface result = null; + try { + result = EGL14.eglCreateWindowSurface(mEglDisplay, + mEglConfig.eglConfig, nativeWindow, surfaceAttribs, 0); + if (result == null || result == EGL14.EGL_NO_SURFACE) { + final int error = EGL14.eglGetError(); + if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + throw new RuntimeException("createWindowSurface failed error=" + error); + } + makeCurrent(result); + // 画面サイズ・フォーマットの取得 + } catch (final Exception e) { + Log.e(TAG, "eglCreateWindowSurface", e); + throw new IllegalArgumentException(e); + } + return result; + } + + /** + * Creates an EGL surface associated with an offscreen buffer. + */ + private final EGLSurface createOffscreenSurface(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreenSurface:"); + final int[] surfaceAttribs = { + EGL14.EGL_WIDTH, width, + EGL14.EGL_HEIGHT, height, + EGL14.EGL_NONE + }; + EGLSurface result = null; + try { + result = EGL14.eglCreatePbufferSurface(mEglDisplay, + mEglConfig.eglConfig, surfaceAttribs, 0); + checkEglError("eglCreatePbufferSurface"); + if (result == null) { + throw new RuntimeException("surface was null"); + } + } catch (final IllegalArgumentException e) { + Log.e(TAG, "createOffscreenSurface", e); + } catch (final RuntimeException e) { + Log.e(TAG, "createOffscreenSurface", e); + } + return result; + } + + private void destroyWindowSurface(EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "destroySurface:"); + + if (surface != EGL14.EGL_NO_SURFACE) { + EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + EGL14.eglDestroySurface(mEglDisplay, surface); + } + surface = EGL14.EGL_NO_SURFACE; +// if (DEBUG) Log.v(TAG, "destroySurface:finished"); + } + + private void checkEglError(final String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + + private EGLConfig getConfig(final int version, + final boolean hasDepthBuffer, final int stencilBits, final boolean isRecordable) { + + int renderableType = EGL_OPENGL_ES2_BIT; + if (version >= 3) { + renderableType |= EGL_OPENGL_ES3_BIT_KHR; + } + final int[] attribList = { + EGL14.EGL_RENDERABLE_TYPE, renderableType, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, +// EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT | swapBehavior, + EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL14.EGL_STENCIL_SIZE, 8, + // this flag need to recording of MediaCodec + EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE, EGL14.EGL_NONE, // with_depth_buffer ? EGL14.EGL_DEPTH_SIZE : EGL14.EGL_NONE, + // with_depth_buffer ? 16 : 0, + EGL14.EGL_NONE + }; + int offset = 10; + if (stencilBits > 0) { // ステンシルバッファ(常時未使用) + attribList[offset++] = EGL14.EGL_STENCIL_SIZE; + attribList[offset++] = stencilBits; + } + if (hasDepthBuffer) { // デプスバッファ + attribList[offset++] = EGL14.EGL_DEPTH_SIZE; + attribList[offset++] = 16; + } + if (isRecordable && BuildCheck.isAndroid4_3()) {// MediaCodecの入力用Surfaceの場合 + attribList[offset++] = EGL_RECORDABLE_ANDROID; + attribList[offset++] = 1; + } + for (int i = attribList.length - 1; i >= offset; i--) { + attribList[i] = EGL14.EGL_NONE; + } + EGLConfig config = internalGetConfig(attribList); + if ((config == null) && (version == 2)) { + if (isRecordable) { + // EGL_RECORDABLE_ANDROIDをつけると失敗する機種もあるので取り除く + final int n = attribList.length; + for (int i = 10; i < n - 1; i += 2) { + if (attribList[i] == EGL_RECORDABLE_ANDROID) { + for (int j = i; j < n; j++) { + attribList[j] = EGL14.EGL_NONE; + } + break; + } + } + config = internalGetConfig(attribList); + } + } + if (config == null) { + Log.w(TAG, "try to fallback to RGB565"); + attribList[3] = 5; + attribList[5] = 6; + attribList[7] = 5; + config = internalGetConfig(attribList); + } + return config; + } + + private EGLConfig internalGetConfig(final int[] attribList) { + final EGLConfig[] configs = new EGLConfig[1]; + final int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(mEglDisplay, + attribList, 0, configs, 0, configs.length, numConfigs, 0)) { + return null; + } + return configs[0]; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java new file mode 100644 index 0000000000..5b72592870 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java @@ -0,0 +1,548 @@ +package com.serenegiant.glutils; +/* + * 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 static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_SIMPLE_OES; +import static com.serenegiant.glutils.ShaderConst.FUNC_HSV2RGB; +import static com.serenegiant.glutils.ShaderConst.FUNC_RGB2HSV; +import static com.serenegiant.glutils.ShaderConst.HEADER_OES; +import static com.serenegiant.glutils.ShaderConst.SAMPLER_OES; +import static com.serenegiant.glutils.ShaderConst.SHADER_VERSION; + +import android.annotation.SuppressLint; +import android.opengl.GLES20; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * GL_TEXTURE_EXTERNAL_OESテクスチャを受け取ってSurfaceへ分配描画するクラス + * RendererHolderにフラグメントシェーダーでのフィルター処理を追加 + * ...カラーマトリックスを掛けるほうがいいかなぁ + * ...色はuniform変数で渡す方がいいかも + */ +public class EffectRendererHolder extends AbstractRendererHolder { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = EffectRendererHolder.class.getSimpleName(); + + private static final int MAX_PARAM_NUM = 18; + + public static final int EFFECT_NON = 0; + public static final int EFFECT_GRAY = 1; + public static final int EFFECT_GRAY_REVERSE = 2; + public static final int EFFECT_BIN = 3; + public static final int EFFECT_BIN_YELLOW = 4; + public static final int EFFECT_BIN_GREEN = 5; + public static final int EFFECT_BIN_REVERSE = 6; + public static final int EFFECT_BIN_REVERSE_YELLOW = 7; + public static final int EFFECT_BIN_REVERSE_GREEN = 8; + /** + * 赤色黄色を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * } + */ + public static final int EFFECT_EMPHASIZE_RED_YELLOW = 9; + /** + * 赤色黄色と白を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * 白のパラメータは今はなし + */ + public static final int EFFECT_EMPHASIZE_RED_YELLOW_WHITE = 10; + /** + * 黄色と白を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 FIXME 未調整 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * 白のパラメータは今はなし + */ + public static final int EFFECT_EMPHASIZE_YELLOW_WHITE = 11; + /** 内蔵映像効果の数 */ + public static final int EFFECT_NUM = 12; + + /** + * グレースケール変換のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)を渡すこと + */ + private static final String FRAGMENT_SHADER_GRAY_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 cl3 = vec3(color, color, color);\n" + + " gl_FragColor = vec4(cl3, 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_GRAY_OES + = String.format(FRAGMENT_SHADER_GRAY_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 白黒反転したグレースケール変換のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)を渡すこと + */ + private static final String FRAGMENT_SHADER_GRAY_REVERSE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 cl3 = vec3(color, color, color);\n" + + " gl_FragColor = vec4(clamp(vec3(1.0, 1.0, 1.0) - cl3, 0.0, 1.0), 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_GRAY_REVERSE_OES + = String.format(FRAGMENT_SHADER_GRAY_REVERSE_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 2値化のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)、 + * 変換後の明るい部分用の色を指定するための文字列(R, G, Bの順)を渡すこと + */ + private static final String FRAGMENT_SHADER_BIN_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "const vec3 cl = vec3(%s);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 bin = step(0.3, vec3(color, color, color));\n" + + " gl_FragColor = vec4(cl * bin, 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_BIN_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 1.0"); + + private static final String FRAGMENT_SHADER_BIN_YELLOW_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 0.0"); + + private static final String FRAGMENT_SHADER_BIN_GREEN_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "0.0, 1.0, 0.0"); + + /** + * 反転した2値化のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)、 + * 変換後の明るい部分用の色を指定するための文字列(R, G, Bの順)を渡すこと + */ + private static final String FRAGMENT_SHADER_BIN_REVERSE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "const vec3 cl = vec3(%s);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 bin = step(0.3, vec3(color, color, color));\n" + + " gl_FragColor = vec4(cl * (vec3(1.0, 1.0, 1.0) - bin), 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_BIN_REVERSE_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 1.0"); + + private static final String FRAGMENT_SHADER_BIN_REVERSE_YELLOW_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 0.0"); + + private static final String FRAGMENT_SHADER_BIN_REVERSE_GREEN_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "0.0, 1.0, 0.0"); + + /** + * 赤と黄色を強調するためのフラグメントシェーダーのベース文字列 + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 hsv = rgb2hsv(texture2D(sTexture, vTextureCoord).rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5]))\n" + // v + " && ((hsv.r <= uParams[0]) || (hsv.r >= uParams[1])) ) {\n" + // h + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 赤色と黄色の範囲 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 赤と黄色と白色を強調するためのフラグメントシェーダーのベース文字列 + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 hsv = rgb2hsv(texture2D(sTexture, vTextureCoord).rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5]))\n" + // v + " && ((hsv.r <= uParams[0]) || (hsv.r >= uParams[1])) ) {\n" + // h + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 赤色と黄色の範囲 + " } else if ((hsv.g < uParams[12]) && (hsv.b < uParams[13])) {\n" + // 彩度が一定以下, 明度が一定以下なら + " hsv = hsv * vec3(1.0, 0.0, 2.0);\n" + // 色相そのまま, 彩度0, 明度x2 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 黄色と白を強調するためのフラグメントシェーダーのベース文字列 + * 今はFRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASEと同じ(違うパラメータ渡せば良いだけなので) + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 rgb = texture2D(sTexture, vTextureCoord).rgb;\n" + // RGB + " vec3 hsv = rgb2hsv(rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.r >= uParams[0]) && (hsv.r <= uParams[1]))\n" + // h + " && ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5])) ) {\n" + // v + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 黄色の範囲 + " } else if ((hsv.g < uParams[12]) && (hsv.b > uParams[13])) {\n" + // 彩度が一定以下, 明度が一定以上なら + " hsv = hsv * vec3(1.0, 0.0, 2.0);\n" + // 色相そのまま, 彩度0, 明度x2 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_BASE, HEADER_OES, SAMPLER_OES); + + public EffectRendererHolder(final int width, final int height, + @Nullable final RenderHolderCallback callback) { + + this(width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE, + callback); + } + + public EffectRendererHolder(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags, + @Nullable final RenderHolderCallback callback) { + + super(width, height, + maxClientVersion, sharedContext, flags, + callback); + } + + @Override + @NonNull + protected RendererTask createRendererTask(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags) { + return new MyRendererTask(this, width, height, + maxClientVersion, sharedContext, flags); + } + +//================================================================================ +// クラス固有publicメソッド +//================================================================================ + + /** + * 映像効果をセット + * 継承して独自の映像効果を追加する時はEFFECT_NUMよりも大きい値を使うこと + * @param effect + */ + public void changeEffect(final int effect) { + ((MyRendererTask)mRendererTask).changeEffect(effect); + } + + /** + * 現在の映像効果番号を取得 + * @return + */ + public int getCurrentEffect() { + return ((MyRendererTask)mRendererTask).mEffect; + } + + /** + * 現在選択中の映像フィルタにパラメータ配列をセット + * 現在対応しているのは色強調用の映像効果のみ(n=12以上必要) + * @param params + */ + public void setParams(@NonNull final float[] params) { + ((MyRendererTask)mRendererTask).setParams(-1, params); + } + + /** + * 指定した映像フィルタにパラメータ配列をセット + * 現在対応しているのは色強調用の映像効果のみ(n=12以上必要) + * @param effect EFFECT_NONより大きいこと + * @param params + * @throws IllegalArgumentException effectが範囲外ならIllegalArgumentException生成 + */ + public void setParams(final int effect, @NonNull final float[] params) + throws IllegalArgumentException { + + if (effect > EFFECT_NON) { + ((MyRendererTask)mRendererTask).setParams(effect, params); + } else { + throw new IllegalArgumentException("invalid effect number:" + effect); + } + } + + /** + * 内蔵映像効果以外のeffectを指定したときの処理 + * 描画用のワーカースレッド上で呼び出される + * このクラスでは無変換のフラグメントシェーダーを適用する + * @param effect + * @param drawer GLDrawer2Dインスタンス + */ + protected void handleDefaultEffect(final int effect, + @NonNull final IDrawer2dES2 drawer) { + + if (drawer instanceof GLDrawer2D) { + ((GLDrawer2D) drawer).resetShader(); + } + } + +//================================================================================ +// 実装 +//================================================================================ + private static final int REQUEST_CHANGE_EFFECT = 100; + private static final int REQUEST_SET_PARAMS = 101; + + /** + * ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス + */ + protected static final class MyRendererTask extends RendererTask { + + private final SparseArray mParams = new SparseArray(); + private int muParamsLoc; + private float[] mCurrentParams; + private int mEffect; + + public MyRendererTask(final EffectRendererHolder parent, + final int width, final int height) { + + super(parent, width, height); + } + + public MyRendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height, + final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + + super(parent, width, height, maxClientVersion, sharedContext, flags); + } + + /** + * ワーカースレッド開始時の処理(ここはワーカースレッド上) + */ + @SuppressLint("NewApi") + @Override + protected void internalOnStart() { +// if (DEBUG) Log.v(TAG, "onStart:"); + super.internalOnStart(); + mParams.clear(); + mParams.put(EFFECT_EMPHASIZE_RED_YELLOW, new float[] { + 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + 0.50f, 1.0f, // 強調する彩度下限, 上限 + 0.40f, 1.0f, // 強調する明度下限, 上限 + 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.0f, 1.0f, 1.0f, // 通常時のファクター(H, S, Vの順) + }); + mParams.put(EFFECT_EMPHASIZE_RED_YELLOW_WHITE, new float[] { + 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + 0.50f, 1.0f, // 強調する彩度下限, 上限 + 0.40f, 1.0f, // 強調する明度下限, 上限 + 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.0f, 1.0f, 1.0f, // 通常時のファクター(H, S, Vの順) + }); + mParams.put(EFFECT_EMPHASIZE_YELLOW_WHITE, new float[] { + 0.10f, 0.19f, // 黄色の色相h下側閾値, 上側閾値 + 0.30f, 1.00f, // 強調する彩度s下限, 上限 + 0.30f, 1.00f, // 強調する明度v下限, 上限 + 1.00f, 1.00f, 5.00f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.00f, 0.80f, 0.80f, // 通常時のファクター(H, S, Vの順) 彩度(x0.8)と明度(x0.8)を少し落とす + 0.15f, 0.40f, // 白強調時の彩度上限, 白強調時の明度下限 + 0, 0, 0, 0, // ダミー + }); + mEffect = EFFECT_NON; + handleChangeEffect(EFFECT_NON); +// if (DEBUG) Log.v(TAG, "onStart:finished"); + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) { + + Object result = null; + switch (request) { + case REQUEST_CHANGE_EFFECT: + handleChangeEffect(arg1); + break; + case REQUEST_SET_PARAMS: + handleSetParam(arg1, (float[])obj); + break; + default: + result = super.processRequest(request, arg1, arg2, obj); + break; + } + return result; + } + + public void changeEffect(final int effect) { + checkFinished(); + if (mEffect != effect) { + offer(REQUEST_CHANGE_EFFECT, effect); + } + } + + public void setParams(final int effect, @NonNull final float[] params) { + checkFinished(); + offer(REQUEST_SET_PARAMS, effect, 0, params); + } + +//================================================================================ +// ワーカースレッド上での処理 +//================================================================================ + /** + * 映像効果を変更 + * @param effect + */ + protected void handleChangeEffect(final int effect) { + mEffect = effect; + switch (effect) { + case EFFECT_NON: + mDrawer.updateShader(FRAGMENT_SHADER_SIMPLE_OES); + break; + case EFFECT_GRAY: + mDrawer.updateShader(FRAGMENT_SHADER_GRAY_OES); + break; + case EFFECT_GRAY_REVERSE: + mDrawer.updateShader(FRAGMENT_SHADER_GRAY_REVERSE_OES); + break; + case EFFECT_BIN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_OES); + break; + case EFFECT_BIN_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_YELLOW_OES); + break; + case EFFECT_BIN_GREEN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_GREEN_OES); + break; + case EFFECT_BIN_REVERSE: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_OES); + break; + case EFFECT_BIN_REVERSE_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_YELLOW_OES); + break; + case EFFECT_BIN_REVERSE_GREEN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_GREEN_OES); + break; + case EFFECT_EMPHASIZE_RED_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_OES); + break; + case EFFECT_EMPHASIZE_RED_YELLOW_WHITE: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_OES); + break; + case EFFECT_EMPHASIZE_YELLOW_WHITE: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_OES); + break; + default: + try { + ((EffectRendererHolder)getParent()) + .handleDefaultEffect(effect, mDrawer); + } catch (final Exception e) { + mDrawer.resetShader(); + Log.w(TAG, e); + } + break; + } + muParamsLoc = mDrawer.glGetUniformLocation("uParams"); + mCurrentParams = mParams.get(effect); + updateParams(); + } + + /** + * 映像効果用のパラメーターをセット + * @param effect + * @param params + */ + private void handleSetParam(final int effect, @NonNull final float[] params) { + if ((effect < EFFECT_NON) || (mEffect == effect)) { + mCurrentParams = params; + mParams.put(mEffect, params); + updateParams(); + } else { + mParams.put(effect, params); + } + } + + /** + * 映像効果用のパラメータをGPUへ適用 + */ + private void updateParams() { + final int n = Math.min(mCurrentParams != null + ? mCurrentParams.length : 0, MAX_PARAM_NUM); + if ((muParamsLoc >= 0) && (n > 0)) { + mDrawer.glUseProgram(); + GLES20.glUniform1fv(muParamsLoc, n, mCurrentParams, 0); + } + } + + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java new file mode 100644 index 0000000000..1f36e6348d --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java @@ -0,0 +1,122 @@ +package com.serenegiant.glutils; +/* + * 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 androidx.annotation.Nullable; + +import com.serenegiant.utils.MessageTask; + +public abstract class EglTask extends MessageTask { +// private static final boolean DEBUG = false; +// private static final String TAG = "EglTask"; + + public static final int EGL_FLAG_DEPTH_BUFFER = 0x01; + public static final int EGL_FLAG_RECORDABLE = 0x02; + public static final int EGL_FLAG_STENCIL_1BIT = 0x04; +// public static final int EGL_FLAG_STENCIL_2BIT = 0x08; +// public static final int EGL_FLAG_STENCIL_4BIT = 0x10; + public static final int EGL_FLAG_STENCIL_8BIT = 0x20; + + private EGLBase mEgl = null; + private EGLBase.IEglSurface mEglHolder; + + public EglTask(final EGLBase.IContext sharedContext, final int flags) { +// if (DEBUG) Log.i(TAG, "shared_context=" + shared_context); + init(flags, 3, sharedContext); + } + + public EglTask(final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + +// if (DEBUG) Log.i(TAG, "shared_context=" + shared_context); + init(flags, maxClientVersion, sharedContext); + } + + /** + * @param flags + * @param maxClientVersion + * @param sharedContext + */ + @Override + protected void onInit(final int flags, + final int maxClientVersion, final Object sharedContext) { + + if ((sharedContext == null) + || (sharedContext instanceof EGLBase.IContext)) { + + final int stencilBits + = (flags & EGL_FLAG_STENCIL_1BIT) == EGL_FLAG_STENCIL_1BIT ? 1 + : ((flags & EGL_FLAG_STENCIL_8BIT) == EGL_FLAG_STENCIL_8BIT ? 8 : 0); + mEgl = EGLBase.createFrom(maxClientVersion, (EGLBase.IContext)sharedContext, + (flags & EGL_FLAG_DEPTH_BUFFER) == EGL_FLAG_DEPTH_BUFFER, + stencilBits, + (flags & EGL_FLAG_RECORDABLE) == EGL_FLAG_RECORDABLE); + } + if (mEgl == null) { + callOnError(new RuntimeException("failed to create EglCore")); + releaseSelf(); + } else { + mEglHolder = mEgl.createOffscreen(1, 1); + mEglHolder.makeCurrent(); + } + } + + @Override + protected Request takeRequest() throws InterruptedException { + final Request result = super.takeRequest(); + mEglHolder.makeCurrent(); + return result; + } + + @Override + protected void onBeforeStop() { + mEglHolder.makeCurrent(); + } + + @Override + protected void onRelease() { + mEglHolder.release(); + mEgl.release(); + } + + protected EGLBase getEgl() { + return mEgl; + } + + protected EGLBase.IContext getEGLContext() { + return mEgl.getContext(); + } + + protected EGLBase.IConfig getConfig() { + return mEgl.getConfig(); + } + + @Nullable + protected EGLBase.IContext getContext() { + return mEgl != null ? mEgl.getContext() : null; + } + + protected void makeCurrent() { + mEglHolder.makeCurrent(); + } + + protected boolean isGLES3() { + return (mEgl != null) && (mEgl.getGlVersion() > 2); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java new file mode 100644 index 0000000000..1d73f13a7d --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java @@ -0,0 +1,307 @@ +package com.serenegiant.glutils; +/* + * 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 static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_SIMPLE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_SIMPLE_OES; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_2D; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; +import static com.serenegiant.glutils.ShaderConst.VERTEX_SHADER; + +import android.opengl.GLES20; +import android.opengl.Matrix; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * 描画領域全面にテクスチャを2D描画するためのヘルパークラス + */ +public class GLDrawer2D implements IDrawer2dES2 { +// private static final boolean DEBUG = false; // FIXME set false on release +// private static final String TAG = "GLDrawer2D"; + + private static final float[] VERTICES = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; + private static final float[] TEXCOORD = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + private static final int FLOAT_SZ = Float.SIZE / 8; + + private final int VERTEX_NUM; + private final int VERTEX_SZ; + private final FloatBuffer pVertex; + private final FloatBuffer pTexCoord; + private final int mTexTarget; + private int hProgram; + int maPositionLoc; + int maTextureCoordLoc; + int muMVPMatrixLoc; + int muTexMatrixLoc; + private final float[] mMvpMatrix = new float[16]; + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。 + * 通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final boolean isOES) { + this(VERTICES, TEXCOORD, isOES); + } + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param vertices 頂点座標, floatを8個 = (x,y) x 4ペア + * @param texcoord テクスチャ座標, floatを8個 = (s,t) x 4ペア + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。 + * 通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final float[] vertices, + final float[] texcoord, final boolean isOES) { + + VERTEX_NUM = Math.min( + vertices != null ? vertices.length : 0, + texcoord != null ? texcoord.length : 0) / 2; + VERTEX_SZ = VERTEX_NUM * 2; + + mTexTarget = isOES ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + pVertex = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pVertex.put(vertices); + pVertex.flip(); + pTexCoord = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pTexCoord.put(texcoord); + pTexCoord.flip(); + + if (isOES) { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE_OES); + } else { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE); + } + // モデルビュー変換行列を初期化 + Matrix.setIdentityM(mMvpMatrix, 0); + init(); + } + + /** + * 破棄処理。GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + */ + @Override + public void release() { + if (hProgram >= 0) { + GLES20.glDeleteProgram(hProgram); + } + hProgram = -1; + } + + /** + * 外部テクスチャを使うかどうか + * @return + */ + public boolean isOES() { + return mTexTarget == GL_TEXTURE_EXTERNAL_OES; + } + + /** + * モデルビュー変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + @Override + public float[] getMvpMatrix() { + return mMvpMatrix; + } + + /** + * モデルビュー変換行列に行列を割り当てる + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + * @return + */ + @Override + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(matrix, offset, mMvpMatrix, 0, 16); + return this; + } + + /** + * モデルビュー変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + */ + @Override + public void getMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(mMvpMatrix, 0, matrix, offset, 16); + } + + /** + * 指定したテクスチャを指定したテクスチャ変換行列を使って描画領域全面に描画するためのヘルパーメソッド + * このクラスインスタンスのモデルビュー変換行列が設定されていればそれも適用された状態で描画する + * @param texId texture ID + * @param tex_matrix テクスチャ変換行列、nullならば以前に適用したものが再利用される。 + * 領域チェックしていないのでoffsetから16個以上確保しておくこと + */ + @Override + public synchronized void draw(final int texId, + final float[] tex_matrix, final int offset) { + +// if (DEBUG) Log.v(TAG, "draw"); + if (hProgram < 0) return; + GLES20.glUseProgram(hProgram); + if (tex_matrix != null) { + // テクスチャ変換行列が指定されている時 + GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, tex_matrix, offset); + } + // モデルビュー変換行列をセット + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mMvpMatrix, 0); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(mTexTarget, texId); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_NUM); + GLES20.glBindTexture(mTexTarget, 0); + GLES20.glUseProgram(0); + } + + /** + * Textureオブジェクトを描画するためのヘルパーメソッド + * Textureオブジェクトで管理しているテクスチャ名とテクスチャ座標変換行列を使って描画する + * @param texture + */ + @Override + public void draw(final ITexture texture) { + draw(texture.getTexture(), texture.getTexMatrix(), 0); + } + + /** + * TextureOffscreenオブジェクトを描画するためのヘルパーメソッド + * @param offscreen + */ + @Override + public void draw(final TextureOffscreen offscreen) { + draw(offscreen.getTexture(), offscreen.getTexMatrix(), 0); + } + + /** + * テクスチャ名生成のヘルパーメソッド + * GLHelper#initTexを呼び出すだけ + * @return texture ID + */ + public int initTex() { + return GLHelper.initTex(mTexTarget, GLES20.GL_NEAREST); + } + + /** + * テクスチャ名破棄のヘルパーメソッド + * GLHelper.deleteTexを呼び出すだけ + * @param hTex + */ + public void deleteTex(final int hTex) { + GLHelper.deleteTex(hTex); + } + + /** + * 頂点シェーダー・フラグメントシェーダーを変更する + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + * glUseProgramが呼ばれた状態で返る + * @param vs 頂点シェーダー文字列 + * @param fs フラグメントシェーダー文字列 + */ + public synchronized void updateShader(final String vs, final String fs) { + release(); + hProgram = GLHelper.loadShader(vs, fs); + init(); + } + + /** + * フラグメントシェーダーを変更する + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + * glUseProgramが呼ばれた状態で返る + * @param fs フラグメントシェーダー文字列 + */ + public void updateShader(final String fs) { + updateShader(VERTEX_SHADER, fs); + } + + /** + * 頂点シェーダー・フラグメントシェーダーをデフォルトに戻す + */ + public void resetShader() { + release(); + if (isOES()) { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE_OES); + } else { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE); + } + init(); + } + + /** + * アトリビュート変数のロケーションを取得 + * glUseProgramが呼ばれた状態で返る + * @param name + * @return + */ + @Override + public int glGetAttribLocation(final String name) { + GLES20.glUseProgram(hProgram); + return GLES20.glGetAttribLocation(hProgram, name); + } + + /** + * ユニフォーム変数のロケーションを取得 + * glUseProgramが呼ばれた状態で返る + * @param name + * @return + */ + @Override + public int glGetUniformLocation(final String name) { + GLES20.glUseProgram(hProgram); + return GLES20.glGetUniformLocation(hProgram, name); + } + + /** + * glUseProgramが呼ばれた状態で返る + */ + @Override + public void glUseProgram() { + GLES20.glUseProgram(hProgram); + } + + /** + * シェーダープログラム変更時の初期化処理 + * glUseProgramが呼ばれた状態で返る + */ + private void init() { + GLES20.glUseProgram(hProgram); + maPositionLoc = GLES20.glGetAttribLocation(hProgram, "aPosition"); + maTextureCoordLoc = GLES20.glGetAttribLocation(hProgram, "aTextureCoord"); + muMVPMatrixLoc = GLES20.glGetUniformLocation(hProgram, "uMVPMatrix"); + muTexMatrixLoc = GLES20.glGetUniformLocation(hProgram, "uTexMatrix"); + // + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, + 1, false, mMvpMatrix, 0); + GLES20.glUniformMatrix4fv(muTexMatrixLoc, + 1, false, mMvpMatrix, 0); + GLES20.glVertexAttribPointer(maPositionLoc, + 2, GLES20.GL_FLOAT, false, VERTEX_SZ, pVertex); + GLES20.glVertexAttribPointer(maTextureCoordLoc, + 2, GLES20.GL_FLOAT, false, VERTEX_SZ, pTexCoord); + GLES20.glEnableVertexAttribArray(maPositionLoc); + GLES20.glEnableVertexAttribArray(maTextureCoordLoc); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java new file mode 100644 index 0000000000..c4c8adfd72 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java @@ -0,0 +1,417 @@ +package com.serenegiant.glutils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.opengl.GLES20; +import android.opengl.GLES30; +import android.opengl.GLUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.serenegiant.utils.AssetsHelper; +import com.serenegiant.utils.BuildCheck; + +import java.io.IOException; + +/** + * OpenGL|ES2/3用のヘルパークラス + */ +public final class GLHelper { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "GLHelper"; + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param op + */ + public static void checkGlError(final String op) { + final int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * テクスチャ名を生成, テクスチャユニットはGL_TEXTURE0, クランプ方法はGL_CLAMP_TO_EDGE + * @param texTarget + * @param filter_param テクスチャの補完方法を指定, min/mag共に同じ値になる, GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final int texTarget, final int filter_param) { + return initTex(texTarget, GLES20.GL_TEXTURE0, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名を生成(GL_TEXTURE0のみ) + * @param texTarget + * @param texUnit テクスチャユニット, GL_TEXTURE0...GL_TEXTURE31 + * @param min_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param mag_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param wrap テクスチャのクランプ方法, GL_CLAMP_TO_EDGE + * @return + */ + public static int initTex(final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + GLES20.glActiveTexture(texUnit); + GLES20.glGenTextures(1, tex, 0); + GLES20.glBindTexture(texTarget, tex[0]); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_WRAP_S, wrap); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_WRAP_T, wrap); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_MIN_FILTER, min_filter); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_MAG_FILTER, mag_filter); + return tex[0]; + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param n 生成するテキスチャ名の数, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param filter_param + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int filter_param) { + + return initTexes(new int[n], texTarget, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param texIds テクスチャ名配列, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param filter_param + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int filter_param) { + + return initTexes(texIds, texTarget, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param n 生成するテキスチャ名の数, 最大32 + * @param texTarget + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int min_filter, final int mag_filter, final int wrap) { + + return initTexes(new int[n], texTarget, min_filter, mag_filter, wrap); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param texIds テクスチャ名配列, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int min_filter, final int mag_filter, final int wrap) { + + int[] textureUnits = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, textureUnits, 0); + Log.v(TAG, "GL_MAX_TEXTURE_IMAGE_UNITS=" + textureUnits[0]); + final int n = texIds.length > textureUnits[0] + ? textureUnits[0] : texIds.length; + for (int i = 0; i < n; i++) { + texIds[i] = GLHelper.initTex(texTarget, ShaderConst.TEX_NUMBERS[i], + min_filter, mag_filter, wrap); + } + return texIds; + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param n 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param texUnit + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + + return initTexes(new int[n], texTarget, texUnit, + min_filter, mag_filter, wrap); + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param texIds 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param texUnit + * @param filter_param + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int texUnit, final int filter_param) { + + return initTexes(texIds, texTarget, texUnit, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param texIds + * @param texTarget + * @param texUnit + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + + int[] textureUnits = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, textureUnits, 0); + final int n = texIds.length > textureUnits[0] + ? textureUnits[0] : texIds.length; + for (int i = 0; i < n; i++) { + texIds[i] = GLHelper.initTex(texTarget, texUnit, + min_filter, mag_filter, wrap); + } + return texIds; + } + + /** + * delete specific texture + */ + public static void deleteTex(final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + GLES20.glDeleteTextures(1, tex, 0); + } + + /** + * delete specific texture + */ + public static void deleteTex(@NonNull final int[] tex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + GLES20.glDeleteTextures(tex.length, tex, 0); + } + + public static int loadTextureFromResource(final Context context, final int resId) { + return loadTextureFromResource(context, resId, null); + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public static int loadTextureFromResource(final Context context, final int resId, final Resources.Theme theme) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // get a background image from resources + // note the image format must match the bitmap format + final Drawable background; + if (BuildCheck.isAndroid5()) { + background = context.getResources().getDrawable(resId, theme); + } else { + background = context.getResources().getDrawable(resId); + } + background.setBounds(0, 0, 256, 256); + background.draw(canvas); // draw the background to our bitmap + + final int[] textures = new int[1]; + + //Generate one texture pointer... + GLES20.glGenTextures(1, textures, 0); + //...and bind it to our array + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + + //Create Nearest Filtered Texture + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + + //Different possible texture parameters, e.g. GLES20.GL_CLAMP_TO_EDGE + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); + + //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + //Clean up + bitmap.recycle(); + + return textures[0]; + } + + public static int createTextureWithTextContent (final String text) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // Draw the text + final Paint textPaint = new Paint(); + textPaint.setTextSize(32); + textPaint.setAntiAlias(true); + textPaint.setARGB(0xff, 0xff, 0xff, 0xff); + // draw the text centered + canvas.drawText(text, 16, 112, textPaint); + + final int texture = initTex(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE0, GLES20.GL_NEAREST, GLES20.GL_LINEAR, GLES20.GL_REPEAT); + + // Alpha blending + // GLES20.glEnable(GLES20.GL_BLEND); + // GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + + // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + // Clean up + bitmap.recycle(); + + return texture; + } + + /** + * load, compile and link shader from Assets files + * @param context + * @param vss_asset source file name in Assets of vertex shader + * @param fss_asset source file name in Assets of fragment shader + * @return + */ + public static int loadShader(@NonNull final Context context, + final String vss_asset, final String fss_asset) { + + int program = 0; + try { + final String vss = AssetsHelper.loadString(context.getAssets(), vss_asset); + final String fss = AssetsHelper.loadString(context.getAssets(), vss_asset); + program = loadShader(vss, fss); + } catch (IOException e) { + } + return program; + } + + /** + * load, compile and link shader + * @param vss source of vertex shader + * @param fss source of fragment shader + * @return + */ + public static int loadShader(final String vss, final String fss) { +// if (DEBUG) Log.v(TAG, "loadShader:"); + final int[] compiled = new int[1]; + // 頂点シェーダーをコンパイル + final int vs = loadShader(GLES20.GL_VERTEX_SHADER, vss); + if (vs == 0) { + return 0; + } + // フラグメントシェーダーをコンパイル + int fs = loadShader(GLES20.GL_FRAGMENT_SHADER, fss); + if (fs == 0) { + return 0; + } + // リンク + final int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vs); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, fs); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + final int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + return 0; + } + return program; + } + + /** + * Compiles the provided shader source. + * + * @return A handle to the shader, or 0 on failure. + */ + public static int loadShader(final int shaderType, final String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + final int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + /** + * Checks to see if the location we obtained is valid. GLES returns -1 if a label + * could not be found, but does not set the GL error. + *

+ * Throws a RuntimeException if the location is invalid. + */ + public static void checkLocation(final int location, final String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + + /** + * Writes GL version info to the log. + */ + @SuppressLint("InlinedApi") + public static void logVersionInfo() { + Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR)); + Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); + Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION)); + + if (BuildCheck.isAndroid4_3()) { + final int[] values = new int[1]; + GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); + final int majorVersion = values[0]; + GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); + final int minorVersion = values[0]; + if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { + Log.i(TAG, "version: " + majorVersion + "." + minorVersion); + } + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java new file mode 100644 index 0000000000..eb1616f031 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java @@ -0,0 +1,66 @@ +package com.serenegiant.glutils; + +/** + * Created by saki on 2018/02/10. + * 共有コンテキストのマスターをを保持するためだけのクラス + * Applicationクラス等でシングルトンとして使う + */ +public class GLMasterContext { + private static final String TAG = GLMasterContext.class.getSimpleName(); + + private MasterTask mMasterTask; + + public GLMasterContext(final int maxClientVersion, final int flags) { + mMasterTask = new MasterTask(maxClientVersion, flags); + new Thread(mMasterTask, TAG).start(); + mMasterTask.waitReady(); + } + + @Override + protected void finalize() throws Throwable { + try { + release(); + } finally { + super.finalize(); + } + } + + public synchronized void release() { + if (mMasterTask != null) { + mMasterTask.release(); + mMasterTask = null; + } + } + + public synchronized EGLBase.IContext getContext() + throws IllegalStateException { + if (mMasterTask != null) { + return mMasterTask.getContext(); + } else { + throw new IllegalStateException("already released"); + } + } + + private static class MasterTask extends EglTask { + public MasterTask(final int maxClientVersion, final int flags) { + super(maxClientVersion, null, flags); + } + + @Override + protected void onStart() { + // do nothing + } + + @Override + protected void onStop() { + // do nothing + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) throws TaskBreak { + // do nothing + return null; + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java new file mode 100644 index 0000000000..9f08ad3abc --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java @@ -0,0 +1,234 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.text.TextUtils; + +import java.io.IOException; + +/** + * OpenGL|ESのテクスチャ操作用のヘルパークラス + */ +public class GLTexture implements ITexture { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること +// private static final String TAG = "GLTexture"; + + /* package */final int mTextureTarget; + /* package */final int mTextureUnit ; + /* package */int mTextureId; + /* package */final float[] mTexMatrix = new float[16]; // テクスチャ変換行列 + /* package */int mTexWidth, mTexHeight; + /* package */int mImageWidth, mImageHeight; + + /** + * コンストラクタ + * テクスチャユニットが常時GL_TEXTURE0なので複数のテクスチャを同時に使えない + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int width, final int height, final int filter_param) { + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, width, height, filter_param); + } + + /** + * コンストラクタ + * @param texTarget GL_TEXTURE_EXTERNAL_OESはだめ + * @param texUnit + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int texTarget, final int texUnit, + final int width, final int height, final int filter_param) { +// if (DEBUG) Log.v(TAG, String.format("コンストラクタ(%d,%d)", width, height)); + mTextureTarget = texTarget; + mTextureUnit = texUnit; + // テクスチャに使うビットマップは縦横サイズが2の乗数でないとダメ。 + // 更に、ミップマップするなら正方形でないとダメ + // 指定したwidth/heightと同じか大きい2の乗数にする + int w = 32; + for (; w < width; w <<= 1); + int h = 32; + for (; h < height; h <<= 1); + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } +// if (DEBUG) Log.v(TAG, String.format("texSize(%d,%d)", mTexWidth, mTexHeight)); + mTextureId = GLHelper.initTex(mTextureTarget, filter_param); + // テクスチャのメモリ領域を確保する + GLES20.glTexImage2D(mTextureTarget, + 0, // ミップマップレベル0(ミップマップしない) + GLES20.GL_RGBA, // 内部フォーマット + mTexWidth, mTexHeight, // サイズ + 0, // 境界幅 + GLES20.GL_RGBA, // 引き渡すデータのフォーマット + GLES20.GL_UNSIGNED_BYTE, // データの型 + null); // ピクセルデータ無し + // テクスチャ変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, "GLTexture:id=" + mTextureId); + } + + @Override + protected void finalize() throws Throwable { + try { + release(); // GLコンテキスト内じゃない可能性があるのであまり良くないけど + } finally { + super.finalize(); + } + } + + /** + * テクスチャを破棄 + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出すこと + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mTextureId > 0) { + GLHelper.deleteTex(mTextureId); + mTextureId = 0; + } + } + + /** + * このインスタンスで管理しているテクスチャを有効にする(バインドする) + */ + @Override + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES20.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES20.glBindTexture(mTextureTarget, mTextureId); + } + + /** + * このインスタンスで管理しているテクスチャを無効にする(アンバインドする) + */ + @Override + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES20.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES20.glBindTexture(mTextureTarget, 0); + } + + /** + * テクスチャターゲットを取得(GL_TEXTURE_2D) + * @return + */ + @Override + public int getTexTarget() { return mTextureTarget; } + /** + * テクスチャ名を取得 + * @return + */ + @Override + public int getTexture() { return mTextureId; } + /** + * テクスチャ座標変換行列を取得(内部配列をそのまま返すので変更時は要注意) + * @return + */ + @Override + public float[] getTexMatrix() { return mTexMatrix; } + /** + * テクスチャ座標変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param offset + */ + @Override + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + /** + * テクスチャ幅を取得 + * @return + */ + @Override + public int getTexWidth() { return mTexWidth; } + /** + * テクスチャ高さを取得 + * @return + */ + @Override + public int getTexHeight() { return mTexHeight; } + + /** + * 指定したファイルから画像をテクスチャに読み込む + * ファイルが存在しないか読み込めなければIOException/NullPointerExceptionを生成 + * @param filePath + */ + @Override + public void loadTexture(final String filePath) throws NullPointerException, IOException { +// if (DEBUG) Log.v(TAG, "loadTexture:path=" + filePath); + if (TextUtils.isEmpty(filePath)) + throw new NullPointerException("image file path should not be a null"); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // Bitmapを生成せずにサイズ等の情報だけを取得する + BitmapFactory.decodeFile(filePath, options); + // テキスチャサイズ内に指定したイメージが収まるためのサブサンプリングを値を求める + final int imageWidth = options.outWidth; + final int imageHeight = options.outHeight; + int inSampleSize = 1; // サブサンプリングサイズ + if ((imageHeight > mTexHeight) || (imageWidth > mTexWidth)) { + if (imageWidth > imageHeight) { + inSampleSize = (int)Math.ceil(imageHeight / (float)mTexHeight); + } else { + inSampleSize = (int)Math.ceil(imageWidth / (float)mTexWidth); + } + } +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),tex(%d,%d),inSampleSize=%d", +// imageWidth, imageHeight, mTexWidth, mTexHeight, inSampleSize)); + // 実際の読み込み処理 + options.inSampleSize = inSampleSize; + options.inJustDecodeBounds = false; + loadTexture(BitmapFactory.decodeFile(filePath, options)); + } + + /** + * 指定したビットマップをテクスチャに読み込む + * @param bitmap + */ + public void loadTexture(final Bitmap bitmap) throws NullPointerException { + mImageWidth = bitmap.getWidth(); // 読み込んだイメージのサイズを取得 + mImageHeight = bitmap.getHeight(); + Bitmap texture = Bitmap.createBitmap(mTexWidth, mTexHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(texture); + canvas.drawBitmap(bitmap, 0, 0, null); + bitmap.recycle(); + // テクスチャ座標変換行列を設定(読み込んだイメージサイズがテクスチャサイズにフィットするようにスケール変換) + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = mImageWidth / (float)mTexWidth; + mTexMatrix[5] = mImageHeight / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),scale(%f,%f)", +// mImageWidth, mImageHeight, mMvpMatrix[0], mMvpMatrix[5])); + bind(); + GLUtils.texImage2D(mTextureTarget, 0, texture, 0); + unbind(); + texture.recycle(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java new file mode 100644 index 0000000000..d7c4ce816e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java @@ -0,0 +1,29 @@ +package com.serenegiant.glutils; +/* + * 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. +*/ + +public interface IDrawer2D { + public void release(); + public float[] getMvpMatrix(); + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset); + public void getMvpMatrix(final float[] matrix, final int offset); + public void draw(final int texId, final float[] tex_matrix, final int offset); + public void draw(final ITexture texture); + public void draw(final TextureOffscreen offscreen); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java new file mode 100644 index 0000000000..9548cb941f --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java @@ -0,0 +1,25 @@ +package com.serenegiant.glutils; +/* + * 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. +*/ + +public interface IDrawer2dES2 extends IDrawer2D { + public int glGetAttribLocation(final String name); + public int glGetUniformLocation(final String name); + public void glUseProgram(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java new file mode 100644 index 0000000000..b13e45d389 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java @@ -0,0 +1,55 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.SurfaceTexture; +import android.view.Surface; + +public interface IRenderer extends IRendererCommon { + + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release(); + + /** + * 描画先のSurfaceをセット + * @param surface + */ + public void setSurface(final Surface surface); + + /** + * 描画先のSurfaceをセット + * @param surface + */ + public void setSurface(final SurfaceTexture surface); + + /** + * Surfaceサイズを変更 + * @param width + * @param height + */ + public void resize(final int width, final int height); + + /** + * 描画要求 + * @param args + */ + public void requestRender(final Object... args); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java new file mode 100644 index 0000000000..f7e080595e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java @@ -0,0 +1,48 @@ +package com.serenegiant.glutils; +/* + * 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 androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public interface IRendererCommon { + public static final int MIRROR_NORMAL = 0; + public static final int MIRROR_HORIZONTAL = 1; + public static final int MIRROR_VERTICAL = 2; + public static final int MIRROR_BOTH = 3; + public static final int MIRROR_NUM = 4; + + @IntDef({MIRROR_NORMAL, MIRROR_HORIZONTAL, MIRROR_VERTICAL, MIRROR_BOTH}) + @Retention(RetentionPolicy.SOURCE) + public @interface MirrorMode {} + + /** + * 映像を上下左右反転させるかどうかをセット + * @param mirror 0:通常, 1:左右反転, 2:上下反転, 3:上下左右反転 + */ + public void setMirror(@MirrorMode final int mirror); + + /** + * 映像を上下左右反転させるかどうかを取得 + * @return 0:通常, 1:左右反転, 2:上下反転, 3:上下左右反転 + */ + public @MirrorMode int getMirror(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java new file mode 100644 index 0000000000..9120cde2f5 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java @@ -0,0 +1,223 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.SurfaceTexture; +import android.view.Surface; + +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 分配描画インターフェース + */ +public interface IRendererHolder extends IRendererCommon { + public static final int DEFAULT_CAPTURE_COMPRESSION = 80; + + public static final int OUTPUT_FORMAT_JPEG = 0; // Bitmap.CompressFormat.JPEG + public static final int OUTPUT_FORMAT_PNG = 1; // Bitmap.CompressFormat.PNG + public static final int OUTPUT_FORMAT_WEBP = 2; // Bitmap.CompressFormat.WEBP + + @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_PNG, OUTPUT_FORMAT_WEBP}) + @Retention(RetentionPolicy.SOURCE) + public @interface StillCaptureFormat {} + + /** + * 実行中かどうか + * @return + */ + public boolean isRunning(); + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release(); + + @Nullable + public EGLBase.IContext getContext(); + + /** + * マスター用の映像を受け取るためのSurfaceを取得 + * @return + */ + public Surface getSurface(); + + /** + * マスター用の映像を受け取るためのSurfaceTextureを取得 + * @return + */ + public SurfaceTexture getSurfaceTexture(); + + /** + * マスター用の映像を受け取るためのマスターをチェックして無効なら再生成要求する + */ + public void reset(); + + /** + * マスター映像サイズをサイズ変更要求 + * @param width + * @param height + */ + public void resize(final int width, final int height) + throws IllegalStateException; + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通は#hashCodeを使う + * @param surface, should be one of Surface, SurfaceTexture or SurfaceHolder + * @param isRecordable + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable) + throws IllegalStateException, IllegalArgumentException; + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通は#hashCodeを使う + * @param surface, should be one of Surface, SurfaceTexture or SurfaceHolder + * @param isRecordable + * @param maxFps 0以下なら制限しない + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable, final int maxFps) + throws IllegalStateException, IllegalArgumentException; + + /** + * 分配描画用のSurfaceを削除 + * このメソッドは指定したSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + */ + public void removeSurface(final int id); + + /** + * 分配描画用のSurfaceを全て削除 + * このメソッドはSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + */ + public void removeSurfaceAll(); + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param id + * @param color + */ + public void clearSurface(final int id, final int color); + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param color + */ + public void clearSurfaceAll(final int color); + + /** + * モデルビュー変換行列をセット + * @param id + * @param offset + * @param matrix offset以降に16要素以上 + */ + public void setMvpMatrix(final int id, + final int offset, @NonNull final float[] matrix); + + /** + * 分配描画用のSurfaceへの描画が有効かどうかを取得 + * @param id + * @return + */ + public boolean isEnabled(final int id); + + /** + * 分配描画用のSurfaceへの描画の有効・無効を切替 + * @param id + * @param enable + */ + public void setEnabled(final int id, final boolean enable); + + /** + * 強制的に現在の最新のフレームを描画要求する + * 分配描画用Surface全てが更新されるので注意 + */ + public void requestFrame(); + + /** + * 追加されている分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount(); + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + */ + @Deprecated + public void captureStillAsync(@NonNull final String path) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + * @param captureCompression JPEGの圧縮率, pngの時は無視 + */ + @Deprecated + public void captureStillAsync(@NonNull final String path, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + */ + public void captureStill(@NonNull final String path) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + * @param captureCompression JPEGの圧縮率, pngの時は無視 + */ + public void captureStill(@NonNull final String path, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param out + * @param stillCaptureFormat + * @param captureCompression + */ + public void captureStill(@NonNull final OutputStream out, + @StillCaptureFormat final int stillCaptureFormat, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws IllegalStateException; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java new file mode 100644 index 0000000000..354b955245 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java @@ -0,0 +1,42 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.Bitmap; + +import java.io.IOException; + +public interface ITexture { + void release(); + + void bind(); + void unbind(); + + int getTexTarget(); + int getTexture(); + + float[] getTexMatrix(); + void getTexMatrix(float[] matrix, int offset); + + int getTexWidth(); + int getTexHeight(); + + void loadTexture(String filePath) throws NullPointerException, IOException; + void loadTexture(Bitmap bitmap) throws NullPointerException; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java new file mode 100644 index 0000000000..c88e9fb27e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java @@ -0,0 +1,266 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +/** + * Draw shared texture on specific whole Surface using OpenGL|ES + * this will deprecate soon because I don't use this now + */ +@Deprecated +public final class RenderHandler extends Handler { +// private static final boolean DEBUG = false; // FIXME set false on release + private static final String TAG = "RenderHandler"; + + private static final int MSG_RENDER_SET_GLCONTEXT = 1; + private static final int MSG_RENDER_DRAW = 2; + private static final int MSG_CHECK_VALID = 3; + private static final int MSG_RENDER_QUIT = 9; + + private int mTexId = -1; + private final RenderThread mThread; + + public static RenderHandler createHandler() { +// if (DEBUG) Log.v(TAG, "createHandler:"); + return createHandler("RenderThread"); + } + + public static final RenderHandler createHandler(final String name) { +// if (DEBUG) Log.v(TAG, "createHandler:name=" + name); + final RenderThread thread = new RenderThread(name); + thread.start(); + return thread.getHandler(); + } + + public final void setEglContext(final EGLBase.IContext sharedContext, + final int tex_id, final Object surface, final boolean isRecordable) { +// if (DEBUG) Log.i(TAG, "RenderHandler:setEglContext:"); + if (!(surface instanceof Surface) + && !(surface instanceof SurfaceTexture) + && !(surface instanceof SurfaceHolder)) + throw new RuntimeException("unsupported window type:" + surface); + mTexId = tex_id; + sendMessage(obtainMessage(MSG_RENDER_SET_GLCONTEXT, + isRecordable ? 1 : 0, 0, new ContextParams(sharedContext, surface))); + } + + public final void draw() { + sendMessage(obtainMessage(MSG_RENDER_DRAW, mTexId, 0, null)); + } + + public final void draw(final int tex_id) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, tex_id, 0, null)); + } + + public final void draw(final float[] tex_matrix) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, mTexId, 0, tex_matrix)); + } + + public final void draw(final int tex_id, final float[] tex_matrix) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, tex_id, 0, tex_matrix)); + } + + public boolean isValid() { + synchronized (mThread.mSync) { + sendEmptyMessage(MSG_CHECK_VALID); + try { + mThread.mSync.wait(); + } catch (final InterruptedException e) { + } + return mThread.mSurface != null && mThread.mSurface.isValid(); + } + } + + public final void release() { +// if (DEBUG) Log.i(TAG, "release:"); + removeMessages(MSG_RENDER_SET_GLCONTEXT); + removeMessages(MSG_RENDER_DRAW); + sendEmptyMessage(MSG_RENDER_QUIT); + } + + @Override + public final void handleMessage(final Message msg) { + switch (msg.what) { + case MSG_RENDER_SET_GLCONTEXT: + final ContextParams params = (ContextParams)msg.obj; + mThread.handleSetEglContext(params.sharedContext, params.surface, msg.arg1 != 0); + break; + case MSG_RENDER_DRAW: + mThread.handleDraw(msg.arg1, (float[])msg.obj); + break; + case MSG_CHECK_VALID: + synchronized (mThread.mSync) { + mThread.mSync.notify(); + } + break; + case MSG_RENDER_QUIT: + Looper.myLooper().quit(); + break; + default: + super.handleMessage(msg); + } + } + +//******************************************************************************** +//******************************************************************************** + private RenderHandler(final RenderThread thread) { +// if (DEBUG) Log.i(TAG, "RenderHandler:"); + mThread = thread; + } + + private static final class ContextParams { + final EGLBase.IContext sharedContext; + final Object surface; + public ContextParams(final EGLBase.IContext sharedContext, final Object surface) { + this.sharedContext = sharedContext; + this.surface = surface; + } + } + + /** + * Thread to execute render methods + * You can also use HandlerThread insted of this and create Handler from its Looper. + */ + private static final class RenderThread extends Thread { + private static final String TAG_THREAD = "RenderThread"; + private final Object mSync = new Object(); + private RenderHandler mHandler; + private EGLBase mEgl; + private EGLBase.IEglSurface mTargetSurface; + private Surface mSurface; + private GLDrawer2D mDrawer; + + public RenderThread(final String name) { + super(name); + } + + public final RenderHandler getHandler() { + synchronized (mSync) { + // create rendering thread + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + return mHandler; + } + + /** + * Set shared context and Surface + * @param shardContext + * @param surface + */ + public final void handleSetEglContext(final EGLBase.IContext shardContext, + final Object surface, final boolean isRecordable) { +// if (DEBUG) Log.i(TAG_THREAD, "setEglContext:"); + release(); + synchronized (mSync) { + mSurface = surface instanceof Surface ? (Surface)surface + : (surface instanceof SurfaceTexture + ? new Surface((SurfaceTexture)surface) : null); + } + mEgl = EGLBase.createFrom(3, shardContext, false, 0, isRecordable); + try { + mTargetSurface = mEgl.createFromSurface(surface); + mDrawer = new GLDrawer2D(isRecordable); + } catch (final Exception e) { + Log.w(TAG, e); + if (mTargetSurface != null) { + mTargetSurface.release(); + mTargetSurface = null; + } + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + } + } + + /** + * drawing + * @param tex_id + * @param tex_matrix + */ + public void handleDraw(final int tex_id, final float[] tex_matrix) { +// if (DEBUG) Log.i(TAG_THREAD, "draw"); + if (tex_id >= 0 && mTargetSurface != null) { + mTargetSurface.makeCurrent(); + mDrawer.draw(tex_id, tex_matrix, 0); + mTargetSurface.swap(); + } + } + + @Override + public final void run() { +// if (DEBUG) Log.v(TAG_THREAD, "started"); + Looper.prepare(); + synchronized (mSync) { + mHandler = new RenderHandler(this); + mSync.notify(); + } + Looper.loop(); +// if (DEBUG) Log.v(TAG_THREAD, "finishing"); + release(); + synchronized (mSync) { + mHandler = null; + } +// if (DEBUG) Log.v(TAG_THREAD, "finished"); + } + + private final void release() { +// if (DEBUG) Log.v(TAG_THREAD, "release:"); + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + synchronized (mSync) { + mSurface = null; + } + if (mTargetSurface != null) { + clear(); + mTargetSurface.release(); + mTargetSurface = null; + } + if (mEgl != null) { + mEgl.release(); + mEgl = null; + } + } + + /** + * Fill black on specific Surface + */ + private final void clear() { +// if (DEBUG) Log.v(TAG_THREAD, "clear:"); + mTargetSurface.makeCurrent(); + GLES20.glClearColor(0, 0, 0, 1); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + mTargetSurface.swap(); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java new file mode 100644 index 0000000000..44477966aa --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java @@ -0,0 +1,30 @@ +package com.serenegiant.glutils; +/* + * 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.view.Surface; + +/** + * RenderHolderのコールバックリスナー + */ +public interface RenderHolderCallback { + public void onCreate(Surface surface); + public void onFrameAvailable(); + public void onDestroy(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java new file mode 100644 index 0000000000..043f5e4302 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java @@ -0,0 +1,79 @@ +package com.serenegiant.glutils; +/* + * 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 androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Hold shared texture that has camera frame and draw them to registered surface if needs
+ */ +public class RendererHolder extends AbstractRendererHolder { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = RendererHolder.class.getSimpleName(); + + public RendererHolder(final int width, final int height, + @Nullable final RenderHolderCallback callback) { + + this(width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE, + callback); + } + + public RendererHolder(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags, + @Nullable final RenderHolderCallback callback) { + + super(width, height, + maxClientVersion, sharedContext, flags, + callback); + } + + @NonNull + protected RendererTask createRendererTask(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags) { + + return new MyRendererTask(this, width, height, + maxClientVersion, sharedContext, flags); + } + +//================================================================================ +// 実装 +//================================================================================ + /** + * ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス + */ + protected static final class MyRendererTask extends RendererTask { + + public MyRendererTask(final RendererHolder parent, + final int width, final int height) { + + super(parent, width, height); + } + + public MyRendererTask(@NonNull final AbstractRendererHolder parent, + final int width, final int height, + final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + + super(parent, width, height, maxClientVersion, sharedContext, flags); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java new file mode 100644 index 0000000000..0d9064f484 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java @@ -0,0 +1,23 @@ +package com.serenegiant.glutils; +/* + * 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. +*/ + +public interface RendererOnFrameAvailableListener { + void onFrameAvailable(long presentationTimeUs); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java new file mode 100644 index 0000000000..a59125d4d9 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java @@ -0,0 +1,191 @@ +package com.serenegiant.glutils; +/* + * 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.opengl.GLES20; +import android.opengl.Matrix; + +import com.serenegiant.utils.Time; + +/** + * 同じ内容のクラスだったからEffectRendererHolder/RendererHolderのインナークラスを外に出した + */ +class RendererSurfaceRec { + + /** + * ファクトリーメソッド + * @param egl + * @param surface + * @param maxFps 0以下なら最大描画フレームレート制限なし, あまり正確じゃない + * @return + */ + static RendererSurfaceRec newInstance(final EGLBase egl, + final Object surface, final int maxFps) { + + return (maxFps > 0) + ? new RendererSurfaceRecHasWait(egl, surface, maxFps) + : new RendererSurfaceRec(egl, surface); // no limitation of maxFps + } + + /** 元々の分配描画用Surface */ + private Object mSurface; + /** 分配描画用Surfaceを元に生成したOpenGL|ESで描画する為のEglSurface */ + private EGLBase.IEglSurface mTargetSurface; + final float[] mMvpMatrix = new float[16]; + protected volatile boolean mEnable = true; + + /** + * コンストラクタ, ファクトリーメソッドの使用を強制するためprivate + * @param egl + * @param surface + */ + private RendererSurfaceRec(final EGLBase egl, final Object surface) { + mSurface = surface; + mTargetSurface = egl.createFromSurface(surface); + Matrix.setIdentityM(mMvpMatrix, 0); + } + + /** + * 生成したEglSurfaceを破棄する + */ + public void release() { + if (mTargetSurface != null) { + mTargetSurface.release(); + mTargetSurface = null; + } + mSurface = null; + } + + /** + * Surfaceが有効かどうかを取得する + * @return + */ + public boolean isValid() { + return (mTargetSurface != null) && mTargetSurface.isValid(); + } + + private void check() throws IllegalStateException { + if (mTargetSurface == null) { + throw new IllegalStateException("already released"); + } + } + + /** + * Surfaceへの描画が有効かどうかを取得する + * @return + */ + public boolean isEnabled() { + return mEnable; + } + + /** + * Surfaceへの描画を一時的に有効/無効にする + * @param enable + */ + public void setEnabled(final boolean enable) { + mEnable = enable; + } + + public boolean canDraw() { + return mEnable; + } + + public void draw(final GLDrawer2D drawer, final int textId, final float[] texMatrix) { + if (mTargetSurface != null) { + mTargetSurface.makeCurrent(); + // 本来は映像が全面に描画されるので#glClearでクリアする必要はないけど + // ハングアップする機種があるのでクリアしとく + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + drawer.setMvpMatrix(mMvpMatrix, 0); + drawer.draw(textId, texMatrix, 0); + mTargetSurface.swap(); + } + } + + /** + * 指定した色で全面を塗りつぶす + * @param color + */ + public void clear(final int color) { + if (mTargetSurface != null) { + mTargetSurface.makeCurrent(); + GLES20.glClearColor( + ((color & 0x00ff0000) >>> 16) / 255.0f, // R + ((color & 0x0000ff00) >>> 8) / 255.0f, // G + ((color & 0x000000ff)) / 255.0f, // B + ((color & 0xff000000) >>> 24) / 255.0f // A + ); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + mTargetSurface.swap(); + } + } + + /** + * #drawの代わりにOpenGL|ES2を使って自前で描画する場合は + * #makeCurrentでレンダリングコンテキストを切り替えてから + * 描画後#swapを呼ぶ + */ + public void makeCurrent() throws IllegalStateException { + check(); + mTargetSurface.makeCurrent(); + } + + /** + * #drawの代わりにOpenGL|ES2を使って自前で描画する場合は + * #makeCurrentでレンダリングコンテキストを切り替えてから + * 描画後#swapを呼ぶ + */ + public void swap() throws IllegalStateException { + check(); + mTargetSurface.swap(); + } + + private static class RendererSurfaceRecHasWait extends RendererSurfaceRec { + private long mNextDraw; + private final long mIntervalsNs; + + /** + * コンストラクタ, ファクトリーメソッドの使用を強制するためprivate + * @param egl + * @param surface + * @param maxFps 正数 + */ + private RendererSurfaceRecHasWait(final EGLBase egl, + final Object surface, final int maxFps) { + + super(egl, surface); + mIntervalsNs = 1000000000L / maxFps; + mNextDraw = Time.nanoTime() + mIntervalsNs; + } + + @Override + public boolean canDraw() { + return mEnable && (Time.nanoTime() - mNextDraw > 0); + } + + @Override + public void draw(final GLDrawer2D drawer, + final int textId, final float[] texMatrix) { + + mNextDraw = Time.nanoTime() + mIntervalsNs; + super.draw(drawer, textId, texMatrix); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java new file mode 100644 index 0000000000..99b05961a8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java @@ -0,0 +1,421 @@ +package com.serenegiant.glutils; +/* + * 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.opengl.GLES20; + +/** + * Created by saki on 16/08/26. + * フラグメントシェーダーとかの文字列定数達を集める + */ +public class ShaderConst { + public static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65; + public static final int GL_TEXTURE_2D = 0x0DE1; + + public static final String SHADER_VERSION = "#version 100\n"; + + public static final String HEADER_2D = ""; + public static final String SAMPLER_2D = "sampler2D"; + + public static final String HEADER_OES = "#extension GL_OES_EGL_image_external : require\n"; + public static final String SAMPLER_OES = "samplerExternalOES"; + + public static final int KERNEL_SIZE3x3 = 9; + public static final int KERNEL_SIZE5x5 = 25; + + public static final int NO_TEXTURE = -1; + + public static final int[] TEX_NUMBERS = { + GLES20.GL_TEXTURE0, GLES20.GL_TEXTURE1, + GLES20.GL_TEXTURE2, GLES20.GL_TEXTURE3, + GLES20.GL_TEXTURE4, GLES20.GL_TEXTURE5, + GLES20.GL_TEXTURE6, GLES20.GL_TEXTURE7, + GLES20.GL_TEXTURE8, GLES20.GL_TEXTURE9, + GLES20.GL_TEXTURE10, GLES20.GL_TEXTURE11, + GLES20.GL_TEXTURE12, GLES20.GL_TEXTURE13, + GLES20.GL_TEXTURE14, GLES20.GL_TEXTURE15, + GLES20.GL_TEXTURE16, GLES20.GL_TEXTURE17, + GLES20.GL_TEXTURE18, GLES20.GL_TEXTURE19, + GLES20.GL_TEXTURE20, GLES20.GL_TEXTURE21, + GLES20.GL_TEXTURE22, GLES20.GL_TEXTURE23, + GLES20.GL_TEXTURE24, GLES20.GL_TEXTURE25, + GLES20.GL_TEXTURE26, GLES20.GL_TEXTURE27, + GLES20.GL_TEXTURE28, GLES20.GL_TEXTURE29, + GLES20.GL_TEXTURE30, GLES20.GL_TEXTURE31, + }; + +// 関数文字列定義 + /** + * RGBをHSVに変換 + * {R[0.0-1.0], G[0.0-1.0], B([0.0-1.0]} => {H[0.0-1.0], S[0.0-1.0], V[0.0-1.0]} + */ + public static final String FUNC_RGB2HSV + = "vec3 rgb2hsv(vec3 c) {\n" + + "vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n" + + "vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n" + + "vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n" + + "float d = q.x - min(q.w, q.y);\n" + + "float e = 1.0e-10;\n" + + "return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n" + + "}\n"; + /** + * HSVをRGBに変換 + * {H[0.0-1.0], S[0.0-1.0], V[0.0-1.0]} => {R[0.0-1.0], G[0.0-1.0], B([0.0-1.0]} + */ + public static final String FUNC_HSV2RGB + = "vec3 hsv2rgb(vec3 c) {\n" + + "vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n" + + "vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n" + + "return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n" + + "}\n"; + + /** + * RGBの輝度を取得 + * 変換係数との内積を計算するだけ + * 係数は(0.2125, 0.7154, 0.0721) + */ + public static final String FUNC_GET_INTENSITY + = "const highp vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);\n" + + "highp float getIntensity(vec3 c) {\n" + + "return dot(c.rgb, luminanceWeighting);\n" + + "}\n"; + +// 頂点シェーダー + /** + * モデルビュー変換行列とテクスチャ変換行列適用するだけの頂点シェーダー + */ + public static final String VERTEX_SHADER = SHADER_VERSION + + "uniform mat4 uMVPMatrix;\n" + // モデルビュー変換行列 + "uniform mat4 uTexMatrix;\n" + // テクスチャ変換行列 + "attribute highp vec4 aPosition;\n" + // 頂点座標 + "attribute highp vec4 aTextureCoord;\n" + // テクスチャ情報 + "varying highp vec2 vTextureCoord;\n" + // フラグメントシェーダーへ引き渡すテクスチャ座標 + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" + + "}\n"; + +// フラグメントシェーダー + public static final String FRAGMENT_SHADER_SIMPLE_OES + = SHADER_VERSION + + HEADER_OES + + "precision mediump float;\n" + + "uniform samplerExternalOES sTexture;\n" + + "varying highp vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}"; + + public static final String FRAGMENT_SHADER_SIMPLE + = SHADER_VERSION + + HEADER_2D + + "precision mediump float;\n" + + "uniform sampler2D sTexture;\n" + + "varying highp vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}"; + +// + // Simple fragment shader for use with "normal" 2D textures. + private static final String FRAGMENT_SHADER_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_2D + = String.format(FRAGMENT_SHADER_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT + = String.format(FRAGMENT_SHADER_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that converts color to black & white with a simple transformation. + private static final String FRAGMENT_SHADER_BW_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;\n" + + " gl_FragColor = vec4(color, color, color, 1.0);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_BW + = String.format(FRAGMENT_SHADER_BW_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_BW + = String.format(FRAGMENT_SHADER_BW_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that attempts to produce a high contrast image + private static final String FRAGMENT_SHADER_NIGHT_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = ((tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11) - 0.5 * 1.5) + 0.8;\n" + + " gl_FragColor = vec4(color, color + 0.15, color, 1.0);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_NIGHT + = String.format(FRAGMENT_SHADER_NIGHT_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_NIGHT + = String.format(FRAGMENT_SHADER_NIGHT_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that applies a Chroma Key effect, making green pixels transparent + private static final String FRAGMENT_SHADER_CHROMA_KEY_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = ((tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11) - 0.5 * 1.5) + 0.8;\n" + + " if(tc.g > 0.6 && tc.b < 0.6 && tc.r < 0.6){ \n" + + " gl_FragColor = vec4(0, 0, 0, 0.0);\n" + + " }else{ \n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + " }\n" + + "}\n"; + public static final String FRAGMENT_SHADER_CHROMA_KEY + = String.format(FRAGMENT_SHADER_CHROMA_KEY_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_CHROMA_KEY + = String.format(FRAGMENT_SHADER_CHROMA_KEY_BASE, HEADER_OES, SAMPLER_OES); + + private static final String FRAGMENT_SHADER_SQUEEZE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = pow(r, 1.0/1.8) * 0.8;\n"+ // Squeeze it + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + public static final String FRAGMENT_SHADER_SQUEEZE + = String.format(FRAGMENT_SHADER_SQUEEZE_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_SQUEEZE + = String.format(FRAGMENT_SHADER_SQUEEZE_BASE, HEADER_OES, SAMPLER_OES); + + public static final String FRAGMENT_SHADER_EXT_TWIRL = + "#version 100\n" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " phi = phi + (1.0 - smoothstep(-0.5, 0.5, r)) * 4.0;\n"+ // Twirl it + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_TUNNEL = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " if (r > 0.5) r = 0.5;\n"+ // Tunnel + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_BULGE = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = r * smoothstep(-0.1, 0.5, r);\n"+ // Bulge + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_DENT = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = 2.0 * r - r * smoothstep(0.0, 0.7, r);\n"+ // Dent + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_FISHEYE = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = r * r / sqrt(2.0);\n"+ // Fisheye + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_STRETCH = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " vec2 s = sign(normCoord + uPosition);\n"+ + " normCoord = abs(normCoord);\n"+ + " normCoord = 0.5 * normCoord + 0.5 * smoothstep(0.25, 0.5, normCoord) * normCoord;\n"+ + " normCoord = s * normCoord;\n"+ + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_MIRROR = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " normCoord.x = normCoord.x * sign(normCoord.x + uPosition.x);\n"+ + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_SOBEL_BASE = SHADER_VERSION + + "%s" + + "#define KERNEL_SIZE3x3 " + KERNEL_SIZE3x3 + "\n" + + "precision highp float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uKernel[18];\n" + + "uniform vec2 uTexOffset[KERNEL_SIZE3x3];\n" + + "uniform float uColorAdjust;\n" + + "void main() {\n" + + " vec3 t0 = texture2D(sTexture, vTextureCoord + uTexOffset[0]).rgb;\n" + + " vec3 t1 = texture2D(sTexture, vTextureCoord + uTexOffset[1]).rgb;\n" + + " vec3 t2 = texture2D(sTexture, vTextureCoord + uTexOffset[2]).rgb;\n" + + " vec3 t3 = texture2D(sTexture, vTextureCoord + uTexOffset[3]).rgb;\n" + + " vec3 t4 = texture2D(sTexture, vTextureCoord + uTexOffset[4]).rgb;\n" + + " vec3 t5 = texture2D(sTexture, vTextureCoord + uTexOffset[5]).rgb;\n" + + " vec3 t6 = texture2D(sTexture, vTextureCoord + uTexOffset[6]).rgb;\n" + + " vec3 t7 = texture2D(sTexture, vTextureCoord + uTexOffset[7]).rgb;\n" + + " vec3 t8 = texture2D(sTexture, vTextureCoord + uTexOffset[8]).rgb;\n" + + " vec3 sumH = t0 * uKernel[0] + t1 * uKernel[1] + t2 * uKernel[2]\n" + + " + t3 * uKernel[3] + t4 * uKernel[4] + t5 * uKernel[5]\n" + + " + t6 * uKernel[6] + t7 * uKernel[7] + t8 * uKernel[8];\n" + +// " vec3 sumV = t0 * uKernel[ 9] + t1 * uKernel[10] + t2 * uKernel[11]\n" + +// " + t3 * uKernel[12] + t4 * uKernel[13] + t5 * uKernel[14]\n" + +// " + t6 * uKernel[15] + t7 * uKernel[16] + t8 * uKernel[17];\n" + +// " float mag = length(abs(sumH) + abs(sumV));\n" + + " float mag = length(sumH);\n" + + " gl_FragColor = vec4(vec3(mag), 1.0);\n" + + "}\n"; + + public static final String FRAGMENT_SHADER_SOBEL + = String.format(FRAGMENT_SHADER_SOBEL_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_SOBEL + = String.format(FRAGMENT_SHADER_SOBEL_BASE, HEADER_OES, SAMPLER_OES); + + public static final float[] KERNEL_NULL = { 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f}; + public static final float[] KERNEL_SOBEL_H = { 1f, 0f, -1f, 2f, 0f, -2f, 1f, 0f, -1f, }; // ソーベル(1次微分) + public static final float[] KERNEL_SOBEL_V = { 1f, 2f, 1f, 0f, 0f, 0f, -1f, -2f, -1f, }; + public static final float[] KERNEL_SOBEL2_H = { 3f, 0f, -3f, 10f, 0f, -10f, 3f, 0f, -3f, }; + public static final float[] KERNEL_SOBEL2_V = { 3f, 10f, 3f, 0f, 0f, 0f, -3f, -10f, -3f, }; + public static final float[] KERNEL_SHARPNESS = { 0f, -1f, 0f, -1f, 5f, -1f, 0f, -1f, 0f,}; // シャープネス + public static final float[] KERNEL_EDGE_DETECT = { -1f, -1f, -1f, -1f, 8f, -1f, -1f, -1f, -1f, }; // エッジ検出 + public static final float[] KERNEL_EMBOSS = { 2f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f }; // エンボス, オフセット0.5f + public static final float[] KERNEL_SMOOTH = { 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, }; // 移動平均 + public static final float[] KERNEL_GAUSSIAN = { 1/16f, 2/16f, 1/16f, 2/16f, 4/16f, 2/16f, 1/16f, 2/16f, 1/16f, }; // ガウシアン(ノイズ除去/) + public static final float[] KERNEL_BRIGHTEN = { 1f, 1f, 1f, 1f, 2f, 1f, 1f, 1f, 1f, }; + public static final float[] KERNEL_LAPLACIAN = { 1f, 1f, 1f, 1f, -8f, 1f, 1f, 1f, 1f, }; // ラプラシアン(2次微分) + + private static final String FRAGMENT_SHADER_FILT3x3_BASE = SHADER_VERSION + + "%s" + + "#define KERNEL_SIZE3x3 " + KERNEL_SIZE3x3 + "\n" + + "precision highp float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uKernel[18];\n" + + "uniform vec2 uTexOffset[KERNEL_SIZE3x3];\n" + + "uniform float uColorAdjust;\n" + + "void main() {\n" + + " vec4 sum = vec4(0.0);\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[0]) * uKernel[0];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[1]) * uKernel[1];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[2]) * uKernel[2];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[3]) * uKernel[3];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[4]) * uKernel[4];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[5]) * uKernel[5];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[6]) * uKernel[6];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[7]) * uKernel[7];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[8]) * uKernel[8];\n" + + " gl_FragColor = sum + uColorAdjust;\n" + + "}\n"; + public static final String FRAGMENT_SHADER_FILT3x3 + = String.format(FRAGMENT_SHADER_FILT3x3_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_FILT3x3 + = String.format(FRAGMENT_SHADER_FILT3x3_BASE, HEADER_OES, SAMPLER_OES); + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java new file mode 100644 index 0000000000..bb833b966e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java @@ -0,0 +1,553 @@ +/* + * 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. +*/ + +package com.serenegiant.glutils; + +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.util.Log; +import android.util.SparseArray; +import android.view.Surface; +import android.view.SurfaceHolder; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * MediaCodecのデコーダーでデコードした動画や + * カメラからの映像の代わりに静止画をSurfaceへ + * 出力するためのクラス + */ +public class StaticTextureSource { + private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = StaticTextureSource.class.getSimpleName(); + + private final Object mSync = new Object(); + private RendererTask mRendererTask; + private volatile boolean isRunning; + + /** + * フレームレート指定付きコンストラクタ + * @param fps + */ + public StaticTextureSource(final float fps) { + this(null, fps); + } + + /** + * ソースの静止画を指定したコンストラクタ, フレームレートは10fps固定 + * @param bitmap + */ + public StaticTextureSource(@Nullable final Bitmap bitmap) { + this(bitmap, 10.0f); + } + + /** + * ソースの静止画とフレームレートを指定可能なコンストラクタ + * @param bitmap + * @param fps + */ + public StaticTextureSource(@Nullable final Bitmap bitmap, final float fps) { + final int width = bitmap != null ? bitmap.getWidth() : 1; + final int height = bitmap != null ? bitmap.getHeight() : 1; + mRendererTask = new RendererTask(this, width, height, fps); + new Thread(mRendererTask, TAG).start(); + if (!mRendererTask.waitReady()) { + // 初期化に失敗した時 + throw new RuntimeException("failed to start renderer thread"); + } + setBitmap(bitmap); + } + + /** + * 実行中かどうか + * @return + */ + public boolean isRunning() { + return isRunning; + } + + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release() { + if (DEBUG) Log.v(TAG, "release:"); + isRunning = false; + synchronized (mSync) { + mSync.notifyAll(); + } + if (mRendererTask != null) { + mRendererTask.release(); + } + synchronized (mSync) { + mRendererTask = null; + mSync.notifyAll(); + } + if (DEBUG) Log.v(TAG, "release:finished"); + } + + /** + * 分配描画用のSurfaceを追加 + * @param id 普通はSurface#hashCodeを使う + * @param surface + * @param isRecordable + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable) { + + if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + synchronized (mSync) { + mRendererTask.addSurface(id, surface); + } + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + * @param isRecordable + * @param maxFps コンストラクタで指定した値より大きくしても速く描画されるわけではない + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable, final int maxFps) { + + if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + synchronized (mSync) { + mRendererTask.addSurface(id, surface, maxFps); + } + } + + /** + * 分配描画用のSurfaceを削除 + * @param id + */ + public void removeSurface(final int id) { + if (DEBUG) Log.v(TAG, "removeSurface:id=" + id); + synchronized (mSync) { + mRendererTask.removeSurface(id); + } + } + + /** + * 強制的に現在の最新のフレームを描画要求する + * 分配描画用Surface全てが更新されるので注意 + */ + public void requestFrame() { + synchronized (mSync) { + mRendererTask.removeRequest(REQUEST_DRAW); + mRendererTask.offer(REQUEST_DRAW); + mSync.notify(); + } + } + + /** + * 追加されている分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount() { + return mRendererTask.getCount(); + } + + /** + * ソース静止画を指定 + * 既にセットされていれば古いほうが破棄される + * @param bitmap nullなら何もしない + */ + public void setBitmap(final Bitmap bitmap) { + if (DEBUG) Log.v(TAG, "setBitmap:bitmap=" + bitmap); + if (bitmap != null) { + synchronized (mSync) { + mRendererTask.setBitmap(bitmap); + } + } + } + + /** + * ソース静止画の幅を取得 + * @return 既にreleaseされていれば0 + */ + public int getWidth() { + synchronized (mSync) { + return mRendererTask != null ? mRendererTask.mVideoWidth : 0; + } + } + + /** + * ソース静止画の高さを取得 + * @return 既にreleaseされていれば0 + */ + public int getHeight() { + synchronized (mSync) { + return mRendererTask != null ? mRendererTask.mVideoHeight : 0; + } + } + + private static final int REQUEST_DRAW = 1; + private static final int REQUEST_ADD_SURFACE = 3; + private static final int REQUEST_REMOVE_SURFACE = 4; + private static final int REQUEST_SET_BITMAP = 7; + + private static class RendererTask extends EglTask { + private final Object mClientSync = new Object(); + private final SparseArray mClients + = new SparseArray(); + private final StaticTextureSource mParent; + private final long mIntervalsNs; + private GLDrawer2D mDrawer; + private int mVideoWidth, mVideoHeight; + private TextureOffscreen mImageSource; + + public RendererTask(final StaticTextureSource parent, + final int width, final int height, final float fps) { + + super(3, null, 0); + mParent = parent; + mVideoWidth = width; + mVideoHeight = height; + mIntervalsNs = fps <= 0 ? 100000000L : (long)(1000000000L / fps); + } + + /** + * ワーカースレッド開始時の処理(ここはワーカースレッド上) + */ + @Override + protected void onStart() { + if (DEBUG) Log.v(TAG, "onStart:"); + mDrawer = new GLDrawer2D(false); // GL_TEXTURE_EXTERNAL_OESを使わない + synchronized (mParent.mSync) { + mParent.isRunning = true; + mParent.mSync.notifyAll(); + } + new Thread(mParent.mOnFrameTask, TAG).start(); + if (DEBUG) Log.v(TAG, "onStart:finished"); + } + + /** + * ワーカースレッド終了時の処理(ここはまだワーカースレッド上) + */ + @Override + protected void onStop() { + if (DEBUG) Log.v(TAG, "onStop"); + synchronized (mParent.mSync) { + mParent.isRunning = false; + mParent.mSync.notifyAll(); + } + makeCurrent(); + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + if (mImageSource != null) { + mImageSource.release(); + mImageSource = null; + } + handleRemoveAll(); + if (DEBUG) Log.v(TAG, "onStop:finished"); + } + + @Override + protected boolean onError(final Exception e) { + if (DEBUG) Log.w(TAG, e); + return false; + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) { + + switch (request) { + case REQUEST_DRAW: + handleDraw(); + break; + case REQUEST_ADD_SURFACE: + handleAddSurface(arg1, obj, arg2); + break; + case REQUEST_REMOVE_SURFACE: + handleRemoveSurface(arg1); + break; + case REQUEST_SET_BITMAP: + handleSetBitmap((Bitmap)obj); + break; + } + return null; + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + */ + public void addSurface(final int id, final Object surface) { + addSurface(id, surface, -1); + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + */ + public void addSurface(final int id, final Object surface, final int maxFps) { + checkFinished(); + if (!((surface instanceof SurfaceTexture) + || (surface instanceof Surface) + || (surface instanceof SurfaceHolder))) { + + throw new IllegalArgumentException( + "Surface should be one of Surface, SurfaceTexture or SurfaceHolder"); + } + synchronized (mClientSync) { + if (mClients.get(id) == null) { + for ( ; ; ) { + if (offer(REQUEST_ADD_SURFACE, id, maxFps, surface)) { + try { + mClientSync.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + try { + mClientSync.wait(10); + } catch (InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * 分配描画用のSurfaceを削除 + * @param id + */ + public void removeSurface(final int id) { + synchronized (mClientSync) { + if (mClients.get(id) != null) { + for ( ; ; ) { + if (offer(REQUEST_REMOVE_SURFACE, id)) { + try { + mClientSync.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + try { + mClientSync.wait(10); + } catch (InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * ソース静止画をセット + * @param bitmap + */ + public void setBitmap(@NonNull final Bitmap bitmap) { + offer(REQUEST_SET_BITMAP, bitmap); + } + + /** + * 分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount() { + synchronized (mClientSync) { + return mClients.size(); + } + } + + private void checkFinished() { + if (isFinished()) { + throw new RuntimeException("already finished"); + } + } + +//================================================================================ +// ワーカースレッド上での処理 +//================================================================================ + /** + * 実際の描画処理 + */ + private void handleDraw() { +// if (DEBUG) Log.v(TAG, "handleDraw:"); + makeCurrent(); + // 各Surfaceへ描画する + if (mImageSource != null) { + final int texId = mImageSource.getTexture(); + synchronized (mClientSync) { + final int n = mClients.size(); + RendererSurfaceRec client; + for (int i = n - 1; i >= 0; i--) { + client = mClients.valueAt(i); + if ((client != null) && client.canDraw()) { + try { + client.draw(mDrawer, texId, null); // client.draw(mDrawer, mTexId, mTexMatrix); + GLHelper.checkGlError("handleSetBitmap"); + } catch (final Exception e) { + // removeSurfaceが呼ばれなかったかremoveSurfaceを呼ぶ前に破棄されてしまった + mClients.removeAt(i); + client.release(); + } + } + } + } + } else { + Log.w(TAG, "mImageSource is not ready"); + } + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glFlush(); +// if (DEBUG) Log.v(TAG, "handleDraw:finish"); + } + + /** + * 指定したIDの分配描画先Surfaceを追加する + * @param id + * @param surface + */ + private void handleAddSurface(final int id, final Object surface, final int maxFps) { + if (DEBUG) Log.v(TAG, "handleAddSurface:id=" + id); + checkSurface(); + synchronized (mClientSync) { + RendererSurfaceRec client = mClients.get(id); + if (client == null) { + try { + client = RendererSurfaceRec.newInstance(getEgl(), surface, maxFps); + mClients.append(id, client); + } catch (final Exception e) { + Log.w(TAG, "invalid surface: surface=" + surface, e); + } + } else { + Log.w(TAG, "surface is already added: id=" + id); + } + mClientSync.notifyAll(); + } + } + + /** + * 指定したIDの分配描画先Surfaceを破棄する + * @param id + */ + private void handleRemoveSurface(final int id) { + if (DEBUG) Log.v(TAG, "handleRemoveSurface:id=" + id); + synchronized (mClientSync) { + final RendererSurfaceRec client = mClients.get(id); + if (client != null) { + mClients.remove(id); + client.release(); + } + checkSurface(); + mClientSync.notifyAll(); + } + } + + /** + * 念の為に分配描画先のSurfaceを全て破棄する + */ + private void handleRemoveAll() { + if (DEBUG) Log.v(TAG, "handleRemoveAll:"); + synchronized (mClientSync) { + final int n = mClients.size(); + RendererSurfaceRec client; + for (int i = 0; i < n; i++) { + client = mClients.valueAt(i); + if (client != null) { + makeCurrent(); + client.release(); + } + } + mClients.clear(); + } + if (DEBUG) Log.v(TAG, "handleRemoveAll:finished"); + } + + /** + * 分配描画先のSurfaceが有効かどうかをチェックして無効なものは削除する + */ + private void checkSurface() { + if (DEBUG) Log.v(TAG, "checkSurface"); + synchronized (mClientSync) { + final int n = mClients.size(); + for (int i = 0; i < n; i++) { + final RendererSurfaceRec client = mClients.valueAt(i); + if ((client != null) && !client.isValid()) { + final int id = mClients.keyAt(i); + if (DEBUG) Log.i(TAG, "checkSurface:found invalid surface:id=" + id); + mClients.valueAt(i).release(); + mClients.remove(id); + } + } + } + if (DEBUG) Log.v(TAG, "checkSurface:finished"); + } + + /** + * ソース静止画をセット + * @param bitmap + */ + private void handleSetBitmap(final Bitmap bitmap) { + if (DEBUG) Log.v(TAG, "handleSetBitmap:bitmap=" + bitmap); + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + if (mImageSource == null) { + mImageSource = new TextureOffscreen(width, height, false); + GLHelper.checkGlError("handleSetBitmap"); + mImageSource.loadBitmap(bitmap); + } else { + mImageSource.loadBitmap(bitmap); + } + mVideoWidth = width; + mVideoHeight = height; + } + + } + + /** + * 一定時間おきに描画要求を送るためのRunnable + */ + private Runnable mOnFrameTask = new Runnable() { + @Override + public void run() { + final long ms = mRendererTask.mIntervalsNs / 1000000L; + final int ns = (int)(mRendererTask.mIntervalsNs % 1000000L); + for (; isRunning; ) { + if (mRendererTask == null) break; + synchronized (mSync) { + try { + mSync.wait(ms, ns); + if (mRendererTask.mImageSource != null) { + mRendererTask.removeRequest(REQUEST_DRAW); + mRendererTask.offer(REQUEST_DRAW); + mSync.notify(); + } + } catch (Exception e) { + Log.w(TAG, e); + } + } + } + } + }; + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java new file mode 100644 index 0000000000..e66fc4ae88 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java @@ -0,0 +1,535 @@ +package com.serenegiant.glutils; +/* + * Copyright 2014 Google Inc. All rights reserved. + * Modified 2014-2018 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 static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_2D; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_BULGE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_BW; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_CHROMA_KEY; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_DENT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_FILT3x3; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_FISHEYE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_MIRROR; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_NIGHT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_SQUEEZE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_STRETCH; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_TUNNEL; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_TWIRL; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_FILT3x3; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; +import static com.serenegiant.glutils.ShaderConst.KERNEL_NULL; +import static com.serenegiant.glutils.ShaderConst.KERNEL_SIZE3x3; +import static com.serenegiant.glutils.ShaderConst.VERTEX_SHADER; + +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.util.Log; +import android.view.MotionEvent; + +import java.nio.FloatBuffer; + +/** + * GL program and supporting functions for textured 2D shapes. + */ +public class Texture2dProgram { + private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "Texture2dProgram"; + + public enum ProgramType { + // ここはGL_TEXTURE_2D + TEXTURE_2D, +// TEXTURE_SOBEL, // フラグメントシェーダーがうまく走らなくて止まってしまう +// TEXTURE_SOBEL2, // フラグメントシェーダーがうまく走らなくて止まってしまう + TEXTURE_FILT3x3, + TEXTURE_CUSTOM, + // ここから下はGL_TEXTURE_EXTERNAL_OES + TEXTURE_EXT, + TEXTURE_EXT_BW, + TEXTURE_EXT_NIGHT, + TEXTURE_EXT_CHROMA_KEY, + TEXTURE_EXT_SQUEEZE, + TEXTURE_EXT_TWIRL, + TEXTURE_EXT_TUNNEL, + TEXTURE_EXT_BULGE, + TEXTURE_EXT_DENT, + TEXTURE_EXT_FISHEYE, + TEXTURE_EXT_STRETCH, + TEXTURE_EXT_MIRROR, +// TEXTURE_EXT_SOBEL, // フラグメントシェーダーがうまく走らなくて止まってしまう +// TEXTURE_EXT_SOBEL2, // フラグメントシェーダーがうまく走らなくて止まってしまう + TEXTURE_EXT_FILT3x3, + } + + private final Object mSync = new Object(); + private final ProgramType mProgramType; + + private float mTexWidth; + private float mTexHeight; + + // Handles to the GL program and various components of it. + private int mProgramHandle; + private final int muMVPMatrixLoc; // モデルビュー変換行列 + private final int muTexMatrixLoc; // テクスチャ行列 + private final int maPositionLoc; // + private final int maTextureCoordLoc;// + private int muKernelLoc; // カーネル行列(float配列) + private int muTexOffsetLoc; // テクスチャオフセット(カーネル行列用) + private int muColorAdjustLoc; // 色調整 + private int muTouchPositionLoc; + private int muFlagsLoc; + + private int mTextureTarget; + + protected boolean mHasKernel2; + /** Inputs for convolution filter based shaders */ + private final float[] mKernel = new float[KERNEL_SIZE3x3 * 2]; + /** Summed touch event delta */ + private final float[] mSummedTouchPosition = new float[2]; + /** Raw location of last touch event */ + private final float[] mLastTouchPosition = new float[2]; + private float[] mTexOffset; + private float mColorAdjust; + private final int[] mFlags = new int[4]; + + public Texture2dProgram(final int target, final String fss) { + this(ProgramType.TEXTURE_CUSTOM, target, VERTEX_SHADER, fss); + } + + public Texture2dProgram(final int target, final String vss, final String fss) { + this(ProgramType.TEXTURE_CUSTOM, target, vss, fss); + } + + public Texture2dProgram(final ProgramType programType) { + this(programType, 0, null, null); + } + + /** + * Prepares the program in the current EGL context. + */ + protected Texture2dProgram(final ProgramType programType, + final int target, final String vss, final String fss) { + + mProgramType = programType; + + float[] kernel = null, kernel2 = null; + switch (programType) { + case TEXTURE_2D: + mTextureTarget = GLES20.GL_TEXTURE_2D; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_2D); + break; +// case TEXTURE_SOBEL: +// mTextureTarget = GLES20.GL_TEXTURE_2D; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SOBEL); +// kernel = KERNEL_SOBEL_H; +// kernel2 = KERNEL_SOBEL_V; +// break; +// case TEXTURE_SOBEL2: +// mTextureTarget = GLES20.GL_TEXTURE_2D; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SOBEL); +// kernel = KERNEL_SOBEL2_H; +// kernel2 = KERNEL_SOBEL2_V; +// break; + case TEXTURE_FILT3x3: + mTextureTarget = GLES20.GL_TEXTURE_2D; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_FILT3x3); + break; + case TEXTURE_EXT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT); + break; + case TEXTURE_EXT_BW: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BW); + break; + case TEXTURE_EXT_NIGHT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_NIGHT); + break; + case TEXTURE_EXT_CHROMA_KEY: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_CHROMA_KEY); + break; + case TEXTURE_EXT_SQUEEZE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SQUEEZE); + break; + case TEXTURE_EXT_TWIRL: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_TWIRL); + break; + case TEXTURE_EXT_TUNNEL: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_TUNNEL); + break; + case TEXTURE_EXT_BULGE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BULGE); + break; + case TEXTURE_EXT_FISHEYE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FISHEYE); + break; + case TEXTURE_EXT_DENT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_DENT); + break; + case TEXTURE_EXT_MIRROR: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_MIRROR); + break; + case TEXTURE_EXT_STRETCH: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_STRETCH); + break; +// case TEXTURE_EXT_SOBEL: +// mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SOBEL); +// kernel = KERNEL_SOBEL_H; +// kernel2 = KERNEL_SOBEL_V; +// break; +// case TEXTURE_EXT_SOBEL2: +// mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SOBEL); +// kernel = KERNEL_SOBEL2_H; +// kernel2 = KERNEL_SOBEL2_V; +// break; + case TEXTURE_EXT_FILT3x3: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FILT3x3); + break; + case TEXTURE_CUSTOM: + switch (target) { + case GLES20.GL_TEXTURE_2D: + case GLES11Ext.GL_TEXTURE_EXTERNAL_OES: + break; + default: + throw new IllegalArgumentException( + "target should be GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES"); + } + mTextureTarget = target; + mProgramHandle = GLHelper.loadShader(vss, fss); + break; + default: + throw new RuntimeException("Unhandled type " + programType); + } + if (mProgramHandle == 0) { + throw new RuntimeException("Unable to create program"); + } + if (DEBUG) Log.d(TAG, "Created program " + mProgramHandle + " (" + programType + ")"); + + // get locations of attributes and uniforms + maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); + GLHelper.checkLocation(maPositionLoc, "aPosition"); + maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord"); + GLHelper.checkLocation(maTextureCoordLoc, "aTextureCoord"); + muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); + GLHelper.checkLocation(muMVPMatrixLoc, "uMVPMatrix"); + muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix"); +// GLHelper.checkLocation(muTexMatrixLoc, "uTexMatrix"); + initLocation(kernel, kernel2); + + } + + /** + * Releases the program. + */ + public void release() { + if (DEBUG) Log.d(TAG, "deleting program " + mProgramHandle); + GLES20.glDeleteProgram(mProgramHandle); + mProgramHandle = -1; + } + + /** + * Returns the program type. + */ + public ProgramType getProgramType() { + return mProgramType; + } + + public int getProgramHandle() { + return mProgramHandle; + } + + /** + * Creates a texture object suitable for use with this program. + *

+ * On exit, the texture will be bound. + */ + public int createTextureObject() { + final int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + GLHelper.checkGlError("glGenTextures"); + + final int texId = textures[0]; + GLES20.glBindTexture(mTextureTarget, texId); + GLHelper.checkGlError("glBindTexture " + texId); + + GLES20.glTexParameterf(mTextureTarget, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(mTextureTarget, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(mTextureTarget, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(mTextureTarget, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLHelper.checkGlError("glTexParameter"); + + return texId; + } + + /** + * Configures the effect offset + * + * This only has an effect for programs that + * use positional effects like SQUEEZE and MIRROR + */ + public void handleTouchEvent(final MotionEvent ev){ + synchronized (mSync) { + if (ev.getAction() == MotionEvent.ACTION_MOVE){ + // A finger is dragging about + if (mTexHeight != 0 && mTexWidth != 0){ + mSummedTouchPosition[0] + += (2 * (ev.getX() - mLastTouchPosition[0])) / mTexWidth; + mSummedTouchPosition[1] + += (2 * (ev.getY() - mLastTouchPosition[1])) / -mTexHeight; + mLastTouchPosition[0] = ev.getX(); + mLastTouchPosition[1] = ev.getY(); + } + } else if (ev.getAction() == MotionEvent.ACTION_DOWN){ + // The primary finger has landed + mLastTouchPosition[0] = ev.getX(); + mLastTouchPosition[1] = ev.getY(); + } + } + } + + /** + * Configures the convolution filter values. + * This only has an effect for programs that use the + * FRAGMENT_SHADER_EXT_FILT3x3 Fragment shader. + * + * @param values Normalized filter values; must be KERNEL_SIZE3x3 elements. + */ + public void setKernel(final float[] values, final float colorAdj) { + if (values.length < KERNEL_SIZE3x3) { + throw new IllegalArgumentException( + "Kernel size is " + values.length + " vs. " + KERNEL_SIZE3x3); + } + System.arraycopy(values, 0, mKernel, 0, KERNEL_SIZE3x3); + mColorAdjust = colorAdj; + } + + public void setKernel2(final float[] values) { + synchronized (mSync) { + mHasKernel2 = values != null && (values.length == KERNEL_SIZE3x3); + if (mHasKernel2) { + System.arraycopy(values, 0, mKernel, KERNEL_SIZE3x3, KERNEL_SIZE3x3); + } + } + } + + public void setColorAdjust(final float adjust) { + synchronized (mSync) { + mColorAdjust = adjust; + } + } + + /** + * Sets the size of the texture. This is used to find adjacent texels when filtering. + */ + public void setTexSize(final int width, final int height) { + mTexHeight = height; + mTexWidth = width; + final float rw = 1.0f / width; + final float rh = 1.0f / height; + + // Don't need to create a new array here, but it's syntactically convenient. + synchronized (mSync) { + mTexOffset = new float[] { + -rw, -rh, 0f, -rh, rw, -rh, + -rw, 0f, 0f, 0f, rw, 0f, + -rw, rh, 0f, rh, rw, rh + }; + } + } + + public void setFlags(final int[] flags) { + final int n = Math.min(4, flags != null ? flags.length : 0); + if (n > 0) { + synchronized (mSync) { + System.arraycopy(flags, 0, mFlags, 0, n); + } + } + } + + public void setFlag(final int index, final int value) { + if ((index >= 0) && (index < mFlags.length)) { + synchronized (mSync) { + mFlags[index] = value; + } + } + } + + /** + * Issues the draw call. Does the full setup on every call. + * + * @param mvpMatrix The 4x4 projection matrix. + * @param mvpMatrixOffset offset of mvpMatrix + * @param vertexBuffer Buffer with vertex position data. + * @param firstVertex Index of first vertex to use in vertexBuffer. + * @param vertexCount Number of vertices in vertexBuffer. + * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2). + * @param vertexStride Width, in bytes, of the position data for each vertex (often + * vertexCount * sizeof(float)). + * @param texMatrix A 4x4 transformation matrix for texture coords. + * @param texMatrixOffset offset of texMatrix + * @param texBuffer Buffer with vertex texture data. + * @param texStride Width, in bytes, of the texture data for each vertex. + */ + public void draw(final float[] mvpMatrix, final int mvpMatrixOffset, + final FloatBuffer vertexBuffer, final int firstVertex, + final int vertexCount, final int coordsPerVertex, final int vertexStride, + final float[] texMatrix, final int texMatrixOffset, + final FloatBuffer texBuffer, final int textureId, final int texStride) { + + GLHelper.checkGlError("draw start"); + + // シェーダープログラムを選択 + GLES20.glUseProgram(mProgramHandle); + GLHelper.checkGlError("glUseProgram"); + + // テクスチャを選択 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(mTextureTarget, textureId); + GLHelper.checkGlError("glBindTexture"); + + synchronized (mSync) { + // モデルビュー変換行列をセット + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, mvpMatrixOffset); + GLHelper.checkGlError("glUniformMatrix4fv"); + + // テクスチャ変換行列をセット + if (muTexMatrixLoc >= 0) { + GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, texMatrixOffset); + GLHelper.checkGlError("glUniformMatrix4fv"); + } + + // 頂点座標バッファを有効にする("aPosition" vertex attribute) + GLES20.glEnableVertexAttribArray(maPositionLoc); + GLHelper.checkGlError("glEnableVertexAttribArray"); + GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, + GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); + GLHelper.checkGlError("glVertexAttribPointer"); + + // テクスチャ座標バッファを有効にする("aTextureCoord" vertex attribute) + GLES20.glEnableVertexAttribArray(maTextureCoordLoc); + GLHelper.checkGlError("glEnableVertexAttribArray"); + GLES20.glVertexAttribPointer(maTextureCoordLoc, 2, + GLES20.GL_FLOAT, false, texStride, texBuffer); + GLHelper.checkGlError("glVertexAttribPointer"); + + // カーネル関数(行列) + if (muKernelLoc >= 0) { + if (!mHasKernel2) { + GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE3x3, mKernel, 0); + } else { + GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE3x3 * 2, mKernel, 0); + } + GLHelper.checkGlError("set kernel"); + } + // テクセルオフセット + if ((muTexOffsetLoc >= 0) && (mTexOffset != null)) { + GLES20.glUniform2fv(muTexOffsetLoc, KERNEL_SIZE3x3, mTexOffset, 0); + } + // 色調整オフセット + if (muColorAdjustLoc >= 0) { + GLES20.glUniform1f(muColorAdjustLoc, mColorAdjust); + } + // タッチ座標 + if (muTouchPositionLoc >= 0){ + GLES20.glUniform2fv(muTouchPositionLoc, 1, mSummedTouchPosition, 0); + } + // フラグ + if (muFlagsLoc >= 0) { + GLES20.glUniform1iv(muFlagsLoc, 4, mFlags, 0); + } + } + + internal_draw(firstVertex, vertexCount); + + // Done -- disable vertex array, texture, and program. + GLES20.glDisableVertexAttribArray(maPositionLoc); + GLES20.glDisableVertexAttribArray(maTextureCoordLoc); + GLES20.glBindTexture(mTextureTarget, 0); + GLES20.glUseProgram(0); + } + + protected void initLocation(float[] kernel, float[] kernel2) { + muKernelLoc = GLES20.glGetUniformLocation(mProgramHandle, "uKernel"); + if (muKernelLoc < 0) { + // no kernel in this one + muKernelLoc = -1; + muTexOffsetLoc = -1; + } else { + // has kernel, must also have tex offset and color adj + muTexOffsetLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexOffset"); + if (muTexOffsetLoc < 0) { + muTexOffsetLoc = -1; + } + // 未使用だと削除されてしまうのでチェックしない +// GLHelper.checkLocation(muTexOffsetLoc, "uTexOffset"); + + // initialize default values + if (kernel == null) { + kernel = KERNEL_NULL; + } + setKernel(kernel, 0f); + setTexSize(256, 256); + } + if (kernel2 != null) { + setKernel2(kernel2); + } + + muColorAdjustLoc = GLES20.glGetUniformLocation(mProgramHandle, "uColorAdjust"); + if (muColorAdjustLoc < 0) { + muColorAdjustLoc = -1; + } + // 未使用だと削除されてしまうのでチェックしない +// GLHelper.checkLocation(muColorAdjustLoc, "uColorAdjust"); + + muTouchPositionLoc = GLES20.glGetUniformLocation(mProgramHandle, "uPosition"); + if (muTouchPositionLoc < 0) { + // Shader doesn't use position + muTouchPositionLoc = -1; + } else { + // initialize default values + //handleTouchEvent(new float[]{0f, 0f}); + } + muFlagsLoc = GLES20.glGetUniformLocation(mProgramHandle, "uFlags"); + if (muFlagsLoc < 0) { + muFlagsLoc = -1; + } else { + } + } + + protected void internal_draw(final int firstVertex, final int vertexCount) { + // Draw the rect. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); + GLHelper.checkGlError("glDrawArrays"); + } +} \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java new file mode 100644 index 0000000000..95bc148e99 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java @@ -0,0 +1,446 @@ +package com.serenegiant.glutils; +/* + * 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.graphics.Bitmap; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +/** + * テクスチャへOpenGL|ESで描画するためのオフスクリーン描画クラス + * テクスチャをカラーバッファとしてFBOに割り当てる + */ +public class TextureOffscreen { + private static final boolean DEBUG = false; + private static final String TAG = "TextureOffscreen"; + + private static final boolean DEFAULT_ADJUST_POWER2 = false; + + private final int TEX_TARGET; + private final int TEX_UNIT; + private final boolean mHasDepthBuffer, mAdjustPower2; + /** 描画領域サイズ */ + private int mWidth, mHeight; + /** テクスチャサイズ */ + private int mTexWidth, mTexHeight; + /** オフスクリーンのカラーバッファに使うテクスチャ名 */ + private int mFBOTextureName = -1; + /** // オフスクリーン用のバッファオブジェクト */ + private int mDepthBufferObj = -1, mFrameBufferObj = -1; + /** テクスチャ座標変換行列 */ + private final float[] mTexMatrix = new float[16]; + + /** + * コンストラクタ(GL_TEXTURE_2D), デプスバッファ無し + * テクスチャユニットはGL_TEXTURE0 + * @param width + * @param height + */ + public TextureOffscreen(final int width, final int height) { + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, false, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D), デプスバッファ無し + * テクスチャユニットはGL_TEXTURE0 + * @param tex_unit + * @param width + * @param height + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, + false, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * テクスチャユニットはGL_TEXTURE0 + * @param width dimension of offscreen(width) + * @param height dimension of offscreen(height) + * @param use_depth_buffer set true if you use depth buffer. the depth is fixed as 16bits + */ + public TextureOffscreen(final int width, final int height, + final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ + * テクスチャユニットはGL_TEXTURE0 + * @param tex_unit + * @param width + * @param height + * @param use_depth_buffer + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height, final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, + use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * テクスチャユニットはGL_TEXTURE0 + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, use_depth_buffer, adjust_power2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * @param tex_unit + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, use_depth_buffer, adjust_power2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ, デプスバッファなし + * @param tex_id + * @param tex_unit + * @param width + * @param height + */ + public TextureOffscreen(final int tex_unit, final int tex_id, + final int width, final int height) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, tex_id, + width, height, + false, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ + * @param tex_unit + * @param tex_id + * @param width + * @param height + * @param use_depth_buffer + */ + public TextureOffscreen(final int tex_unit, final int tex_id, + final int width, final int height, final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, tex_id, + width, height, + use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャをwrapするためのコンストラクタ + * @param tex_target GL_TEXTURE_2D + * @param tex_id + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int tex_target, final int tex_unit, final int tex_id, + final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + if (DEBUG) Log.v(TAG, "Constructor"); + TEX_TARGET = tex_target; + TEX_UNIT = tex_unit; + mWidth = width; + mHeight = height; + mHasDepthBuffer = use_depth_buffer; + mAdjustPower2 = adjust_power2; + + createFrameBuffer(width, height); + int tex = tex_id; + if (tex < 0) { + tex = genTexture(tex_target, tex_unit, mTexWidth, mTexHeight); + } + assignTexture(tex, width, height); + } + + /** 破棄する */ + public void release() { + if (DEBUG) Log.v(TAG, "release"); + releaseFrameBuffer(); + } + + /** + * オフスクリーン描画用のレンダリングバッファに切り替える + * Viewportも変更になるので必要であればunbind後にViewportの設定をすること + */ + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, mFBOTextureName); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLES20.glViewport(0, 0, mWidth, mHeight); + } + + /** + * デフォルトのレンダリングバッファに戻す + */ + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, 0); + } + + private final float[] mResultMatrix = new float[16]; + /** + * get copy of texture matrix + * @return + */ + public float[] getTexMatrix() { + System.arraycopy(mTexMatrix, 0, mResultMatrix, 0, 16); + return mResultMatrix; + } + + /** + * テクスチャ座標変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + public float[] getRawTexMatrix() { + return mTexMatrix; + } + + /** + * テクスチャ変換行列のコピーを返す + * 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param matrix + */ + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + + /** + * オフスクリーンテクスチャ名を取得 + * このオフスクリーンへ書き込んだ画像をテクスチャとして使って他の描画を行う場合に使用できる + * @return + */ + public int getTexture() { + return mFBOTextureName; + } + + /** 指定したテクスチャをこのオフスクリーンに割り当てる */ + public void assignTexture(final int texture_name, + final int width, final int height) { + + if ((width > mTexWidth) || (height > mTexHeight)) { + mWidth = width; + mHeight = height; + releaseFrameBuffer(); + createFrameBuffer(width, height); + } + mFBOTextureName = texture_name; + GLES20.glActiveTexture(TEX_UNIT); + // フレームバッファオブジェクトをbindする + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLHelper.checkGlError("glBindFramebuffer " + mFrameBufferObj); + // フレームバッファにカラーバッファ(テクスチャ)を接続する + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, + TEX_TARGET, mFBOTextureName, 0); + GLHelper.checkGlError("glFramebufferTexture2D"); + + if (mHasDepthBuffer) { + // フレームバッファにデプスバッファを接続する + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, + GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, mDepthBufferObj); + GLHelper.checkGlError("glFramebufferRenderbuffer"); + } + + // 正常に終了したかどうかを確認する + final int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("Framebuffer not complete, status=" + status); + } + + // デフォルトのフレームバッファに戻す + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + // テクスチャ座標変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; + } + + /** Bitmapからテクスチャを読み込む */ + public void loadBitmap(final Bitmap bitmap) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + if ((width > mTexWidth) || (height > mTexHeight)) { + mWidth = width; + mHeight = height; + releaseFrameBuffer(); + createFrameBuffer(width, height); + } + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, mFBOTextureName); + GLUtils.texImage2D(TEX_TARGET, 0, bitmap, 0); + GLES20.glBindTexture(TEX_TARGET, 0); + // initialize texture matrix + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; + } + + /** + * カラーバッファのためにテクスチャを生成する + * @param tex_target + * @param tex_unit + * @param tex_width + * @param tex_height + * @return + */ + private static int genTexture(final int tex_target, final int tex_unit, + final int tex_width, final int tex_height) { + // カラーバッファのためにテクスチャを生成する + final int tex_name = GLHelper.initTex(tex_target, tex_unit, + GLES20.GL_LINEAR, GLES20.GL_LINEAR, GLES20.GL_CLAMP_TO_EDGE); + // テクスチャのメモリ領域を確保する + GLES20.glTexImage2D(tex_target, 0, GLES20.GL_RGBA, tex_width, tex_height, 0, + GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); + GLHelper.checkGlError("glTexImage2D"); + return tex_name; + } + + /** オフスクリーン描画用のフレームバッファオブジェクトを生成する */ + private final void createFrameBuffer(final int width, final int height) { + final int[] ids = new int[1]; + + if (mAdjustPower2) { + // テクスチャのサイズは2の乗数にする + int w = 1; + for (; w < width; w <<= 1) ; + int h = 1; + for (; h < height; h <<= 1) ; + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } + } else { + mTexWidth = width; + mTexHeight = height; + } + + if (mHasDepthBuffer) { + // デプスバッファが必要な場合は、レンダーバッファオブジェクトを生成・初期化する + GLES20.glGenRenderbuffers(1, ids, 0); + mDepthBufferObj = ids[0]; + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBufferObj); + // デプスバッファは16ビット + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, + GLES20.GL_DEPTH_COMPONENT16, mTexWidth, mTexHeight); + } + // フレームバッファオブジェクトを生成してbindする + GLES20.glGenFramebuffers(1, ids, 0); + GLHelper.checkGlError("glGenFramebuffers"); + mFrameBufferObj = ids[0]; + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLHelper.checkGlError("glBindFramebuffer " + mFrameBufferObj); + + // デフォルトのフレームバッファに戻す + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + } + + /** オフスクリーンフレームバッファを破棄 */ + private final void releaseFrameBuffer() { + final int[] names = new int[1]; + // デプスバッファがある時はデプスバッファを破棄 + if (mDepthBufferObj >= 0) { + names[0] = mDepthBufferObj; + GLES20.glDeleteRenderbuffers(1, names, 0); + mDepthBufferObj = -1; + } + // オフスクリーンのカラーバッファ用のテクスチャを破棄 + if (mFBOTextureName >= 0) { + names[0] = mFBOTextureName; + GLES20.glDeleteTextures(1, names, 0); + mFBOTextureName = -1; + } + // オフスクリーンのフレームバッファーオブジェクトを破棄 + if (mFrameBufferObj >= 0) { + names[0] = mFrameBufferObj; + GLES20.glDeleteFramebuffers(1, names, 0); + mFrameBufferObj = -1; + } + } + + /** + * get dimension(width) of this offscreen + * @return + */ + public int getWidth() { + return mWidth; + } + + /** + * get dimension(height) of this offscreen + * @return + */ + public int getHeight() { + return mHeight; + } + + /** + * get backing texture dimension(width) of this offscreen + * @return + */ + public int getTexWidth() { + return mTexWidth; + } + + /** + * get backing texture dimension(height) of this offscreen + * @return + */ + public int getTexHeight() { + return mTexHeight; + } + + public int getTexTarget() { + return TEX_TARGET; + } + + public int getTexUnit() { + return TEX_UNIT; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java new file mode 100644 index 0000000000..54f56183df --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java @@ -0,0 +1,132 @@ +package com.serenegiant.glutils.es1; +/* + * 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 static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_2D; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; + +import android.opengl.GLES10; +import android.opengl.Matrix; + +import com.serenegiant.glutils.IDrawer2D; +import com.serenegiant.glutils.ITexture; +import com.serenegiant.glutils.TextureOffscreen; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class GLDrawer2D implements IDrawer2D { + private static final float[] VERTICES = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; + private static final float[] TEXCOORD = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + private static final int FLOAT_SZ = Float.SIZE / 8; + private static final int VERTEX_NUM = 4; + private static final int VERTEX_SZ = VERTEX_NUM * 2; + + private final float[] mMvpMatrix = new float[16]; + private final FloatBuffer pVertex; + private final FloatBuffer pTexCoord; + private final int mTexTarget; + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final boolean isOES) { + mTexTarget = isOES ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + pVertex = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pVertex.put(VERTICES); + pVertex.flip(); + pTexCoord = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pTexCoord.put(TEXCOORD); + pTexCoord.flip(); + // モデルビュー変換行列を初期化 + Matrix.setIdentityM(mMvpMatrix, 0); + } + + @Override + public void release() { + + } + + /** + * モデルビュー変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + @Override + public float[] getMvpMatrix() { + return mMvpMatrix; + } + + /** + * モデルビュー変換行列に行列を割り当てる + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + * @return + */ + @Override + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(matrix, offset, mMvpMatrix, 0, 16); + return this; + } + + /** + * モデルビュー変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + */ + @Override + public void getMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(mMvpMatrix, 0, matrix, offset, 16); + } + + @Override + public void draw(final int texId, final float[] tex_matrix, final int offset) { + // FIXME Matrixを適用 + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + pVertex.position(0); + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, VERTEX_SZ, pVertex); +//-------------------------------------------------------------------------------- + GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + pTexCoord.position(0); + GLES10.glTexCoordPointer(VERTEX_NUM, GLES10.GL_FLOAT, VERTEX_SZ, pTexCoord); + GLES10.glActiveTexture(GLES10.GL_TEXTURE0); + GLES10.glBindTexture(mTexTarget, texId); +//-------------------------------------------------------------------------------- + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_STRIP, 0, VERTEX_NUM); +//-------------------------------------------------------------------------------- + GLES10.glBindTexture(mTexTarget, 0); + GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); +//-------------------------------------------------------------------------------- + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + } + + @Override + public void draw(final ITexture texture) { + draw(texture.getTexture(), texture.getTexMatrix(), 0); + } + + @Override + public void draw(final TextureOffscreen offscreen) { + draw(offscreen.getTexture(), offscreen.getTexMatrix(), 0); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java new file mode 100644 index 0000000000..3a4943f935 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java @@ -0,0 +1,459 @@ +package com.serenegiant.glutils.es1; +/* + * 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.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.opengl.GLES10; +import android.opengl.GLES30; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.serenegiant.utils.BuildCheck; + +import javax.microedition.khronos.opengles.GL10; + +/** + * OpenGL|ES用のヘルパークラス + */ +public final class GLHelper { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "GLHelper"; + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param op + */ + public static void checkGlError(final String op) { + final int error = GLES10.glGetError(); + if (error != GLES10.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param gl + * @param op + */ + public static void checkGlError(final GL10 gl, final String op) { + final int error = gl.glGetError(); + if (error != GL10.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * テクスチャ名を生成, テクスチャユニットはGL_TEXTURE0, クランプ方法はGL_CLAMP_TO_EDGE + * @param texTarget + * @param filter_param テクスチャの補完方法を指定, min/mag共に同じ値になる, GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final int texTarget, final int filter_param) { + return initTex(texTarget, GLES10.GL_TEXTURE0, filter_param, filter_param, GLES10.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名を生成 + * @param texTarget + * @param texUnit テクスチャユニット, GL_TEXTURE0...GL_TEXTURE31 + * @param min_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param mag_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param wrap テクスチャのクランプ方法, GL_CLAMP_TO_EDGE + * @return + */ + public static int initTex(final int texTarget, final int texUnit, final int min_filter, final int mag_filter, final int wrap) { +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + GLES10.glActiveTexture(texUnit); + GLES10.glGenTextures(1, tex, 0); + GLES10.glBindTexture(texTarget, tex[0]); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_WRAP_S, wrap); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_WRAP_T, wrap); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_MIN_FILTER, min_filter); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_MAG_FILTER, mag_filter); + return tex[0]; + } + + /** + * テクスチャ名を生成(GL_TEXTURE0のみ) + * @param gl + * @param texTarget + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final GL10 gl, final int texTarget, final int filter_param) { +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + gl.glActiveTexture(GL10.GL_TEXTURE0); + gl.glGenTextures(1, tex, 0); + gl.glBindTexture(texTarget, tex[0]); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_MIN_FILTER, filter_param); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_MAG_FILTER, filter_param); + return tex[0]; + } + + /** + * delete specific texture + */ + public static void deleteTex(final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + GLES10.glDeleteTextures(1, tex, 0); + } + + /** + * delete specific texture + */ + public static void deleteTex(final GL10 gl, final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + gl.glDeleteTextures(1, tex, 0); + } + + public static int loadTextureFromResource(final Context context, final int resId) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // get a background image from resources + // note the image format must match the bitmap format + final Drawable background = context.getResources().getDrawable(resId); + background.setBounds(0, 0, 256, 256); + background.draw(canvas); // draw the background to our bitmap + + final int[] textures = new int[1]; + + //Generate one texture pointer... + GLES10.glGenTextures(1, textures, 0); + //...and bind it to our array + GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, textures[0]); + + //Create Nearest Filtered Texture + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR); + + //Different possible texture parameters, e.g. GLES10.GL_CLAMP_TO_EDGE + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_REPEAT); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_REPEAT); + + //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); + //Clean up + bitmap.recycle(); + + return textures[0]; + } + + public static int createTextureWithTextContent (final String text) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // Draw the text + final Paint textPaint = new Paint(); + textPaint.setTextSize(32); + textPaint.setAntiAlias(true); + textPaint.setARGB(0xff, 0xff, 0xff, 0xff); + // draw the text centered + canvas.drawText(text, 16, 112, textPaint); + + final int texture = initTex(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE0, GLES10.GL_NEAREST, GLES10.GL_LINEAR, GLES10.GL_REPEAT); + + // Alpha blending + // GLES10.glEnable(GLES10.GL_BLEND); + // GLES10.glBlendFunc(GLES10.GL_SRC_ALPHA, GLES10.GL_ONE_MINUS_SRC_ALPHA); + + // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); + // Clean up + bitmap.recycle(); + + return texture; + } + + /** + * Checks to see if the location we obtained is valid. GLES returns -1 if a label + * could not be found, but does not set the GL error. + *

+ * Throws a RuntimeException if the location is invalid. + */ + public static void checkLocation(final int location, final String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + + /** + * Writes GL version info to the log. + */ + @SuppressLint("InlinedApi") + public static void logVersionInfo() { + Log.i(TAG, "vendor : " + GLES10.glGetString(GLES10.GL_VENDOR)); + Log.i(TAG, "renderer: " + GLES10.glGetString(GLES10.GL_RENDERER)); + Log.i(TAG, "version : " + GLES10.glGetString(GLES10.GL_VERSION)); + + if (BuildCheck.isAndroid4_3()) { + final int[] values = new int[1]; + GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); + final int majorVersion = values[0]; + GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); + final int minorVersion = values[0]; + if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { + Log.i(TAG, "version: " + majorVersion + "." + minorVersion); + } + } + } + +// came from GLU + /** + * Return an error string from a GL or GLU error code. + * + * @param error - a GL or GLU error code. + * @return the error string for the input error code, or NULL if the input + * was not a valid GL or GLU error code. + */ + public static String gluErrorString(final int error) { + switch (error) { + case GLES10.GL_NO_ERROR: + return "no error"; + case GLES10.GL_INVALID_ENUM: + return "invalid enum"; + case GLES10.GL_INVALID_VALUE: + return "invalid value"; + case GLES10.GL_INVALID_OPERATION: + return "invalid operation"; + case GLES10.GL_STACK_OVERFLOW: + return "stack overflow"; + case GLES10.GL_STACK_UNDERFLOW: + return "stack underflow"; + case GLES10.GL_OUT_OF_MEMORY: + return "out of memory"; + default: + return null; + } + } + + /** + * Define a viewing transformation in terms of an eye point, a center of + * view, and an up vector. + * + * @param eyeX eye point X + * @param eyeY eye point Y + * @param eyeZ eye point Z + * @param centerX center of view X + * @param centerY center of view Y + * @param centerZ center of view Z + * @param upX up vector X + * @param upY up vector Y + * @param upZ up vector Z + */ + public static void gluLookAt(final float eyeX, final float eyeY, final float eyeZ, + final float centerX, final float centerY, final float centerZ, + final float upX, final float upY, final float upZ) { + + final float[] scratch = sScratch; + synchronized (scratch) { + Matrix.setLookAtM(scratch, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, + upX, upY, upZ); + GLES10.glMultMatrixf(scratch, 0); + } + } + + /** + * Set up a 2D orthographic projection matrix + * + * @param left + * @param right + * @param bottom + * @param top + */ + public static void gluOrtho2D(final float left, final float right, + final float bottom, final float top) { + GLES10.glOrthof(left, right, bottom, top, -1.0f, 1.0f); + } + + /** + * Set up a perspective projection matrix + * + * @param fovy specifies the field of view angle, in degrees, in the Y + * direction. + * @param aspect specifies the aspect ration that determins the field of + * view in the x direction. The aspect ratio is the ratio of x + * (width) to y (height). + * @param zNear specifies the distance from the viewer to the near clipping + * plane (always positive). + * @param zFar specifies the distance from the viewer to the far clipping + * plane (always positive). + */ + public static void gluPerspective(final float fovy, final float aspect, + final float zNear, final float zFar) { + + final float top = zNear * (float) Math.tan(fovy * (Math.PI / 360.0)); + final float bottom = -top; + final float left = bottom * aspect; + final float right = top * aspect; + GLES10.glFrustumf(left, right, bottom, top, zNear, zFar); + } + + /** + * Map object coordinates into window coordinates. gluProject transforms the + * specified object coordinates into window coordinates using model, proj, + * and view. The result is stored in win. + *

+ * Note that you can use the OES_matrix_get extension, if present, to get + * the current modelView and projection matrices. + * + * @param objX object coordinates X + * @param objY object coordinates Y + * @param objZ object coordinates Z + * @param model the current modelview matrix + * @param modelOffset the offset into the model array where the modelview + * maxtrix data starts. + * @param project the current projection matrix + * @param projectOffset the offset into the project array where the project + * matrix data starts. + * @param view the current view, {x, y, width, height} + * @param viewOffset the offset into the view array where the view vector + * data starts. + * @param win the output vector {winX, winY, winZ}, that returns the + * computed window coordinates. + * @param winOffset the offset into the win array where the win vector data + * starts. + * @return A return value of GL_TRUE indicates success, a return value of + * GL_FALSE indicates failure. + */ + public static int gluProject(final float objX, final float objY, final float objZ, + final float[] model, final int modelOffset, final float[] project, final int projectOffset, + final int[] view, final int viewOffset, final float[] win, final int winOffset) { + + final float[] scratch = sScratch; + synchronized (scratch) { + final int M_OFFSET = 0; // 0..15 + final int V_OFFSET = 16; // 16..19 + final int V2_OFFSET = 20; // 20..23 + Matrix.multiplyMM(scratch, M_OFFSET, project, projectOffset, model, modelOffset); + + scratch[V_OFFSET + 0] = objX; + scratch[V_OFFSET + 1] = objY; + scratch[V_OFFSET + 2] = objZ; + scratch[V_OFFSET + 3] = 1.0f; + + Matrix.multiplyMV(scratch, V2_OFFSET, scratch, M_OFFSET, scratch, V_OFFSET); + + final float w = scratch[V2_OFFSET + 3]; + if (w == 0.0f) { + return GLES10.GL_FALSE; + } + + final float rw = 1.0f / w; + + win[winOffset] = view[viewOffset] + + view[viewOffset + 2] + * (scratch[V2_OFFSET + 0] * rw + 1.0f) + * 0.5f; + win[winOffset + 1] = + view[viewOffset + 1] + view[viewOffset + 3] + * (scratch[V2_OFFSET + 1] * rw + 1.0f) * 0.5f; + win[winOffset + 2] = (scratch[V2_OFFSET + 2] * rw + 1.0f) * 0.5f; + } + + return GL10.GL_TRUE; + } + + /** + * Map window coordinates to object coordinates. gluUnProject maps the + * specified window coordinates into object coordinates using model, proj, + * and view. The result is stored in obj. + *

+ * Note that you can use the OES_matrix_get extension, if present, to get + * the current modelView and projection matrices. + * + * @param winX window coordinates X + * @param winY window coordinates Y + * @param winZ window coordinates Z + * @param model the current modelview matrix + * @param modelOffset the offset into the model array where the modelview + * maxtrix data starts. + * @param project the current projection matrix + * @param projectOffset the offset into the project array where the project + * matrix data starts. + * @param view the current view, {x, y, width, height} + * @param viewOffset the offset into the view array where the view vector + * data starts. + * @param obj the output vector {objX, objY, objZ}, that returns the + * computed object coordinates. + * @param objOffset the offset into the obj array where the obj vector data + * starts. + * @return A return value of GL10.GL_TRUE indicates success, a return value + * of GL10.GL_FALSE indicates failure. + */ + public static int gluUnProject(final float winX, final float winY, final float winZ, + final float[] model, final int modelOffset, final float[] project, final int projectOffset, + final int[] view, final int viewOffset, final float[] obj, final int objOffset) { + + final float[] scratch = sScratch; + synchronized (scratch) { + final int PM_OFFSET = 0; // 0..15 + final int INVPM_OFFSET = 16; // 16..31 + final int V_OFFSET = 0; // 0..3 Reuses PM_OFFSET space + Matrix.multiplyMM(scratch, PM_OFFSET, project, projectOffset, model, modelOffset); + + if (!Matrix.invertM(scratch, INVPM_OFFSET, scratch, PM_OFFSET)) { + return GL10.GL_FALSE; + } + + scratch[V_OFFSET + 0] = + 2.0f * (winX - view[viewOffset + 0]) / view[viewOffset + 2] + - 1.0f; + scratch[V_OFFSET + 1] = + 2.0f * (winY - view[viewOffset + 1]) / view[viewOffset + 3] + - 1.0f; + scratch[V_OFFSET + 2] = 2.0f * winZ - 1.0f; + scratch[V_OFFSET + 3] = 1.0f; + + Matrix.multiplyMV(obj, objOffset, scratch, INVPM_OFFSET, scratch, V_OFFSET); + } + + return GL10.GL_TRUE; + } + + private static final float[] sScratch = new float[32]; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java new file mode 100644 index 0000000000..599d36a72e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java @@ -0,0 +1,234 @@ +package com.serenegiant.glutils.es1; +/* + * 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.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.opengl.GLES10; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.text.TextUtils; + +import com.serenegiant.glutils.ITexture; + +import java.io.IOException; + +/** + * OpenGL|ESのテクスチャ操作用のヘルパークラス + */ +public class GLTexture implements ITexture { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること +// private static final String TAG = "GLTexture"; + + /* package */final int mTextureTarget; + /* package */final int mTextureUnit; + /* package */int mTextureId; + /* package */final float[] mTexMatrix = new float[16]; // テクスチャ変換行列 + /* package */int mTexWidth, mTexHeight; + /* package */int mImageWidth, mImageHeight; + + /** + * コンストラクタ + * テクスチャユニットが常時GL_TEXTURE0なので複数のテクスチャを同時に使えない + * @param width + * @param height + * @param filter_param + */ + public GLTexture(final int width, final int height, final int filter_param) { + this(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE0, width, height, filter_param); + } + + /** + * コンストラクタ + * @param texTarget GL_TEXTURE_EXTERNAL_OESはだめ + * @param texUnit + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int texTarget, final int texUnit, + final int width, final int height, final int filter_param) { + +// if (DEBUG) Log.v(TAG, String.format("コンストラクタ(%d,%d)", width, height)); + mTextureTarget = texTarget; + mTextureUnit = texUnit; + // テクスチャに使うビットマップは縦横サイズが2の乗数でないとダメ。 + // 更に、ミップマップするなら正方形でないとダメ + // 指定したwidth/heightと同じか大きい2の乗数にする + int w = 32; + for (; w < width; w <<= 1); + int h = 32; + for (; h < height; h <<= 1); + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } +// if (DEBUG) Log.v(TAG, String.format("texSize(%d,%d)", mTexWidth, mTexHeight)); + mTextureId = GLHelper.initTex(mTextureTarget, filter_param); + // テクスチャのメモリ領域を確保する + GLES10.glTexImage2D(mTextureTarget, + 0, // ミップマップレベル0(ミップマップしない) + GLES10.GL_RGBA, // 内部フォーマット + mTexWidth, mTexHeight, // サイズ + 0, // 境界幅 + GLES10.GL_RGBA, // 引き渡すデータのフォーマット + GLES10.GL_UNSIGNED_BYTE, // データの型 + null); // ピクセルデータ無し + // テクスチャ変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, "GLTexture:id=" + mTextureId); + } + + @Override + protected void finalize() throws Throwable { + release(); // GLコンテキスト内じゃない可能性があるのであまり良くないけど + super.finalize(); + } + + /** + * テクスチャを破棄 + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出すこと + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mTextureId > 0) { + GLHelper.deleteTex(mTextureId); + mTextureId = 0; + } + } + + /** + * このインスタンスで管理しているテクスチャを有効にする(バインドする) + */ + @Override + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES10.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES10.glBindTexture(mTextureTarget, mTextureId); + } + + /** + * このインスタンスで管理しているテクスチャを無効にする(アンバインドする) + */ + @Override + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES10.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES10.glBindTexture(mTextureTarget, 0); + } + + /** + * テクスチャターゲットを取得(GL_TEXTURE_2D) + * @return + */ + @Override + public int getTexTarget() { return mTextureTarget; } + /** + * テクスチャ名を取得 + * @return + */ + @Override + public int getTexture() { return mTextureId; } + /** + * テクスチャ座標変換行列を取得(内部配列をそのまま返すので変更時は要注意) + * @return + */ + @Override + public float[] getTexMatrix() { return mTexMatrix; } + /** + * テクスチャ座標変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param offset + */ + @Override + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + /** + * テクスチャ幅を取得 + * @return + */ + @Override + public int getTexWidth() { return mTexWidth; } + /** + * テクスチャ高さを取得 + * @return + */ + @Override + public int getTexHeight() { return mTexHeight; } + + /** + * 指定したファイルから画像をテクスチャに読み込む + * ファイルが存在しないか読み込めなければIOException/NullPointerExceptionを生成 + * @param filePath + */ + @Override + public void loadTexture(final String filePath) throws NullPointerException, IOException { +// if (DEBUG) Log.v(TAG, "loadTexture:path=" + filePath); + if (TextUtils.isEmpty(filePath)) + throw new NullPointerException("image file path should not be a null"); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // Bitmapを生成せずにサイズ等の情報だけを取得する + BitmapFactory.decodeFile(filePath, options); + // テキスチャサイズ内に指定したイメージが収まるためのサブサンプリングを値を求める + final int imageWidth = options.outWidth; + final int imageHeight = options.outHeight; + int inSampleSize = 1; // サブサンプリングサイズ + if ((imageHeight > mTexHeight) || (imageWidth > mTexWidth)) { + if (imageWidth > imageHeight) { + inSampleSize = (int)Math.ceil(imageHeight / (float)mTexHeight); + } else { + inSampleSize = (int)Math.ceil(imageWidth / (float)mTexWidth); + } + } +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),tex(%d,%d),inSampleSize=%d", imageWidth, imageHeight, mTexWidth, mTexHeight, inSampleSize)); + // 実際の読み込み処理 + options.inSampleSize = inSampleSize; + options.inJustDecodeBounds = false; + loadTexture(BitmapFactory.decodeFile(filePath, options)); + } + + /** + * ビットマップからテクスチャを読み込む + * @param bitmap + * @throws NullPointerException + */ + @Override + public void loadTexture(final Bitmap bitmap) throws NullPointerException { + mImageWidth = bitmap.getWidth(); // 読み込んだイメージのサイズを取得 + mImageHeight = bitmap.getHeight(); + Bitmap texture = Bitmap.createBitmap(mTexWidth, mTexHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(texture); + canvas.drawBitmap(bitmap, 0, 0, null); + bitmap.recycle(); + // テクスチャ座標変換行列を設定(読み込んだイメージサイズがテクスチャサイズにフィットするようにスケール変換) + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = mImageWidth / (float)mTexWidth; + mTexMatrix[5] = mImageHeight / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),scale(%f,%f)", mImageWidth, mImageHeight, mMvpMatrix[0], mMvpMatrix[5])); + bind(); + GLUtils.texImage2D(mTextureTarget, 0, texture, 0); + unbind(); + texture.recycle(); + } +} 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 new file mode 100644 index 0000000000..dfad92686f --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java @@ -0,0 +1,238 @@ +/* + * 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; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.usb.UsbDevice; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.CheckedTextView; +import android.widget.Spinner; + + +import com.mogo.usbcamera.R; + +import java.util.ArrayList; +import java.util.List; + +public class CameraDialog extends DialogFragment { + private static final String TAG = CameraDialog.class.getSimpleName(); + + 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 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 ClassCastException e) { + } catch (final NullPointerException e) { + } + if (mUSBMonitor == null) { + throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + 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 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); + 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; + } + + + @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 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); + } + + 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); + } + + private static final class DeviceListAdapter extends BaseAdapter { + + private final LayoutInflater mInflater; + private final List mList; + + 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 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 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 new file mode 100644 index 0000000000..2e94263c92 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java @@ -0,0 +1,527 @@ +/* + * 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; +import android.content.res.Resources.NotFoundException; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.text.TextUtils; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class DeviceFilter { + + private static final String TAG = "DeviceFilter"; + + // USB Vendor ID (or -1 for unspecified) + public final int mVendorId; + // USB Product ID (or -1 for unspecified) + public final int mProductId; + // USB device or interface class (or -1 for unspecified) + public final int mClass; + // USB device subclass (or -1 for unspecified) + public final int mSubclass; + // USB device protocol (or -1 for unspecified) + public final int mProtocol; + // USB device manufacturer name string (or null for unspecified) + public final String mManufacturerName; + // USB device product name string (or null for unspecified) + public final String mProductName; + // USB device serial number string (or null for unspecified) + public final String mSerialNumber; + // set true if specific device(s) should exclude + public final boolean isExclude; + + public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass, + final int protocol, final String manufacturer, final String product, final String serialNum) { + this(vid, pid, clasz, subclass, protocol, manufacturer, product, serialNum, false); + } + + public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass, + final int protocol, final String manufacturer, final String product, final String serialNum, final boolean isExclude) { + mVendorId = vid; + mProductId = pid; + mClass = clasz; + mSubclass = subclass; + mProtocol = protocol; + mManufacturerName = manufacturer; + mProductName = product; + mSerialNumber = serialNum; + this.isExclude = isExclude; +/* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x", + mVendorId, mProductId, mClass, mSubclass, mProtocol)); */ + } + + public DeviceFilter(final UsbDevice device) { + this(device, false); + } + + public DeviceFilter(final UsbDevice device, final boolean isExclude) { + mVendorId = device.getVendorId(); + mProductId = device.getProductId(); + mClass = device.getDeviceClass(); + mSubclass = device.getDeviceSubclass(); + mProtocol = device.getDeviceProtocol(); + mManufacturerName = null; // device.getManufacturerName(); + mProductName = null; // device.getProductName(); + mSerialNumber = null; // device.getSerialNumber(); + this.isExclude = isExclude; +/* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x", + mVendorId, mProductId, mClass, mSubclass, mProtocol)); */ + } + + /** + * 指定したxmlリソースからDeviceFilterリストを生成する + * @param context + * @param deviceFilterXmlId + * @return + */ + public static List getDeviceFilters(final Context context, final int deviceFilterXmlId) { + final XmlPullParser parser = context.getResources().getXml(deviceFilterXmlId); + final List deviceFilters = new ArrayList(); + try { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final DeviceFilter deviceFilter = readEntryOne(context, parser); + if (deviceFilter != null) { + deviceFilters.add(deviceFilter); + } + } + eventType = parser.next(); + } + } catch (final XmlPullParserException e) { + Log.d(TAG, "XmlPullParserException", e); + } catch (final IOException e) { + Log.d(TAG, "IOException", e); + } + + return Collections.unmodifiableList(deviceFilters); + } + + /** + * read as integer values with default value from xml(w/o exception throws) + * resource integer id is also resolved into integer + * @param parser + * @param namespace + * @param name + * @param defaultValue + * @return + */ + private static final int getAttributeInteger(final Context context, final XmlPullParser parser, final String namespace, final String name, final int defaultValue) { + int result = defaultValue; + try { + String v = parser.getAttributeValue(namespace, name); + if (!TextUtils.isEmpty(v) && v.startsWith("@")) { + final String r = v.substring(1); + final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); + if (resId > 0) { + result = context.getResources().getInteger(resId); + } + } else { + int radix = 10; + if (v != null && v.length() > 2 && v.charAt(0) == '0' && + (v.charAt(1) == 'x' || v.charAt(1) == 'X')) { + // allow hex values starting with 0x or 0X + radix = 16; + v = v.substring(2); + } + result = Integer.parseInt(v, radix); + } + } catch (final NotFoundException e) { + result = defaultValue; + } catch (final NumberFormatException e) { + result = defaultValue; + } catch (final NullPointerException e) { + result = defaultValue; + } + return result; + } + + /** + * read as boolean values with default value from xml(w/o exception throws) + * resource boolean id is also resolved into boolean + * if the value is zero, return false, if the value is non-zero integer, return true + * @param context + * @param parser + * @param namespace + * @param name + * @param defaultValue + * @return + */ + private static final boolean getAttributeBoolean(final Context context, final XmlPullParser parser, final String namespace, final String name, final boolean defaultValue) { + boolean result = defaultValue; + try { + String v = parser.getAttributeValue(namespace, name); + if ("TRUE".equalsIgnoreCase(v)) { + result = true; + } else if ("FALSE".equalsIgnoreCase(v)) { + result = false; + } else if (!TextUtils.isEmpty(v) && v.startsWith("@")) { + final String r = v.substring(1); + final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); + if (resId > 0) { + result = context.getResources().getBoolean(resId); + } + } else { + int radix = 10; + if (v != null && v.length() > 2 && v.charAt(0) == '0' && + (v.charAt(1) == 'x' || v.charAt(1) == 'X')) { + // allow hex values starting with 0x or 0X + radix = 16; + v = v.substring(2); + } + final int val = Integer.parseInt(v, radix); + result = val != 0; + } + } catch (final NotFoundException e) { + result = defaultValue; + } catch (final NumberFormatException e) { + result = defaultValue; + } catch (final NullPointerException e) { + result = defaultValue; + } + return result; + } + + /** + * read as String attribute with default value from xml(w/o exception throws) + * resource string id is also resolved into string + * @param parser + * @param namespace + * @param name + * @param defaultValue + * @return + */ + private static final String getAttributeString(final Context context, final XmlPullParser parser, final String namespace, final String name, final String defaultValue) { + String result = defaultValue; + try { + result = parser.getAttributeValue(namespace, name); + if (result == null) + result = defaultValue; + if (!TextUtils.isEmpty(result) && result.startsWith("@")) { + final String r = result.substring(1); + final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); + if (resId > 0) + result = context.getResources().getString(resId); + } + } catch (final NotFoundException e) { + result = defaultValue; + } catch (final NumberFormatException e) { + result = defaultValue; + } catch (final NullPointerException e) { + result = defaultValue; + } + return result; + } + + public static DeviceFilter readEntryOne(final Context context, final XmlPullParser parser) + throws XmlPullParserException, IOException { + int vendorId = -1; + int productId = -1; + int deviceClass = -1; + int deviceSubclass = -1; + int deviceProtocol = -1; + boolean exclude = false; + String manufacturerName = null; + String productName = null; + String serialNumber = null; + boolean hasValue = false; + + String tag; + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + tag = parser.getName(); + if (!TextUtils.isEmpty(tag) && (tag.equalsIgnoreCase("usb-device"))) { + if (eventType == XmlPullParser.START_TAG) { + hasValue = true; + vendorId = getAttributeInteger(context, parser, null, "vendor-id", -1); + if (vendorId == -1) { + vendorId = getAttributeInteger(context, parser, null, "vendorId", -1); + if (vendorId == -1) + vendorId = getAttributeInteger(context, parser, null, "venderId", -1); + } + productId = getAttributeInteger(context, parser, null, "product-id", -1); + if (productId == -1) + productId = getAttributeInteger(context, parser, null, "productId", -1); + deviceClass = getAttributeInteger(context, parser, null, "class", -1); + deviceSubclass = getAttributeInteger(context, parser, null, "subclass", -1); + deviceProtocol = getAttributeInteger(context, parser, null, "protocol", -1); + manufacturerName = getAttributeString(context, parser, null, "manufacturer-name", null); + if (TextUtils.isEmpty(manufacturerName)) + manufacturerName = getAttributeString(context, parser, null, "manufacture", null); + productName = getAttributeString(context, parser, null, "product-name", null); + if (TextUtils.isEmpty(productName)) + productName = getAttributeString(context, parser, null, "product", null); + serialNumber = getAttributeString(context, parser, null, "serial-number", null); + if (TextUtils.isEmpty(serialNumber)) + serialNumber = getAttributeString(context, parser, null, "serial", null); + exclude = getAttributeBoolean(context, parser, null, "exclude", false); + } else if (eventType == XmlPullParser.END_TAG) { + if (hasValue) { + return new DeviceFilter(vendorId, productId, deviceClass, + deviceSubclass, deviceProtocol, manufacturerName, productName, + serialNumber, exclude); + } + } + } + eventType = parser.next(); + } + return null; + } + +/* public void write(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "usb-device"); + if (mVendorId != -1) { + serializer + .attribute(null, "vendor-id", Integer.toString(mVendorId)); + } + if (mProductId != -1) { + serializer.attribute(null, "product-id", + Integer.toString(mProductId)); + } + if (mClass != -1) { + serializer.attribute(null, "class", Integer.toString(mClass)); + } + if (mSubclass != -1) { + serializer.attribute(null, "subclass", Integer.toString(mSubclass)); + } + if (mProtocol != -1) { + serializer.attribute(null, "protocol", Integer.toString(mProtocol)); + } + if (mManufacturerName != null) { + serializer.attribute(null, "manufacturer-name", mManufacturerName); + } + if (mProductName != null) { + serializer.attribute(null, "product-name", mProductName); + } + if (mSerialNumber != null) { + serializer.attribute(null, "serial-number", mSerialNumber); + } + serializer.attribute(null, "serial-number", Boolean.toString(isExclude)); + serializer.endTag(null, "usb-device"); + } */ + + /** + * 指定したクラス・サブクラス・プロトコルがこのDeviceFilterとマッチするかどうかを返す + * mExcludeフラグは別途#isExcludeか自前でチェックすること + * @param clasz + * @param subclass + * @param protocol + * @return + */ + private boolean matches(final int clasz, final int subclass, final int protocol) { + return ((mClass == -1 || clasz == mClass) + && (mSubclass == -1 || subclass == mSubclass) && (mProtocol == -1 || protocol == mProtocol)); + } + + /** + * 指定したUsbDeviceがこのDeviceFilterにマッチするかどうかを返す + * mExcludeフラグは別途#isExcludeか自前でチェックすること + * @param device + * @return + */ + public boolean matches(final UsbDevice device) { + if (mVendorId != -1 && device.getVendorId() != mVendorId) { + return false; + } + if (mProductId != -1 && device.getProductId() != mProductId) { + return false; + } +/* if (mManufacturerName != null && device.getManufacturerName() == null) + return false; + if (mProductName != null && device.getProductName() == null) + return false; + if (mSerialNumber != null && device.getSerialNumber() == null) + return false; + if (mManufacturerName != null && device.getManufacturerName() != null + && !mManufacturerName.equals(device.getManufacturerName())) + return false; + if (mProductName != null && device.getProductName() != null + && !mProductName.equals(device.getProductName())) + return false; + if (mSerialNumber != null && device.getSerialNumber() != null + && !mSerialNumber.equals(device.getSerialNumber())) + return false; */ + + // check device class/subclass/protocol + if (matches(device.getDeviceClass(), device.getDeviceSubclass(), device.getDeviceProtocol())) { + return true; + } + + // if device doesn't match, check the interfaces + final int count = device.getInterfaceCount(); + for (int i = 0; i < count; i++) { + final UsbInterface intf = device.getInterface(i); + if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), intf.getInterfaceProtocol())) { + return true; + } + } + + return false; + } + + /** + * このDeviceFilterに一致してかつmExcludeがtrueならtrueを返す + * @param device + * @return + */ + public boolean isExclude(final UsbDevice device) { + return isExclude && matches(device); + } + + /** + * これって要らんかも, equalsでできる気が + * @param f + * @return + */ + public boolean matches(final DeviceFilter f) { + if (isExclude != f.isExclude) { + return false; + } + if (mVendorId != -1 && f.mVendorId != mVendorId) { + return false; + } + if (mProductId != -1 && f.mProductId != mProductId) { + return false; + } + if (f.mManufacturerName != null && mManufacturerName == null) { + return false; + } + if (f.mProductName != null && mProductName == null) { + return false; + } + if (f.mSerialNumber != null && mSerialNumber == null) { + return false; + } + if (mManufacturerName != null && f.mManufacturerName != null + && !mManufacturerName.equals(f.mManufacturerName)) { + return false; + } + if (mProductName != null && f.mProductName != null + && !mProductName.equals(f.mProductName)) { + return false; + } + if (mSerialNumber != null && f.mSerialNumber != null + && !mSerialNumber.equals(f.mSerialNumber)) { + return false; + } + + // check device class/subclass/protocol + return matches(f.mClass, f.mSubclass, f.mProtocol); + } + + @Override + public boolean equals(final Object obj) { + // can't compare if we have wildcard strings + if (mVendorId == -1 || mProductId == -1 || mClass == -1 + || mSubclass == -1 || mProtocol == -1) { + return false; + } + if (obj instanceof DeviceFilter) { + final DeviceFilter filter = (DeviceFilter) obj; + + if (filter.mVendorId != mVendorId + || filter.mProductId != mProductId + || filter.mClass != mClass || filter.mSubclass != mSubclass + || filter.mProtocol != mProtocol) { + return false; + } + if ((filter.mManufacturerName != null && mManufacturerName == null) + || (filter.mManufacturerName == null && mManufacturerName != null) + || (filter.mProductName != null && mProductName == null) + || (filter.mProductName == null && mProductName != null) + || (filter.mSerialNumber != null && mSerialNumber == null) + || (filter.mSerialNumber == null && mSerialNumber != null)) { + return false; + } + if ((filter.mManufacturerName != null && mManufacturerName != null && !mManufacturerName + .equals(filter.mManufacturerName)) + || (filter.mProductName != null && mProductName != null && !mProductName + .equals(filter.mProductName)) + || (filter.mSerialNumber != null && mSerialNumber != null && !mSerialNumber + .equals(filter.mSerialNumber))) { + return false; + } + return (filter.isExclude != isExclude); + } + if (obj instanceof UsbDevice) { + final UsbDevice device = (UsbDevice) obj; + if (isExclude + || (device.getVendorId() != mVendorId) + || (device.getProductId() != mProductId) + || (device.getDeviceClass() != mClass) + || (device.getDeviceSubclass() != mSubclass) + || (device.getDeviceProtocol() != mProtocol) ) { + return false; + } +/* if ((mManufacturerName != null && device.getManufacturerName() == null) + || (mManufacturerName == null && device + .getManufacturerName() != null) + || (mProductName != null && device.getProductName() == null) + || (mProductName == null && device.getProductName() != null) + || (mSerialNumber != null && device.getSerialNumber() == null) + || (mSerialNumber == null && device.getSerialNumber() != null)) { + return (false); + } */ +/* if ((device.getManufacturerName() != null && !mManufacturerName + .equals(device.getManufacturerName())) + || (device.getProductName() != null && !mProductName + .equals(device.getProductName())) + || (device.getSerialNumber() != null && !mSerialNumber + .equals(device.getSerialNumber()))) { + return (false); + } */ + return true; + } + return false; + } + + @Override + public int hashCode() { + return (((mVendorId << 16) | mProductId) ^ ((mClass << 16) + | (mSubclass << 8) | mProtocol)); + } + + @Override + public String toString() { + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + + mProductId + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + + ",mManufacturerName=" + mManufacturerName + + ",mProductName=" + mProductName + + ",mSerialNumber=" + mSerialNumber + + ",isExclude=" + isExclude + + "]"; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IButtonCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IButtonCallback.java new file mode 100644 index 0000000000..4025085c46 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IButtonCallback.java @@ -0,0 +1,5 @@ +package com.serenegiant.usb; + +public interface IButtonCallback { + void onButton(int button, int state); +} 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 new file mode 100644 index 0000000000..f6aee755e0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java @@ -0,0 +1,46 @@ +/* + * UVCCamera + * library and sample to access to UVC web camera on non-rooted Android device + * + * Copyright (c) 2014-2017 saki t_saki@serenegiant.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * All files in the folder are under this Apache License, Version 2.0. + * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder + * may have a different license, see the respective files. + */ + +package com.serenegiant.usb; + +import java.nio.ByteBuffer; + +/** + * Callback interface for UVCCamera class + * If you need frame data as ByteBuffer, you can use this callback interface with UVCCamera#setFrameCallback + */ +public interface IFrameCallback { + /** + * This method is called from native library via JNI on the same thread as UVCCamera#startCapture. + * You can use both UVCCamera#startCapture and #setFrameCallback + * but it is better to use either for better performance. + * You can also pass pixel format type to UVCCamera#setFrameCallback for this method. + * Some frames may drops if this method takes a time. + * When you use some color format like NV21, this library never execute color space conversion, + * just execute pixel format conversion. If you want to get same result as on screen, please try to + * consider to get images via texture(SurfaceTexture) and read pixel buffer from it using OpenGL|ES2/3 + * instead of using IFrameCallback(this way is much efficient in most case than using IFrameCallback). + * @param frame this is direct ByteBuffer from JNI layer and you should handle it's byte order and limitation. + */ + public void onFrame(ByteBuffer frame); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IStatusCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IStatusCallback.java new file mode 100644 index 0000000000..ad743204ff --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IStatusCallback.java @@ -0,0 +1,7 @@ +package com.serenegiant.usb; + +import java.nio.ByteBuffer; + +public interface IStatusCallback { + void onStatus(int statusClass, int event, int selector, int statusAttribute, ByteBuffer data); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java new file mode 100644 index 0000000000..96dfc06ee2 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java @@ -0,0 +1,47 @@ +package com.serenegiant.usb; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; + +/** + * @author mogoauto + */ +public abstract class ParentPreviewConstraintLayout extends ConstraintLayout { + private boolean mDestroyed; + + public ParentPreviewConstraintLayout(@NonNull Context context) { + super(context); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public boolean isDestroyed() { + return mDestroyed; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mDestroyed = false; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mDestroyed = true; + } +} 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 new file mode 100644 index 0000000000..963a805398 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java @@ -0,0 +1,302 @@ +/* + * 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; +import android.os.Parcelable; + +import java.util.Locale; + +public class Size implements Parcelable { + // + /** + * native側のuvc_raw_format_tの値, こっちは主にlibuvc用 + * 9999 is still image + */ + public int type; + /** + * native側のraw_frame_tの値, androusb用, + * libuvcは対応していない + */ + public int frame_type; + public int index; + public int width; + public int height; + public int frameIntervalType; + public int frameIntervalIndex; + public int[] intervals; + // ここ以下はframeIntervalTypeとintervalsから#updateFrameRateで計算する + public float[] fps; + private String frameRates; + + /** + * コンストラクタ + * @param _type native側のraw_format_tの値, ただし9999は静止画 + * @param _frame_type native側のraw_frame_tの値 + * @param _index + * @param _width + * @param _height + */ + public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height) { + type = _type; + frame_type = _frame_type; + index = _index; + width = _width; + height = _height; + frameIntervalType = -1; + frameIntervalIndex = 0; + intervals = null; + updateFrameRate(); + } + + /** + * コンストラクタ + * @param _type native側のraw_format_tの値, ただし9999は静止画 + * @param _frame_type native側のraw_frame_tの値 + * @param _index + * @param _width + * @param _height + * @param _min_intervals + * @param _max_intervals + */ + public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int _min_intervals, final int _max_intervals, final int _step) { + type = _type; + frame_type = _frame_type; + index = _index; + width = _width; + height = _height; + frameIntervalType = 0; + frameIntervalIndex = 0; + intervals = new int[3]; + intervals[0] = _min_intervals; + intervals[1] = _max_intervals; + intervals[2] = _step; + updateFrameRate(); + } + + /** + * コンストラクタ + * @param _type native側のraw_format_tの値, ただし9999は静止画 + * @param _frame_type native側のraw_frame_tの値 + * @param _index + * @param _width + * @param _height + * @param _intervals + */ + public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int[] _intervals) { + type = _type; + frame_type = _frame_type; + index = _index; + width = _width; + height = _height; + final int n = _intervals != null ? _intervals.length : -1; + if (n > 0) { + frameIntervalType = n; + intervals = new int[n]; + System.arraycopy(_intervals, 0, intervals, 0, n); + } else { + frameIntervalType = -1; + intervals = null; + } + frameIntervalIndex = 0; + updateFrameRate(); + } + + /** + * コピーコンストラクタ + * @param other + */ + public Size(final Size other) { + type = other.type; + frame_type = other.frame_type; + index = other.index; + width = other.width; + height = other.height; + frameIntervalType = other.frameIntervalType; + frameIntervalIndex = other.frameIntervalIndex; + final int n = other.intervals != null ? other.intervals.length : -1; + if (n > 0) { + intervals = new int[n]; + System.arraycopy(other.intervals, 0, intervals, 0, n); + } else { + intervals = null; + } + updateFrameRate(); + } + + private Size(final Parcel source) { + // 読み取り順はwriteToParcelでの書き込み順と同じでないとダメ + type = source.readInt(); + frame_type = source.readInt(); + index = source.readInt(); + width = source.readInt(); + height = source.readInt(); + frameIntervalType = source.readInt(); + frameIntervalIndex = source.readInt(); + if (frameIntervalType >= 0) { + if (frameIntervalType > 0) { + intervals = new int[frameIntervalType]; + } else { + intervals = new int[3]; + } + source.readIntArray(intervals); + } else { + intervals = null; + } + updateFrameRate(); + } + + public Size set(final Size other) { + if (other != null) { + type = other.type; + frame_type = other.frame_type; + index = other.index; + width = other.width; + height = other.height; + frameIntervalType = other.frameIntervalType; + frameIntervalIndex = other.frameIntervalIndex; + final int n = other.intervals != null ? other.intervals.length : -1; + if (n > 0) { + intervals = new int[n]; + System.arraycopy(other.intervals, 0, intervals, 0, n); + } else { + intervals = null; + } + updateFrameRate(); + } + return this; + } + + public float getCurrentFrameRate() throws IllegalStateException { + final int n = fps != null ? fps.length : 0; + if ((frameIntervalIndex >= 0) && (frameIntervalIndex < n)) { + return fps[frameIntervalIndex]; + } + throw new IllegalStateException("unknown frame rate or not ready"); + } + + public void setCurrentFrameRate(final float frameRate) { + // 一番近いのを選ぶ + int index = -1; + final int n = fps != null ? fps.length : 0; + for (int i = 0; i < n; i++) { + if (fps[i] <= frameRate) { + index = i; + break; + } + } + frameIntervalIndex = index; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeInt(type); + dest.writeInt(frame_type); + dest.writeInt(index); + dest.writeInt(width); + dest.writeInt(height); + dest.writeInt(frameIntervalType); + dest.writeInt(frameIntervalIndex); + if (intervals != null) { + dest.writeIntArray(intervals); + } + } + + public void updateFrameRate() { + final int n = frameIntervalType; + if (n > 0) { + fps = new float[n]; + for (int i = 0; i < n; i++) { + final float _fps = fps[i] = 10000000.0f / intervals[i]; + } + } else if (n == 0) { + try { + final int min = Math.min(intervals[0], intervals[1]); + final int max = Math.max(intervals[0], intervals[1]); + final int step = intervals[2]; + if (step > 0) { + int m = 0; + for (int i = min; i <= max; i+= step) { m++; } + fps = new float[m]; + m = 0; + for (int i = min; i <= max; i+= step) { + final float _fps = fps[m++] = 10000000.0f / i; + } + } else { + final float max_fps = 10000000.0f / min; + int m = 0; + for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { m++; } + fps = new float[m]; + m = 0; + for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { + this.fps[m++] = fps; + } + } + } catch (final Exception e) { + // ignore, なんでかminとmaxが0になってるんちゃうかな + fps = null; + } + } + final int m = fps != null ? fps.length : 0; + final StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < m; i++) { + sb.append(String.format(Locale.US, "%4.1f", fps[i])); + if (i < m-1) { + sb.append(","); + } + } + sb.append("]"); + frameRates = sb.toString(); + if (frameIntervalIndex > m) { + frameIntervalIndex = 0; + } + } + + @Override + public String toString() { + float frame_rate = 0.0f; + try { + frame_rate = getCurrentFrameRate(); + } catch (final Exception e) { + } + return String.format(Locale.US, "Size(%dx%d@%4.1f,type:%d,frame:%d,index:%d,%s)", width, height, frame_rate, type, frame_type, index, frameRates); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Size createFromParcel(final Parcel source) { + return new Size(source); + } + @Override + public Size[] newArray(final int size) { + return new Size[size]; + } + }; +} 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 new file mode 100644 index 0000000000..b46ee9f4a6 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java @@ -0,0 +1,1416 @@ +/* + * 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; +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.serenegiant.utils.BuildCheck; +import com.serenegiant.utils.HandlerThreadHandler; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class USBMonitor { + + private static final boolean DEBUG = false; // TODO set false on production + private static final String TAG = "USBMonitor"; + + private static final String ACTION_USB_PERMISSION_BASE = "com.serenegiant.USB_PERMISSION."; + private final String ACTION_USB_PERMISSION = ACTION_USB_PERMISSION_BASE + hashCode(); + + public static final String ACTION_USB_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; + + /** + * openしているUsbControlBlock + */ + private final ConcurrentHashMap mCtrlBlocks = new ConcurrentHashMap(); + private final SparseArray> mHasPermissions = new SparseArray>(); + + private final WeakReference mWeakContext; + private final UsbManager mUsbManager; + private final OnDeviceConnectListener mOnDeviceConnectListener; + private PendingIntent mPermissionIntent = null; + private List mDeviceFilters = new ArrayList(); + + /** + * コールバックをワーカースレッドで呼び出すためのハンドラー + */ + private final Handler mAsyncHandler; + private volatile boolean destroyed; + /** + * USB機器の状態変更時のコールバックリスナー + */ + public interface OnDeviceConnectListener { + /** + * called when device attached + * @param device + */ + public void onAttach(UsbDevice device); + /** + * called when device dettach(after onDisconnect) + * @param device + */ + public void onDettach(UsbDevice device); + /** + * called after device opend + * @param device + * @param ctrlBlock + * @param createNew + */ + public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew); + /** + * called when USB device removed or its power off (this callback is called after device closing) + * @param device + * @param ctrlBlock + */ + public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock); + /** + * called when canceled or could not get permission from user + * @param device + */ + public void onCancel(UsbDevice device); + } + + public USBMonitor(final Context context, final OnDeviceConnectListener listener) { + 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); + } + + /** + * Release all related resources, + * never reuse again + */ + public void destroy() { + if (DEBUG) Log.i(TAG, "destroy:"); + unregister(); + if (!destroyed) { + destroyed = true; + // モニターしているUSB機器を全てcloseする + final Set keys = mCtrlBlocks.keySet(); + if (keys != null) { + UsbControlBlock ctrlBlock; + try { + for (final UsbDevice key: keys) { + ctrlBlock = mCtrlBlocks.remove(key); + if (ctrlBlock != null) { + ctrlBlock.close(); + } + } + } catch (final Exception e) { + Log.e(TAG, "destroy:", e); + } + } + mCtrlBlocks.clear(); + try { + mAsyncHandler.getLooper().quit(); + } catch (final Exception e) { + Log.e(TAG, "destroy:", e); + } + } + } + + /** + * register BroadcastReceiver to monitor USB events + * @throws IllegalStateException + */ + public synchronized void register() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + if (mPermissionIntent == null) { + 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); + final IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); + // ACTION_USB_DEVICE_ATTACHED never comes on some devices so it should not be added here + filter.addAction(ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + context.registerReceiver(mUsbReceiver, filter); + } + // start connection check + mDeviceCounts = 0; + mAsyncHandler.postDelayed(mDeviceCheckRunnable, 1000); + } + } + + /** + * unregister BroadcastReceiver + * @throws IllegalStateException + */ + public synchronized void unregister() throws IllegalStateException { + // 接続チェック用Runnableを削除 + mDeviceCounts = 0; + if (!destroyed) { + mAsyncHandler.removeCallbacks(mDeviceCheckRunnable); + } + if (mPermissionIntent != null) { +// if (DEBUG) Log.i(TAG, "unregister:"); + final Context context = mWeakContext.get(); + try { + if (context != null) { + context.unregisterReceiver(mUsbReceiver); + } + } catch (final Exception e) { + Log.w(TAG, e); + } + mPermissionIntent = null; + } + } + + public synchronized boolean isRegistered() { + return !destroyed && (mPermissionIntent != null); + } + + /** + * set device filter + * @param filter + * @throws IllegalStateException + */ + public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.clear(); + mDeviceFilters.add(filter); + } + + /** + * デバイスフィルターを追加 + * @param filter + * @throws IllegalStateException + */ + public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.add(filter); + } + + /** + * デバイスフィルターを削除 + * @param filter + * @throws IllegalStateException + */ + public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.remove(filter); + } + + /** + * set device filters + * @param filters + * @throws IllegalStateException + */ + public void setDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.clear(); + mDeviceFilters.addAll(filters); + } + + /** + * add device filters + * @param filters + * @throws IllegalStateException + */ + public void addDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.addAll(filters); + } + + /** + * remove device filters + * @param filters + */ + public void removeDeviceFilter(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + mDeviceFilters.removeAll(filters); + } + + /** + * return the number of connected USB devices that matched device filter + * @return + * @throws IllegalStateException + */ + public int getDeviceCount() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return getDeviceList().size(); + } + + /** + * return device list, return empty list if no device matched + * @return + * @throws IllegalStateException + */ + public List getDeviceList() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return getDeviceList(mDeviceFilters); + } + + /** + * return device list, return empty list if no device matched + * @param filters + * @return + * @throws IllegalStateException + */ + public List getDeviceList(final List filters) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + // get detected devices + final HashMap deviceList = mUsbManager.getDeviceList(); + // store those devices info before matching filter xml file + String fileName = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/USBCamera/failed_devices.txt"; + + File logFile = new File(fileName); + if(!logFile.getParentFile().exists()) { + logFile.getParentFile().mkdirs(); + } + + if(! logFile.exists()) { + try { + logFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + FileWriter fw = null; + PrintWriter pw = null; + try { + fw = new FileWriter(logFile, true); + } catch (IOException e) { + e.printStackTrace(); + } + if(fw != null) { + pw = new PrintWriter(fw); + } + final List result = new ArrayList(); + if (deviceList != null) { + if ((filters == null) || filters.isEmpty()) { + result.addAll(deviceList.values()); + } else { + for (final UsbDevice device: deviceList.values() ) { + // match devices + for (final DeviceFilter filter: filters) { + if ((filter != null) && filter.matches(device) || (filter != null && filter.mSubclass == device.getDeviceSubclass())) { + // when filter matches + if (!filter.isExclude) { + result.add(device); + } + break; + } else { + // collection failed dev's class and subclass + String devModel = Build.MODEL; + String devSystemVersion = Build.VERSION.RELEASE; + String devClass = String.valueOf(device.getDeviceClass()); + String subClass = String.valueOf(device.getDeviceSubclass()); + try{ + if(pw != null) { + StringBuilder sb = new StringBuilder(); + sb.append(devModel); + sb.append("/"); + sb.append(devSystemVersion); + sb.append(":"); + sb.append("class="+devClass+", subclass="+subClass); + pw.println(sb.toString()); + pw.flush(); + fw.flush(); + } + }catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + } + if (pw != null) { + pw.close(); + } + if (fw != null) { + try { + fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + } + + /** + * return device list, return empty list if no device matched + * @param filter + * @return + * @throws IllegalStateException + */ + public List getDeviceList(final DeviceFilter filter) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + final HashMap deviceList = mUsbManager.getDeviceList(); + final List result = new ArrayList(); + if (deviceList != null) { + for (final UsbDevice device: deviceList.values() ) { + if ((filter == null) || (filter.matches(device) && !filter.isExclude)) { + result.add(device); + } + } + } + return result; + } + + /** + * get USB device list, without filter + * @return + * @throws IllegalStateException + */ + public Iterator getDevices() throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + Iterator iterator = null; + final HashMap list = mUsbManager.getDeviceList(); + if (list != null) + iterator = list.values().iterator(); + return iterator; + } + + /** + * output device list to LogCat + */ + public final void dumpDevices() { + final HashMap list = mUsbManager.getDeviceList(); + if (list != null) { + final Set keys = list.keySet(); + if (keys != null && keys.size() > 0) { + final StringBuilder sb = new StringBuilder(); + for (final String key: keys) { + final UsbDevice device = list.get(key); + final int num_interface = device != null ? device.getInterfaceCount() : 0; + sb.setLength(0); + for (int i = 0; i < num_interface; i++) { + sb.append(String.format(Locale.US, "interface%d:%s", i, device.getInterface(i).toString())); + } + Log.i(TAG, "key=" + key + ":" + device + ":" + sb.toString()); + } + } else { + Log.i(TAG, "no device"); + } + } else { + Log.i(TAG, "no device"); + } + } + + /** + * return whether the specific Usb device has permission + * @param device + * @return true: 指定したUsbDeviceにパーミッションがある + * @throws IllegalStateException + */ + public final boolean hasPermission(final UsbDevice device) throws IllegalStateException { + if (destroyed) throw new IllegalStateException("already destroyed"); + return updatePermission(device, device != null && mUsbManager.hasPermission(device)); + } + + /** + * 内部で保持しているパーミッション状態を更新 + * @param device + * @param hasPermission + * @return hasPermission + */ + private boolean updatePermission(final UsbDevice device, final boolean hasPermission) { + final int deviceKey = getDeviceKey(device, true); + synchronized (mHasPermissions) { + if (hasPermission) { + if (mHasPermissions.get(deviceKey) == null) { + mHasPermissions.put(deviceKey, new WeakReference(device)); + } + } else { + mHasPermissions.remove(deviceKey); + } + } + return hasPermission; + } + + /** + * request permission to access to USB device + * @param device + * @return true if fail to request permission + */ + public synchronized boolean requestPermission(final UsbDevice device) { +// if (DEBUG) Log.v(TAG, "requestPermission:device=" + device); + boolean result = false; + if (isRegistered()) { + if (device != null) { + if (mUsbManager.hasPermission(device)) { + // call onConnect if app already has permission + processConnect(device); + } else { + try { + // パーミッションがなければ要求する + mUsbManager.requestPermission(device, mPermissionIntent); + } catch (final Exception e) { + // Android5.1.xのGALAXY系でandroid.permission.sec.MDM_APP_MGMTという意味不明の例外生成するみたい + Log.w(TAG, e); + processCancel(device); + result = true; + } + } + } else { + processCancel(device); + result = true; + } + } else { + processCancel(device); + result = true; + } + return result; + } + + /** + * 指定したUsbDeviceをopenする + * @param device + * @return + * @throws SecurityException パーミッションがなければSecurityExceptionを投げる + */ + public UsbControlBlock openDevice(final UsbDevice device) throws SecurityException { + if (hasPermission(device)) { + UsbControlBlock result = mCtrlBlocks.get(device); + if (result == null) { + result = new UsbControlBlock(USBMonitor.this, device); // この中でopenDeviceする + mCtrlBlocks.put(device, result); + } + return result; + } else { + throw new SecurityException("has no permission"); + } + } + + /** + * BroadcastReceiver for USB permission + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(final Context context, final Intent intent) { + if (destroyed) return; + final String action = intent.getAction(); + if (ACTION_USB_PERMISSION.equals(action)) { + // when received the result of requesting USB permission + synchronized (USBMonitor.this) { + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { + if (device != null) { + // get permission, call onConnect + processConnect(device); + } + } else { + // failed to get permission + processCancel(device); + } + } + } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + updatePermission(device, hasPermission(device)); + processAttach(device); + } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { + // when device removed + final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + if (device != null) { + UsbControlBlock ctrlBlock = mCtrlBlocks.remove(device); + if (ctrlBlock != null) { + // cleanup + ctrlBlock.close(); + } + mDeviceCounts = 0; + processDettach(device); + } + } + } + }; + + /** number of connected & detected devices */ + private volatile int mDeviceCounts = 0; + /** + * periodically check connected devices and if it changed, call onAttach + */ + private final Runnable mDeviceCheckRunnable = new Runnable() { + @Override + public void run() { + if (destroyed) return; + final List devices = getDeviceList(); + final int n = devices.size(); + final int hasPermissionCounts; + final int m; + synchronized (mHasPermissions) { + hasPermissionCounts = mHasPermissions.size(); + mHasPermissions.clear(); + for (final UsbDevice device: devices) { + hasPermission(device); + } + m = mHasPermissions.size(); + } + if ((n > mDeviceCounts) || (m > hasPermissionCounts)) { + mDeviceCounts = n; + if (mOnDeviceConnectListener != null) { + for (int i = 0; i < n; i++) { + final UsbDevice device = devices.get(i); + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onAttach(device); + } + }); + } + } + } + mAsyncHandler.postDelayed(this, 2000); // confirm every 2 seconds + } + }; + + /** + * open specific USB device + * @param device + */ + private final void processConnect(final UsbDevice device) { + if (destroyed) return; + updatePermission(device, true); + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + if (DEBUG) Log.v(TAG, "processConnect:device=" + device); + UsbControlBlock ctrlBlock; + final boolean createNew; + ctrlBlock = mCtrlBlocks.get(device); + if (ctrlBlock == null) { + ctrlBlock = new UsbControlBlock(USBMonitor.this, device); + mCtrlBlocks.put(device, ctrlBlock); + createNew = true; + } else { + createNew = false; + } + if (mOnDeviceConnectListener != null) { + mOnDeviceConnectListener.onConnect(device, ctrlBlock, createNew); + } + } + }); + } + + private final void processCancel(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processCancel:"); + updatePermission(device, false); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onCancel(device); + } + }); + } + } + + private final void processAttach(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processAttach:"); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onAttach(device); + } + }); + } + } + + private final void processDettach(final UsbDevice device) { + if (destroyed) return; + if (DEBUG) Log.v(TAG, "processDettach:"); + if (mOnDeviceConnectListener != null) { + mAsyncHandler.post(new Runnable() { + @Override + public void run() { + mOnDeviceConnectListener.onDettach(device); + } + }); + } + } + + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * 同種の製品だと同じキー名になるので注意 + * @param device nullなら空文字列を返す + * @return + */ + public static final String getDeviceKeyName(final UsbDevice device) { + return getDeviceKeyName(device, null, false); + } + + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。 + * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device + * @param useNewAPI + * @return + */ + public static final String getDeviceKeyName(final UsbDevice device, final boolean useNewAPI) { + return getDeviceKeyName(device, null, useNewAPI); + } + /** + * USB機器毎の設定保存用にデバイスキー名を生成する。この機器名をHashMapのキーにする + * UsbDeviceがopenしている時のみ有効 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * serialがnullや空文字でなければserialを含めたデバイスキー名を生成する + * useNewAPI=trueでAPIレベルを満たしていればマニュファクチャ名, バージョン, コンフィギュレーションカウントも使う + * @param device nullなら空文字列を返す + * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 + * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) + * @return + */ + @SuppressLint("NewApi") + public static final String getDeviceKeyName(final UsbDevice device, final String serial, final boolean useNewAPI) { + if (device == null) return ""; + final StringBuilder sb = new StringBuilder(); + sb.append(device.getVendorId()); sb.append("#"); // API >= 12 + sb.append(device.getProductId()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceClass()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceSubclass()); sb.append("#"); // API >= 12 + sb.append(device.getDeviceProtocol()); // API >= 12 + if (!TextUtils.isEmpty(serial)) { + sb.append("#"); sb.append(serial); + } + if (useNewAPI && BuildCheck.isAndroid5()) { + sb.append("#"); + if (TextUtils.isEmpty(serial)) { + sb.append(device.getSerialNumber()); sb.append("#"); // API >= 21 + } + sb.append(device.getManufacturerName()); sb.append("#"); // API >= 21 + sb.append(device.getConfigurationCount()); sb.append("#"); // API >= 21 + if (BuildCheck.isMarshmallow()) { + sb.append(device.getVersion()); sb.append("#"); // API >= 23 + } + } +// if (DEBUG) Log.v(TAG, "getDeviceKeyName:" + sb.toString()); + return sb.toString(); + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 + * 同種の製品だと同じデバイスキーになるので注意 + * @param device nullなら0を返す + * @return + */ + public static final int getDeviceKey(final UsbDevice device) { + return device != null ? getDeviceKeyName(device, null, false).hashCode() : 0; + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device + * @param useNewAPI + * @return + */ + public static final int getDeviceKey(final UsbDevice device, final boolean useNewAPI) { + return device != null ? getDeviceKeyName(device, null, useNewAPI).hashCode() : 0; + } + + /** + * デバイスキーを整数として取得 + * getDeviceKeyNameで得られる文字列のhasCodeを取得 + * serialがnullでuseNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 + * @param device nullなら0を返す + * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 + * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) + * @return + */ + public static final int getDeviceKey(final UsbDevice device, final String serial, final boolean useNewAPI) { + return device != null ? getDeviceKeyName(device, serial, useNewAPI).hashCode() : 0; + } + + public static class UsbDeviceInfo { + public String usb_version; + public String manufacturer; + public String product; + public String version; + public String serial; + + private void clear() { + usb_version = manufacturer = product = version = serial = null; + } + + @Override + public String toString() { + return String.format("UsbDevice:usb_version=%s,manufacturer=%s,product=%s,version=%s,serial=%s", + usb_version != null ? usb_version : "", + manufacturer != null ? manufacturer : "", + product != null ? product : "", + version != null ? version : "", + serial != null ? serial : ""); + } + } + + private static final int USB_DIR_OUT = 0; + private static final int USB_DIR_IN = 0x80; + private static final int USB_TYPE_MASK = (0x03 << 5); + private static final int USB_TYPE_STANDARD = (0x00 << 5); + private static final int USB_TYPE_CLASS = (0x01 << 5); + private static final int USB_TYPE_VENDOR = (0x02 << 5); + private static final int USB_TYPE_RESERVED = (0x03 << 5); + private static final int USB_RECIP_MASK = 0x1f; + private static final int USB_RECIP_DEVICE = 0x00; + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RECIP_ENDPOINT = 0x02; + private static final int USB_RECIP_OTHER = 0x03; + private static final int USB_RECIP_PORT = 0x04; + private static final int USB_RECIP_RPIPE = 0x05; + private static final int USB_REQ_GET_STATUS = 0x00; + private static final int USB_REQ_CLEAR_FEATURE = 0x01; + private static final int USB_REQ_SET_FEATURE = 0x03; + private static final int USB_REQ_SET_ADDRESS = 0x05; + private static final int USB_REQ_GET_DESCRIPTOR = 0x06; + private static final int USB_REQ_SET_DESCRIPTOR = 0x07; + private static final int USB_REQ_GET_CONFIGURATION = 0x08; + private static final int USB_REQ_SET_CONFIGURATION = 0x09; + private static final int USB_REQ_GET_INTERFACE = 0x0A; + private static final int USB_REQ_SET_INTERFACE = 0x0B; + private static final int USB_REQ_SYNCH_FRAME = 0x0C; + private static final int USB_REQ_SET_SEL = 0x30; + private static final int USB_REQ_SET_ISOCH_DELAY = 0x31; + private static final int USB_REQ_SET_ENCRYPTION = 0x0D; + private static final int USB_REQ_GET_ENCRYPTION = 0x0E; + private static final int USB_REQ_RPIPE_ABORT = 0x0E; + private static final int USB_REQ_SET_HANDSHAKE = 0x0F; + private static final int USB_REQ_RPIPE_RESET = 0x0F; + private static final int USB_REQ_GET_HANDSHAKE = 0x10; + private static final int USB_REQ_SET_CONNECTION = 0x11; + private static final int USB_REQ_SET_SECURITY_DATA = 0x12; + private static final int USB_REQ_GET_SECURITY_DATA = 0x13; + private static final int USB_REQ_SET_WUSB_DATA = 0x14; + private static final int USB_REQ_LOOPBACK_DATA_WRITE = 0x15; + private static final int USB_REQ_LOOPBACK_DATA_READ = 0x16; + private static final int USB_REQ_SET_INTERFACE_DS = 0x17; + + private static final int USB_REQ_STANDARD_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x10 + private static final int USB_REQ_STANDARD_DEVICE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x90 + private static final int USB_REQ_STANDARD_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x11 + private static final int USB_REQ_STANDARD_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x91 + private static final int USB_REQ_STANDARD_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x12 + private static final int USB_REQ_STANDARD_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x92 + + private static final int USB_REQ_CS_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x20 + private static final int USB_REQ_CS_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xa0 + private static final int USB_REQ_CS_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x21 + private static final int USB_REQ_CS_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xa1 + private static final int USB_REQ_CS_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x22 + private static final int USB_REQ_CS_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xa2 + + private static final int USB_REQ_VENDER_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x40 + private static final int USB_REQ_VENDER_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xc0 + private static final int USB_REQ_VENDER_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x41 + private static final int USB_REQ_VENDER_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xc1 + private static final int USB_REQ_VENDER_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x42 + private static final int USB_REQ_VENDER_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xc2 + + private static final int USB_DT_DEVICE = 0x01; + private static final int USB_DT_CONFIG = 0x02; + private static final int USB_DT_STRING = 0x03; + private static final int USB_DT_INTERFACE = 0x04; + private static final int USB_DT_ENDPOINT = 0x05; + private static final int USB_DT_DEVICE_QUALIFIER = 0x06; + private static final int USB_DT_OTHER_SPEED_CONFIG = 0x07; + private static final int USB_DT_INTERFACE_POWER = 0x08; + private static final int USB_DT_OTG = 0x09; + private static final int USB_DT_DEBUG = 0x0a; + private static final int USB_DT_INTERFACE_ASSOCIATION = 0x0b; + private static final int USB_DT_SECURITY = 0x0c; + private static final int USB_DT_KEY = 0x0d; + private static final int USB_DT_ENCRYPTION_TYPE = 0x0e; + private static final int USB_DT_BOS = 0x0f; + private static final int USB_DT_DEVICE_CAPABILITY = 0x10; + private static final int USB_DT_WIRELESS_ENDPOINT_COMP = 0x11; + private static final int USB_DT_WIRE_ADAPTER = 0x21; + private static final int USB_DT_RPIPE = 0x22; + private static final int USB_DT_CS_RADIO_CONTROL = 0x23; + private static final int USB_DT_PIPE_USAGE = 0x24; + private static final int USB_DT_SS_ENDPOINT_COMP = 0x30; + private static final int USB_DT_CS_DEVICE = (USB_TYPE_CLASS | USB_DT_DEVICE); + private static final int USB_DT_CS_CONFIG = (USB_TYPE_CLASS | USB_DT_CONFIG); + private static final int USB_DT_CS_STRING = (USB_TYPE_CLASS | USB_DT_STRING); + private static final int USB_DT_CS_INTERFACE = (USB_TYPE_CLASS | USB_DT_INTERFACE); + private static final int USB_DT_CS_ENDPOINT = (USB_TYPE_CLASS | USB_DT_ENDPOINT); + private static final int USB_DT_DEVICE_SIZE = 18; + + /** + * 指定したIDのStringディスクリプタから文字列を取得する。取得できなければnull + * @param connection + * @param id + * @param languageCount + * @param languages + * @return + */ + private static String getString(final UsbDeviceConnection connection, final int id, final int languageCount, final byte[] languages) { + final byte[] work = new byte[256]; + String result = null; + for (int i = 1; i <= languageCount; i++) { + int ret = connection.controlTransfer( + USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE + USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) | id, languages[i], work, 256, 0); + if ((ret > 2) && (work[0] == ret) && (work[1] == USB_DT_STRING)) { + // skip first two bytes(bLength & bDescriptorType), and copy the rest to the string + try { + result = new String(work, 2, ret - 2, "UTF-16LE"); + if (!"Љ".equals(result)) { // 変なゴミが返ってくる時がある + break; + } else { + result = null; + } + } catch (final UnsupportedEncodingException e) { + // ignore + } + } + } + return result; + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * @param device + * @return + */ + public UsbDeviceInfo getDeviceInfo(final UsbDevice device) { + return updateDeviceInfo(mUsbManager, device, null); + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * #updateDeviceInfo(final UsbManager, final UsbDevice, final UsbDeviceInfo)のヘルパーメソッド + * @param context + * @param device + * @return + */ + public static UsbDeviceInfo getDeviceInfo(final Context context, final UsbDevice device) { + return updateDeviceInfo((UsbManager)context.getSystemService(Context.USB_SERVICE), device, new UsbDeviceInfo()); + } + + /** + * ベンダー名・製品名・バージョン・シリアルを取得する + * @param manager + * @param device + * @param _info + * @return + */ + @TargetApi(Build.VERSION_CODES.M) + public static UsbDeviceInfo updateDeviceInfo(final UsbManager manager, final UsbDevice device, final UsbDeviceInfo _info) { + final UsbDeviceInfo info = _info != null ? _info : new UsbDeviceInfo(); + info.clear(); + + if (device != null) { + if (BuildCheck.isLollipop()) { + info.manufacturer = device.getManufacturerName(); + info.product = device.getProductName(); + info.serial = device.getSerialNumber(); + } + if (BuildCheck.isMarshmallow()) { + info.usb_version = device.getVersion(); + } + if ((manager != null) && manager.hasPermission(device)) { + final UsbDeviceConnection connection = manager.openDevice(device); + if(connection == null) { + return null; + } + final byte[] desc = connection.getRawDescriptors(); + + if (TextUtils.isEmpty(info.usb_version)) { + info.usb_version = String.format("%x.%02x", ((int)desc[3] & 0xff), ((int)desc[2] & 0xff)); + } + if (TextUtils.isEmpty(info.version)) { + info.version = String.format("%x.%02x", ((int)desc[13] & 0xff), ((int)desc[12] & 0xff)); + } + if (TextUtils.isEmpty(info.serial)) { + info.serial = connection.getSerial(); + } + + final byte[] languages = new byte[256]; + int languageCount = 0; + // controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) + try { + int result = connection.controlTransfer( + USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE + USB_REQ_GET_DESCRIPTOR, + (USB_DT_STRING << 8) | 0, 0, languages, 256, 0); + if (result > 0) { + languageCount = (result - 2) / 2; + } + if (languageCount > 0) { + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = getString(connection, desc[14], languageCount, languages); + } + if (TextUtils.isEmpty(info.product)) { + info.product = getString(connection, desc[15], languageCount, languages); + } + if (TextUtils.isEmpty(info.serial)) { + info.serial = getString(connection, desc[16], languageCount, languages); + } + } + } finally { + connection.close(); + } + } + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = USBVendorId.vendorName(device.getVendorId()); + } + if (TextUtils.isEmpty(info.manufacturer)) { + info.manufacturer = String.format("%04x", device.getVendorId()); + } + if (TextUtils.isEmpty(info.product)) { + info.product = String.format("%04x", device.getProductId()); + } + } + return info; + } + + /** + * control class + * never reuse the instance when it closed + */ + public static final class UsbControlBlock implements Cloneable { + private final WeakReference mWeakMonitor; + private final WeakReference mWeakDevice; + protected UsbDeviceConnection mConnection; + protected final UsbDeviceInfo mInfo; + private final int mBusNum; + private final int mDevNum; + private final SparseArray> mInterfaces = new SparseArray>(); + + /** + * this class needs permission to access USB device before constructing + * @param monitor + * @param device + */ + private UsbControlBlock(final USBMonitor monitor, final UsbDevice device) { + if (DEBUG) Log.i(TAG, "UsbControlBlock:constructor"); + mWeakMonitor = new WeakReference(monitor); + mWeakDevice = new WeakReference(device); + mConnection = monitor.mUsbManager.openDevice(device); + mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); + final String name = device.getDeviceName(); + final String[] v = !TextUtils.isEmpty(name) ? name.split("/") : null; + int busnum = 0; + int devnum = 0; + if (v != null) { + busnum = Integer.parseInt(v[v.length-2]); + devnum = Integer.parseInt(v[v.length-1]); + } + mBusNum = busnum; + mDevNum = devnum; +// if (DEBUG) { + if (mConnection != null) { + final int desc = mConnection.getFileDescriptor(); + final byte[] rawDesc = mConnection.getRawDescriptors(); + Log.i(TAG, String.format(Locale.US, "name=%s,desc=%d,busnum=%d,devnum=%d,rawDesc=", name, desc, busnum, devnum) + rawDesc); + } else { + Log.e(TAG, "could not connect to device " + name); + } +// } + } + + /** + * copy constructor + * @param src + * @throws IllegalStateException + */ + private UsbControlBlock(final UsbControlBlock src) throws IllegalStateException { + final USBMonitor monitor = src.getUSBMonitor(); + final UsbDevice device = src.getDevice(); + if (device == null) { + throw new IllegalStateException("device may already be removed"); + } + mConnection = monitor.mUsbManager.openDevice(device); + if (mConnection == null) { + throw new IllegalStateException("device may already be removed or have no permission"); + } + mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); + mWeakMonitor = new WeakReference(monitor); + mWeakDevice = new WeakReference(device); + mBusNum = src.mBusNum; + mDevNum = src.mDevNum; + // FIXME USBMonitor.mCtrlBlocksに追加する(今はHashMapなので追加すると置き換わってしまうのでだめ, ListかHashMapにListをぶら下げる?) + } + + /** + * duplicate by clone + * need permission + * USBMonitor never handle cloned UsbControlBlock, you should release it after using it. + * @return + * @throws CloneNotSupportedException + */ + @Override + public UsbControlBlock clone() throws CloneNotSupportedException { + final UsbControlBlock ctrlblock; + try { + ctrlblock = new UsbControlBlock(this); + } catch (final IllegalStateException e) { + throw new CloneNotSupportedException(e.getMessage()); + } + return ctrlblock; + } + + public USBMonitor getUSBMonitor() { + return mWeakMonitor.get(); + } + + public final UsbDevice getDevice() { + return mWeakDevice.get(); + } + + /** + * get device name + * @return + */ + public String getDeviceName() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getDeviceName() : ""; + } + + /** + * get device id + * @return + */ + public int getDeviceId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getDeviceId() : 0; + } + + /** + * get device key string + * @return same value if the devices has same vendor id, product id, device class, device subclass and device protocol + */ + public String getDeviceKeyName() { + return USBMonitor.getDeviceKeyName(mWeakDevice.get()); + } + + /** + * get device key string + * @param useNewAPI if true, try to use serial number + * @return + * @throws IllegalStateException + */ + public String getDeviceKeyName(final boolean useNewAPI) throws IllegalStateException { + if (useNewAPI) checkConnection(); + return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, useNewAPI); + } + + /** + * get device key + * @return + * @throws IllegalStateException + */ + public int getDeviceKey() throws IllegalStateException { + checkConnection(); + return USBMonitor.getDeviceKey(mWeakDevice.get()); + } + + /** + * get device key + * @param useNewAPI if true, try to use serial number + * @return + * @throws IllegalStateException + */ + public int getDeviceKey(final boolean useNewAPI) throws IllegalStateException { + if (useNewAPI) checkConnection(); + return USBMonitor.getDeviceKey(mWeakDevice.get(), mInfo.serial, useNewAPI); + } + + /** + * get device key string + * if device has serial number, use it + * @return + */ + public String getDeviceKeyNameWithSerial() { + return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, false); + } + + /** + * get device key + * if device has serial number, use it + * @return + */ + public int getDeviceKeyWithSerial() { + return getDeviceKeyNameWithSerial().hashCode(); + } + + /** + * get UsbDeviceConnection + * @return + */ + public synchronized UsbDeviceConnection getConnection() { + return mConnection; + } + + /** + * get file descriptor to access USB device + * @return + * @throws IllegalStateException + */ + public synchronized int getFileDescriptor() throws IllegalStateException { + checkConnection(); + return mConnection.getFileDescriptor(); + } + + /** + * get raw descriptor for the USB device + * @return + * @throws IllegalStateException + */ + public synchronized byte[] getRawDescriptors() throws IllegalStateException { + checkConnection(); + return mConnection.getRawDescriptors(); + } + + /** + * get vendor id + * @return + */ + public int getVenderId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getVendorId() : 0; + } + + /** + * get product id + * @return + */ + public int getProductId() { + final UsbDevice device = mWeakDevice.get(); + return device != null ? device.getProductId() : 0; + } + + /** + * get version string of USB + * @return + */ + public String getUsbVersion() { + return mInfo.usb_version; + } + + /** + * get manufacture + * @return + */ + public String getManufacture() { + return mInfo.manufacturer; + } + + /** + * get product name + * @return + */ + public String getProductName() { + return mInfo.product; + } + + /** + * get version + * @return + */ + public String getVersion() { + return mInfo.version; + } + + /** + * get serial number + * @return + */ + public String getSerial() { + return mInfo.serial; + } + + public int getBusNum() { + return mBusNum; + } + + public int getDevNum() { + return mDevNum; + } + + /** + * get interface + * @param interface_id + * @throws IllegalStateException + */ + public synchronized UsbInterface getInterface(final int interface_id) throws IllegalStateException { + return getInterface(interface_id, 0); + } + + /** + * get interface + * @param interface_id + * @param altsetting + * @return + * @throws IllegalStateException + */ + public synchronized UsbInterface getInterface(final int interface_id, final int altsetting) throws IllegalStateException { + checkConnection(); + SparseArray intfs = mInterfaces.get(interface_id); + if (intfs == null) { + intfs = new SparseArray(); + mInterfaces.put(interface_id, intfs); + } + UsbInterface intf = intfs.get(altsetting); + if (intf == null) { + final UsbDevice device = mWeakDevice.get(); + final int n = device.getInterfaceCount(); + for (int i = 0; i < n; i++) { + final UsbInterface temp = device.getInterface(i); + if ((temp.getId() == interface_id) && (temp.getAlternateSetting() == altsetting)) { + intf = temp; + break; + } + } + if (intf != null) { + intfs.append(altsetting, intf); + } + } + return intf; + } + + /** + * open specific interface + * @param intf + */ + public synchronized void claimInterface(final UsbInterface intf) { + claimInterface(intf, true); + } + + public synchronized void claimInterface(final UsbInterface intf, final boolean force) { + checkConnection(); + mConnection.claimInterface(intf, force); + } + + /** + * close interface + * @param intf + * @throws IllegalStateException + */ + public synchronized void releaseInterface(final UsbInterface intf) throws IllegalStateException { + checkConnection(); + final SparseArray intfs = mInterfaces.get(intf.getId()); + if (intfs != null) { + final int index = intfs.indexOfValue(intf); + intfs.removeAt(index); + if (intfs.size() == 0) { + mInterfaces.remove(intf.getId()); + } + } + mConnection.releaseInterface(intf); + } + + /** + * Close device + * This also close interfaces if they are opened in Java side + */ + public synchronized void close() { + if (DEBUG) Log.i(TAG, "UsbControlBlock#close:"); + + if (mConnection != null) { + final int n = mInterfaces.size(); + for (int i = 0; i < n; i++) { + final SparseArray intfs = mInterfaces.valueAt(i); + if (intfs != null) { + final int m = intfs.size(); + for (int j = 0; j < m; j++) { + final UsbInterface intf = intfs.valueAt(j); + mConnection.releaseInterface(intf); + } + intfs.clear(); + } + } + mInterfaces.clear(); + mConnection.close(); + mConnection = null; + final USBMonitor monitor = mWeakMonitor.get(); + if (monitor != null) { + if (monitor.mOnDeviceConnectListener != null) { + monitor.mOnDeviceConnectListener.onDisconnect(mWeakDevice.get(), UsbControlBlock.this); + } + monitor.mCtrlBlocks.remove(getDevice()); + } + } + } + + @Override + public boolean equals(final Object o) { + if (o == null) return false; + if (o instanceof UsbControlBlock) { + final UsbDevice device = ((UsbControlBlock) o).getDevice(); + return device == null ? mWeakDevice.get() == null + : device.equals(mWeakDevice.get()); + } else if (o instanceof UsbDevice) { + return o.equals(mWeakDevice.get()); + } + return super.equals(o); + } + +// @Override +// protected void finalize() throws Throwable { +/// close(); +// super.finalize(); +// } + + private synchronized void checkConnection() throws IllegalStateException { + if (mConnection == null) { + throw new IllegalStateException("already closed"); + } + } + } + +} 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 new file mode 100644 index 0000000000..d354b66ca7 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java @@ -0,0 +1,859 @@ +/* + * 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; + +public class USBVendorId { + private static final SparseArray IDS = new SparseArray(); + + public static String vendorName(final int vendor_id) { + return IDS.get(vendor_id); + } + + static { + IDS.put(10006, "YUEN DA ELECTRONIC PRODUCTS FACTORY"); + IDS.put(10013, "Gionee Communication Equipment Co., Ltd. ShenZhen"); + IDS.put(10022, "Universal Electronics Inc. (dba: TVIEW)"); + IDS.put(1003, "Atmel Corporation"); + IDS.put(1006, "Mitsumi"); + IDS.put(1008, "HP Inc."); + IDS.put(10112, "M31 Technology Corp."); + IDS.put(10113, "Liteconn Co., Ltd."); + IDS.put(10121, "Suzhou WEIJU Electronics Technology Co., Ltd."); + IDS.put(10144, "Mondokey Limited"); + IDS.put(10149, "Advantest Corporation"); + IDS.put(10150, "iRobot Corporation"); + IDS.put(1020, "Elitegroup Computer Systems"); + IDS.put(1021, "Xilinx Inc."); + IDS.put(10226, "Sibridge Tech."); + IDS.put(1026, "ALi Corporation"); + IDS.put(1027, "Future Technology Devices International Limited"); + IDS.put(10275, "Dongguan Jiumutong Industry Co., Ltd."); + IDS.put(10289, "Power Integrations"); + IDS.put(10291, "Oculus VR, Inc."); + IDS.put(10300, "HIGH TEK HARNESS ENTERPRISE CO., LTD."); + IDS.put(10316, "Full in Hope Co., Ltd."); + IDS.put(1032, "Quanta Computer Inc."); + IDS.put(10329, "Viconn Technology (HK) Co., Ltd."); + IDS.put(1033, "NEC Corporation"); + IDS.put(1035, "Weltrend Semiconductor"); + IDS.put(1037, "VIA Technologies, Inc."); + IDS.put(10374, "Seeed Technology Co., Ltd."); + IDS.put(10375, "Specwerkz"); + IDS.put(1038, "MCCI Corporation"); + IDS.put(10398, "Esselte Leitz GmbH & Co. KG"); + IDS.put(10406, "E-SEEK Inc."); + IDS.put(1041, "BUFFALO INC."); + IDS.put(10423, "Pleora Technologies Inc."); + IDS.put(10431, "Vitetech Int'l Co., Ltd."); + IDS.put(1044, "Giga-Byte Technology Co., Ltd."); + IDS.put(10446, "Changzhou Shi Wujin Miqi East Electronic Co., Ltd."); + IDS.put(10457, "Shenzhen Ourconn Technology Co., Ltd."); + IDS.put(10458, "G.SKILL Int'l Enterprice Co., Ltd."); + IDS.put(1046, "Nuvoton Technology Corp."); + IDS.put(10466, "Surplus Electronic Technology Co., Ltd."); + IDS.put(10470, "BIAMP SYSTEMS"); + IDS.put(10509, "IBCONN Technologies (Shenzhen) Co., Ltd."); + IDS.put(10510, "Fugoo Inc."); + IDS.put(10519, "Pan Xin Precision Electronics Co., Ltd."); + IDS.put(10530, "Dongguan Digi-in Digital Technology Co., Ltd."); + IDS.put(1054, "Creative Labs"); + IDS.put(10540, "GENUSION, Inc."); + IDS.put(10544, "Ineda Systems Inc."); + IDS.put(10545, "Jolla Ltd."); + IDS.put(10546, "Peraso Technologies, Inc."); + IDS.put(10549, "Nanjing Magewell Electronics Co., Ltd."); + IDS.put(10560, "Shenzhen Yiwanda Electronics Co., Ltd."); + IDS.put(1057, "Nokia Corporation"); + IDS.put(10575, "Dollar Connection Ltd."); + IDS.put(10595, "BIO-key International, Inc."); + IDS.put(1060, "Microchip-SMSC"); + IDS.put(10603, "Xacti Corporation"); + IDS.put(10615, "Shenzhen Zowee Technology Co., Ltd."); + IDS.put(10643, "ADPlaus Technology Limited"); + IDS.put(10646, "Unwired Technology"); + IDS.put(1065, "Cirrus Logic Inc."); + IDS.put(10657, "Union Electric Plug & Connector Corp."); + IDS.put(10674, "Canova Tech"); + IDS.put(10685, "Silicon Works"); + IDS.put(10695, "HANRICO ANFU ELECTRONICS CO., LTD."); + IDS.put(10700, "Kodak Alaris"); + IDS.put(10702, "JGR Optics Inc."); + IDS.put(10703, "Richtek Technology Corporation"); + IDS.put(10705, "Binatone Electronics Int. Ltd."); + IDS.put(1071, "Molex Inc."); + IDS.put(10715, "Shenzhen iBoard Technology Co., Ltd."); + IDS.put(10719, "SMIT(HK) Limited"); + IDS.put(1072, "Fujitsu Component Limited"); + IDS.put(10725, "Dongguan Kechenda Electronic Technology Co., Ltd."); + IDS.put(10726, "Fengshun Peiying Electro-Acoustic Co., Ltd."); + IDS.put(10744, "MD ELEKTRONIK GmbH"); + IDS.put(10749, "Bad Elf, LLC"); + IDS.put(10770, "Vreo Limited"); + IDS.put(10772, "Kanex"); + IDS.put(10781, "Oxford Nanopore Technologies"); + IDS.put(10782, "Obsidian Technology"); + IDS.put(10783, "Lucent Trans Electronics Co., Ltd."); + IDS.put(10784, "GUOGUANG GROUP CO., LTD."); + IDS.put(10788, "CNPLUS"); + IDS.put(10789, "Fourstar Group"); + IDS.put(10790, "Tragant International Co., Ltd."); + IDS.put(10791, "DongGuan LianGang Optoelectronic Technology Co., Ltd."); + IDS.put(10797, "Atrust Computer Corp."); + IDS.put(10798, "VIA Alliance Semiconductor Co., Ltd."); + IDS.put(10799, "BSUN Electronics Co., Ltd."); + IDS.put(1080, "Advanced Micro Devices"); + IDS.put(10807, "RTD Embedded Technologies, Inc."); + IDS.put(10816, "Shenzhen Choseal Industrial Co., Ltd."); + IDS.put(10817, "Canyon Semiconductor"); + IDS.put(10818, "Spectra7 Microsystems Corp."); + IDS.put(10821, "Meizu Technology Co., Ltd."); + IDS.put(10822, "Hubei Yingtong Telecommunication Cable Inc."); + IDS.put(10829, "Wilder Technologies"); + IDS.put(10837, "Diodes Inc."); + IDS.put(10846, "DuPont"); + IDS.put(1085, "Lexmark International Inc."); + IDS.put(10852, "Zhejiang Songcheng Electronics Co., Ltd."); + IDS.put(10859, "VSN Mobil"); + IDS.put(10875, "Bellwether Electronic Corp."); + IDS.put(10878, "VAIO Corporation"); + IDS.put(10879, "Perixx Computer GmbH"); + IDS.put(10885, "HANK ELECTRONICS CO., LTD"); + IDS.put(10892, "Sonnet Technologies, Inc."); + IDS.put(10893, "Keysight Technologies Inc."); + IDS.put(10895, "Manutronics Vietnam Joint Stock Company"); + IDS.put(10900, "G2 Touch Co., Ltd."); + IDS.put(10902, "Micromax Informatics Ltd"); + IDS.put(10910, "SEIKO SOLUTIONS Inc."); + IDS.put(10912, "Casco Products Corp."); + IDS.put(10922, "Virtium Technology, Inc."); + IDS.put(10923, "Field and Company LLC, dba Leef USA"); + IDS.put(10928, "GM Global Technology Operations LLC"); + IDS.put(10931, "Key Asic Inc."); + IDS.put(10943, "Revolabs, Inc."); + IDS.put(10945, "Lattice Semiconductor Corp"); + IDS.put(10947, "Foshan Nanhai Saga Audio Equipment Co., Ltd."); + IDS.put(10957, "Silergy Corp."); + IDS.put(10963, "Shenzhen Hali-Power Industrial Co., Ltd."); + IDS.put(10971, "I-PEX (Dai-ichi Seiko)"); + IDS.put(10973, "SEE-PLUS INDUSTRIAL LTD."); + IDS.put(10990, "Adapt-IP Company"); + IDS.put(10997, "Libratone A/S"); + IDS.put(10999, "Shenzhen Hazens Automotive Electronics (SZ) Co., Ltd."); + IDS.put(11000, "Jiangsu Toppower Automotive Electronics Co., Ltd."); + IDS.put(11001, "Drapho Electronics Technology Co., Ltd."); + IDS.put(1102, "Alps Electric Co., Ltd."); + IDS.put(11022, "Le Shi Zhi Xin Electronic Technology (Tian Jin) Limited"); + IDS.put(11024, "Cardiac Insight, Inc."); + IDS.put(11028, "EverPro Technologies Company, Ltd."); + IDS.put(11029, "Rosenberger Hochfrequenztechnik"); + IDS.put(11035, "Dongguan City Sanji Electronics Co., Ltd."); + IDS.put(11037, "Lintes Technology Co., Ltd."); + IDS.put(11039, "KinnexA, Inc."); + IDS.put(11042, "Metra Electronics Corp."); + IDS.put(11044, "KeepKey, LLC"); + IDS.put(11047, "FluxData Incorporated"); + IDS.put(1105, "Texas Instruments"); + IDS.put(11061, "Assem Technology Co., Ltd."); + IDS.put(11062, "Dongguan City Jianghan Electronics Co., Ltd."); + IDS.put(11063, "Huizhou Desay SV Automotive Co., Ltd."); + IDS.put(11064, "Ningbo Rixing Electronics Co., Ltd."); + IDS.put(11069, "GuangDong YuanFeng Automotive Electroics Co., Ltd."); + IDS.put(11080, "Sounding Audio Industrial Limited"); + IDS.put(11082, "Yueqing Huaxin Electronic Co., Ltd."); + IDS.put(11098, "Universal Audio, Inc."); + IDS.put(11111, "Lifesize, Inc."); + IDS.put(11123, "Pioneer DJ Corporation"); + IDS.put(11124, "Embedded Intelligence, Inc."); + IDS.put(11125, "New Matter"); + IDS.put(11126, "Shanghai Wingtech Electronic Technology Co., Ltd."); + IDS.put(11127, "Epiphan Systems Inc."); + IDS.put(11130, "Spin Master Far East Ltd."); + IDS.put(11131, "Gigaset Digital Technology (Shenzhen) Co., Ltd."); + IDS.put(11132, "Noveltek Semiconductor Corp."); + IDS.put(11139, "Silicon Line GmbH"); + IDS.put(11140, "Ever Win International Corp."); + IDS.put(11144, "Socionext Inc."); + IDS.put(11145, "Ugreen Group Limited"); + IDS.put(11146, "Shanghai Pateo Electronic Equipment Mfg. Co., Ltd."); + IDS.put(1115, "Renesas Electronics Corp."); + IDS.put(11154, "i-BLADES, Inc."); + IDS.put(11155, "Altia Systems Inc."); + IDS.put(11156, "ShenZhen Baoyuanda Electronics Co., Ltd."); + IDS.put(11157, "iST - Integrated Service Technology Inc."); + IDS.put(11158, "HYUNDAI MOBIS Co., Ltd."); + IDS.put(11161, "360fly, Inc."); + IDS.put(11162, "HUIZHOU CHENG SHUO HARDWARE PLASTIC CO., LTD."); + IDS.put(11163, "Zhongshan Aute Electronics Technology Co., Ltd."); + IDS.put(11164, "Guangdong King Link Industrial Co., Ltd."); + IDS.put(11167, "Scietera Technologies, Inc."); + IDS.put(11168, "InVue Security Products"); + IDS.put(11169, "I-Sheng Electric Wire & Cable Co., Ltd."); + IDS.put(11170, "China Daheng Group Inc Beijing Image Vision Tech Branch"); + IDS.put(11171, "Shenzhen FeiTianXia Technology Ltd."); + IDS.put(11172, "Shenzhen HengJia New Energy Auto Part Co., Ltd."); + IDS.put(11175, "77 Elektronika Kft."); + IDS.put(11176, "YUDU EASON ELECTRONIC CO., LTD."); + IDS.put(1118, "Microsoft Corporation"); + IDS.put(11181, "XIN JI (SHENZHEN) COMPUTER PARTS CO., LTD."); + IDS.put(11189, "Silk ID Systems"); + IDS.put(11190, "3D Imaging & Simulations Corp. (3DISC)"); + IDS.put(11191, "Dongguan ChengXiang Industrial Co., Ltd."); + IDS.put(11192, "OCC (Zhuhai) Electronic Co., Ltd."); + IDS.put(11194, "Sinseader Electronic Co., Ltd."); + IDS.put(11195, "DONGGUAN YELLOWKNIFE Industrial Co., Ltd."); + IDS.put(11197, "RF Creations Ltd."); + IDS.put(11198, "Chengyi Semiconductors (Shanghai) Co., Ltd."); + IDS.put(11199, "Shenzhen Shinning Electronic Co., Ltd."); + IDS.put(11200, "Shenzhen WFD Electronics Co., Ltd."); + IDS.put(11201, "Dongguan Sino Syncs Industrial Co., Ltd."); + IDS.put(11202, "JNTC Co., Ltd."); + IDS.put(11208, "DONGGUAN POLIXIN ELECTRIC CO., LTD."); + IDS.put(11209, "Tama Electric (Suzhou) Co., Ltd."); + IDS.put(1121, "Primax Electronics"); + IDS.put(11210, "Exvision, Inc."); + IDS.put(11216, "mophie, LLC"); + IDS.put(11219, "Dongguan ULT-unite electronic technology co., LTD"); + IDS.put(11220, "JL Audio, Inc."); + IDS.put(11221, "Cable Matters Inc."); + IDS.put(11222, "CoroWare, Inc."); + IDS.put(11229, "Charm Sciences Inc."); + IDS.put(1123, "EATON"); + IDS.put(11230, "Pickering Interfaces Limited"); + IDS.put(11231, "Hangzhou Hikvision Digital Technology Co., Ltd."); + IDS.put(11232, "FULLINK ELECTRONICS TECHNOLOGY (SZ) LTD"); + IDS.put(11233, "AutoChips Inc."); + IDS.put(11234, "Electric Connector Technology Co., Ltd."); + IDS.put(11237, "LELTEK"); + IDS.put(11238, "Dongguan KaiWin Electronics Co., Ltd."); + IDS.put(11239, "BEFS Co., Ltd."); + IDS.put(11240, "Archisite, Inc."); + IDS.put(11241, "Magneti Marelli S.p.A Electr BL"); + IDS.put(11246, "Ventev Mobile"); + IDS.put(11247, "Quanta Storage Inc."); + IDS.put(11248, "Tech-Top Technology Limited"); + IDS.put(11253, "Shenzhen YOOBAO Technology Co., Ltd."); + IDS.put(11254, "Shenzhen Sinotek Technology Co., Ltd."); + IDS.put(11255, "KEYW"); + IDS.put(11256, "Visual Land Inc."); + IDS.put(11264, "MEEM SL Ltd"); + IDS.put(11265, "Dongguan Arin Electronics Technology Co., Ltd."); + IDS.put(11266, "DongGuan City JianNuo Electronics Co., Ltd."); + IDS.put(11268, "Shenzhen XOX Electronics Co., Ltd."); + IDS.put(11269, "Protop International Inc."); + IDS.put(11270, "Microsemi Semiconductor (US) Inc."); + IDS.put(11271, "Webcloak LLC"); + IDS.put(11272, "INVECAS INC."); + IDS.put(11274, "ATANS Technology Inc."); + IDS.put(11275, "Triple Win Precision Technology Co., Ltd."); + IDS.put(11276, "IC Realtech"); + IDS.put(11277, "Embrava Pty Ltd"); + IDS.put(1128, "Wieson Technologies Co., Ltd."); + IDS.put(11280, "Sinotronics Co., Ltd."); + IDS.put(11281, "ALLBEST ELECTRONICS TECHNOLOGY CO., LTD."); + IDS.put(11282, "Shenzhen Xin Kai Feng Electronics Factory"); + IDS.put(11283, "MOST WELL Technology Corp."); + IDS.put(11284, "Buffalo Memory Co., Ltd."); + IDS.put(11285, "Xentris Wireless"); + IDS.put(11286, "Priferential Accessories Ltd"); + IDS.put(11289, "Sunlike Technology Co., Ltd."); + IDS.put(11290, "Young Fast Optoelectronics Co., Ltd."); + IDS.put(11291, "ISAW Camera Inc"); + IDS.put(11298, "Qanba USA, LLC"); + IDS.put(11299, "Super Micro Computer Inc."); + IDS.put(11302, "Micromax International Corporation"); + IDS.put(11304, "Granite River Labs Japan Ltd."); + IDS.put(11305, "Coagent Enterprise Limited"); + IDS.put(11306, "LEIA Inc."); + IDS.put(11309, "Shenzhen Ebull Technology Limited"); + IDS.put(1131, "American Megatrends"); + IDS.put(11310, "Hualun Technology Co., Ltd."); + IDS.put(11311, "Sensel, Inc."); + IDS.put(11319, "Shenzhen Adition Audio Science & Technology Co., Ltd."); + IDS.put(11320, "Goldenconn Electronics Technology (Suzhou) Co., Ltd."); + IDS.put(11321, "JIB Electronics Technology Co., Ltd."); + IDS.put(11322, "Changzhou Shinco Automotive Electronics Co., Ltd."); + IDS.put(11323, "Shenzhen Hangsheng Electronics Corp., Ltd."); + IDS.put(11324, "Beartooth Radio, Inc."); + IDS.put(11325, "Audience, A Knowles Company"); + IDS.put(11327, "Nextbit Systems, Inc."); + IDS.put(11328, "Leadtrend"); + IDS.put(11329, "Adaptertek Technology Co., Ltd."); + IDS.put(1133, "Logitech Inc."); + IDS.put(11330, "Feature Integration Technology Inc."); + IDS.put(11331, "Avegant Corporation"); + IDS.put(11335, "Chunghsin International Electronics Co., Ltd."); + IDS.put(11336, "Delphi Electrical Centers (Shanghai) Co., Ltd."); + IDS.put(11341, "VVETEK DOO"); + IDS.put(11347, "Huizhou Foryou General Electronics Co., Ltd."); + IDS.put(11348, "LifeWatch Technologies Ltd."); + IDS.put(11349, "Magicleap"); + IDS.put(11355, "Dongguan City Shenglan Electronics Co., LTD."); + IDS.put(11356, "Neusoft Corporation"); + IDS.put(11357, "SIP Simya Electronics Technology Co., Ltd."); + IDS.put(11358, "GNSD Automotive Co., Ltd."); + IDS.put(11359, "YOODS Co., Ltd."); + IDS.put(11360, "Sirin Mobile Technologies AG"); + IDS.put(11361, "Jadmam Corporation dba: Boytone"); + IDS.put(11373, "Gibson Innovations"); + IDS.put(11374, "Shen Zhen Xian Shuo Technology Co. LTD"); + IDS.put(11375, "PST Eletronica LTDA"); + IDS.put(11376, "PERI, Inc."); + IDS.put(11377, "Bozhou BoTong Information Technology Co., Ltd."); + IDS.put(11383, "Profindustry GmbH"); + IDS.put(11384, "BRAGI GmbH"); + IDS.put(11385, "WAWGD, Inc. (DBA: Foresight Sports)"); + IDS.put(11390, "Dongguan Allpass Electronic Co., Ltd."); + IDS.put(11391, "SHENZHEN D-VITEC INDUSTRIAL CO., LTD."); + IDS.put(11392, "motomobile AG"); + IDS.put(11393, "Indie Semiconductor"); + IDS.put(11397, "Audientes"); + IDS.put(11403, "Huizhou Dehong Technology Co., Ltd."); + IDS.put(11404, "PowerCenter Technology Limited"); + IDS.put(11405, "Mizco International, Inc."); + IDS.put(11408, "I. AM. PLUS, LLC"); + IDS.put(11409, "Corigine, Inc."); + IDS.put(11410, "Ningbo Yinzhou Shengke Electronics Co., Ltd."); + IDS.put(11417, "Prusa Research s.r.o."); + IDS.put(11423, "e-Smart Systems Pvt. Ltd."); + IDS.put(11424, "Leagtech Jiangxi Electronic Co., Ltd."); + IDS.put(11425, "Dongguan Yujia Electronics Technology Co., Ltd."); + IDS.put(11426, "GuangZhou MingPing Electronics Technology"); + IDS.put(11427, "DJI Technology Co., Ltd."); + IDS.put(11428, "Shenzhen Alex Technology Co., Ltd."); + IDS.put(11433, "JITS TECHNOLOGY CO., LIMITED"); + IDS.put(11434, "LIVV Brand llc"); + IDS.put(11444, "Ava Enterprises, Inc. dba: Boss Audio Systems"); + IDS.put(11448, "Shenzhen Sydixon Electronic Technology Co., Ltd."); + IDS.put(11449, "On-Bright Electronics (Shanghai) Co., Ltd."); + IDS.put(11450, "Dongguan Puxu Industrial Co., Ltd."); + IDS.put(11451, "Shenzhen Soling Indusrtial Co., Ltd."); + IDS.put(11453, "EGGCYTE, INC."); + IDS.put(11455, "Donggguan Yuhua Electronic Co., Ltd."); + IDS.put(11456, "Hangzhou Zero Zero Technology Co., Ltd."); + IDS.put(11462, "Prodigy Technovations Pvt Ltd"); + IDS.put(11463, "EmergiTech, Inc"); + IDS.put(11464, "Hewlett Packard Enterprise"); + IDS.put(11465, "Monolithic Power Systems Inc."); + IDS.put(11467, "USB Memory Direct"); + IDS.put(11468, "Silicon Mitus Inc."); + IDS.put(11472, "Technics Global Electronics & JCE Co., Ltd."); + IDS.put(11478, "Immersive Media"); + IDS.put(11479, "Cosemi Technologies Inc."); + IDS.put(11481, "Cambrionix Ltd"); + IDS.put(11482, "CXUN Co. Ltd."); + IDS.put(11483, "China Tsp Inc"); + IDS.put(11490, "Yanfeng Visteon (Chongqing) Automotive Electronics Co"); + IDS.put(11491, "Alcorlink Corp."); + IDS.put(11492, "ISBC Ltd."); + IDS.put(11493, "InX8 Inc dba: AKiTiO"); + IDS.put(11494, "SDAN Tecchnology Co., Ltd."); + IDS.put(11495, "Lemobile Information Technology (Beijing) Co., Ltd."); + IDS.put(11496, "GongGuan HWX Electronic Technology Co., Ltd."); + IDS.put(11497, "Suzhu Jingshi Electronic Technology Co., Ltd."); + IDS.put(11498, "Zhong Shan City Richsound Electronic Industrial Ltd."); + IDS.put(11499, "Dongguang Kangbang Electronics Co., Ltd."); + IDS.put(1151, "Plantronics, Inc."); + IDS.put(1154, "Kyocera Corporation"); + IDS.put(1155, "STMicroelectronics"); + IDS.put(1161, "Foxconn / Hon Hai"); + IDS.put(1165, "ITE Tech Inc."); + IDS.put(1177, "Yamaha Corporation"); + IDS.put(1188, "Hitachi, Ltd."); + IDS.put(1191, "Visioneer"); + IDS.put(1193, "Canon Inc."); + IDS.put(1200, "Nikon Corporation"); + IDS.put(1201, "Pan International"); + IDS.put(1204, "Cypress Semiconductor"); + IDS.put(1205, "ROHM Co., Ltd."); + IDS.put(1207, "Compal Electronics, Inc."); + IDS.put(1208, "Seiko Epson Corp."); + IDS.put(1211, "I-O Data Device, Inc."); + IDS.put(1221, "Fujitsu Ltd."); + IDS.put(1227, "FUJIFILM Corporation"); + IDS.put(1238, "Mentor Graphics"); + IDS.put(1240, "Microchip Technology Inc."); + IDS.put(1241, "Holtek Semiconductor, Inc."); + IDS.put(1242, "Panasonic Corporation"); + IDS.put(1245, "Sharp Corporation"); + IDS.put(1250, "Exar Corporation"); + IDS.put(1254, "Identiv, Inc."); + IDS.put(1256, "Samsung Electronics Co., Ltd."); + IDS.put(1260, "Tokyo Electron Device Limited"); + IDS.put(1266, "Chicony Electronics Co., Ltd."); + IDS.put(1271, "Newnex Technology Corp."); + IDS.put(1273, "Brother Industries, Ltd."); + IDS.put(1276, "SUNPLUS TECHNOLOGY CO., LTD."); + IDS.put(1278, "PFU Limited"); + IDS.put(1281, "Fujikura/DDK"); + IDS.put(1282, "Acer, Inc."); + IDS.put(1287, "Hosiden Corporation"); + IDS.put(1293, "Belkin International, Inc."); + IDS.put(1300, "FCI Electronics"); + IDS.put(1302, "Longwell Electronics/Longwell Company"); + IDS.put(1305, "Star Micronics Co., LTD"); + IDS.put(1309, "American Power Conversion"); + IDS.put(1314, "ACON, Advanced-Connectek, Inc."); + IDS.put(1343, "Synopsys, Inc."); + IDS.put(1356, "Sony Corporation"); + IDS.put(1360, "Fuji Xerox Co., Ltd."); + IDS.put(1367, "ATEN International Co. Ltd."); + IDS.put(1369, "Cadence Design Systems, Inc."); + IDS.put(1386, "WACOM Co., Ltd."); + IDS.put(1389, "EIZO Corporation"); + IDS.put(1390, "Elecom Co., Ltd."); + IDS.put(1394, "Conexant Systems, Inc."); + IDS.put(1398, "BAFO/Quality Computer Accessories"); + IDS.put(1403, "Y-E Data, Inc."); + IDS.put(1404, "AVM GmbH"); + IDS.put(1410, "Roland Corporation"); + IDS.put(1412, "RATOC Systems, Inc."); + IDS.put(1419, "Infineon Technologies"); + IDS.put(1423, "Alcor Micro, Corp."); + IDS.put(1424, "OMRON Corporation"); + IDS.put(1447, "Bose Corporation"); + IDS.put(1449, "OmniVision Technologies, Inc."); + IDS.put(1452, "Apple"); + IDS.put(1453, "Y.C. Cable U.S.A., Inc"); + IDS.put(14627, "National Instruments"); + IDS.put(1470, "Tyco Electronics Corp., a TE Connectivity Ltd. company"); + IDS.put(1473, "MegaChips Corporation"); + IDS.put(1478, "Qualcomm, Inc"); + IDS.put(1480, "Foxlink/Cheng Uei Precision Industry Co., Ltd."); + IDS.put(1482, "Ricoh Company Ltd."); + IDS.put(1498, "Microtek International Inc."); + IDS.put(1504, "Symbol Technologies"); + IDS.put(1507, "Genesys Logic, Inc."); + IDS.put(1509, "Fuji Electric Co., Ltd."); + IDS.put(1525, "Unixtar Technology Inc."); + IDS.put(1529, "Datalogic ADC"); + IDS.put(1535, "LeCroy Corporation"); + IDS.put(1539, "Novatek Microelectronics Corp."); + IDS.put(1545, "SMK Manufacturing Inc."); + IDS.put(1551, "Joinsoon Electronics Mfg. Co., Ltd."); + IDS.put(1555, "TransAct Technologies Incorporated"); + IDS.put(1561, "Seiko Instruments Inc."); + IDS.put(1582, "JPC/MAIN SUPER Inc."); + IDS.put(1583, "Sin Sheng Terminal & Machine Inc."); + IDS.put(1593, "Chrontel, Inc."); + IDS.put(1611, "Analog Devices, Inc. Development Tools"); + IDS.put(1612, "Ji-Haw Industrial Co., Ltd"); + IDS.put(1614, "Suyin Corporation"); + IDS.put(1621, "Space Shuttle Hi-Tech Co.,Ltd."); + IDS.put(1622, "Glory Mark Electronic Ltd."); + IDS.put(1623, "Tekcon Electronics Corp."); + IDS.put(1624, "Sigma Designs, Inc."); + IDS.put(1631, "Good Way Technology Co., Ltd. & GWC technology Inc"); + IDS.put(1632, "TSAY-E (BVI) International Inc."); + IDS.put(1633, "Hamamatsu Photonics K.K."); + IDS.put(1642, "Total Technologies, Ltd."); + IDS.put(1659, "Prolific Technology, Inc."); + IDS.put(16700, "Dell Inc."); + IDS.put(1680, "Golden Bridge Electech Inc."); + IDS.put(1689, "Tektronix, Inc."); + IDS.put(1690, "Askey Computer Corporation"); + IDS.put(1709, "Greatland Electronics Taiwan Ltd."); + IDS.put(1710, "Eurofins Digital Testing Belgium"); + IDS.put(1720, "Pixela Corporation"); + IDS.put(1724, "Oki Data Corporation"); + IDS.put(1727, "Leoco Corporation"); + IDS.put(1732, "Bizlink Technology, Inc."); + IDS.put(1736, "SIIG, Inc."); + IDS.put(1747, "Mitsubishi Electric Corporation"); + IDS.put(1758, "Heisei Technology Co., Ltd."); + IDS.put(1802, "Oki Electric Industry Co., Ltd."); + IDS.put(1805, "Comoss Electronic Co., Ltd."); + IDS.put(1809, "Magic Control Technology Corp."); + IDS.put(1816, "Imation Corp."); + IDS.put(1838, "Sunix Co., Ltd."); + IDS.put(1846, "Lorom Industrial Co., Ltd."); + IDS.put(1848, "Mad Catz, Inc."); + IDS.put(1899, "HID Global GmbH"); + IDS.put(1901, "Denso Corporation"); + IDS.put(1913, "Fairchild Semiconductor"); + IDS.put(1921, "SanDisk Corporation"); + IDS.put(1937, "Copartner Technology Corporation"); + IDS.put(1954, "National Technical Systems"); + IDS.put(1971, "Plustek, Inc."); + IDS.put(1972, "OLYMPUS CORPORATION"); + IDS.put(1975, "TIME Interconnect Ltd."); + IDS.put(1994, "AVerMedia Technologies, Inc."); + IDS.put(1999, "Casio Computer Co., Ltd."); + IDS.put(2015, "David Electronics Company, Ltd."); + IDS.put(2039, "Century Corporation"); + IDS.put(2058, "Evermuch Technology Co., Ltd."); + IDS.put(2101, "Action Star Enterprise Co., Ltd."); + IDS.put(2112, "Argosy Research Inc."); + IDS.put(2122, "Wipro Limited"); + IDS.put(2159, "MEC IMEX INC/HPT"); + IDS.put(2205, "Icron Technologies Corporation"); + IDS.put(2247, "TAI TWUN ENTERPRISE CO., LTD."); + IDS.put(2276, "Pioneer Corporation"); + IDS.put(2278, "Gemalto SA"); + IDS.put(2310, "FARADAY Technology Corp."); + IDS.put(2313, "Audio-Technica Corp."); + IDS.put(2316, "Silicon Motion, Inc. - Taiwan"); + IDS.put(2334, "Garmin International"); + IDS.put(2352, "Toshiba Corporation"); + IDS.put(2362, "Pixart Imaging, Inc."); + IDS.put(2363, "Plextor LLC"); + IDS.put(2366, "J.S.T. Mfg. Co., Ltd."); + IDS.put(2385, "Kingston Technology Company"); + IDS.put(2389, "NVIDIA"); + IDS.put(2395, "Medialogic Corporation"); + IDS.put(2397, "Polycom, Inc."); + IDS.put(2468, "Contech Research, Inc."); + IDS.put(2472, "Lin Shiung Enterprise Co., Ltd."); + IDS.put(2475, "Japan Cash Machine Co., Ltd."); + IDS.put(2498, "NISCA Corporation"); + IDS.put(2511, "Electronics Testing Center, Taiwan"); + IDS.put(2522, "A-FOUR TECH CO., LTD."); + IDS.put(2555, "Altera"); + IDS.put(2578, "Cambridge Silicon Radio Ltd."); + IDS.put(2583, "HOYA Corporation"); + IDS.put(2631, "Hirose Electric Co., Ltd."); + IDS.put(2636, "COMPUTEX Co., Ltd."); + IDS.put(2640, "Mimaki Engineering Co., Ltd."); + IDS.put(2652, "Broadcom Corp."); + IDS.put(2667, "Green House Co., Ltd."); + IDS.put(2702, "Japan Aviation Electronics Industry Ltd. (JAE)"); + IDS.put(2727, "Wincor Nixdorf GmbH & Co KG"); + IDS.put(2733, "Rohde & Schwarz GmbH & Co. KG"); + IDS.put(2787, "Allion Labs, Inc."); + IDS.put(2821, "ASUSTek Computer Inc."); + IDS.put(2849, "Yokogawa Electric Corporation"); + IDS.put(2851, "Pan-Asia Electronics Co., Ltd."); + IDS.put(2894, "Musical Electronics Ltd."); + IDS.put(2907, "Anritsu Corporation"); + IDS.put(2922, "Maxim Integrated Products"); + IDS.put(2965, "ASIX Electronics Corporation"); + IDS.put(2967, "O2Micro, Inc."); + IDS.put(3010, "Seagate Technology LLC"); + IDS.put(3034, "Realtek Semiconductor Corp."); + IDS.put(3035, "Ericsson AB"); + IDS.put(3044, "Elka International Ltd."); + IDS.put(3056, "Pace Micro Technology PLC"); + IDS.put(3108, "Taiyo Yuden Co., Ltd."); + IDS.put(3129, "Aeroflex"); + IDS.put(3132, "Radius Co., Ltd."); + IDS.put(3141, "Sonix Technology Co., Ltd."); + IDS.put(3158, "Billion Bright (HK) Corporation Limited"); + IDS.put(3161, "Dong Guan Shinko Wire Co., Ltd."); + IDS.put(3170, "Chant Sincere Co., Ltd"); + IDS.put(3190, "Solid State System Co., Ltd."); + IDS.put(3209, "Honda Tsushin Kogyo Co., Ltd"); + IDS.put(3245, "Motorola Solutions"); + IDS.put(3255, "Singatron Enterprise Co. Ltd."); + IDS.put(3268, "emsys Embedded Systems GmbH"); + IDS.put(32902, "Intel Corporation"); + IDS.put(3294, "Z-Com INC."); + IDS.put(3313, "e-CONN ELECTRONIC CO., LTD."); + IDS.put(3314, "ENE Technology Inc."); + IDS.put(3351, "NALTEC, Inc."); + IDS.put(3402, "NF Corporation"); + IDS.put(3403, "Grape Systems Inc."); + IDS.put(3409, "Volex (Asia) Pte Ltd"); + IDS.put(3425, "MEILU ELECTRONICS (SHENZHEN) CO., LTD."); + IDS.put(3441, "Hirakawa Hewtech Corp."); + IDS.put(3452, "Taiwan Line Tek Electronic Co., Ltd."); + IDS.put(3463, "Dolby Laboratories Inc."); + IDS.put(3468, "C-MEDIA ELECTRONICS INC."); + IDS.put(3472, "Sure-Fire Electrical Corporation"); + IDS.put(3495, "IOGEAR, Inc."); + IDS.put(3504, "Micro-Star International Co., Ltd."); + IDS.put(3537, "Contek Electronics Co., Ltd."); + IDS.put(3540, "Custom Engineering SPA"); + IDS.put(3641, "Smart Modular Technologies, Inc."); + IDS.put(3658, "Shenzhen Bao Hing Electric Wire & Cable Mfr. Co."); + IDS.put(3673, "Bourns, Inc."); + IDS.put(3690, "Megawin Technology Co., Ltd."); + IDS.put(3698, "Hsi-Chin Electronics Co., Ltd."); + IDS.put(3714, "Ching Tai Electric Wire & Cable Co., Ltd."); + IDS.put(3724, "Well Force Electronic Co., Ltd"); + IDS.put(3725, "MediaTek Inc."); + IDS.put(3728, "CRU"); + IDS.put(3744, "Ours Technology Inc."); + IDS.put(3762, "Y-S ELECTRONIC CO., LTD."); + IDS.put(3778, "Sweetray Industrial Ltd."); + IDS.put(3779, "Axell Corporation"); + IDS.put(3782, "InnoVISION Multimedia Limited"); + IDS.put(3790, "TaiSol Electronics Co., Ltd."); + IDS.put(3812, "Sunrich Technology (H.K.) Ltd."); + IDS.put(3868, "Funai Electric Co., Ltd."); + IDS.put(3873, "IOI Technology Corporation"); + IDS.put(3890, "YFC-BonEagle Electric Co., Ltd."); + IDS.put(3896, "Nien-Yi Industrial Corp."); + IDS.put(3916, "WORLDWIDE CABLE OPTO CORP."); + IDS.put(3923, "Taiyo Cable (Dongguan) Co. Ltd."); + IDS.put(3924, "Kawai Musical Instruments Mfg. Co., Ltd."); + IDS.put(3936, "GuangZhou Chief Tech Electronic Technology Co. Ltd."); + IDS.put(3944, "UQUEST, LTD."); + IDS.put(3991, "CviLux Corporation"); + IDS.put(4003, "Chief Land Electronic Co., Ltd."); + IDS.put(4046, "Sony Mobile Communications"); + IDS.put(4087, "CHI SHING COMPUTER ACCESSORIES CO., LTD."); + IDS.put(4096, "Speed Tech Corp."); + IDS.put(4100, "LG Electronics Inc."); + IDS.put(4101, "Apacer Technology Inc."); + IDS.put(4134, "Newly Corporation"); + IDS.put(4168, "Targus Group International"); + IDS.put(4172, "AMCO TEC International Inc."); + IDS.put(4183, "ON Semiconductor"); + IDS.put(4184, "Western Digital Technologies, Inc."); + IDS.put(4227, "CANON ELECTRONICS INC."); + IDS.put(4235, "Grand-tek Technology Co., Ltd."); + IDS.put(4236, "Robert Bosch GmbH"); + IDS.put(4238, "Lotes Co., Ltd."); + IDS.put(4266, "Cables To Go"); + IDS.put(4267, "Universal Global Scientific Industrial Co., Ltd."); + IDS.put(4292, "Silicon Laboratories, Inc."); + IDS.put(4301, "Kycon Inc."); + IDS.put(4362, "Moxa Inc."); + IDS.put(4370, "Golden Bright (Sichuan) Electronic Technology Co Ltd"); + IDS.put(4382, "VSO ELECTRONICS CO., LTD."); + IDS.put(4398, "Master Hill Electric Wire and Cable Co., Ltd."); + IDS.put(4477, "Santa Electronic Inc."); + IDS.put(4505, "Sierra Wireless Inc."); + IDS.put(4522, "GlobalMedia Group, LLC"); + IDS.put(4528, "ATECH FLASH TECHNOLOGY"); + IDS.put(4643, "SKYCABLE ENTERPRISE CO., LTD."); + IDS.put(4703, "ADATA Technology Co., Ltd."); + IDS.put(4716, "Aristocrat Technologies"); + IDS.put(4717, "Bel Stewart"); + IDS.put(4742, "MARVELL SEMICONDUCTOR, INC."); + IDS.put(4756, "RISO KAGAKU CORP."); + IDS.put(4792, "Zhejiang Xinya Electronic Technology Co., Ltd."); + IDS.put(4817, "Huawei Technologies Co., Ltd."); + IDS.put(4823, "Better Holdings (HK) Limited"); + IDS.put(4907, "Konica Minolta, Inc."); + IDS.put(4925, "Jasco Products Company"); + IDS.put(4989, "Pericom Semiconductor Corp."); + IDS.put(5008, "TomTom International B.V."); + IDS.put(5075, "AzureWave Technologies, Inc."); + IDS.put(5117, "Initio Corporation"); + IDS.put(5118, "Phison Electronics Corp."); + IDS.put(5134, "Telechips, Inc."); + IDS.put(5145, "ABILITY ENTERPRISE CO., LTD."); + IDS.put(5148, "Leviton Manufacturing"); + IDS.put(5271, "Panstrong Company Ltd."); + IDS.put(5293, "CTK Corporation"); + IDS.put(5296, "StarTech.com Ltd."); + IDS.put(5376, "Ellisys"); + IDS.put(5404, "VeriSilicon Holdings Co., Ltd."); + IDS.put(5421, "JMicron Technology Corp."); + IDS.put(5422, "HLDS (Hitachi-LG Data Storage, Inc.)"); + IDS.put(5440, "Phihong Technology Co., Ltd."); + IDS.put(5451, "PNY Technologies Inc."); + IDS.put(5453, "Rapid Conn, Connect County Holdings Bhd"); + IDS.put(5454, "D & M Holdings, Inc."); + IDS.put(5480, "Sunf Pu Technology Co., Ltd"); + IDS.put(5488, "ALLTOP TECHNOLOGY CO., LTD."); + IDS.put(5510, "Palconn Technology Co., Ltd."); + IDS.put(5528, "Kunshan Guoji Electronics Co., Ltd."); + IDS.put(5546, "DongGuan Ya Lian Electronics Co., Ltd."); + IDS.put(5645, "Samtec"); + IDS.put(5694, "HongLin Electronics Co., Ltd."); + IDS.put(5753, "Total Phase"); + IDS.put(5766, "ZOOM Corporation"); + IDS.put(5836, "silex technology, Inc."); + IDS.put(5946, "F. Hoffmann-La Roche AG"); + IDS.put(5960, "MQP Electronics Ltd."); + IDS.put(5964, "ASMedia Technology Inc."); + IDS.put(5998, "UD electronic corp."); + IDS.put(6001, "Shenzhen Alex Connector Co., Ltd."); + IDS.put(6002, "System Level Solutions, Inc."); + IDS.put(6018, "Spreadtrum Hong Kong Limited"); + IDS.put(6024, "ShenZhen Litkconn Technology Co., Ltd."); + IDS.put(6053, "Advanced Connection Technology Inc."); + IDS.put(6095, "Hip Hing Cable & Plug Mfy. Ltd."); + IDS.put(6121, "DisplayLink (UK) Ltd."); + IDS.put(6127, "Lenovo"); + IDS.put(6133, "K.K. Rocky"); + IDS.put(6160, "Wanshih Electronic Co., Ltd."); + IDS.put(6185, "Dongguan YuQiu Electronics Co., Ltd."); + IDS.put(6193, "Gwo Jinn Industries Co., Ltd."); + IDS.put(6297, "Linkiss Co., Ltd."); + IDS.put(6353, "Google Inc."); + IDS.put(6394, "Kuang Ying Computer Equipment Co., Ltd."); + IDS.put(6421, "Nordic Semiconductor ASA"); + IDS.put(6448, "Shenzhen Xianhe Technology Co., Ltd."); + IDS.put(6449, "Ningbo Broad Telecommunication Co., Ltd."); + IDS.put(6470, "Irisguard UK Ltd"); + IDS.put(6473, "Lab126"); + IDS.put(6481, "Hyperstone GmbH"); + IDS.put(6487, "BIOS Corporation"); + IDS.put(6626, "Solomon Systech Limited"); + IDS.put(6639, "Pak Heng Technology (Shenzhen) Co., Ltd."); + IDS.put(6655, "Best Buy China Ltd."); + IDS.put(6666, "USB-IF non-workshop"); + IDS.put(6709, "Artesyn Technologies Inc."); + IDS.put(6720, "TERMINUS TECHNOLOGY INC."); + IDS.put(6766, "Global Unichip Corp."); + IDS.put(6786, "Proconn Technology Co., Ltd."); + IDS.put(6794, "Simula Technology Inc."); + IDS.put(6795, "SGS Taiwan Ltd."); + IDS.put(6830, "Johnson Component & Equipments Co., Ltd."); + IDS.put(6834, "Allied Vision Technologies GmbH"); + IDS.put(6859, "Salcomp Plc"); + IDS.put(6865, "Desan Wire Co., Ltd."); + IDS.put(6944, "MStar Semiconductor, Inc."); + IDS.put(6984, "Plastron Precision Co., Ltd."); + IDS.put(7013, "The Hong Kong Standards and Testing Centre Ltd."); + IDS.put(7048, "ShenMing Electron (Dong Guan) Co., Ltd."); + IDS.put(7086, "Vuzix Corporation"); + IDS.put(7108, "Ford Motor Co."); + IDS.put(7118, "Contac Cable Industrial Limited"); + IDS.put(7119, "Sunplus Innovation Technology Inc."); + IDS.put(7120, "Hangzhou Riyue Electronics Co., Ltd."); + IDS.put(7158, "Orient Semiconductor Electronics, Ltd."); + IDS.put(7207, "SHENZHEN DNS INDUSTRIES CO., LTD."); + IDS.put(7217, "LS Mtron Ltd."); + IDS.put(7229, "NONIN MEDICAL INC."); + IDS.put(7275, "Philips & Lite-ON Digital Solutions Corporation"); + IDS.put(7310, "ASTRON INTERNATIONAL CORP."); + IDS.put(7320, "ALPINE ELECTRONICS, INC."); + IDS.put(7347, "Aces Electronics Co., Ltd."); + IDS.put(7348, "OPEX CORPORATION"); + IDS.put(7390, "Telecommunications Technology Association (TTA)"); + IDS.put(7434, "Visteon Corporation"); + IDS.put(7465, "Horng Tong Enterprise Co., Ltd."); + IDS.put(7501, "Pegatron Corporation"); + IDS.put(7516, "Fresco Logic Inc."); + IDS.put(7529, "Walta Electronic Co., Ltd."); + IDS.put(7543, "Yueqing Changling Electronic Instrument Corp., Ltd."); + IDS.put(7584, "Parade Technologies, Inc."); + IDS.put(7647, "L&T Technology Services"); + IDS.put(7649, "Actions Microelectronics Co., Ltd."); + IDS.put(7666, "China Telecommunication Technology Labs - Terminals"); + IDS.put(7668, "SHEN ZHEN FORMAN PRECISION INDUSTRY CO., LTD."); + IDS.put(7682, "GLOBEMASTER TECHNOLOGIES CO., LTD."); + IDS.put(7696, "Point Grey Research Inc."); + IDS.put(7751, "HUNG TA H.T.ENTERPRISE CO., LTD."); + IDS.put(7758, "Etron Technology, Inc."); + IDS.put(7795, "COMLINK ELECTRONICS CO., LTD."); + IDS.put(7818, "HIBEST Electronic (DongGuan) Co., Ltd."); + IDS.put(7825, "Other World Computing"); + IDS.put(7863, "WIN WIN PRECISION INDUSTRIAL CO., LTD."); + IDS.put(7879, "Gefen Inc."); + IDS.put(7881, "MOSER BAER INDIA LIMITED"); + IDS.put(7898, "AIRTIES WIRELESS NETWORKS"); + IDS.put(7956, "Astoria Networks GmbH"); + IDS.put(7969, "Scosche Industries"); + IDS.put(7976, "Cal-Comp Electronics & Communications"); + IDS.put(7977, "Analogix Semiconductor, Inc."); + IDS.put(7989, "Amphenol ShouhMin Industry (ShenZhen) Co., Ltd"); + IDS.put(7996, "Chang Yang Electronics Company Ltd."); + IDS.put(8073, "Dongguan Goldconn Electronics Co., Ltd."); + IDS.put(8074, "Morning Star Industrial Co., Ltd."); + IDS.put(8117, "Unify Software and Solutions GmbH & Co. KG"); + IDS.put(8137, "NXP Semiconductors"); + IDS.put(8181, "Changzhou Wujin BEST Electronic Cables Co., Ltd."); + IDS.put(8205, "Belkin Electronic (Changzhou) Co., Ltd."); + IDS.put(8220, "Freeport Resources Enterprises Corp."); + IDS.put(8222, "Qingdao Haier Telecom Co., Ltd."); + IDS.put(8284, "Shenzhen Tronixin Electronics Co., Ltd."); + IDS.put(8294, "Unicorn Electronics Components Co., Ltd."); + IDS.put(8334, "Luxshare-ICT"); + IDS.put(8341, "CE LINK LIMITED"); + IDS.put(8342, "Microconn Electronic Co., Ltd."); + IDS.put(8367, "Shenzhen CARVE Electronics Co., Ltd."); + IDS.put(8382, "BURY GmbH & Co. KG"); + IDS.put(8384, "FENGHUA KINGSUN CO., LTD."); + IDS.put(8386, "Sumitomo Electric Ind., Ltd., Optical Comm. R&D Lab"); + IDS.put(8439, "XIMEA s.r.o."); + IDS.put(8457, "VIA Labs, Inc."); + IDS.put(8492, "Shenzhen Linoya Electronic Co., Ltd."); + IDS.put(8494, "Amphenol AssembleTech (Xiamen) Co., Ltd."); + IDS.put(8524, "Y Soft Corporation"); + IDS.put(8550, "JVC KENWOOD Corporation"); + IDS.put(8564, "Transcend Information, Inc."); + IDS.put(8566, "TMC/Allion Test Labs"); + IDS.put(8613, "Genesis Technology USA, Inc."); + IDS.put(8627, "Dongguan Teconn Electronics Technology Co., Ltd."); + IDS.put(8644, "Netcom Technology (HK) Limited"); + IDS.put(8659, "Compupack Technology Co., Ltd."); + IDS.put(8667, "G-Max Technology Co., Ltd."); + IDS.put(8679, "Sagemcom Broadband SAS"); + IDS.put(8695, "Wuerth-Elektronik eiSos GmbH & Co. KG"); + IDS.put(8707, "Shin Shin Co., Ltd."); + IDS.put(8709, "3eYamaichi Electronics Co., Ltd."); + IDS.put(8710, "Wiretek International Investment Ltd."); + IDS.put(8711, "Fuzhou Rockchip Electronics Co., Ltd."); + IDS.put(8752, "Plugable Technologies"); + IDS.put(8756, "T-CONN PRECISION CORPORATION"); + IDS.put(8831, "Granite River Labs"); + IDS.put(8842, "Hotron Precision Electronic Ind. Corp."); + IDS.put(8875, "Trigence Semiconductor, Inc."); + IDS.put(8888, "Motorola Mobility Inc."); + IDS.put(8904, "Karming Electronic (Shenzhen) Co., Ltd."); + IDS.put(8981, "Avery Design Systems, Inc."); + IDS.put(8993, "iKingdom Corp. (d.b.a. iConnectivity)"); + IDS.put(9051, "KangXiang Electronic Co., Ltd."); + IDS.put(9068, "ZheJiang Chunsheng Electronics Co., Ltd."); + IDS.put(9130, "DOK (HK) Trading Limited"); + IDS.put(9132, "Marunix Electron Limited"); + IDS.put(9165, "Avconn Precise Connector Co., Ltd."); + IDS.put(9184, "BitifEye Digital Test Solutions GmbH"); + IDS.put(9205, "Speed Conn Co., Ltd."); + IDS.put(9222, "INSIDE Secure"); + IDS.put(9292, "Minebea Co., Ltd."); + IDS.put(9299, "BAANTO"); + IDS.put(9338, "Suzhou Jutze Technologies Co., Ltd"); + IDS.put(9355, "DONGGUAN SYNCONN PRECISION INDUSTRY CO. LTD."); + IDS.put(9382, "Shenzhen Pangngai Industrial Co., Ltd."); + IDS.put(9422, "Shenzhen Deren Electronic Co., Ltd."); + IDS.put(9424, "Smith Micro Software, Inc."); + IDS.put(9453, "ZEN FACTORY GROUP (ASIA) LTD."); + IDS.put(9481, "Chain-In Electronic Co., Ltd."); + IDS.put(9514, "SUZHOU KELI TECHNOLOGY DEVELOPMENT CO., LTD."); + IDS.put(9515, "TOP Exactitude Industry (ShenZhen) Co., Ltd."); + IDS.put(9525, "ShenZhen Hogend Precision Technology Co., Ltd."); + IDS.put(9527, "Norel Systems Ltd."); + IDS.put(9556, "ASSA ABLOY AB"); + IDS.put(9575, "DongGuan LongTao Electronic Co., Ltd."); + IDS.put(9577, "DongGuan City MingJi Electronics Co., Ltd."); + IDS.put(9589, "Weida Hi-Tech Co., Ltd."); + IDS.put(9593, "Dongguan Wisechamp Electronic Co., Ltd."); + IDS.put(9613, "Sequans Communications"); + IDS.put(9636, "ALGOLTEK, INC."); + IDS.put(9651, "DongGuan Elinke Industrial Co., Ltd."); + IDS.put(9679, "Corning Optical Communications LLC"); + IDS.put(9714, "Dongguan Jinyue Electronics Co., Ltd."); + IDS.put(9723, "RICOH IMAGING COMPANY, LTD."); + IDS.put(9742, "DongGuan HYX Industrial Co., Ltd."); + IDS.put(9753, "Advanced Silicon SA"); + IDS.put(9756, "EISST Limited"); + IDS.put(9771, "YTOP Electronics Technical (Kunshan) Co., Ltd."); + IDS.put(9841, "Innovative Logic"); + IDS.put(9842, "GoPro"); + IDS.put(9846, "Basler AG"); + IDS.put(9851, "Palpilot International Corp."); + IDS.put(9896, "UNIREX CORPORATION"); + IDS.put(9917, "Integral Memory Plc."); + IDS.put(9973, "Morning Star Digital Connector Co., Ltd."); + IDS.put(9984, "MITACHI CO., LTD."); + IDS.put(9999, "HGST, a Western Digital Company"); + } +} 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 new file mode 100644 index 0000000000..15a4180b75 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java @@ -0,0 +1,1238 @@ +/* + * 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; +import android.hardware.usb.UsbDevice; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.serenegiant.usb.USBMonitor.UsbControlBlock; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class UVCCamera { + private static final boolean DEBUG = false; // TODO set false when releasing + private static final String TAG = UVCCamera.class.getSimpleName(); + private static final String DEFAULT_USBFS = "/dev/bus/usb"; + + public static final int DEFAULT_PREVIEW_WIDTH = 640; + public static final int DEFAULT_PREVIEW_HEIGHT = 480; + public static final int DEFAULT_PREVIEW_MODE = 0; + public static final int DEFAULT_PREVIEW_MIN_FPS = 1; + public static final int DEFAULT_PREVIEW_MAX_FPS = 31; + public static final float DEFAULT_BANDWIDTH = 1.0f; + + public static final int FRAME_FORMAT_YUYV = 0; + public static final int FRAME_FORMAT_MJPEG = 1; + + public static final int PIXEL_FORMAT_RAW = 0; + public static final int PIXEL_FORMAT_YUV = 1; + public static final int PIXEL_FORMAT_RGB565 = 2; + public static final int PIXEL_FORMAT_RGBX = 3; + public static final int PIXEL_FORMAT_YUV420SP = 4; // NV12 + public static final int PIXEL_FORMAT_NV21 = 5; // = YVU420SemiPlanar,NV21,但是保存到jpg颜色失真 + + //-------------------------------------------------------------------------------- + public static final int CTRL_SCANNING = 0x00000001; // D0: Scanning Mode + public static final int CTRL_AE = 0x00000002; // D1: Auto-Exposure Mode + public static final int CTRL_AE_PRIORITY = 0x00000004; // D2: Auto-Exposure Priority + public static final int CTRL_AE_ABS = 0x00000008; // D3: Exposure Time (Absolute) + public static final int CTRL_AR_REL = 0x00000010; // D4: Exposure Time (Relative) + public static final int CTRL_FOCUS_ABS = 0x00000020; // D5: Focus (Absolute) + public static final int CTRL_FOCUS_REL = 0x00000040; // D6: Focus (Relative) + public static final int CTRL_IRIS_ABS = 0x00000080; // D7: Iris (Absolute) + public static final int CTRL_IRIS_REL = 0x00000100; // D8: Iris (Relative) + public static final int CTRL_ZOOM_ABS = 0x00000200; // D9: Zoom (Absolute) + public static final int CTRL_ZOOM_REL = 0x00000400; // D10: Zoom (Relative) + public static final int CTRL_PANTILT_ABS = 0x00000800; // D11: PanTilt (Absolute) + public static final int CTRL_PANTILT_REL = 0x00001000; // D12: PanTilt (Relative) + public static final int CTRL_ROLL_ABS = 0x00002000; // D13: Roll (Absolute) + public static final int CTRL_ROLL_REL = 0x00004000; // D14: Roll (Relative) + public static final int CTRL_FOCUS_AUTO = 0x00020000; // D17: Focus, Auto + public static final int CTRL_PRIVACY = 0x00040000; // D18: Privacy + public static final int CTRL_FOCUS_SIMPLE = 0x00080000; // D19: Focus, Simple + public static final int CTRL_WINDOW = 0x00100000; // D20: Window + + public static final int PU_BRIGHTNESS = 0x80000001; // D0: Brightness + public static final int PU_CONTRAST = 0x80000002; // D1: Contrast + public static final int PU_HUE = 0x80000004; // D2: Hue + public static final int PU_SATURATION = 0x80000008; // D3: Saturation + public static final int PU_SHARPNESS = 0x80000010; // D4: Sharpness + public static final int PU_GAMMA = 0x80000020; // D5: Gamma + public static final int PU_WB_TEMP = 0x80000040; // D6: White Balance Temperature + public static final int PU_WB_COMPO = 0x80000080; // D7: White Balance Component + public static final int PU_BACKLIGHT = 0x80000100; // D8: Backlight Compensation + public static final int PU_GAIN = 0x80000200; // D9: Gain + public static final int PU_POWER_LF = 0x80000400; // D10: Power Line Frequency + public static final int PU_HUE_AUTO = 0x80000800; // D11: Hue, Auto + public static final int PU_WB_TEMP_AUTO = 0x80001000; // D12: White Balance Temperature, Auto + public static final int PU_WB_COMPO_AUTO = 0x80002000; // D13: White Balance Component, Auto + public static final int PU_DIGITAL_MULT = 0x80004000; // D14: Digital Multiplier + public static final int PU_DIGITAL_LIMIT = 0x80008000; // D15: Digital Multiplier Limit + public static final int PU_AVIDEO_STD = 0x80010000; // D16: Analog Video Standard + public static final int PU_AVIDEO_LOCK = 0x80020000; // D17: Analog Video Lock Status + public static final int PU_CONTRAST_AUTO = 0x80040000; // D18: Contrast, Auto + + // uvc_status_class from libuvc.h + public static final int STATUS_CLASS_CONTROL = 0x10; + public static final int STATUS_CLASS_CONTROL_CAMERA = 0x11; + public static final int STATUS_CLASS_CONTROL_PROCESSING = 0x12; + + // uvc_status_attribute from libuvc.h + public static final int STATUS_ATTRIBUTE_VALUE_CHANGE = 0x00; + public static final int STATUS_ATTRIBUTE_INFO_CHANGE = 0x01; + public static final int STATUS_ATTRIBUTE_FAILURE_CHANGE = 0x02; + public static final int STATUS_ATTRIBUTE_UNKNOWN = 0xff; + + private static boolean isLoaded; + static { + if (!isLoaded) { + System.loadLibrary("jpeg-turbo1500"); + System.loadLibrary("usb100"); + System.loadLibrary("uvc"); + System.loadLibrary("UVCCamera"); + isLoaded = true; + } + } + + private UsbControlBlock mCtrlBlock; + protected long mControlSupports; // カメラコントロールでサポートしている機能フラグ + protected long mProcSupports; // プロセッシングユニットでサポートしている機能フラグ + protected int mCurrentFrameFormat = FRAME_FORMAT_MJPEG; + protected int mCurrentWidth = DEFAULT_PREVIEW_WIDTH, mCurrentHeight = DEFAULT_PREVIEW_HEIGHT; + protected float mCurrentBandwidthFactor = DEFAULT_BANDWIDTH; + protected String mSupportedSize; + protected List mCurrentSizeList; + // these fields from here are accessed from native code and do not change name and remove + protected long mNativePtr; + protected int mScanningModeMin, mScanningModeMax, mScanningModeDef; + protected int mExposureModeMin, mExposureModeMax, mExposureModeDef; + protected int mExposurePriorityMin, mExposurePriorityMax, mExposurePriorityDef; + protected int mExposureMin, mExposureMax, mExposureDef; + protected int mAutoFocusMin, mAutoFocusMax, mAutoFocusDef; + protected int mFocusMin, mFocusMax, mFocusDef; + protected int mFocusRelMin, mFocusRelMax, mFocusRelDef; + protected int mFocusSimpleMin, mFocusSimpleMax, mFocusSimpleDef; + protected int mIrisMin, mIrisMax, mIrisDef; + protected int mIrisRelMin, mIrisRelMax, mIrisRelDef; + protected int mPanMin, mPanMax, mPanDef; + protected int mTiltMin, mTiltMax, mTiltDef; + protected int mRollMin, mRollMax, mRollDef; + protected int mPanRelMin, mPanRelMax, mPanRelDef; + protected int mTiltRelMin, mTiltRelMax, mTiltRelDef; + protected int mRollRelMin, mRollRelMax, mRollRelDef; + protected int mPrivacyMin, mPrivacyMax, mPrivacyDef; + protected int mAutoWhiteBlanceMin, mAutoWhiteBlanceMax, mAutoWhiteBlanceDef; + protected int mAutoWhiteBlanceCompoMin, mAutoWhiteBlanceCompoMax, mAutoWhiteBlanceCompoDef; + protected int mWhiteBlanceMin, mWhiteBlanceMax, mWhiteBlanceDef; + protected int mWhiteBlanceCompoMin, mWhiteBlanceCompoMax, mWhiteBlanceCompoDef; + protected int mWhiteBlanceRelMin, mWhiteBlanceRelMax, mWhiteBlanceRelDef; + protected int mBacklightCompMin, mBacklightCompMax, mBacklightCompDef; + protected int mBrightnessMin, mBrightnessMax, mBrightnessDef; + protected int mContrastMin, mContrastMax, mContrastDef; + protected int mSharpnessMin, mSharpnessMax, mSharpnessDef; + protected int mGainMin, mGainMax, mGainDef; + protected int mGammaMin, mGammaMax, mGammaDef; + protected int mSaturationMin, mSaturationMax, mSaturationDef; + protected int mHueMin, mHueMax, mHueDef; + protected int mZoomMin, mZoomMax, mZoomDef; + protected int mZoomRelMin, mZoomRelMax, mZoomRelDef; + protected int mPowerlineFrequencyMin, mPowerlineFrequencyMax, mPowerlineFrequencyDef; + protected int mMultiplierMin, mMultiplierMax, mMultiplierDef; + protected int mMultiplierLimitMin, mMultiplierLimitMax, mMultiplierLimitDef; + protected int mAnalogVideoStandardMin, mAnalogVideoStandardMax, mAnalogVideoStandardDef; + protected int mAnalogVideoLockStateMin, mAnalogVideoLockStateMax, mAnalogVideoLockStateDef; + // until here + /** + * the sonctructor of this class should be call within the thread that has a looper + * (UI thread or a thread that called Looper.prepare) + */ + public UVCCamera() { + mNativePtr = nativeCreate(); + mSupportedSize = null; + } + + /** + * connect to a UVC camera + * USB permission is necessary before this method is called + * @param ctrlBlock + */ + public synchronized void open(final UsbControlBlock ctrlBlock) { + int result = -2; + StringBuilder sb = new StringBuilder(); + try { + mCtrlBlock = ctrlBlock.clone(); + result = nativeConnect(mNativePtr, + mCtrlBlock.getVenderId(), mCtrlBlock.getProductId(), + mCtrlBlock.getFileDescriptor(), + mCtrlBlock.getBusNum(), + mCtrlBlock.getDevNum(), + getUSBFSName(mCtrlBlock)); + sb.append("调用nativeConnect返回值:"+result); +// long id_camera, int venderId, int productId, int fileDescriptor, int busNum, int devAddr, String usbfs + } catch (final Exception e) { + Log.w(TAG, e); + for(int i = 0; i< e.getStackTrace().length; i++){ + sb.append(e.getStackTrace()[i].toString()); + sb.append("\n"); + } + sb.append("core message ->"+e.getLocalizedMessage()); + result = -1; + } + + if (result != 0) { + throw new UnsupportedOperationException("open failed:result=" + result+"----->" + + "id_camera="+mNativePtr+";venderId="+mCtrlBlock.getVenderId() + +";productId="+mCtrlBlock.getProductId()+";fileDescriptor="+mCtrlBlock.getFileDescriptor() + +";busNum="+mCtrlBlock.getBusNum()+";devAddr="+mCtrlBlock.getDevNum() + +";usbfs="+getUSBFSName(mCtrlBlock)+"\n"+"Exception:"+sb.toString()); + } + + if (mNativePtr != 0 && TextUtils.isEmpty(mSupportedSize)) { + mSupportedSize = nativeGetSupportedSize(mNativePtr); + } + nativeSetPreviewSize(mNativePtr, DEFAULT_PREVIEW_WIDTH, DEFAULT_PREVIEW_HEIGHT, + DEFAULT_PREVIEW_MIN_FPS, DEFAULT_PREVIEW_MAX_FPS, DEFAULT_PREVIEW_MODE, DEFAULT_BANDWIDTH); + } + + /** + * set status callback + * @param callback + */ + public void setStatusCallback(final IStatusCallback callback) { + if (mNativePtr != 0) { + nativeSetStatusCallback(mNativePtr, callback); + } + } + + /** + * set button callback + * @param callback + */ + public void setButtonCallback(final IButtonCallback callback) { + if (mNativePtr != 0) { + nativeSetButtonCallback(mNativePtr, callback); + } + } + + /** + * close and release UVC camera + */ + public synchronized void close() { + stopPreview(); + if (mNativePtr != 0) { + nativeRelease(mNativePtr); +// mNativePtr = 0; // nativeDestroyを呼ぶのでここでクリアしちゃダメ + } + if (mCtrlBlock != null) { + mCtrlBlock.close(); + mCtrlBlock = null; + } + mControlSupports = mProcSupports = 0; + mCurrentFrameFormat = -1; + mCurrentBandwidthFactor = 0; + mSupportedSize = null; + mCurrentSizeList = null; + if (DEBUG) Log.v(TAG, "close:finished"); + } + + public UsbDevice getDevice() { + return mCtrlBlock != null ? mCtrlBlock.getDevice() : null; + } + + public String getDeviceName(){ + return mCtrlBlock != null ? mCtrlBlock.getDeviceName() : null; + } + + public UsbControlBlock getUsbControlBlock() { + return mCtrlBlock; + } + + public synchronized String getSupportedSize() { + return !TextUtils.isEmpty(mSupportedSize) ? mSupportedSize : (mSupportedSize = nativeGetSupportedSize(mNativePtr)); + } + + public Size getPreviewSize() { + Size result = null; + final List list = getSupportedSizeList(); + for (final Size sz: list) { + if ((sz.width == mCurrentWidth) + || (sz.height == mCurrentHeight)) { + result =sz; + break; + } + } + return result; + } + + /** + * Set preview size and preview mode + * @param width + @param height + */ + public void setPreviewSize(final int width, final int height) { + setPreviewSize(width, height, DEFAULT_PREVIEW_MIN_FPS, DEFAULT_PREVIEW_MAX_FPS, mCurrentFrameFormat, mCurrentBandwidthFactor); + } + + /** + * Set preview size and preview mode + * @param width + * @param height + * @param frameFormat either FRAME_FORMAT_YUYV(0) or FRAME_FORMAT_MJPEG(1) + */ + public void setPreviewSize(final int width, final int height, final int frameFormat) { + setPreviewSize(width, height, DEFAULT_PREVIEW_MIN_FPS, DEFAULT_PREVIEW_MAX_FPS, frameFormat, mCurrentBandwidthFactor); + } + + /** + * Set preview size and preview mode + * @param width + @param height + @param frameFormat either FRAME_FORMAT_YUYV(0) or FRAME_FORMAT_MJPEG(1) + @param bandwidth [0.0f,1.0f] + */ + public void setPreviewSize(final int width, final int height, final int frameFormat, final float bandwidth) { + setPreviewSize(width, height, DEFAULT_PREVIEW_MIN_FPS, DEFAULT_PREVIEW_MAX_FPS, frameFormat, bandwidth); + } + + /** + * Set preview size and preview mode + * @param width + * @param height + * @param min_fps + * @param max_fps + * @param frameFormat either FRAME_FORMAT_YUYV(0) or FRAME_FORMAT_MJPEG(1) + * @param bandwidthFactor + */ + public void setPreviewSize(final int width, final int height, final int min_fps, final int max_fps, final int frameFormat, final float bandwidthFactor) { + if ((width == 0) || (height == 0)) + throw new IllegalArgumentException("invalid preview size"); + if (mNativePtr != 0) { + final int result = nativeSetPreviewSize(mNativePtr, width, height, min_fps, max_fps, frameFormat, bandwidthFactor); + if (result != 0) + throw new IllegalArgumentException("Failed to set preview size"); + mCurrentFrameFormat = frameFormat; + mCurrentWidth = width; + mCurrentHeight = height; + mCurrentBandwidthFactor = bandwidthFactor; + } + } + + public List getSupportedSizeList() { + final int type = (mCurrentFrameFormat > 0) ? 6 : 4; + return getSupportedSize(type, mSupportedSize); + } + + public static List getSupportedSize(final int type, final String supportedSize) { + final List result = new ArrayList(); + if (!TextUtils.isEmpty(supportedSize)) + try { + final JSONObject json = new JSONObject(supportedSize); + final JSONArray formats = json.getJSONArray("formats"); + final int format_nums = formats.length(); + for (int i = 0; i < format_nums; i++) { + final JSONObject format = formats.getJSONObject(i); + if(format.has("type") && format.has("size")) { + final int format_type = format.getInt("type"); + if ((format_type == type) || (type == -1)) { + addSize(format, format_type, 0, result); + } + } + } + } catch (final JSONException e) { + e.printStackTrace(); + } + return result; + } + + private static final void addSize(final JSONObject format, final int formatType, final int frameType, final List size_list) throws JSONException { + final JSONArray size = format.getJSONArray("size"); + final int size_nums = size.length(); + for (int j = 0; j < size_nums; j++) { + final String[] sz = size.getString(j).split("x"); + try { + size_list.add(new Size(formatType, frameType, j, Integer.parseInt(sz[0]), Integer.parseInt(sz[1]))); + } catch (final Exception e) { + break; + } + } + } + + /** + * set preview surface with SurfaceHolder
+ * you can use SurfaceHolder came from SurfaceView/GLSurfaceView + * @param holder + */ + public synchronized void setPreviewDisplay(final SurfaceHolder holder) { + nativeSetPreviewDisplay(mNativePtr, holder.getSurface()); + } + + /** + * set preview surface with SurfaceTexture. + * this method require API >= 14 + * @param texture + */ + public synchronized void setPreviewTexture(final SurfaceTexture texture) { // API >= 11 + final Surface surface = new Surface(texture); // XXX API >= 14 + nativeSetPreviewDisplay(mNativePtr, surface); + } + + /** + * set preview surface with Surface + * @param surface + */ + public synchronized void setPreviewDisplay(final Surface surface) { + nativeSetPreviewDisplay(mNativePtr, surface); + } + + /** + * set frame callback + * @param callback + * @param pixelFormat + */ + public void setFrameCallback(final IFrameCallback callback, final int pixelFormat) { + if (mNativePtr != 0) { + nativeSetFrameCallback(mNativePtr, callback, pixelFormat); + } + } + + /** + * start preview + */ + public synchronized void startPreview() { + if (mCtrlBlock != null) { + nativeStartPreview(mNativePtr); + } + } + + /** + * stop preview + */ + public synchronized void stopPreview() { + setFrameCallback(null, 0); + if (mCtrlBlock != null) { + nativeStopPreview(mNativePtr); + } + } + + /** + * destroy UVCCamera object + */ + public synchronized void destroy() { + close(); + if (mNativePtr != 0) { + nativeDestroy(mNativePtr); + mNativePtr = 0; + } + } + + // wrong result may return when you call this just after camera open. + // it is better to wait several hundreads millseconds. + public boolean checkSupportFlag(final long flag) { + updateCameraParams(); + if ((flag & 0x80000000) == 0x80000000) + return ((mProcSupports & flag) == (flag & 0x7ffffffF)); + else + return (mControlSupports & flag) == flag; + } + +//================================================================================ + public synchronized void setAutoFocus(final boolean autoFocus) { + if (mNativePtr != 0) { + nativeSetAutoFocus(mNativePtr, autoFocus); + } + } + + public synchronized boolean getAutoFocus() { + boolean result = true; + if (mNativePtr != 0) { + result = nativeGetAutoFocus(mNativePtr) > 0; + } + return result; + } +//================================================================================ + /** + * @param focus [%] + */ + public synchronized void setFocus(final int focus) { + if (mNativePtr != 0) { + final float range = Math.abs(mFocusMax - mFocusMin); + if (range > 0) + nativeSetFocus(mNativePtr, (int)(focus / 100.f * range) + mFocusMin); + } + } + + /** + * @param focus_abs + * @return focus[%] + */ + public synchronized int getFocus(final int focus_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateFocusLimit(mNativePtr); + final float range = Math.abs(mFocusMax - mFocusMin); + if (range > 0) { + result = (int)((focus_abs - mFocusMin) * 100.f / range); + } + } + return result; + } + + /** + * @return focus[%] + */ + public synchronized int getFocus() { + return getFocus(nativeGetFocus(mNativePtr)); + } + + public synchronized void resetFocus() { + if (mNativePtr != 0) { + nativeSetFocus(mNativePtr, mFocusDef); + } + } + +//================================================================================ + public synchronized void setAutoWhiteBlance(final boolean autoWhiteBlance) { + if (mNativePtr != 0) { + nativeSetAutoWhiteBlance(mNativePtr, autoWhiteBlance); + } + } + + public synchronized boolean getAutoWhiteBlance() { + boolean result = true; + if (mNativePtr != 0) { + result = nativeGetAutoWhiteBlance(mNativePtr) > 0; + } + return result; + } + +//================================================================================ + /** + * @param whiteBlance [%] + */ + public synchronized void setWhiteBlance(final int whiteBlance) { + if (mNativePtr != 0) { + final float range = Math.abs(mWhiteBlanceMax - mWhiteBlanceMin); + if (range > 0) + nativeSetWhiteBlance(mNativePtr, (int)(whiteBlance / 100.f * range) + mWhiteBlanceMin); + } + } + + /** + * @param whiteBlance_abs + * @return whiteBlance[%] + */ + public synchronized int getWhiteBlance(final int whiteBlance_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateWhiteBlanceLimit(mNativePtr); + final float range = Math.abs(mWhiteBlanceMax - mWhiteBlanceMin); + if (range > 0) { + result = (int)((whiteBlance_abs - mWhiteBlanceMin) * 100.f / range); + } + } + return result; + } + + /** + * @return white blance[%] + */ + public synchronized int getWhiteBlance() { + return getFocus(nativeGetWhiteBlance(mNativePtr)); + } + + public synchronized void resetWhiteBlance() { + if (mNativePtr != 0) { + nativeSetWhiteBlance(mNativePtr, mWhiteBlanceDef); + } + } +//================================================================================ + /** + * @param brightness [%] + */ + public synchronized void setBrightness(final int brightness) { + if (mNativePtr != 0) { + final float range = Math.abs(mBrightnessMax - mBrightnessMin); + if (range > 0) + nativeSetBrightness(mNativePtr, (int)(brightness / 100.f * range) + mBrightnessMin); + } + } + + /** + * @param brightness_abs + * @return brightness[%] + */ + public synchronized int getBrightness(final int brightness_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateBrightnessLimit(mNativePtr); + final float range = Math.abs(mBrightnessMax - mBrightnessMin); + if (range > 0) { + result = (int)((brightness_abs - mBrightnessMin) * 100.f / range); + } + } + return result; + } + + /** + * @return brightness[%] + */ + public synchronized int getBrightness() { + return getBrightness(nativeGetBrightness(mNativePtr)); + } + + public synchronized void resetBrightness() { + if (mNativePtr != 0) { + nativeSetBrightness(mNativePtr, mBrightnessDef); + } + } + +//================================================================================ + /** + * @param contrast [%] + */ + public synchronized void setContrast(final int contrast) { + if (mNativePtr != 0) { + nativeUpdateContrastLimit(mNativePtr); + final float range = Math.abs(mContrastMax - mContrastMin); + if (range > 0) + nativeSetContrast(mNativePtr, (int)(contrast / 100.f * range) + mContrastMin); + } + } + + /** + * @param contrast_abs + * @return contrast[%] + */ + public synchronized int getContrast(final int contrast_abs) { + int result = 0; + if (mNativePtr != 0) { + final float range = Math.abs(mContrastMax - mContrastMin); + if (range > 0) { + result = (int)((contrast_abs - mContrastMin) * 100.f / range); + } + } + return result; + } + + /** + * @return contrast[%] + */ + public synchronized int getContrast() { + return getContrast(nativeGetContrast(mNativePtr)); + } + + public synchronized void resetContrast() { + if (mNativePtr != 0) { + nativeSetContrast(mNativePtr, mContrastDef); + } + } + +//================================================================================ + /** + * @param sharpness [%] + */ + public synchronized void setSharpness(final int sharpness) { + if (mNativePtr != 0) { + final float range = Math.abs(mSharpnessMax - mSharpnessMin); + if (range > 0) + nativeSetSharpness(mNativePtr, (int)(sharpness / 100.f * range) + mSharpnessMin); + } + } + + /** + * @param sharpness_abs + * @return sharpness[%] + */ + public synchronized int getSharpness(final int sharpness_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateSharpnessLimit(mNativePtr); + final float range = Math.abs(mSharpnessMax - mSharpnessMin); + if (range > 0) { + result = (int)((sharpness_abs - mSharpnessMin) * 100.f / range); + } + } + return result; + } + + /** + * @return sharpness[%] + */ + public synchronized int getSharpness() { + return getSharpness(nativeGetSharpness(mNativePtr)); + } + + public synchronized void resetSharpness() { + if (mNativePtr != 0) { + nativeSetSharpness(mNativePtr, mSharpnessDef); + } + } +//================================================================================ + /** + * @param gain [%] + */ + public synchronized void setGain(final int gain) { + if (mNativePtr != 0) { + final float range = Math.abs(mGainMax - mGainMin); + if (range > 0) + nativeSetGain(mNativePtr, (int)(gain / 100.f * range) + mGainMin); + } + } + + /** + * @param gain_abs + * @return gain[%] + */ + public synchronized int getGain(final int gain_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateGainLimit(mNativePtr); + final float range = Math.abs(mGainMax - mGainMin); + if (range > 0) { + result = (int)((gain_abs - mGainMin) * 100.f / range); + } + } + return result; + } + + /** + * @return gain[%] + */ + public synchronized int getGain() { + return getGain(nativeGetGain(mNativePtr)); + } + + public synchronized void resetGain() { + if (mNativePtr != 0) { + nativeSetGain(mNativePtr, mGainDef); + } + } + +//================================================================================ + /** + * @param gamma [%] + */ + public synchronized void setGamma(final int gamma) { + if (mNativePtr != 0) { + final float range = Math.abs(mGammaMax - mGammaMin); + if (range > 0) + nativeSetGamma(mNativePtr, (int)(gamma / 100.f * range) + mGammaMin); + } + } + + /** + * @param gamma_abs + * @return gamma[%] + */ + public synchronized int getGamma(final int gamma_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateGammaLimit(mNativePtr); + final float range = Math.abs(mGammaMax - mGammaMin); + if (range > 0) { + result = (int)((gamma_abs - mGammaMin) * 100.f / range); + } + } + return result; + } + + /** + * @return gamma[%] + */ + public synchronized int getGamma() { + return getGamma(nativeGetGamma(mNativePtr)); + } + + public synchronized void resetGamma() { + if (mNativePtr != 0) { + nativeSetGamma(mNativePtr, mGammaDef); + } + } + +//================================================================================ + /** + * @param saturation [%] + */ + public synchronized void setSaturation(final int saturation) { + if (mNativePtr != 0) { + final float range = Math.abs(mSaturationMax - mSaturationMin); + if (range > 0) + nativeSetSaturation(mNativePtr, (int)(saturation / 100.f * range) + mSaturationMin); + } + } + + /** + * @param saturation_abs + * @return saturation[%] + */ + public synchronized int getSaturation(final int saturation_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateSaturationLimit(mNativePtr); + final float range = Math.abs(mSaturationMax - mSaturationMin); + if (range > 0) { + result = (int)((saturation_abs - mSaturationMin) * 100.f / range); + } + } + return result; + } + + /** + * @return saturation[%] + */ + public synchronized int getSaturation() { + return getSaturation(nativeGetSaturation(mNativePtr)); + } + + public synchronized void resetSaturation() { + if (mNativePtr != 0) { + nativeSetSaturation(mNativePtr, mSaturationDef); + } + } +//================================================================================ + /** + * @param hue [%] + */ + public synchronized void setHue(final int hue) { + if (mNativePtr != 0) { + final float range = Math.abs(mHueMax - mHueMin); + if (range > 0) + nativeSetHue(mNativePtr, (int)(hue / 100.f * range) + mHueMin); + } + } + + /** + * @param hue_abs + * @return hue[%] + */ + public synchronized int getHue(final int hue_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateHueLimit(mNativePtr); + final float range = Math.abs(mHueMax - mHueMin); + if (range > 0) { + result = (int)((hue_abs - mHueMin) * 100.f / range); + } + } + return result; + } + + /** + * @return hue[%] + */ + public synchronized int getHue() { + return getHue(nativeGetHue(mNativePtr)); + } + + public synchronized void resetHue() { + if (mNativePtr != 0) { + nativeSetHue(mNativePtr, mSaturationDef); + } + } + +//================================================================================ + public void setPowerlineFrequency(final int frequency) { + if (mNativePtr != 0) + nativeSetPowerlineFrequency(mNativePtr, frequency); + } + + public int getPowerlineFrequency() { + return nativeGetPowerlineFrequency(mNativePtr); + } + +//================================================================================ + /** + * this may not work well with some combination of camera and device + * @param zoom [%] + */ + public synchronized void setZoom(final int zoom) { + if (mNativePtr != 0) { + final float range = Math.abs(mZoomMax - mZoomMin); + if (range > 0) { + final int z = (int)(zoom / 100.f * range) + mZoomMin; +// Log.d(TAG, "setZoom:zoom=" + zoom + " ,value=" + z); + nativeSetZoom(mNativePtr, z); + } + } + } + + /** + * @param zoom_abs + * @return zoom[%] + */ + public synchronized int getZoom(final int zoom_abs) { + int result = 0; + if (mNativePtr != 0) { + nativeUpdateZoomLimit(mNativePtr); + final float range = Math.abs(mZoomMax - mZoomMin); + if (range > 0) { + result = (int)((zoom_abs - mZoomMin) * 100.f / range); + } + } + return result; + } + + /** + * @return zoom[%] + */ + public synchronized int getZoom() { + return getZoom(nativeGetZoom(mNativePtr)); + } + + public synchronized void resetZoom() { + if (mNativePtr != 0) { + nativeSetZoom(mNativePtr, mZoomDef); + } + } + +//================================================================================ + public synchronized void updateCameraParams() { + if (mNativePtr != 0) { + if ((mControlSupports == 0) || (mProcSupports == 0)) { + // サポートしている機能フラグを取得 + if (mControlSupports == 0) + mControlSupports = nativeGetCtrlSupports(mNativePtr); + if (mProcSupports == 0) + mProcSupports = nativeGetProcSupports(mNativePtr); + // 設定値を取得 + if ((mControlSupports != 0) && (mProcSupports != 0)) { + nativeUpdateBrightnessLimit(mNativePtr); + nativeUpdateContrastLimit(mNativePtr); + nativeUpdateSharpnessLimit(mNativePtr); + nativeUpdateGainLimit(mNativePtr); + nativeUpdateGammaLimit(mNativePtr); + nativeUpdateSaturationLimit(mNativePtr); + nativeUpdateHueLimit(mNativePtr); + nativeUpdateZoomLimit(mNativePtr); + nativeUpdateWhiteBlanceLimit(mNativePtr); + nativeUpdateFocusLimit(mNativePtr); + } + if (DEBUG) { + dumpControls(mControlSupports); + dumpProc(mProcSupports); + Log.v(TAG, String.format("Brightness:min=%d,max=%d,def=%d", mBrightnessMin, mBrightnessMax, mBrightnessDef)); + Log.v(TAG, String.format("Contrast:min=%d,max=%d,def=%d", mContrastMin, mContrastMax, mContrastDef)); + Log.v(TAG, String.format("Sharpness:min=%d,max=%d,def=%d", mSharpnessMin, mSharpnessMax, mSharpnessDef)); + Log.v(TAG, String.format("Gain:min=%d,max=%d,def=%d", mGainMin, mGainMax, mGainDef)); + Log.v(TAG, String.format("Gamma:min=%d,max=%d,def=%d", mGammaMin, mGammaMax, mGammaDef)); + Log.v(TAG, String.format("Saturation:min=%d,max=%d,def=%d", mSaturationMin, mSaturationMax, mSaturationDef)); + Log.v(TAG, String.format("Hue:min=%d,max=%d,def=%d", mHueMin, mHueMax, mHueDef)); + Log.v(TAG, String.format("Zoom:min=%d,max=%d,def=%d", mZoomMin, mZoomMax, mZoomDef)); + Log.v(TAG, String.format("WhiteBlance:min=%d,max=%d,def=%d", mWhiteBlanceMin, mWhiteBlanceMax, mWhiteBlanceDef)); + Log.v(TAG, String.format("Focus:min=%d,max=%d,def=%d", mFocusMin, mFocusMax, mFocusDef)); + } + } + } else { + mControlSupports = mProcSupports = 0; + } + } + + private static final String[] SUPPORTS_CTRL = { + "D0: Scanning Mode", + "D1: Auto-Exposure Mode", + "D2: Auto-Exposure Priority", + "D3: Exposure Time (Absolute)", + "D4: Exposure Time (Relative)", + "D5: Focus (Absolute)", + "D6: Focus (Relative)", + "D7: Iris (Absolute)", + "D8: Iris (Relative)", + "D9: Zoom (Absolute)", + "D10: Zoom (Relative)", + "D11: PanTilt (Absolute)", + "D12: PanTilt (Relative)", + "D13: Roll (Absolute)", + "D14: Roll (Relative)", + "D15: Reserved", + "D16: Reserved", + "D17: Focus, Auto", + "D18: Privacy", + "D19: Focus, Simple", + "D20: Window", + "D21: Region of Interest", + "D22: Reserved, set to zero", + "D23: Reserved, set to zero", + }; + + private static final String[] SUPPORTS_PROC = { + "D0: Brightness", + "D1: Contrast", + "D2: Hue", + "D3: Saturation", + "D4: Sharpness", + "D5: Gamma", + "D6: White Balance Temperature", + "D7: White Balance Component", + "D8: Backlight Compensation", + "D9: Gain", + "D10: Power Line Frequency", + "D11: Hue, Auto", + "D12: White Balance Temperature, Auto", + "D13: White Balance Component, Auto", + "D14: Digital Multiplier", + "D15: Digital Multiplier Limit", + "D16: Analog Video Standard", + "D17: Analog Video Lock Status", + "D18: Contrast, Auto", + "D19: Reserved. Set to zero", + "D20: Reserved. Set to zero", + "D21: Reserved. Set to zero", + "D22: Reserved. Set to zero", + "D23: Reserved. Set to zero", + }; + + private static final void dumpControls(final long controlSupports) { + Log.i(TAG, String.format("controlSupports=%x", controlSupports)); + for (int i = 0; i < SUPPORTS_CTRL.length; i++) { + Log.i(TAG, SUPPORTS_CTRL[i] + ((controlSupports & (0x1 << i)) != 0 ? "=enabled" : "=disabled")); + } + } + + private static final void dumpProc(final long procSupports) { + Log.i(TAG, String.format("procSupports=%x", procSupports)); + for (int i = 0; i < SUPPORTS_PROC.length; i++) { + Log.i(TAG, SUPPORTS_PROC[i] + ((procSupports & (0x1 << i)) != 0 ? "=enabled" : "=disabled")); + } + } + + private final String getUSBFSName(final UsbControlBlock ctrlBlock) { + String result = null; + final String name = ctrlBlock.getDeviceName(); + final String[] v = !TextUtils.isEmpty(name) ? name.split("/") : null; + if ((v != null) && (v.length > 2)) { + final StringBuilder sb = new StringBuilder(v[0]); + for (int i = 1; i < v.length - 2; i++) + sb.append("/").append(v[i]); + result = sb.toString(); + } + if (TextUtils.isEmpty(result)) { + Log.w(TAG, "failed to get USBFS path, try to use default path:" + name); + result = DEFAULT_USBFS; + } + return result; + } + + // #nativeCreate and #nativeDestroy are not static methods. + private final native long nativeCreate(); + private final native void nativeDestroy(final long id_camera); + + private final native int nativeConnect(long id_camera, int venderId, int productId, int fileDescriptor, int busNum, int devAddr, String usbfs); + private static final native int nativeRelease(final long id_camera); + + private static final native int nativeSetStatusCallback(final long mNativePtr, final IStatusCallback callback); + private static final native int nativeSetButtonCallback(final long mNativePtr, final IButtonCallback callback); + + private static final native int nativeSetPreviewSize(final long id_camera, final int width, final int height, final int min_fps, final int max_fps, final int mode, final float bandwidth); + private static final native String nativeGetSupportedSize(final long id_camera); + private static final native int nativeStartPreview(final long id_camera); + private static final native int nativeStopPreview(final long id_camera); + private static final native int nativeSetPreviewDisplay(final long id_camera, final Surface surface); + private static final native int nativeSetFrameCallback(final long mNativePtr, final IFrameCallback callback, final int pixelFormat); + +//********************************************************************** + /** + * start movie capturing(this should call while previewing) + * @param surface + */ + public void startCapture(final Surface surface) { + if (mCtrlBlock != null && surface != null) { + nativeSetCaptureDisplay(mNativePtr, surface); + } else + throw new NullPointerException("startCapture"); + } + + /** + * stop movie capturing + */ + public void stopCapture() { + if (mCtrlBlock != null) { + nativeSetCaptureDisplay(mNativePtr, null); + } + } + private static final native int nativeSetCaptureDisplay(final long id_camera, final Surface surface); + + private static final native long nativeGetCtrlSupports(final long id_camera); + private static final native long nativeGetProcSupports(final long id_camera); + + private final native int nativeUpdateScanningModeLimit(final long id_camera); + private static final native int nativeSetScanningMode(final long id_camera, final int scanning_mode); + private static final native int nativeGetScanningMode(final long id_camera); + + private final native int nativeUpdateExposureModeLimit(final long id_camera); + private static final native int nativeSetExposureMode(final long id_camera, final int exposureMode); + private static final native int nativeGetExposureMode(final long id_camera); + + private final native int nativeUpdateExposurePriorityLimit(final long id_camera); + private static final native int nativeSetExposurePriority(final long id_camera, final int priority); + private static final native int nativeGetExposurePriority(final long id_camera); + + private final native int nativeUpdateExposureLimit(final long id_camera); + private static final native int nativeSetExposure(final long id_camera, final int exposure); + private static final native int nativeGetExposure(final long id_camera); + + private final native int nativeUpdateExposureRelLimit(final long id_camera); + private static final native int nativeSetExposureRel(final long id_camera, final int exposure_rel); + private static final native int nativeGetExposureRel(final long id_camera); + + private final native int nativeUpdateAutoFocusLimit(final long id_camera); + private static final native int nativeSetAutoFocus(final long id_camera, final boolean autofocus); + private static final native int nativeGetAutoFocus(final long id_camera); + + private final native int nativeUpdateFocusLimit(final long id_camera); + private static final native int nativeSetFocus(final long id_camera, final int focus); + private static final native int nativeGetFocus(final long id_camera); + + private final native int nativeUpdateFocusRelLimit(final long id_camera); + private static final native int nativeSetFocusRel(final long id_camera, final int focus_rel); + private static final native int nativeGetFocusRel(final long id_camera); + + private final native int nativeUpdateIrisLimit(final long id_camera); + private static final native int nativeSetIris(final long id_camera, final int iris); + private static final native int nativeGetIris(final long id_camera); + + private final native int nativeUpdateIrisRelLimit(final long id_camera); + private static final native int nativeSetIrisRel(final long id_camera, final int iris_rel); + private static final native int nativeGetIrisRel(final long id_camera); + + private final native int nativeUpdatePanLimit(final long id_camera); + private static final native int nativeSetPan(final long id_camera, final int pan); + private static final native int nativeGetPan(final long id_camera); + + private final native int nativeUpdatePanRelLimit(final long id_camera); + private static final native int nativeSetPanRel(final long id_camera, final int pan_rel); + private static final native int nativeGetPanRel(final long id_camera); + + private final native int nativeUpdateTiltLimit(final long id_camera); + private static final native int nativeSetTilt(final long id_camera, final int tilt); + private static final native int nativeGetTilt(final long id_camera); + + private final native int nativeUpdateTiltRelLimit(final long id_camera); + private static final native int nativeSetTiltRel(final long id_camera, final int tilt_rel); + private static final native int nativeGetTiltRel(final long id_camera); + + private final native int nativeUpdateRollLimit(final long id_camera); + private static final native int nativeSetRoll(final long id_camera, final int roll); + private static final native int nativeGetRoll(final long id_camera); + + private final native int nativeUpdateRollRelLimit(final long id_camera); + private static final native int nativeSetRollRel(final long id_camera, final int roll_rel); + private static final native int nativeGetRollRel(final long id_camera); + + private final native int nativeUpdateAutoWhiteBlanceLimit(final long id_camera); + private static final native int nativeSetAutoWhiteBlance(final long id_camera, final boolean autoWhiteBlance); + private static final native int nativeGetAutoWhiteBlance(final long id_camera); + + private final native int nativeUpdateAutoWhiteBlanceCompoLimit(final long id_camera); + private static final native int nativeSetAutoWhiteBlanceCompo(final long id_camera, final boolean autoWhiteBlanceCompo); + private static final native int nativeGetAutoWhiteBlanceCompo(final long id_camera); + + private final native int nativeUpdateWhiteBlanceLimit(final long id_camera); + private static final native int nativeSetWhiteBlance(final long id_camera, final int whiteBlance); + private static final native int nativeGetWhiteBlance(final long id_camera); + + private final native int nativeUpdateWhiteBlanceCompoLimit(final long id_camera); + private static final native int nativeSetWhiteBlanceCompo(final long id_camera, final int whiteBlance_compo); + private static final native int nativeGetWhiteBlanceCompo(final long id_camera); + + private final native int nativeUpdateBacklightCompLimit(final long id_camera); + private static final native int nativeSetBacklightComp(final long id_camera, final int backlight_comp); + private static final native int nativeGetBacklightComp(final long id_camera); + + private final native int nativeUpdateBrightnessLimit(final long id_camera); + private static final native int nativeSetBrightness(final long id_camera, final int brightness); + private static final native int nativeGetBrightness(final long id_camera); + + private final native int nativeUpdateContrastLimit(final long id_camera); + private static final native int nativeSetContrast(final long id_camera, final int contrast); + private static final native int nativeGetContrast(final long id_camera); + + private final native int nativeUpdateAutoContrastLimit(final long id_camera); + private static final native int nativeSetAutoContrast(final long id_camera, final boolean autocontrast); + private static final native int nativeGetAutoContrast(final long id_camera); + + private final native int nativeUpdateSharpnessLimit(final long id_camera); + private static final native int nativeSetSharpness(final long id_camera, final int sharpness); + private static final native int nativeGetSharpness(final long id_camera); + + private final native int nativeUpdateGainLimit(final long id_camera); + private static final native int nativeSetGain(final long id_camera, final int gain); + private static final native int nativeGetGain(final long id_camera); + + private final native int nativeUpdateGammaLimit(final long id_camera); + private static final native int nativeSetGamma(final long id_camera, final int gamma); + private static final native int nativeGetGamma(final long id_camera); + + private final native int nativeUpdateSaturationLimit(final long id_camera); + private static final native int nativeSetSaturation(final long id_camera, final int saturation); + private static final native int nativeGetSaturation(final long id_camera); + + private final native int nativeUpdateHueLimit(final long id_camera); + private static final native int nativeSetHue(final long id_camera, final int hue); + private static final native int nativeGetHue(final long id_camera); + + private final native int nativeUpdateAutoHueLimit(final long id_camera); + private static final native int nativeSetAutoHue(final long id_camera, final boolean autohue); + private static final native int nativeGetAutoHue(final long id_camera); + + private final native int nativeUpdatePowerlineFrequencyLimit(final long id_camera); + private static final native int nativeSetPowerlineFrequency(final long id_camera, final int frequency); + private static final native int nativeGetPowerlineFrequency(final long id_camera); + + private final native int nativeUpdateZoomLimit(final long id_camera); + private static final native int nativeSetZoom(final long id_camera, final int zoom); + private static final native int nativeGetZoom(final long id_camera); + + private final native int nativeUpdateZoomRelLimit(final long id_camera); + private static final native int nativeSetZoomRel(final long id_camera, final int zoom_rel); + private static final native int nativeGetZoomRel(final long id_camera); + + private final native int nativeUpdateDigitalMultiplierLimit(final long id_camera); + private static final native int nativeSetDigitalMultiplier(final long id_camera, final int multiplier); + private static final native int nativeGetDigitalMultiplier(final long id_camera); + + private final native int nativeUpdateDigitalMultiplierLimitLimit(final long id_camera); + private static final native int nativeSetDigitalMultiplierLimit(final long id_camera, final int multiplier_limit); + private static final native int nativeGetDigitalMultiplierLimit(final long id_camera); + + private final native int nativeUpdateAnalogVideoStandardLimit(final long id_camera); + private static final native int nativeSetAnalogVideoStandard(final long id_camera, final int standard); + private static final native int nativeGetAnalogVideoStandard(final long id_camera); + + private final native int nativeUpdateAnalogVideoLockStateLimit(final long id_camera); + private static final native int nativeSetAnalogVideoLoackState(final long id_camera, final int state); + private static final native int nativeGetAnalogVideoLoackState(final long id_camera); + + private final native int nativeUpdatePrivacyLimit(final long id_camera); + private static final native int nativeSetPrivacy(final long id_camera, final boolean privacy); + private static final native int nativeGetPrivacy(final long id_camera); +} 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 new file mode 100644 index 0000000000..992d1cf562 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java @@ -0,0 +1,1144 @@ +package com.serenegiant.usb.common; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.graphics.YuvImage; +import android.hardware.usb.UsbDevice; +import android.media.MediaScannerConnection; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +import com.serenegiant.usb.IFrameCallback; +import com.serenegiant.usb.Size; +import com.serenegiant.usb.USBMonitor; +import com.serenegiant.usb.UVCCamera; +import com.serenegiant.usb.encoder.MediaEncoder; +import com.serenegiant.usb.encoder.MediaMuxerWrapper; +import com.serenegiant.usb.encoder.MediaSurfaceEncoder; +import com.serenegiant.usb.encoder.MediaVideoBufferEncoder; +import com.serenegiant.usb.encoder.MediaVideoEncoder; +import com.serenegiant.usb.encoder.RecordParams; +import com.serenegiant.usb.encoder.biz.AACEncodeConsumer; +import com.serenegiant.usb.encoder.biz.H264EncodeConsumer; +import com.serenegiant.usb.encoder.biz.Mp4MediaMuxer; +import com.serenegiant.usb.widget.CameraViewInterface; + +import org.easydarwin.sw.TxtOverlay; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Camera业务处理抽象类 + */ +public abstract class AbstractUVCCameraHandler extends Handler { + + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "AbsUVCCameraHandler"; + + + // 对外回调接口 + public interface CameraCallback { + public void onOpen(); + + public void onClose(); + + public void onStartPreview(); + + public void onStopPreview(); + + public void onStartRecording(); + + public void onStopRecording(); + + public void onError(final Exception e); + } + + public static OnEncodeResultListener mListener; + public static OnPreViewResultListener mPreviewListener; + public static OnCaptureListener mCaptureListener; + + public interface OnEncodeResultListener { + void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type); + + void onRecordResult(String videoPath); + } + + public interface OnPreViewResultListener { + void onPreviewResult(byte[] data); + } + + public interface OnCaptureListener { + void onCaptureResult(String picPath); + } + + private static final int MSG_OPEN = 0; + private static final int MSG_CLOSE = 1; + private static final int MSG_PREVIEW_START = 2; + private static final int MSG_PREVIEW_STOP = 3; + private static final int MSG_CAPTURE_STILL = 4; + private static final int MSG_CAPTURE_START = 5; + private static final int MSG_CAPTURE_STOP = 6; + private static final int MSG_MEDIA_UPDATE = 7; + private static final int MSG_RELEASE = 9; + private static final int MSG_CAMERA_FOUCS = 10; + // 音频线程 +// private static final int MSG_AUDIO_START = 10; +// private static final int MSG_AUDIO_STOP = 11; + + private final WeakReference mWeakThread; + private volatile boolean mReleased; + protected static boolean isCaptureStill; + + protected AbstractUVCCameraHandler(final CameraThread thread) { + mWeakThread = new WeakReference(thread); + } + + public int getWidth() { + final CameraThread thread = mWeakThread.get(); + return thread != null ? thread.getWidth() : 0; + } + + public int getHeight() { + final CameraThread thread = mWeakThread.get(); + return thread != null ? thread.getHeight() : 0; + } + + public boolean isOpened() { + final CameraThread thread = mWeakThread.get(); + return thread != null && thread.isCameraOpened(); + } + + public boolean isPreviewing() { + final CameraThread thread = mWeakThread.get(); + return thread != null && thread.isPreviewing(); + } + + public boolean isRecording() { + final CameraThread thread = mWeakThread.get(); + return thread != null && thread.isRecording(); + } + +// public boolean isAudioThreadStart() { +// final CameraThread thread = mWeakThread.get(); +// return thread != null && thread.isAudioRecording(); +// } + + public boolean isEqual(final UsbDevice device) { + final CameraThread thread = mWeakThread.get(); + return (thread != null) && thread.isEqual(device); + } + + protected boolean isCameraThread() { + final CameraThread thread = mWeakThread.get(); + return thread != null && (thread.getId() == Thread.currentThread().getId()); + } + + protected boolean isReleased() { + final CameraThread thread = mWeakThread.get(); + return mReleased || (thread == null); + } + + protected void checkReleased() { + if (isReleased()) { + throw new IllegalStateException("already released"); + } + } + + public void open(final USBMonitor.UsbControlBlock ctrlBlock) { + checkReleased(); + sendMessage(obtainMessage(MSG_OPEN, ctrlBlock)); + } + + public void close() { + if (DEBUG) Log.v(TAG, "close:"); + if (isOpened()) { + stopPreview(); + sendEmptyMessage(MSG_CLOSE); + } + if (DEBUG) Log.v(TAG, "close:finished"); + } + + // 切换分辨率 + public void resize(final int width, final int height) { + checkReleased(); + throw new UnsupportedOperationException("does not support now"); + } + + // 开启Camera预览 + public void startPreview(final Object surface) { + checkReleased(); + if (!((surface instanceof SurfaceHolder) || (surface instanceof Surface) || (surface instanceof SurfaceTexture))) { + throw new IllegalArgumentException("surface should be one of SurfaceHolder, Surface or SurfaceTexture: " + surface); + } + + sendMessage(obtainMessage(MSG_PREVIEW_START, surface)); + } + + public void setOnPreViewResultListener(OnPreViewResultListener listener) { + AbstractUVCCameraHandler.mPreviewListener = listener; + } + + // 关闭Camera预览 + public void 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; + synchronized (thread.mSync) { + sendEmptyMessage(MSG_PREVIEW_STOP); + if (!isCameraThread()) { + // wait for actually preview stopped to avoid releasing Surface/SurfaceTexture + // while preview is still running. + // therefore this method will take a time to execute + try { + thread.mSync.wait(); + } catch (final InterruptedException e) { + } + } + } + } + if (DEBUG) Log.v(TAG, "stopPreview:finished"); + } + + public void captureStill(final String path, OnCaptureListener listener) { + AbstractUVCCameraHandler.mCaptureListener = listener; + checkReleased(); + sendMessage(obtainMessage(MSG_CAPTURE_STILL, path)); + isCaptureStill = true; + } + + // 开始录制 + public void startRecording(final RecordParams params, OnEncodeResultListener listener) { + AbstractUVCCameraHandler.mListener = listener; + checkReleased(); + sendMessage(obtainMessage(MSG_CAPTURE_START, params)); + } + + // 停止录制 + public void stopRecording() { + sendEmptyMessage(MSG_CAPTURE_STOP); + } + + public void startCameraFoucs() { + sendEmptyMessage(MSG_CAMERA_FOUCS); + } + + public List getSupportedPreviewSizes() { + return mWeakThread.get().getSupportedSizes(); + } + +// // 启动音频线程 +// public void startAudioThread(){ +// sendEmptyMessage(MSG_AUDIO_START); +// } +// +// // 关闭音频线程 +// public void stopAudioThread(){ +// sendEmptyMessage(MSG_AUDIO_STOP); +// } + + public void release() { + mReleased = true; + close(); + sendEmptyMessage(MSG_RELEASE); + } + + // 对外注册监听事件 + public void addCallback(final CameraCallback callback) { + checkReleased(); + if (!mReleased && (callback != null)) { + final CameraThread thread = mWeakThread.get(); + if (thread != null) { + thread.mCallbacks.add(callback); + } + } + } + + public void removeCallback(final CameraCallback callback) { + if (callback != null) { + final CameraThread thread = mWeakThread.get(); + if (thread != null) { + thread.mCallbacks.remove(callback); + } + } + } + + protected void updateMedia(final String path) { + sendMessage(obtainMessage(MSG_MEDIA_UPDATE, path)); + } + + public boolean checkSupportFlag(final long flag) { + checkReleased(); + final CameraThread thread = mWeakThread.get(); + return thread != null && thread.mUVCCamera != null && thread.mUVCCamera.checkSupportFlag(flag); + } + + public int getValue(final int flag) { + checkReleased(); + final CameraThread thread = mWeakThread.get(); + final UVCCamera camera = thread != null ? thread.mUVCCamera : null; + if (camera != null) { + if (flag == UVCCamera.PU_BRIGHTNESS) { + return camera.getBrightness(); + } else if (flag == UVCCamera.PU_CONTRAST) { + return camera.getContrast(); + } + } + throw new IllegalStateException(); + } + + public int setValue(final int flag, final int value) { + checkReleased(); + final CameraThread thread = mWeakThread.get(); + final UVCCamera camera = thread != null ? thread.mUVCCamera : null; + if (camera != null) { + if (flag == UVCCamera.PU_BRIGHTNESS) { + camera.setBrightness(value); + return camera.getBrightness(); + } else if (flag == UVCCamera.PU_CONTRAST) { + camera.setContrast(value); + return camera.getContrast(); + } + } + throw new IllegalStateException(); + } + + public int resetValue(final int flag) { + checkReleased(); + final CameraThread thread = mWeakThread.get(); + final UVCCamera camera = thread != null ? thread.mUVCCamera : null; + if (camera != null) { + if (flag == UVCCamera.PU_BRIGHTNESS) { + camera.resetBrightness(); + return camera.getBrightness(); + } else if (flag == UVCCamera.PU_CONTRAST) { + camera.resetContrast(); + return camera.getContrast(); + } + } + throw new IllegalStateException(); + } + + @Override + public void handleMessage(final Message msg) { + final CameraThread thread = mWeakThread.get(); + if (thread == null) return; + switch (msg.what) { + case MSG_OPEN: + thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj); + break; + case MSG_CLOSE: + thread.handleClose(); + break; + case MSG_PREVIEW_START: + thread.handleStartPreview(msg.obj); + break; + case MSG_PREVIEW_STOP: + thread.handleStopPreview(); + break; + case MSG_CAPTURE_STILL: +// thread.handleCaptureStill((String)msg.obj); + thread.handleStillPicture((String) msg.obj); + break; + case MSG_CAPTURE_START: +// thread.handleStartRecording((String)msg.obj); + thread.handleStartPusher((RecordParams) msg.obj); + break; + case MSG_CAPTURE_STOP: + thread.handleStopPusher(); + break; + case MSG_MEDIA_UPDATE: + thread.handleUpdateMedia((String) msg.obj); + break; + case MSG_RELEASE: + thread.handleRelease(); + break; + // 自动对焦 + case MSG_CAMERA_FOUCS: + thread.handleCameraFoucs(); + break; + default: + throw new RuntimeException("unsupported message:what=" + msg.what); + } + } + + public static final class CameraThread extends Thread { + private static final String TAG_THREAD = "CameraThread"; + private final Object mSync = new Object(); + private final Class mHandlerClass; + private final WeakReference mWeakParent; + private final WeakReference mWeakCameraView; + private final int mEncoderType; + private final Set mCallbacks = new CopyOnWriteArraySet(); + private int mWidth, mHeight, mPreviewMode; + private float mBandwidthFactor; + private boolean mIsPreviewing; + private boolean mIsRecording; + + // 播放声音 +// private SoundPool mSoundPool; +// private int mSoundId; + private AbstractUVCCameraHandler mHandler; + // 处理与Camera相关的逻辑,比如获取byte数据流等 + private UVCCamera mUVCCamera; + + // private MediaMuxerWrapper mMuxer; + private MediaVideoBufferEncoder mVideoEncoder; + private Mp4MediaMuxer mMuxer; + private boolean isPushing; + private String videoPath; + private boolean isSupportOverlay; +// private boolean isAudioThreadStart; + + /** + * 构造方法 + *

+ * clazz 继承于AbstractUVCCameraHandler + * parent Activity子类 + * cameraView 用于捕获静止图像 + * encoderType 0表示使用MediaSurfaceEncoder;1表示使用MediaVideoEncoder, 2表示使用MediaVideoBufferEncoder + * width 分辨率的宽 + * height 分辨率的高 + * format 颜色格式,0为FRAME_FORMAT_YUYV;1为FRAME_FORMAT_MJPEG + * bandwidthFactor + */ + CameraThread(final Class clazz, + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format, + final float bandwidthFactor) { + + super("CameraThread"); + mHandlerClass = clazz; + mEncoderType = encoderType; + mWidth = width; + mHeight = height; + mPreviewMode = format; + mBandwidthFactor = bandwidthFactor; + mWeakParent = new WeakReference<>(parent); + mWeakCameraView = new WeakReference<>(cameraView); +// loadShutterSound(parent); + } + + @Override + protected void finalize() throws Throwable { + Log.i(TAG, "CameraThread#finalize"); + super.finalize(); + } + + public AbstractUVCCameraHandler getHandler() { + if (DEBUG) Log.v(TAG_THREAD, "getHandler:"); + synchronized (mSync) { + if (mHandler == null) + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + return mHandler; + } + + public int getWidth() { + synchronized (mSync) { + return mWidth; + } + } + + public int getHeight() { + synchronized (mSync) { + return mHeight; + } + } + + public boolean isCameraOpened() { + synchronized (mSync) { + return mUVCCamera != null; + } + } + + public boolean isPreviewing() { + synchronized (mSync) { + return mUVCCamera != null && mIsPreviewing; + } + } + + public boolean isRecording() { + synchronized (mSync) { + return (mUVCCamera != null) && (mH264Consumer != null); + } + } + +// public boolean isAudioRecording(){ +// synchronized (mSync){ +// return isAudioThreadStart; +// } +// } + + public boolean isEqual(final UsbDevice device) { + return (mUVCCamera != null) && (mUVCCamera.getDevice() != null) && mUVCCamera.getDevice().equals(device); + } + + public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) { + if (DEBUG) Log.v(TAG_THREAD, "handleOpen:"); + handleClose(); + try { + final UVCCamera camera = new UVCCamera(); + camera.open(ctrlBlock); + synchronized (mSync) { + mUVCCamera = camera; + } + callOnOpen(); + } catch (final Exception e) { + callOnError(e); + } + if (DEBUG) + Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null)); + } + + public void handleClose() { + if (DEBUG) Log.v(TAG_THREAD, "handleClose:"); + handleStopPusher(); + final UVCCamera camera; + synchronized (mSync) { + camera = mUVCCamera; + mUVCCamera = null; + } + if (camera != null) { + camera.stopPreview(); + camera.destroy(); + callOnClose(); + } + } + + public void handleStartPreview(final Object surface) { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || mIsPreviewing) return; + try { + mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor); + // 获取USB Camera预览数据,使用NV21颜色会失真 + // 无论使用YUV还是MPEG,setFrameCallback的设置效果一致 +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); + mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP); + } catch (final IllegalArgumentException e) { + try { + // fallback to YUV mode + mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, UVCCamera.DEFAULT_PREVIEW_MODE, mBandwidthFactor); + } catch (final IllegalArgumentException e1) { + callOnError(e1); + return; + } + } + if (surface instanceof SurfaceHolder) { + mUVCCamera.setPreviewDisplay((SurfaceHolder) surface); + } + if (surface instanceof Surface) { + mUVCCamera.setPreviewDisplay((Surface) surface); + } else { + mUVCCamera.setPreviewTexture((SurfaceTexture) surface); + } + mUVCCamera.startPreview(); + mUVCCamera.updateCameraParams(); + synchronized (mSync) { + mIsPreviewing = true; + } + callOnStartPreview(); + } + + public void handleStopPreview() { + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:"); + if (mIsPreviewing) { + if (mUVCCamera != null) { + mUVCCamera.stopPreview(); + mUVCCamera.setFrameCallback(null, 0); + } + synchronized (mSync) { + mIsPreviewing = false; + mSync.notifyAll(); + } + callOnStopPreview(); + } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished"); + } + + // 捕获静态图片 + public void handleCaptureStill(final String path) { + if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:"); + final Activity parent = mWeakParent.get(); + if (parent == null) return; +// mSoundPool.play(mSoundId, 0.2f, 0.2f, 0, 0, 1.0f); // play shutter sound + try { + final Bitmap bitmap = mWeakCameraView.get().captureStillImage(mWidth, mHeight); + // get buffered output stream for saving a captured still image as a file on external storage. + // the file name is came from current time. + // You should use extension name as same as CompressFormat when calling Bitmap#compress. + final File outputFile = TextUtils.isEmpty(path) + ? MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".jpg") + : new File(path); + final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); + try { + try { + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath())); + } catch (final IOException e) { + } + } finally { + os.close(); + } + if (mCaptureListener != null) { + mCaptureListener.onCaptureResult(path); + } + } catch (final Exception e) { + callOnError(e); + } + } + + // 开始录制视频 +// public void handleStartRecording2(String path) { +// if (DEBUG) Log.v(TAG_THREAD, "handleStartRecording:"); +// try { +// if ((mUVCCamera == null) || (mMuxer != null)) return; +//// final MediaMuxerWrapper muxer = new MediaMuxerWrapper(".mp4"); // if you record audio only, ".m4a" is also OK. +// final MediaMuxerWrapper muxer = new MediaMuxerWrapper(path); +// MediaVideoBufferEncoder videoEncoder = null; +// switch (mEncoderType) { +// case 1: // for video capturing using MediaVideoEncoder +// // 开启视频编码线程 +// new MediaVideoEncoder(muxer,getWidth(), getHeight(), mMediaEncoderListener); +// break; +// case 2: // for video capturing using MediaVideoBufferEncoder +// videoEncoder = new MediaVideoBufferEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); +// break; +// // case 0: // for video capturing using MediaSurfaceEncoder +// default: +// new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); +// break; +// } +// // 开启音频编码线程 +// if (true) { +// // for audio capturing +//// new MediaAudioEncoder(muxer, mMediaEncoderListener); +// } +// muxer.prepare(); +// muxer.startRecording(); +// if (videoEncoder != null) { +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); +// } +// synchronized (mSync) { +// mMuxer = muxer; +// mVideoEncoder = videoEncoder; +// } +// callOnStartRecording(); +// } catch (final IOException e) { +// callOnError(e); +// Log.e(TAG, "startCapture:", e); +// } +// } + + private AACEncodeConsumer mAacConsumer; + private H264EncodeConsumer mH264Consumer; + + public void handleStartPusher(RecordParams params) { + if ((mUVCCamera == null) || (mH264Consumer != null)) + return; +// // 获取USB Camera预览数据 +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); + + // 初始化混合器 + if (params != null) { + isSupportOverlay = params.isSupportOverlay(); + if(isSupportOverlay) { + // init overlay engine + TxtOverlay.getInstance().init(mWidth, mHeight); + } + videoPath = params.getRecordPath(); + File file = new File(videoPath); + if(! Objects.requireNonNull(file.getParentFile()).exists()) { + file.getParentFile().mkdirs(); + } + mMuxer = new Mp4MediaMuxer(params.getRecordPath(), + params.getRecordDuration() * 60 * 1000, params.isVoiceClose()); + } + // 启动视频编码线程 + startVideoRecord(); + // 启动音频编码线程 + if (params != null && !params.isVoiceClose()) { + startAudioRecord(); + } + callOnStartRecording(); + } + + + public void handleStopPusher() { + // 停止混合器 + if (mMuxer != null) { + mMuxer.release(); + mMuxer = null; + Log.i(TAG, TAG + "---->停止本地录制"); + } + // 停止音视频编码线程 + stopAudioRecord(); + stopVideoRecord(); + if(isSupportOverlay) + TxtOverlay.getInstance().release(); +// // 停止捕获视频数据 +// if (mUVCCamera != null) { +// mUVCCamera.stopCapture(); +// } + mWeakCameraView.get().setVideoEncoder(null); + // you should not wait here + callOnStopRecording(); + // 返回路径 + if (mListener != null) { + mListener.onRecordResult(videoPath + ".mp4"); + } + } + + private void startVideoRecord() { + mH264Consumer = new H264EncodeConsumer(getWidth(), getHeight()); + mH264Consumer.setOnH264EncodeResultListener(new H264EncodeConsumer.OnH264EncodeResultListener() { + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, 1); + } + } + }); + mH264Consumer.start(); + // 添加混合器 + if (mMuxer != null) { + if (mH264Consumer != null) { + mH264Consumer.setTmpuMuxer(mMuxer); + } + } + } + + private void stopVideoRecord() { + if (mH264Consumer != null) { + mH264Consumer.exit(); + mH264Consumer.setTmpuMuxer(null); + try { + Thread t2 = mH264Consumer; + mH264Consumer = null; + if (t2 != null) { + t2.interrupt(); + t2.join(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void startAudioRecord() { + mAacConsumer = new AACEncodeConsumer(); + mAacConsumer.setOnAACEncodeResultListener(new AACEncodeConsumer.OnAACEncodeResultListener() { + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, 0); + } + } + }); + mAacConsumer.start(); + // 添加混合器 + if (mMuxer != null) { + if (mAacConsumer != null) { + mAacConsumer.setTmpuMuxer(mMuxer); + } + } + } + + private void stopAudioRecord() { + if (mAacConsumer != null) { + mAacConsumer.exit(); + mAacConsumer.setTmpuMuxer(null); + try { + Thread t1 = mAacConsumer; + mAacConsumer = null; + if (t1 != null) { + t1.interrupt(); + t1.join(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +// isAudioThreadStart = false; + } + + private String picPath = null; + + public void handleStillPicture(String picPath) { + this.picPath = picPath; + } + + private final IFrameCallback mIFrameCallback = new IFrameCallback() { + @Override + public void onFrame(final ByteBuffer frame) { +// final MediaVideoBufferEncoder videoEncoder; +// synchronized (mSync) { +// videoEncoder = mVideoEncoder; +// } +// if (videoEncoder != null) { +// videoEncoder.frameAvailableSoon(); +// videoEncoder.encode(frame); +// } + int len = frame.capacity(); + final byte[] yuv = new byte[len]; + frame.get(yuv); + // nv21 yuv data callback + if (mPreviewListener != null) { + mPreviewListener.onPreviewResult(yuv); + } + // picture + if (isCaptureStill && !TextUtils.isEmpty(picPath)) { + isCaptureStill = false; + new Thread(new Runnable() { + @Override + public void run() { + saveYuv2Jpeg(picPath, yuv); + } + }).start(); + } + // video + if (mH264Consumer != null) { + // overlay + if(isSupportOverlay) { + TxtOverlay.getInstance().overlay(yuv, new SimpleDateFormat("yyyy-MM-dd EEEE HH:mm:ss").format(new Date())); + } + + mH264Consumer.setRawYuv(yuv, mWidth, mHeight); + } + } + }; + + private void saveYuv2Jpeg(String path, byte[] data) { + YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, mWidth, mHeight, null); + ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length); + boolean result = yuvImage.compressToJpeg(new Rect(0, 0, mWidth, mHeight), 100, bos); + if (result) { + + byte[] buffer = bos.toByteArray(); + File file = new File(path); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + // fixing bm is null bug instead of using BitmapFactory.decodeByteArray + fos.write(buffer); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + if (mCaptureListener != null) { + mCaptureListener.onCaptureResult(path); + } + } + try { + bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public void handleUpdateMedia(final String path) { + if (DEBUG) Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); + final Activity parent = mWeakParent.get(); + final boolean released = (mHandler == null) || mHandler.mReleased; + if (parent != null && parent.getApplicationContext() != null) { + try { + if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile"); + MediaScannerConnection.scanFile(parent.getApplicationContext(), new String[]{path}, null, null); + } catch (final Exception e) { + Log.e(TAG, "handleUpdateMedia:", e); + } + if (released || parent.isDestroyed()) + handleRelease(); + } else { + Log.w(TAG, "MainActivity already destroyed"); + // give up to add this movie to MediaStore now. + // Seeing this movie on Gallery app etc. will take a lot of time. + handleRelease(); + } + } + + public void handleRelease() { + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); + handleClose(); + mCallbacks.clear(); + if (!mIsRecording) { + mHandler.mReleased = true; + Looper.myLooper().quit(); + } + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished"); + } + + // 自动对焦 + public void handleCameraFoucs() { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || !mIsPreviewing) + return; + mUVCCamera.setAutoFocus(true); + } + + // 获取支持的分辨率 + public List getSupportedSizes() { + 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); + mIsRecording = true; + if (encoder instanceof MediaVideoEncoder) + try { + mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder); + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + if (encoder instanceof MediaSurfaceEncoder) + try { + mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder); + mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface()); + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @Override + public void onStopped(final MediaEncoder encoder) { + if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); + if ((encoder instanceof MediaVideoEncoder) + || (encoder instanceof MediaSurfaceEncoder)) + try { + mIsRecording = false; + final Activity parent = mWeakParent.get(); + mWeakCameraView.get().setVideoEncoder(null); + synchronized (mSync) { + if (mUVCCamera != null) { + mUVCCamera.stopCapture(); + } + } + final String path = encoder.getOutputPath(); + if (!TextUtils.isEmpty(path)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_MEDIA_UPDATE, path), 1000); + } else { + final boolean released = (mHandler == null) || mHandler.mReleased; + if (released || parent == null || parent.isDestroyed()) { + handleRelease(); + } + } + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + } + + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, type); + } + } + + }; + +// private void loadShutterSound(final Context context) { +// // get system stream type using reflection +// int streamType; +// try { +// final Class audioSystemClass = Class.forName("android.media.AudioSystem"); +// final Field sseField = audioSystemClass.getDeclaredField("STREAM_SYSTEM_ENFORCED"); +// streamType = sseField.getInt(null); +// } catch (final Exception e) { +// streamType = AudioManager.STREAM_SYSTEM; // set appropriate according to your app policy +// } +// if (mSoundPool != null) { +// try { +// mSoundPool.release(); +// } catch (final Exception e) { +// } +// mSoundPool = null; +// } +// // load shutter sound from resource +// mSoundPool = new SoundPool(2, streamType, 0); +// mSoundId = mSoundPool.load(context, R.raw.camera_click, 1); +// } + + /** + * prepare and load shutter sound for still image capturing + */ + @SuppressWarnings("deprecation") +// private void loadShutterSound(final Context context) { +// // get system stream type using reflection +// int streamType; +// try { +// final Class audioSystemClass = Class.forName("android.media.AudioSystem"); +// final Field sseField = audioSystemClass.getDeclaredField("STREAM_SYSTEM_ENFORCED"); +// streamType = sseField.getInt(null); +// } catch (final Exception e) { +// streamType = AudioManager.STREAM_SYSTEM; // set appropriate according to your app policy +// } +// if (mSoundPool != null) { +// try { +// mSoundPool.release(); +// } catch (final Exception e) { +// } +// mSoundPool = null; +// } +// // load shutter sound from resource +// mSoundPool = new SoundPool(2, streamType, 0); +// mSoundId = mSoundPool.load(context, R.raw.camera_click, 1); +// } + + @Override + public void run() { + Looper.prepare(); + AbstractUVCCameraHandler handler = null; + try { + final Constructor constructor = mHandlerClass.getDeclaredConstructor(CameraThread.class); + handler = constructor.newInstance(this); + } catch (final NoSuchMethodException e) { + Log.w(TAG, e); + } catch (final IllegalAccessException e) { + Log.w(TAG, e); + } catch (final InstantiationException e) { + Log.w(TAG, e); + } catch (final InvocationTargetException e) { + Log.w(TAG, e); + } + if (handler != null) { + synchronized (mSync) { + mHandler = handler; + mSync.notifyAll(); + } + Looper.loop(); +// if (mSoundPool != null) { +// mSoundPool.release(); +// mSoundPool = null; +// } + if (mHandler != null) { + mHandler.mReleased = true; + } + } + mCallbacks.clear(); + synchronized (mSync) { + mHandler = null; + mSync.notifyAll(); + } + } + + private void callOnOpen() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onOpen(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnClose() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onClose(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStartPreview() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStartPreview(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStopPreview() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStopPreview(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStartRecording() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStartRecording(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStopRecording() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStopRecording(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnError(final Exception e) { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onError(e); + } catch (final Exception e1) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + } +} diff --git a/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 new file mode 100644 index 0000000000..2d01f1cd25 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java @@ -0,0 +1,140 @@ +/* + * UVCCamera + * library and sample to access to UVC web camera on non-rooted Android device + * + * Copyright (c) 2014-2017 saki t_saki@serenegiant.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * All files in the folder are under this Apache License, Version 2.0. + * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder + * may have a different license, see the respective files. + */ + +package com.serenegiant.usb.common; + +import android.app.Activity; + +import com.serenegiant.usb.UVCCamera; +import com.serenegiant.usb.widget.CameraViewInterface; + +public class UVCCameraHandler extends AbstractUVCCameraHandler { + + /** + * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG, default bandwidth + * @param parent + * @param cameraView + * @param width + * @param height + * @return + */ + public static final UVCCameraHandler createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int width, final int height) { + + return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG + * @param parent + * @param cameraView + * @param width + * @param height + * @param bandwidthFactor + * @return + */ + public static final UVCCameraHandler createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int width, final int height, final float bandwidthFactor) { + + return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor); + } + + /** + * create UVCCameraHandler, try MJPEG, default bandwidth + * @param parent + * @param cameraView + * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder + * @param width + * @param height + * @return + */ + public static final UVCCameraHandler createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height) { + + return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandler, default bandwidth + * @param parent + * @param cameraView + * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder + * @param width + * @param height + * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) + * @return + */ + public static final UVCCameraHandler createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format) { + + return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandler + * @param parent + * @param cameraView + * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder + * @param width + * @param height + * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) + * @param bandwidthFactor + * @return + */ + public static final UVCCameraHandler createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) { + + final CameraThread thread = new CameraThread(UVCCameraHandler.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor); + thread.start(); + return (UVCCameraHandler)thread.getHandler(); + } + + protected UVCCameraHandler(final CameraThread thread) { + super(thread); + } + + @Override + public void startPreview(final Object surface) { + super.startPreview(surface); + } + +// @Override +// public void captureStill() { +// super.captureStill(); +// } + + @Override + public void captureStill(final String path,OnCaptureListener listener) { + super.captureStill(path,listener); + } + + @Override + public void startCameraFoucs() { + super.startCameraFoucs(); + } +} 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 new file mode 100644 index 0000000000..d8df6c26d1 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java @@ -0,0 +1,186 @@ +/* + * UVCCamera + * library and sample to access to UVC web camera on non-rooted Android device + * + * Copyright (c) 2014-2017 saki t_saki@serenegiant.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * All files in the folder are under this Apache License, Version 2.0. + * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder + * may have a different license, see the respective files. + */ + +package com.serenegiant.usb.common; + +import android.app.Activity; +import android.view.Surface; + +import com.serenegiant.glutils.RendererHolder; +import com.serenegiant.usb.UVCCamera; +import com.serenegiant.usb.widget.CameraViewInterface; + +import java.io.FileNotFoundException; + +public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { + /** + * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG, default bandwidth + * @param parent + * @param cameraView + * @param width + * @param height + * @return + */ + public static final UVCCameraHandlerMultiSurface createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int width, final int height) { + + return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG + * @param parent + * @param cameraView + * @param width + * @param height + * @param bandwidthFactor + * @return + */ + public static final UVCCameraHandlerMultiSurface createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int width, final int height, final float bandwidthFactor) { + + return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor); + } + + /** + * create UVCCameraHandlerMultiSurface, try MJPEG, default bandwidth + * @param parent + * @param cameraView + * @param encoderType + * @param width + * @param height + * @return + */ + public static final UVCCameraHandlerMultiSurface createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height) { + + return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandlerMultiSurface, default bandwidth + * @param parent + * @param cameraView + * @param encoderType + * @param width + * @param height + * @param format + * @return + */ + public static final UVCCameraHandlerMultiSurface createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format) { + + return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH); + } + + /** + * create UVCCameraHandlerMultiSurface + * @param parent + * @param cameraView + * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder + * @param width + * @param height + * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) + * @param bandwidthFactor + * @return + */ + public static final UVCCameraHandlerMultiSurface createHandler( + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) { + + final CameraThread thread = new CameraThread(UVCCameraHandlerMultiSurface.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor); + thread.start(); + return (UVCCameraHandlerMultiSurface)thread.getHandler(); + } + + private RendererHolder mRendererHolder; + protected UVCCameraHandlerMultiSurface(final CameraThread thread) { + super(thread); + mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null); + } + + public synchronized void release() { + if (mRendererHolder != null) { + mRendererHolder.release(); + mRendererHolder = null; + } + super.release(); + } + + public synchronized void resize(final int width, final int height) { + super.resize(width, height); + if (mRendererHolder != null) { + mRendererHolder.resize(width, height); + } + } + + public synchronized void startPreview() { + checkReleased(); + if (mRendererHolder != null) { + super.startPreview(mRendererHolder.getSurface()); + } else { + throw new IllegalStateException(); + } + } + + public synchronized void addSurface(final int surfaceId, final Surface surface, final boolean isRecordable) { + checkReleased(); + mRendererHolder.addSurface(surfaceId, surface, isRecordable); + } + + public synchronized void removeSurface(final int surfaceId) { + if (mRendererHolder != null) { + mRendererHolder.removeSurface(surfaceId); + } + } + +// @Override +// public void captureStill() { +// checkReleased(); +// super.captureStill(); +// } + + @Override + public void captureStill(final String path,OnCaptureListener listener) { + checkReleased(); + post(new Runnable() { + @Override + public void run() { + synchronized (UVCCameraHandlerMultiSurface.this) { + if (mRendererHolder != null) { + try { + mRendererHolder.captureStill(path); + updateMedia(path); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + } + }); + } +} 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 new file mode 100644 index 0000000000..9a0c0bdb7e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java @@ -0,0 +1,27 @@ +/* + * 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 new file mode 100644 index 0000000000..7b8429d262 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java @@ -0,0 +1,28 @@ +/* + * 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 { + public boolean frameAvailableSoon(); +} 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 new file mode 100644 index 0000000000..3246364cff --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java @@ -0,0 +1,234 @@ +/* + * 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; +import android.media.AudioRecord; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.media.MediaRecorder; +import android.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaAudioEncoder"; + + private static final String MIME_TYPE = "audio/mp4a-latm"; + public static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices. + private static final int BIT_RATE = 64000; + public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel + public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec + + private AudioThread mAudioThread = null; + + public MediaAudioEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) { + super(muxer, listener); + } + + @Override + protected void prepare() throws IOException { + if (DEBUG) Log.v(TAG, "prepare:"); + mTrackIndex = -1; + mMuxerStarted = mIsEOS = false; + // prepare MediaCodec for AAC encoding of audio data from inernal mic. + final MediaCodecInfo audioCodecInfo = selectAudioCodec(MIME_TYPE); + if (audioCodecInfo == null) { + Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); + return; + } + if (DEBUG) Log.i(TAG, "selected codec: " + audioCodecInfo.getName()); + + final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1); + audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO); + audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); +// audioFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, inputFile.length()); +// audioFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs ); + if (DEBUG) Log.i(TAG, "format: " + audioFormat); + mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mMediaCodec.start(); + if (DEBUG) Log.i(TAG, "prepare finishing"); + if (mListener != null) { + try { + mListener.onPrepared(this); + } catch (final Exception e) { + Log.e(TAG, "prepare:", e); + } + } + } + + @Override + protected void startRecording() { + super.startRecording(); + // create and execute audio capturing thread using internal mic + if (mAudioThread == null) { + mAudioThread = new AudioThread(); + mAudioThread.start(); + } + } + + @Override + protected void release() { + mAudioThread = null; + super.release(); + } + + private static final int[] AUDIO_SOURCES = new int[] { + MediaRecorder.AudioSource.DEFAULT, + MediaRecorder.AudioSource.MIC, + MediaRecorder.AudioSource.CAMCORDER, + }; + + /** + * Thread to capture audio data from internal mic as uncompressed 16bit PCM data + * and write them to the MediaCodec encoder + */ + private class AudioThread extends Thread { + @Override + public void run() { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); // THREAD_PRIORITY_URGENT_AUDIO + int cnt = 0; + final int min_buffer_size = AudioRecord.getMinBufferSize( + SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); + int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER; + if (buffer_size < min_buffer_size) + buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2; + final ByteBuffer buf = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME).order(ByteOrder.nativeOrder()); + AudioRecord audioRecord = null; + for (final int src: AUDIO_SOURCES) { + try { + audioRecord = new AudioRecord(src, + SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size); + if (audioRecord != null) { + if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { + audioRecord.release(); + audioRecord = null; + } + } + } catch (final Exception e) { + audioRecord = null; + } + if (audioRecord != null) { + break; + } + } + if (audioRecord != null) { + try { + if (mIsCapturing) { + if (DEBUG) Log.v(TAG, "AudioThread:start audio recording"); + int readBytes; + audioRecord.startRecording(); + try { + for ( ; mIsCapturing && !mRequestStop && !mIsEOS ; ) { + // read audio data from internal mic + buf.clear(); + try { + readBytes = audioRecord.read(buf, SAMPLES_PER_FRAME); + } catch (final Exception e) { + break; + } + if (readBytes > 0) { + // set audio data to encoder + buf.position(readBytes); + buf.flip(); + encode(buf, readBytes, getPTSUs()); + frameAvailableSoon(); + cnt++; + } + } + if (cnt > 0) { + frameAvailableSoon(); + } + } finally { + audioRecord.stop(); + } + } + } catch (final Exception e) { + Log.e(TAG, "AudioThread#run", e); + } finally { + audioRecord.release(); + } + } + if (cnt == 0) { + for (int i = 0; mIsCapturing && (i < 5); i++) { + buf.position(SAMPLES_PER_FRAME); + buf.flip(); + try { + encode(buf, SAMPLES_PER_FRAME, getPTSUs()); + frameAvailableSoon(); + } catch (final Exception e) { + break; + } + synchronized(this) { + try { + wait(50); + } catch (final InterruptedException e) { + + } + } + } + } + if (DEBUG) Log.v(TAG, "AudioThread:finished"); + } + } + + /** + * select the first codec that match a specific MIME type + * @param mimeType + * @return + */ + private static final MediaCodecInfo selectAudioCodec(final String mimeType) { + if (DEBUG) Log.v(TAG, "selectAudioCodec:"); + + MediaCodecInfo result = null; + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); +LOOP: for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (DEBUG) Log.i(TAG, "supportedType:" + codecInfo.getName() + ",MIME=" + types[j]); + if (types[j].equalsIgnoreCase(mimeType)) { + if (result == null) { + result = codecInfo; + break LOOP; + } + } + } + } + return result; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java new file mode 100644 index 0000000000..d11770bdc5 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java @@ -0,0 +1,570 @@ +package com.serenegiant.usb.encoder; + +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Environment; +import android.util.Log; + + +import com.mogo.usbcamera.utils.FileUtils; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; + +public abstract class MediaEncoder implements Runnable { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaEncoder"; + public static final int TYPE_AUDIO = 0; // 音频数据 + public static final int TYPE_VIDEO = 1; // 视频数据 + + protected static final int TIMEOUT_USEC = 10000; // 10毫秒 + protected static final int MSG_FRAME_AVAILABLE = 1; + protected static final int MSG_STOP_RECORDING = 9; + private long lastPush; + private long millisPerframe; + private boolean isExit; + + public interface MediaEncoderListener { + void onPrepared(MediaEncoder encoder); + void onStopped(MediaEncoder encoder); + // 音频或视频流,type=0为音频,type=1为视频 + void onEncodeResult(byte[] data, int offset, + int length, long timestamp, int type); + } + + protected final Object mSync = new Object(); + /** + * Flag that indicate this encoder is capturing now. + */ + protected volatile boolean mIsCapturing; + /** + * Flag that indicate the frame data will be available soon. + */ + private int mRequestDrain; + /** + * Flag to request stop capturing + */ + protected volatile boolean mRequestStop; + /** + * Flag that indicate encoder received EOS(End Of Stream) + */ + protected boolean mIsEOS; + /** + * Flag the indicate the muxer is running + */ + protected boolean mMuxerStarted; + /** + * Track Number + */ + protected int mTrackIndex; + /** + * MediaCodec instance for encoding + */ + protected MediaCodec mMediaCodec; // API >= 16(Android4.1.2) + /** + * Weak refarence of MediaMuxerWarapper instance + */ + protected final WeakReference mWeakMuxer; + /** + * BufferInfo instance for dequeuing + */ + private MediaCodec.BufferInfo mBufferInfo; // API >= 16(Android4.1.2) + + protected final MediaEncoderListener mListener; + + /** + * There are 13 supported frequencies by ADTS. + **/ + public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 + 88200, // 1 + 64000, // 2 + 48000, // 3 + 44100, // 4 + 32000, // 5 + 24000, // 6 + 22050, // 7 + 16000, // 8 + 12000, // 9 + 11025, // 10 + 8000, // 11 + 7350, // 12 + -1, // 13 + -1, // 14 + -1, // 15 + }; + + public MediaEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) { + if (listener == null) throw new NullPointerException("MediaEncoderListener is null"); + if (muxer == null) throw new NullPointerException("MediaMuxerWrapper is null"); + mWeakMuxer = new WeakReference(muxer); + muxer.addEncoder(this); + mListener = listener; + synchronized (mSync) { + // create BufferInfo here for effectiveness(to reduce GC) + mBufferInfo = new MediaCodec.BufferInfo(); + // wait for starting thread + new Thread(this, getClass().getSimpleName()).start(); + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + } + + public String getOutputPath() { + final MediaMuxerWrapper muxer = mWeakMuxer.get(); + return muxer != null ? muxer.getOutputPath() : null; + } + + /** + * the method to indicate frame data is soon available or already available + * @return return true if encoder is ready to encod. + */ + public boolean frameAvailableSoon() { +// if (DEBUG) Log.v(TAG, "frameAvailableSoon"); + synchronized (mSync) { + if (!mIsCapturing || mRequestStop) { + return false; + } + mRequestDrain++; + mSync.notifyAll(); + } + return true; + } + + /** + * encoding loop on private thread + */ + @Override + public void run() { +// android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); + synchronized (mSync) { + mRequestStop = false; + mRequestDrain = 0; + mSync.notify(); + } + final boolean isRunning = true; + boolean localRequestStop; + boolean localRequestDrain; + boolean localIsNotExit; + // 创建h264 + FileUtils.createfile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/test222.h264"); + + while (isRunning) { + synchronized (mSync) { + localRequestStop = mRequestStop; + localRequestDrain = (mRequestDrain > 0); + if (localRequestDrain) + mRequestDrain--; + } + if (localRequestStop) { + drain(); + // request stop recording + signalEndOfInputStream(); + // process output data again for EOS signale + drain(); + // release all related objects + release(); + break; + } + if (localRequestDrain) { + drain(); + } else { + synchronized (mSync) { + try { + mSync.wait(); + } catch (final InterruptedException e) { + break; + } + } + } + } // end of while + synchronized (mSync) { + mRequestStop = true; + mIsCapturing = false; + } + FileUtils.releaseFile(); + } + + /* + * prepareing method for each sub class + * this method should be implemented in sub class, so set this as abstract method + * @throws IOException + */ + /*package*/ abstract void prepare() throws IOException; + + /*package*/ void startRecording() { + if (DEBUG) Log.v(TAG, "startRecording"); + synchronized (mSync) { + mIsCapturing = true; + mRequestStop = false; + isExit = false; + mSync.notifyAll(); + } + } + + /** + * the method to request stop encoding + */ + /*package*/ void stopRecording() { + if (DEBUG) Log.v(TAG, "stopRecording"); + synchronized (mSync) { + if (!mIsCapturing || mRequestStop) { + return; + } + mRequestStop = true; // for rejecting newer frame + isExit = true; + mSync.notifyAll(); + // We can not know when the encoding and writing finish. + // so we return immediately after request to avoid delay of caller thread + } + } + +//******************************************************************************** +//******************************************************************************** + /** + * Release all releated objects + */ + protected void release() { + if (DEBUG) Log.d(TAG, "release:"); + try { + mListener.onStopped(this); + } catch (final Exception e) { + Log.e(TAG, "failed onStopped", e); + } + mIsCapturing = false; + if (mMediaCodec != null) { + try { + mMediaCodec.stop(); + mMediaCodec.release(); + mMediaCodec = null; + } catch (final Exception e) { + Log.e(TAG, "failed releasing MediaCodec", e); + } + } + if (mMuxerStarted) { + final MediaMuxerWrapper muxer = mWeakMuxer.get(); + if (muxer != null) { + try { + muxer.stop(); + } catch (final Exception e) { + Log.e(TAG, "failed stopping muxer", e); + } + } + } + mBufferInfo = null; + } + + protected void signalEndOfInputStream() { + if (DEBUG) Log.d(TAG, "sending EOS to encoder"); + // signalEndOfInputStream is only avairable for video encoding with surface + // and equivalent sending a empty buffer with BUFFER_FLAG_END_OF_STREAM flag. +// mMediaCodec.signalEndOfInputStream(); // API >= 18 + encode((byte[])null, 0, getPTSUs()); + } + + /** + * Method to set byte array to the MediaCodec encoder + * @param buffer + * @param length length of byte array, zero means EOS. + * @param presentationTimeUs + */ + @SuppressWarnings("deprecation") + protected void encode(final byte[] buffer, final int length, final long presentationTimeUs) { + if (!mIsCapturing) return; + int ix = 0, sz; + final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); + while (mIsCapturing && ix < length) { + final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufferIndex >= 0) { + final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; + inputBuffer.clear(); + sz = inputBuffer.remaining(); + sz = (ix + sz < length) ? sz : length - ix; + if (sz > 0 && (buffer != null)) { + inputBuffer.put(buffer, ix, sz); + } + ix += sz; +// if (DEBUG) Log.v(TAG, "encode:queueInputBuffer"); + if (length <= 0) { + // send EOS + mIsEOS = true; + if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM"); + mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, + presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + break; + } else { + mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sz, + presentationTimeUs, 0); + } + } else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + // wait for MediaCodec encoder is ready to encode + // nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC) + // will wait for maximum TIMEOUT_USEC(10msec) on each call + } + } + } + + protected void encode(ByteBuffer yuvBuffer,int len){ + if (!mIsCapturing) return; + try { + if (lastPush == 0) { + lastPush = System.currentTimeMillis(); + } + long time = System.currentTimeMillis() - lastPush; + if (time >= 0) { + time = millisPerframe - time; + if (time > 0) + Thread.sleep(time / 2); + } + final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); + int bufferIndex = -1; + try{ + bufferIndex = mMediaCodec.dequeueInputBuffer(0); + }catch (IllegalStateException e){ + e.printStackTrace(); + } + if (bufferIndex >= 0) { + ByteBuffer mBuffer; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mBuffer = mMediaCodec.getInputBuffer(bufferIndex); + } else { + mBuffer = inputBuffers[bufferIndex]; + } + byte[] yuvData = new byte[yuvBuffer.capacity()]; + yuvBuffer.get(yuvData); + + mBuffer.clear(); + mBuffer.put(yuvData); + mBuffer.clear(); + mMediaCodec.queueInputBuffer(bufferIndex, 0, yuvData.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME); + } + lastPush = System.currentTimeMillis(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + /** + * Method to set ByteBuffer to the MediaCodec encoder + * @param buffer null means EOS + * @param presentationTimeUs + */ + @SuppressWarnings("deprecation") + protected void encode(final ByteBuffer buffer, final int length, final long presentationTimeUs) { +// if (DEBUG) Log.v(TAG, "encode:buffer=" + buffer); + if (!mIsCapturing) return; + int ix = 0, sz; + final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); + while (mIsCapturing && ix < length) { + final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC); + if (inputBufferIndex >= 0) { + final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; + inputBuffer.clear(); + sz = inputBuffer.remaining(); + sz = (ix + sz < length) ? sz : length - ix; + if (sz > 0 && (buffer != null)) { + buffer.position(ix + sz); + buffer.flip(); + inputBuffer.put(buffer); + } + ix += sz; +// if (DEBUG) Log.v(TAG, "encode:queueInputBuffer"); + if (length <= 0) { + // send EOS + mIsEOS = true; + if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM"); + mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, + presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + break; + } else { + mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sz, + presentationTimeUs, 0); + } + } else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + // wait for MediaCodec encoder is ready to encode + // nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC) + // will wait for maximum TIMEOUT_USEC(10msec) on each call + } + } + } + + ByteBuffer mBuffer = ByteBuffer.allocate(10240); + + /** + * drain encoded data and write them to muxer + */ + @SuppressWarnings("deprecation") + protected void drain() { + if (mMediaCodec == null) + return; + ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers(); + int encoderStatus, count = 0; + final MediaMuxerWrapper muxer = mWeakMuxer.get(); + if (muxer == null) { + Log.w(TAG, "muxer is unexpectedly null"); + return; + } + byte[] mPpsSps = new byte[0]; + byte[] h264 = new byte[640 * 480]; + + while (mIsCapturing) { + encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + // 等待 TIMEOUT_USEC x 5 = 50毫秒 + // 如果还没有数据,终止循环 + if (!mIsEOS) { + if (++count > 5) + break; + } + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + encoderOutputBuffers = mMediaCodec.getOutputBuffers(); + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + if (mMuxerStarted) { + throw new RuntimeException("format changed twice"); + } + final MediaFormat format = mMediaCodec.getOutputFormat(); + mTrackIndex = muxer.addTrack(format); + mMuxerStarted = true; + if (!muxer.start()) { + synchronized (muxer) { + while (!muxer.isStarted()) + try { + muxer.wait(100); + } catch (final InterruptedException e) { + break; + } + } + } + } else if (encoderStatus < 0) { + if (DEBUG) Log.w(TAG, "drain:unexpected result from encoder#dequeueOutputBuffer: " + encoderStatus); + } else { + ByteBuffer encodedData; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + encodedData = mMediaCodec.getOutputBuffer(encoderStatus); + } else { + encodedData = encoderOutputBuffers[encoderStatus]; + } + encodedData.position(mBufferInfo.offset); + encodedData.limit(mBufferInfo.offset + mBufferInfo.size); + +// final ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; + if (encodedData == null) { + throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); + } + // BUFFER_FLAG_CODEC_CONFIG标志 + // BufferInfo清零 + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + if (DEBUG) Log.d(TAG, "drain:BUFFER_FLAG_CODEC_CONFIG"); + mBufferInfo.size = 0; + } + // BUFFER_FLAG_END_OF_STREAM标志 + // 流结束,终止循环 + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + mMuxerStarted = mIsCapturing = false; + break; + } + // 有效编码数据流 + if (mBufferInfo.size != 0) { + count = 0; + if (!mMuxerStarted) { + throw new RuntimeException("drain:muxer hasn't started"); + } + // 写入音频流或视频流到混合器 + mBufferInfo.presentationTimeUs = getPTSUs(); + muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); + prevOutputPTSUs = mBufferInfo.presentationTimeUs; + + // 推流,获取h.264数据流 + // mTrackIndex=0 视频;mTrackIndex=1 音频 + if(mTrackIndex == 0) { + encodedData.position(mBufferInfo.offset); + encodedData.limit(mBufferInfo.offset + mBufferInfo.size); + boolean sync = false; + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps + sync = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + if (!sync) { + byte[] temp = new byte[mBufferInfo.size]; + encodedData.get(temp); + mPpsSps = temp; + mMediaCodec.releaseOutputBuffer(encoderStatus, false); + continue; + } else { + mPpsSps = new byte[0]; + } + } + sync |= (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + int len = mPpsSps.length + mBufferInfo.size; + if (len > h264.length) { + h264 = new byte[len]; + } + if (sync) { + System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length); + encodedData.get(h264, mPpsSps.length, mBufferInfo.size); + if(mListener != null) { + mListener.onEncodeResult(h264, 0,mPpsSps.length + mBufferInfo.size, + mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO); + } + // 保存数据流到文件 + FileUtils.putFileStream(h264, 0,mPpsSps.length + mBufferInfo.size); + } else { + encodedData.get(h264, 0, mBufferInfo.size); + if(mListener != null) { + mListener.onEncodeResult(h264, 0,mBufferInfo.size, + mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO); + } + FileUtils.putFileStream(h264, 0,mBufferInfo.size); + } + } else if(mTrackIndex == 1){ + mBuffer.clear(); + encodedData.get(mBuffer.array(), 7, mBufferInfo.size); + encodedData.clear(); + mBuffer.position(7 + mBufferInfo.size); + addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); + mBuffer.flip(); + if(mListener != null){ + mListener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, + mBufferInfo.presentationTimeUs / 1000,TYPE_AUDIO); + } + } + } + // 释放输出缓存,将其还给编码器 + mMediaCodec.releaseOutputBuffer(encoderStatus, false); + } + } + } + + private void addADTStoPacket(byte[] packet, int packetLen) { + packet[0] = (byte) 0xFF; + packet[1] = (byte) 0xF1; + packet[2] = (byte) (((2 - 1) << 6) + (getSamplingRateIndex() << 2) + (1 >> 2)); + packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11)); + packet[4] = (byte) ((packetLen & 0x7FF) >> 3); + packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F); + packet[6] = (byte) 0xFC; + } + + private int getSamplingRateIndex(){ + int mSamplingRateIndex = -1; + for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { + if (AUDIO_SAMPLING_RATES[i] == MediaAudioEncoder.SAMPLE_RATE) { + mSamplingRateIndex = i; + break; + } + } + return mSamplingRateIndex; + } + + + private long prevOutputPTSUs = 0; + + protected long getPTSUs() { + long result = System.nanoTime() / 1000L; + if (result < prevOutputPTSUs) + result = (prevOutputPTSUs - result) + result; + return result; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaMuxerWrapper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaMuxerWrapper.java new file mode 100644 index 0000000000..f13982674b --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaMuxerWrapper.java @@ -0,0 +1,191 @@ +package com.serenegiant.usb.encoder; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.os.Build; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; +import java.util.Locale; + +public class MediaMuxerWrapper { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaMuxerWrapper"; + + private static final String DIR_NAME = "USBCameraTest"; + private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); + + private String mOutputPath; + private final MediaMuxer mMediaMuxer; // API >= 18 + private int mEncoderCount, mStatredCount; + private boolean mIsStarted; + private MediaEncoder mVideoEncoder, mAudioEncoder; + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public MediaMuxerWrapper(String path) throws IOException { + try { + // 保存到自定义路径还是手机默认Movies路径 + if (TextUtils.isEmpty(path)) + mOutputPath = getCaptureFile(Environment.DIRECTORY_MOVIES, ".mp4").toString(); + mOutputPath = path; + + } catch (final NullPointerException e) { + throw new RuntimeException("This app has no permission of writing external storage"); + } + mMediaMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + mEncoderCount = mStatredCount = 0; + mIsStarted = false; + } + + public String getOutputPath() { + return mOutputPath; + } + + public void prepare() throws IOException { + if (mVideoEncoder != null) + mVideoEncoder.prepare(); + if (mAudioEncoder != null) + mAudioEncoder.prepare(); + } + + public void startRecording() { + if (mVideoEncoder != null) + mVideoEncoder.startRecording(); + if (mAudioEncoder != null) + mAudioEncoder.startRecording(); + } + + public void stopRecording() { + if (mVideoEncoder != null) + mVideoEncoder.stopRecording(); + mVideoEncoder = null; + if (mAudioEncoder != null) + mAudioEncoder.stopRecording(); + mAudioEncoder = null; + } + + public synchronized boolean isStarted() { + return mIsStarted; + } + +//********************************************************************** +//********************************************************************** + /** + * assign encoder to this calss. this is called from encoder. + * @param encoder instance of MediaVideoEncoder or MediaAudioEncoder + */ + /*package*/ void addEncoder(final MediaEncoder encoder) { + if (encoder instanceof MediaVideoEncoder) { + if (mVideoEncoder != null) + throw new IllegalArgumentException("Video encoder already added."); + mVideoEncoder = encoder; + } else if (encoder instanceof MediaSurfaceEncoder) { + if (mVideoEncoder != null) + throw new IllegalArgumentException("Video encoder already added."); + mVideoEncoder = encoder; + } else if (encoder instanceof MediaVideoBufferEncoder) { + if (mVideoEncoder != null) + throw new IllegalArgumentException("Video encoder already added."); + mVideoEncoder = encoder; + } else if (encoder instanceof MediaAudioEncoder) { + if (mAudioEncoder != null) + throw new IllegalArgumentException("Video encoder already added."); + mAudioEncoder = encoder; + } else + throw new IllegalArgumentException("unsupported encoder"); + mEncoderCount = (mVideoEncoder != null ? 1 : 0) + (mAudioEncoder != null ? 1 : 0); + } + + /** + * request start recording from encoder + * @return true when muxer is ready to write + */ + /*package*/ synchronized boolean start() { + if (DEBUG) Log.v(TAG, "start:"); + mStatredCount++; + if ((mEncoderCount > 0) && (mStatredCount == mEncoderCount)) { + mMediaMuxer.start(); + mIsStarted = true; + notifyAll(); + if (DEBUG) Log.v(TAG, "MediaMuxer started:"); + } + return mIsStarted; + } + + /** + * request stop recording from encoder when encoder received EOS + */ + /*package*/ synchronized void stop() { + if (DEBUG) Log.v(TAG, "stop:mStatredCount=" + mStatredCount); + mStatredCount--; + if ((mEncoderCount > 0) && (mStatredCount <= 0)) { + try { + mMediaMuxer.stop(); + } catch (final Exception e) { + Log.w(TAG, e); + } + mIsStarted = false; + if (DEBUG) Log.v(TAG, "MediaMuxer stopped:"); + } + } + + /** + * assign encoder to muxer + * @param format + * @return minus value indicate error + */ + /*package*/ synchronized int addTrack(final MediaFormat format) { + if (mIsStarted) + throw new IllegalStateException("muxer already started"); + final int trackIx = mMediaMuxer.addTrack(format); + if (DEBUG) Log.i(TAG, "addTrack:trackNum=" + mEncoderCount + ",trackIx=" + trackIx + ",format=" + format); + return trackIx; + } + + /** + * write encoded data to muxer + * @param trackIndex + * @param byteBuf + * @param bufferInfo + */ + /*package*/ synchronized void writeSampleData(final int trackIndex, final ByteBuffer byteBuf, final MediaCodec.BufferInfo bufferInfo) { + if (mStatredCount > 0) + mMediaMuxer.writeSampleData(trackIndex, byteBuf, bufferInfo); + } + +//********************************************************************** +//********************************************************************** + /** + * generate output file + * @param type Environment.DIRECTORY_MOVIES / Environment.DIRECTORY_DCIM etc. + * @param ext .mp4(.m4a for audio) or .png + * @return return null when this app has no writing permission to external storage. + */ + public static final File getCaptureFile(final String type, final String ext) { + final File dir = new File(Environment.getExternalStoragePublicDirectory(type), DIR_NAME); + Log.d(TAG, "path=" + dir.toString()); + dir.mkdirs(); + if (dir.canWrite()) { + return new File(dir, getDateTimeString() + ext); + } + return null; + } + + /** + * get current date and time as String + * @return + */ + private static final String getDateTimeString() { + final GregorianCalendar now = new GregorianCalendar(); + return mDateTimeFormat.format(now.getTime()); + } + +} 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 new file mode 100644 index 0000000000..47eb607e08 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java @@ -0,0 +1,196 @@ +/* + * 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; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.util.Log; +import android.view.Surface; + +import java.io.IOException; + +public class MediaSurfaceEncoder extends MediaEncoder implements IVideoEncoder { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaSurfaceEncoder"; + + private static final String MIME_TYPE = "video/avc"; + // parameters for recording + private final int mWidth, mHeight; + private static final int FRAME_RATE = 15; + private static final float BPP = 0.50f; + + private Surface mSurface; + + public MediaSurfaceEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { + super(muxer, listener); + if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); + mWidth = width; + mHeight = height; + } + + /** + * Returns the encoder's input surface. + */ + public Surface getInputSurface() { + return mSurface; + } + + @Override + protected void prepare() throws IOException { + if (DEBUG) Log.i(TAG, "prepare: "); + mTrackIndex = -1; + mMuxerStarted = mIsEOS = false; + + final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); + if (videoCodecInfo == null) { + Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); + return; + } + if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); + + final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18 + format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); + if (DEBUG) Log.i(TAG, "format: " + format); + + mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + // get Surface for encoder input + // this method only can call between #configure and #start + mSurface = mMediaCodec.createInputSurface(); // API >= 18 + mMediaCodec.start(); + if (DEBUG) Log.i(TAG, "prepare finishing"); + if (mListener != null) { + try { + mListener.onPrepared(this); + } catch (final Exception e) { + Log.e(TAG, "prepare:", e); + } + } + } + + @Override + protected void release() { + if (DEBUG) Log.i(TAG, "release:"); + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + super.release(); + } + + private int calcBitRate() { + final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); + Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); + return bitrate; + } + + /** + * select the first codec that match a specific MIME type + * @param mimeType + * @return null if no codec matched + */ + protected static final MediaCodecInfo selectVideoCodec(final String mimeType) { + if (DEBUG) Log.v(TAG, "selectVideoCodec:"); + + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + // select first codec that match a specific MIME type and color format + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); + final int format = selectColorFormat(codecInfo, mimeType); + if (format > 0) { + return codecInfo; + } + } + } + } + return null; + } + + /** + * select color format available on specific codec and we can use. + * @return 0 if no colorFormat is matched + */ + protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { + if (DEBUG) Log.i(TAG, "selectColorFormat: "); + int result = 0; + final MediaCodecInfo.CodecCapabilities caps; + try { + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + caps = codecInfo.getCapabilitiesForType(mimeType); + } finally { + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + } + int colorFormat; + for (int i = 0; i < caps.colorFormats.length; i++) { + colorFormat = caps.colorFormats[i]; + if (isRecognizedVideoFormat(colorFormat)) { + if (result == 0) + result = colorFormat; + break; + } + } + if (result == 0) + Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); + return result; + } + + /** + * color formats that we can use in this class + */ + protected static int[] recognizedFormats; + static { + recognizedFormats = new int[] { +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, +// MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, + }; + } + + private static final boolean isRecognizedVideoFormat(final int colorFormat) { + if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat); + final int n = recognizedFormats != null ? recognizedFormats.length : 0; + for (int i = 0; i < n; i++) { + if (recognizedFormats[i] == colorFormat) { + return true; + } + } + return false; + } + +} 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 new file mode 100644 index 0000000000..40d26bfaa0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java @@ -0,0 +1,184 @@ +/* + * 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; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class MediaVideoBufferEncoder extends MediaEncoder implements IVideoEncoder { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaVideoBufferEncoder"; + + private static final String MIME_TYPE = "video/avc"; + private static final int FRAME_RATE = 15; + private static final float BPP = 0.50f; + + private final int mWidth, mHeight; + protected int mColorFormat; + + public MediaVideoBufferEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { + super(muxer, listener); + if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); + mWidth = width; + mHeight = height; + } + + public void encode(final ByteBuffer buffer) { + synchronized (mSync) { + if (!mIsCapturing || mRequestStop) return; + } +// encode(buffer, buffer.capacity(), getPTSUs()); + encode(buffer, buffer.capacity()); + } + + @Override + protected void prepare() throws IOException { + if (DEBUG) Log.i(TAG, "prepare: "); + mTrackIndex = -1; + mMuxerStarted = mIsEOS = false; + + final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); + if (videoCodecInfo == null) { + Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); + return; + } + if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); + + final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat); + format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); + if (DEBUG) Log.i(TAG, "format: " + format); + + mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mMediaCodec.start(); + + Bundle params = new Bundle(); + params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mMediaCodec.setParameters(params); + } + if (DEBUG) Log.i(TAG, "prepare finishing"); + if (mListener != null) { + try { + mListener.onPrepared(this); + } catch (final Exception e) { + Log.e(TAG, "prepare:", e); + } + } + } + + private int calcBitRate() { + final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); + Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); + return bitrate; + } + + // 选择第一个与制定MIME类型匹配的编码器 + @SuppressWarnings("deprecation") + protected final MediaCodecInfo selectVideoCodec(final String mimeType) { + if (DEBUG) Log.v(TAG, "selectVideoCodec:"); + + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + // select first codec that match a specific MIME type and color format + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); + final int format = selectColorFormat(codecInfo, mimeType); + if (format > 0) { + mColorFormat = format; + return codecInfo; + } + } + } + } + return null; + } + + // 选择编码器支持的格式 + protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { + if (DEBUG) Log.i(TAG, "selectColorFormat: "); + int result = 0; + final MediaCodecInfo.CodecCapabilities caps; + try { + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + caps = codecInfo.getCapabilitiesForType(mimeType); + } finally { + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + } + int colorFormat; + for (int i = 0; i < caps.colorFormats.length; i++) { + colorFormat = caps.colorFormats[i]; + if (isRecognizedViewoFormat(colorFormat)) { + if (result == 0) + result = colorFormat; + break; + } + } + if (result == 0) + Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); + return result; + } + + // YUV颜色格式 + protected static int[] recognizedFormats; + static { + recognizedFormats = new int[] { +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, +// MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, + }; + } + + private static final boolean isRecognizedViewoFormat(final int colorFormat) { + if (DEBUG) Log.i(TAG, "isRecognizedViewoFormat:colorFormat=" + colorFormat); + final int n = recognizedFormats != null ? recognizedFormats.length : 0; + for (int i = 0; i < n; i++) { + if (recognizedFormats[i] == colorFormat) { + return true; + } + } + return false; + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoEncoder.java new file mode 100644 index 0000000000..59762351b6 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoEncoder.java @@ -0,0 +1,231 @@ +/* + * 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.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.os.Build; +import android.util.Log; +import android.view.Surface; + +import com.serenegiant.glutils.EGLBase; +import com.serenegiant.glutils.RenderHandler; + +import java.io.IOException; + +/** + * Encode texture images as H.264 video + * using MediaCodec. + * This class render texture images into recording surface + * camera from MediaCodec encoder using Open GL|ES + */ +public class MediaVideoEncoder extends MediaEncoder implements IVideoEncoder { + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "MediaVideoEncoder"; + + private static final String MIME_TYPE = "video/avc"; + // parameters for recording + private final int mWidth, mHeight; + private static final int FRAME_RATE = 15; + private static final float BPP = 0.50f; + + private RenderHandler mRenderHandler; + private Surface mSurface; + + public MediaVideoEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { + super(muxer, listener); + if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); + mRenderHandler = RenderHandler.createHandler(TAG); + mWidth = width; + mHeight = height; + } + + public boolean frameAvailableSoon(final float[] tex_matrix) { + boolean result; + if (result = super.frameAvailableSoon()) + mRenderHandler.draw(tex_matrix); + return result; + } + + /** + * This method does not work correctly on this class, + * use #frameAvailableSoon(final float[]) instead + * @return + */ + @Override + public boolean frameAvailableSoon() { + boolean result; + if (result = super.frameAvailableSoon()) + mRenderHandler.draw(null); + return result; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + @Override + protected void prepare() throws IOException { + if (DEBUG) Log.i(TAG, "prepare: "); + mTrackIndex = -1; + mMuxerStarted = mIsEOS = false; + + final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); + if (videoCodecInfo == null) { + Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); + return; + } + if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); + + final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18 + format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + if (DEBUG) Log.i(TAG, "format: " + format); + + mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + // get Surface for encoder input + // this method only can call between #configure and #start + mSurface = mMediaCodec.createInputSurface(); // API >= 18 + mMediaCodec.start(); + if (DEBUG) Log.i(TAG, "prepare finishing"); + if (mListener != null) { + try { + mListener.onPrepared(this); + } catch (final Exception e) { + Log.e(TAG, "prepare:", e); + } + } + } + + public void setEglContext(final EGLBase.IContext sharedContext, final int tex_id) { + mRenderHandler.setEglContext(sharedContext, tex_id, mSurface, true); + } + + @Override + protected void release() { + if (DEBUG) Log.i(TAG, "release:"); + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + if (mRenderHandler != null) { + mRenderHandler.release(); + mRenderHandler = null; + } + super.release(); + } + + private int calcBitRate() { + final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); + Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); + return bitrate; + } + + /** + * select the first codec that match a specific MIME type + * @param mimeType + * @return null if no codec matched + */ + protected static final MediaCodecInfo selectVideoCodec(final String mimeType) { + if (DEBUG) Log.v(TAG, "selectVideoCodec:"); + + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + // select first codec that match a specific MIME type and color format + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); + final int format = selectColorFormat(codecInfo, mimeType); + if (format > 0) { + return codecInfo; + } + } + } + } + return null; + } + + /** + * select color format available on specific codec and we can use. + * @return 0 if no colorFormat is matched + */ + protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { + if (DEBUG) Log.i(TAG, "selectColorFormat: "); + int result = 0; + final MediaCodecInfo.CodecCapabilities caps; + try { + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + caps = codecInfo.getCapabilitiesForType(mimeType); + } finally { + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + } + int colorFormat; + for (int i = 0; i < caps.colorFormats.length; i++) { + colorFormat = caps.colorFormats[i]; + if (isRecognizedVideoFormat(colorFormat)) { + if (result == 0) + result = colorFormat; + break; + } + } + if (result == 0) + Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); + return result; + } + + /** + * color formats that we can use in this class + */ + protected static int[] recognizedFormats; + static { + recognizedFormats = new int[] { +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, +// MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, + }; + } + + private static final boolean isRecognizedVideoFormat(final int colorFormat) { + if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat); + final int n = recognizedFormats != null ? recognizedFormats.length : 0; + for (int i = 0; i < n; i++) { + if (recognizedFormats[i] == colorFormat) { + return true; + } + } + return false; + } + +} 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 new file mode 100644 index 0000000000..0265cac1ea --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java @@ -0,0 +1,57 @@ +package com.serenegiant.usb.encoder; + +/** 录制参数 + * + * Created by jiangdongguo on 2017/10/19. + */ + +public class RecordParams { + private String recordPath; + private int recordDuration; + private boolean voiceClose; + private boolean isAutoSave; + private boolean isSupportOverlay; + + public RecordParams() { + } + + public boolean isSupportOverlay() { + return isSupportOverlay; + } + + public void setSupportOverlay(boolean supportOverlay) { + isSupportOverlay = supportOverlay; + } + + public boolean isVoiceClose() { + return voiceClose; + } + + public void setVoiceClose(boolean voiceClose) { + this.voiceClose = voiceClose; + } + + public String getRecordPath() { + return recordPath; + } + + public void setRecordPath(String recordPath) { + this.recordPath = recordPath; + } + + public int getRecordDuration() { + return recordDuration; + } + + public void setRecordDuration(int recordDuration) { + this.recordDuration = recordDuration; + } + + public boolean isAutoSave() { + return isAutoSave; + } + + public void setAutoSave(boolean autoSave) { + isAutoSave = autoSave; + } +} 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 new file mode 100644 index 0000000000..69319e9b7d --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java @@ -0,0 +1,381 @@ +package com.serenegiant.usb.encoder.biz; + +import android.annotation.TargetApi; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.media.MediaRecorder; +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +/**将PCM编码为AAC + * + * Created by jianddongguo on 2017/7/21. + */ + +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"; + private static final long TIMES_OUT = 1000; + private static final int SAMPLE_RATE = 8000; // 采样率 + private static final int BIT_RATE = 16000; // 比特率 + private static final int BUFFER_SIZE = 1920; // 最小缓存 + private int outChannel = 1; + private int bitRateForLame = 32; + private int qaulityDegree = 7; + private int bufferSizeInBytes; + + private AudioRecord mAudioRecord; // 音频采集 + private MediaCodec mAudioEncoder; // 音频编码 + private OnAACEncodeResultListener listener; + private int mSamplingRateIndex = 0;//ADTS + private boolean isEncoderStart = false; + private boolean isRecMp3 = false; + private boolean isExit = false; + private long prevPresentationTimes = 0; + private WeakReference mMuxerRef; + private MediaFormat newFormat; + + private static final int[] AUDIO_SOURCES = new int[] { + MediaRecorder.AudioSource.DEFAULT, + MediaRecorder.AudioSource.MIC, + MediaRecorder.AudioSource.CAMCORDER, + }; + /** + * There are 13 supported frequencies by ADTS. + **/ + public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 + 88200, // 1 + 64000, // 2 + 48000, // 3 + 44100, // 4 + 32000, // 5 + 24000, // 6 + 22050, // 7 + 16000, // 8 + 12000, // 9 + 11025, // 10 + 8000, // 11 + 7350, // 12 + -1, // 13 + -1, // 14 + -1, // 15 + }; + + private FileOutputStream fops; + + // 编码流结果回调接口 + 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++) { + if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) { + mSamplingRateIndex = i; + break; + } + } + } + + public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){ + this.listener = listener; + } + + public void exit(){ + isExit = true; + } + + public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){ + this.mMuxerRef = new WeakReference<>(mMuxer); + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null && newFormat != null) { + muxer.addTrack(newFormat, false); + } + } + + @Override + public void run() { + // 开启音频采集、编码 + if(! isEncoderStart){ + initAudioRecord(); + initMediaCodec(); + } + // 初始化音频文件参数 + byte[] mp3Buffer = new byte[1024]; + + // 这里有问题,当本地录制结束后,没有写入 + while(! isExit){ + byte[] audioBuffer = new byte[2048]; + // 采集音频 + int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE); + + if(DEBUG) + Log.i(TAG,"采集音频readBytes = "+readBytes); + // 编码音频 + if(readBytes > 0){ + encodeBytes(audioBuffer,readBytes); + } + } + // 停止音频采集、编码 + stopMediaCodec(); + stopAudioRecord(); + } + + + @TargetApi(21) + private void encodeBytes(byte[] audioBuf, int readBytes) { + ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers(); + ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers(); + //返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区 + int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT); + if(inputBufferIndex >= 0){ + // 绑定一个被空的、可写的输入缓存区inputBuffer到客户端 + ByteBuffer inputBuffer = null; + if(!isLollipop()){ + inputBuffer = inputBuffers[inputBufferIndex]; + }else{ + inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex); + } + // 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理 + 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); + } + } + + // 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区 + // mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间 + 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){ + // 如果API小于21,APP需要重新绑定编码器的输入缓存区; + // 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED + if(!isLollipop()){ + outputBuffers = mAudioEncoder.getOutputBuffers(); + } + }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ + // 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次 + // 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步) + if(DEBUG) + Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器"); + synchronized (AACEncodeConsumer.this) { + newFormat = mAudioEncoder.getOutputFormat(); + if(mMuxerRef != null){ + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null) { + muxer.addTrack(newFormat, false); + } + } + } + }else{ + // 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了 + 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,"数据流结束,退出循环"); + break; + } + // 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据 + ByteBuffer mBuffer = ByteBuffer.allocate(10240); + ByteBuffer outputBuffer = null; + if(!isLollipop()){ + outputBuffer = outputBuffers[outputBufferIndex]; + }else{ + outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); + } + if(mBufferInfo.size != 0){ + // 获取输出缓存区失败,抛出异常 + if(outputBuffer == null){ + throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null"); + } + // 添加视频流到混合器 + if(mMuxerRef != null){ + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null) { + muxer.pumpStream(outputBuffer, mBufferInfo, false); + } + } + // AAC流添加ADTS头,缓存到mBuffer + mBuffer.clear(); + outputBuffer.get(mBuffer.array(), 7, mBufferInfo.size); + outputBuffer.clear(); + mBuffer.position(7 + mBufferInfo.size); + 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); + } + } + // 处理结束,释放输出缓存区资源 + mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false); + } + }while (outputBufferIndex >= 0); + } + + 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) { + try { + mAudioRecord = new AudioRecord(src, + SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); + if (mAudioRecord != null) { + if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { + mAudioRecord.release(); + mAudioRecord = null; + } + } + } catch (final Exception e) { + mAudioRecord = null; + } + if (mAudioRecord != null) { + break; + } + } + mAudioRecord.startRecording(); + } + + private void initMediaCodec(){ + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->开始编码音频"); + MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE); + if(mCodecInfo == null){ + Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型"); + return; + } + try{ + mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName()); + }catch(IOException e){ + Log.e(TAG,"创建编码器失败"+e.getMessage()); + e.printStackTrace(); + } + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); + format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); + format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, BUFFER_SIZE); + mAudioEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mAudioEncoder.start(); + isEncoderStart = true; + } + + private void stopAudioRecord() { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止采集音频"); + if(mAudioRecord != null){ + mAudioRecord.stop(); + mAudioRecord.release(); + mAudioRecord = null; + } + } + + private void stopMediaCodec() { + if(DEBUG) + Log.d(TAG,"AACEncodeConsumer-->停止编码音频"); + if(mAudioEncoder != null){ + mAudioEncoder.stop(); + mAudioEncoder.release(); + mAudioEncoder = null; + } + isEncoderStart = false; + } + + // API>=21 + private boolean isLollipop(){ + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + // API<=19 + 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; + } + return result; + } + + /** + * 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器 + * 判断是否有支持指定mime类型的编码器 + * */ + private MediaCodecInfo selectSupportCodec(String mimeType){ + int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + // 判断是否为编码器,否则直接进入下一次循环 + if (!codecInfo.isEncoder()) { + continue; + } + // 如果是编码器,判断是否支持Mime类型 + String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + return codecInfo; + } + } + } + return null; + } + + private void addADTStoPacket(byte[] packet, int packetLen) { + packet[0] = (byte) 0xFF; + packet[1] = (byte) 0xF1; + packet[2] = (byte) (((2 - 1) << 6) + (mSamplingRateIndex << 2) + (1 >> 2)); + packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11)); + packet[4] = (byte) ((packetLen & 0x7FF) >> 3); + packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F); + packet[6] = (byte) 0xFC; + } + + + private short[] transferByte2Short(byte[] data,int readBytes){ + // byte[] 转 short[],数组长度缩减一半 + int shortLen = readBytes / 2; + // 将byte[]数组装如ByteBuffer缓冲区 + ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, readBytes); + // 将ByteBuffer转成小端并获取shortBuffer + ShortBuffer shortBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + short[] shortData = new short[shortLen]; + shortBuffer.get(shortData, 0, shortLen); + return shortData; + } +} 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 new file mode 100644 index 0000000000..9310ce1ae4 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java @@ -0,0 +1,461 @@ +package com.serenegiant.usb.encoder.biz; + +import android.annotation.SuppressLint; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +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; + private static final String TAG = "H264EncodeConsumer"; + private static final String MIME_TYPE = "video/avc"; + // 间隔1s插入一帧关键帧 + private static final int FRAME_INTERVAL = 1; + // 绑定编码器缓存区超时时间为10s + private static final int TIMES_OUT = 10000; + // 硬编码器 + private MediaCodec mMediaCodec; + private int mColorFormat; + private boolean isExit = false; + private boolean isEncoderStart = false; + + private MediaFormat mFormat; + private static String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test2.h264"; + private BufferedOutputStream outputStream; + final int millisPerframe = 1000 / 20; + long lastPush = 0; + private OnH264EncodeResultListener listener; + private int mWidth; + private int mHeight; + private MediaFormat newFormat; + private WeakReference mMuxerRef; + private boolean isAddKeyFrame = false; + + public interface OnH264EncodeResultListener { + void onEncodeResult(byte[] data, int offset, + int length, long timestamp); + } + + public void setOnH264EncodeResultListener(OnH264EncodeResultListener listener) { + this.listener = listener; + } + + public H264EncodeConsumer(int width, int height) { + this.mWidth = width; + this.mHeight = height; + } + + public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer) { + this.mMuxerRef = new WeakReference<>(mMuxer); + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null && newFormat != null) { + muxer.addTrack(newFormat, true); + } + } + + private ByteBuffer[] inputBuffers; + private ByteBuffer[] outputBuffers; + + public void setRawYuv(byte[] yuvData, int width, int height) { + if (!isEncoderStart) + return; + if (mWidth != width || mHeight != height) { + mWidth = width; + mHeight = height; + return; + } + try { + if (lastPush == 0) { + lastPush = System.currentTimeMillis(); + } + long time = System.currentTimeMillis() - lastPush; + if (time >= 0) { + time = millisPerframe - time; + if (time > 0) + Thread.sleep(time / 2); + } + // 将数据写入编码器 + + feedMediaCodecData(nv12ToNV21(yuvData, mWidth, mHeight)); + + if (time > 0) + Thread.sleep(time / 2); + lastPush = System.currentTimeMillis(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + + private void feedMediaCodecData(byte[] data) { + if (!isEncoderStart) + return; + int bufferIndex = -1; + try { + bufferIndex = mMediaCodec.dequeueInputBuffer(0); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + if (bufferIndex >= 0) { + ByteBuffer buffer; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + buffer = mMediaCodec.getInputBuffer(bufferIndex); + } else { + buffer = inputBuffers[bufferIndex]; + } + buffer.clear(); + buffer.put(data); + buffer.clear(); + mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME); + } + } + + public void exit() { + isExit = true; + } + + @SuppressLint("WrongConstant") + @Override + public void run() { + if (!isEncoderStart) { + startMediaCodec(); + } + // 休眠200ms,等待音频线程开启 + // 否则视频第一秒会卡住 + try { + Thread.sleep(200); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + + // 如果编码器没有启动或者没有图像数据,线程阻塞等待 + while (!isExit) { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int outputBufferIndex = 0; + byte[] mPpsSps = new byte[0]; + byte[] h264 = new byte[mWidth * mHeight]; + do { + outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000); + if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + // no output available yet + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // not expected for an encoder + outputBuffers = mMediaCodec.getOutputBuffers(); + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + synchronized (H264EncodeConsumer.this) { + newFormat = mMediaCodec.getOutputFormat(); + if (mMuxerRef != null) { + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null) { + muxer.addTrack(newFormat, true); + } + } + } + } else if (outputBufferIndex < 0) { + // let's ignore it + } else { + ByteBuffer outputBuffer; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex); + } else { + outputBuffer = outputBuffers[outputBufferIndex]; + } + outputBuffer.position(bufferInfo.offset); + outputBuffer.limit(bufferInfo.offset + bufferInfo.size); + + boolean sync = false; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps + sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + if (!sync) { + byte[] temp = new byte[bufferInfo.size]; + outputBuffer.get(temp); + mPpsSps = temp; + mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); + continue; + } else { + mPpsSps = new byte[0]; + } + } + sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + int len = mPpsSps.length + bufferInfo.size; + if (len > h264.length) { + h264 = new byte[len]; + } + if (sync) { + System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length); + outputBuffer.get(h264, mPpsSps.length, bufferInfo.size); + if (listener != null) { + listener.onEncodeResult(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000); + } + + // 添加视频流到混合器 + if (mMuxerRef != null) { + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null) { + muxer.pumpStream(outputBuffer, bufferInfo, true); + } + isAddKeyFrame = true; + } + if (DEBUG) + Log.i(TAG, "关键帧 h264.length = " + h264.length + ";mPpsSps.length=" + mPpsSps.length + + " bufferInfo.size = " + bufferInfo.size); + } else { + outputBuffer.get(h264, 0, bufferInfo.size); + if (listener != null) { + listener.onEncodeResult(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000); + } + // 添加视频流到混合器 + if (isAddKeyFrame && mMuxerRef != null) { + Mp4MediaMuxer muxer = mMuxerRef.get(); + if (muxer != null) { + muxer.pumpStream(outputBuffer, bufferInfo, true); + } + } + if (DEBUG) + Log.i(TAG, "普通帧 h264.length = " + h264.length + " bufferInfo.size = " + bufferInfo.size); + } + mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); + } + } while (!isExit && isEncoderStart); + } + stopMediaCodec(); + } + + private void startMediaCodec() { + final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); + if (videoCodecInfo == null) { + Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); + return; + } + + final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat); + format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + + try { + mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + } catch (IOException e) { + e.printStackTrace(); + } + mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mMediaCodec.start(); + + + isEncoderStart = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + 1) { + inputBuffers = outputBuffers = null; + } else { + inputBuffers = mMediaCodec.getInputBuffers(); + outputBuffers = mMediaCodec.getOutputBuffers(); + } + + Bundle params = new Bundle(); + params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mMediaCodec.setParameters(params); + } + } + + private void stopMediaCodec() { + isEncoderStart = false; + if (mMediaCodec != null) { + mMediaCodec.stop(); + mMediaCodec.release(); + Log.d(TAG, "关闭视频编码器"); + } + } + + private static final int FRAME_RATE = 15; + private static final float BPP = 0.50f; + + private int calcBitRate() { + final int bitrate = (int) (BPP * FRAME_RATE * mWidth * mHeight); + Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); + return bitrate; + } + + /** + * select the first codec that match a specific MIME type + * + * @param mimeType + * @return null if no codec matched + */ + @SuppressWarnings("deprecation") + protected final MediaCodecInfo selectVideoCodec(final String mimeType) { + + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + // select first codec that match a specific MIME type and color format + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + final int format = selectColorFormat(codecInfo, mimeType); + if (format > 0) { + mColorFormat = format; + return codecInfo; + } + } + } + } + return null; + } + + /** + * select color format available on specific codec and we can use. + * + * @return 0 if no colorFormat is matched + */ + protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { + int result = 0; + final MediaCodecInfo.CodecCapabilities caps; + try { + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + caps = codecInfo.getCapabilitiesForType(mimeType); + } finally { + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + } + int colorFormat; + for (int i = 0; i < caps.colorFormats.length; i++) { + colorFormat = caps.colorFormats[i]; + if (isRecognizedViewoFormat(colorFormat)) { + if (result == 0) + result = colorFormat; + break; + } + } + if (result == 0) + Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); + return result; + } + + /** + * color formats that we can use in this class + */ + protected static int[] recognizedFormats; + + static { + recognizedFormats = new int[]{ +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar, +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, +// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + }; + } + + private static final boolean isRecognizedViewoFormat(final int colorFormat) { + final int n = recognizedFormats != null ? recognizedFormats.length : 0; + for (int i = 0; i < n; i++) { + if (recognizedFormats[i] == colorFormat) { + return true; + } + } + return false; + } + + + private byte[] nv21ToI420(byte[] data, int width, int height) { + byte[] ret = new byte[width * height * 3 / 2]; + int total = width * height; + + ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量 + ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量 + ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量 + // NV21 YYYYYYYY VUVU + bufferY.put(data, 0, total); + for (int i = total; i < data.length; i += 2) { + bufferV.put(data[i]); + bufferU.put(data[i + 1]); + } + + return ret; + } + + private byte[] nv12ToI420(byte[] data, int width, int height) { + byte[] ret = new byte[width * height * 3 / 2]; + int total = width * height; + + ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量 + ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量 + ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量 + + // NV12 YYYYYYYY UVUV + bufferY.put(data, 0, total); + for (int i = total; i < data.length; i += 2) { + bufferU.put(data[i]); + bufferV.put(data[i + 1]); + } + return ret; + } + + private byte[] nv12ToNv21(byte[] data, int width, int height) { + byte[] ret = new byte[width * height * 3 / 2]; + int total = width * height; + + ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量 + ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量 + ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量 + + // NV12 YYYYYYYY UVUV + bufferY.put(data, 0, total); + for (int i = total; i < data.length; i += 2) { + bufferU.put(data[i]); + bufferV.put(data[i + 1]); + } + return ret; + } + + // YYYYYYYY UVUV(nv21)--> YYYYYYYY VUVU(nv12) + private byte[] nv21ToNV12(byte[] nv21, int width, int height) { + byte[] ret = new byte[width * height * 3 /2]; + int framesize = width * height; + int i = 0, j = 0; + // 拷贝Y分量 + System.arraycopy(nv21, 0,ret , 0, framesize); + // 拷贝UV分量 + for (j = framesize; j < nv21.length; j += 2) { + ret[j+1] = nv21[j+1]; + ret[j] = nv21[j]; + } + return ret; + } + + // YYYYYYYY UVUV(nv12)--> YYYYYYYY VUVU(nv21) + private byte[] nv12ToNV21(byte[] nv12, int width, int height) { + byte[] ret = new byte[width * height * 3 /2]; + int framesize = width * height; + int i = 0, j = 0; + // 拷贝Y分量 + System.arraycopy(nv12, 0,ret , 0, framesize); + // 拷贝UV分量 + for (j = framesize; j < nv12.length; j += 2) { + ret[j] = nv12[j+1]; + ret[j+1] = nv12[j]; + } + return ret; + } + +} 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 new file mode 100644 index 0000000000..64307af008 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java @@ -0,0 +1,158 @@ +package com.serenegiant.usb.encoder.biz; + +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.os.Build; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +/**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(); + private String mFilePath; + private MediaMuxer mMuxer; + private long durationMillis; + private int index = 0; + private int mVideoTrackIndex = -1; + private int mAudioTrackIndex = -1; + private long mBeginMillis; + private MediaFormat mVideoFormat; + private MediaFormat mAudioFormat; + private boolean isVoiceClose; + + // 文件路径;文件时长 + public Mp4MediaMuxer(String path, long durationMillis,boolean isVoiceClose) { + String mFilePath; + this.isVoiceClose = isVoiceClose; + this.durationMillis = durationMillis; + if(durationMillis != 0) { + mFilePath = path + "-" + index++ + ".mp4"; + }else{ + mFilePath = path+".mp4"; + } + Object mux = null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mux = new MediaMuxer(mFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + mMuxer = (MediaMuxer) mux; + } + } + + public synchronized void addTrack(MediaFormat format, boolean isVideo) { + // now that we have the Magic Goodies, start the muxer + if ((!isVoiceClose && mAudioTrackIndex != -1) && mVideoTrackIndex != -1) + throw new RuntimeException("already add all tracks"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + int track = mMuxer.addTrack(format); + if (VERBOSE) + Log.i(TAG, String.format("addTrack %s result %d", isVideo ? "video" : "audio", track)); + if (isVideo) { + mVideoFormat = format; + mVideoTrackIndex = track; + // 当音频轨道添加 + // 或者开启静音就start + if (isVoiceClose || mAudioTrackIndex != -1) { + if (VERBOSE) + Log.i(TAG, "both audio and video added,and muxer is started"); + mMuxer.start(); + mBeginMillis = System.currentTimeMillis(); + } + } else { + mAudioFormat = format; + mAudioTrackIndex = track; + if (mVideoTrackIndex != -1) { + mMuxer.start(); + mBeginMillis = System.currentTimeMillis(); + } + } + } + } + + public synchronized void pumpStream(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo, boolean isVideo) { + if ((!isVoiceClose && mAudioTrackIndex == -1) || mVideoTrackIndex == -1) { +// Log.i(TAG, String.format("pumpStream [%s] but muxer is not start.ignore..", isVideo ? "video" : "audio")); + return; + } + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. + } else if (bufferInfo.size != 0) { + if (isVideo && mVideoTrackIndex == -1) { + throw new RuntimeException("muxer hasn't started"); + } + + // adjust the ByteBuffer values to match BufferInfo (not needed?) + outputBuffer.position(bufferInfo.offset); + outputBuffer.limit(bufferInfo.offset + bufferInfo.size); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mMuxer.writeSampleData(isVideo ? mVideoTrackIndex : mAudioTrackIndex, outputBuffer, bufferInfo); + } +// if (VERBOSE) +// Log.d(TAG, String.format("sent %s [" + bufferInfo.size + "] with timestamp:[%d] to muxer", isVideo ? "video" : "audio", bufferInfo.presentationTimeUs / 1000)); + } + + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { +// if (VERBOSE) +// Log.i(TAG, "BUFFER_FLAG_END_OF_STREAM received"); + } + + if (durationMillis!=0 && System.currentTimeMillis() - mBeginMillis >= durationMillis) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { +// if (VERBOSE) +// Log.i(TAG, String.format("record file reach expiration.create new file:" + index)); + mMuxer.stop(); + mMuxer.release(); + mMuxer = null; + mVideoTrackIndex = mAudioTrackIndex = -1; + try { + mMuxer = new MediaMuxer(mFilePath + "-" + ++index + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + addTrack(mVideoFormat, true); + addTrack(mAudioFormat, false); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public synchronized void release() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (mMuxer != null) { + //(!isVoiceClose&&mAudioTrackIndex != -1) + if (mVideoTrackIndex != -1) { + if (VERBOSE) + Log.i(TAG, String.format("muxer is started. now it will be stoped.")); + try { + mMuxer.stop(); + mMuxer.release(); + } catch (IllegalStateException ex) { + ex.printStackTrace(); + } + + if (System.currentTimeMillis() - mBeginMillis <= 1500){ + new File(mFilePath + "-" + index + ".mp4").delete(); + } + mAudioTrackIndex = mVideoTrackIndex = -1; + }else{ + if (VERBOSE) + Log.i(TAG, String.format("muxer is failed to be stoped.")); + } + } + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java new file mode 100644 index 0000000000..c616a494ef --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java @@ -0,0 +1,115 @@ +/* + * UVCCamera + * library and sample to access to UVC web camera on non-rooted Android device + * + * Copyright (c) 2014-2017 saki t_saki@serenegiant.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * All files in the folder are under this Apache License, Version 2.0. + * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder + * may have a different license, see the respective files. + */ + +package com.serenegiant.usb.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.TextureView; + +/** + * change the view size with keeping the specified aspect ratio. + * if you set this view with in a FrameLayout and set property "android:layout_gravity="center", + * you can show this view in the center of screen and keep the aspect ratio of content + * XXX it is better that can set the aspect ratio as xml property + * + * @author mogoauto + */ +public class AspectRatioTextureView extends TextureView // API >= 14 + implements IAspectRatioView { + + /**TODO set false on release*/ + private static final boolean DEBUG = true; + private static final String TAG = "AbstractCameraView"; + + private double mRequestedAspect = -1.0; + private CameraViewInterface.Callback mCallback; + + public AspectRatioTextureView(final Context context) { + this(context, null, 0); + } + + public AspectRatioTextureView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + } + + // 设置屏幕宽高比 + @Override + public void setAspectRatio(final double aspectRatio) { + if (aspectRatio < 0) { + throw new IllegalArgumentException(); + } + if (mRequestedAspect != aspectRatio) { + mRequestedAspect = aspectRatio; + requestLayout(); + } + } + + @Override + public void setAspectRatio(final int width, final int height) { + setAspectRatio(width / (double) height); + } + + @Override + public double getAspectRatio() { + return mRequestedAspect; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + if (mRequestedAspect > 0) { + int initialWidth = MeasureSpec.getSize(widthMeasureSpec); + int initialHeight = MeasureSpec.getSize(heightMeasureSpec); + + final int horizPadding = getPaddingLeft() + getPaddingRight(); + final int vertPadding = getPaddingTop() + getPaddingBottom(); + initialWidth -= horizPadding; + initialHeight -= vertPadding; + + final double viewAspectRatio = (double) initialWidth / initialHeight; + final double aspectDiff = mRequestedAspect / viewAspectRatio - 1; + + if (Math.abs(aspectDiff) > 0.01) { + if (aspectDiff > 0) { + // width priority decision + initialHeight = (int) (initialWidth / mRequestedAspect); + } else { + // height priority decision + initialWidth = (int) (initialHeight * mRequestedAspect); + } + initialWidth += horizPadding; + initialHeight += vertPadding; + widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY); + } + } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java new file mode 100644 index 0000000000..413174669c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java @@ -0,0 +1,23 @@ +package com.serenegiant.usb.widget; + +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.view.Surface; + +import com.serenegiant.usb.encoder.IVideoEncoder; + +public interface CameraViewInterface extends IAspectRatioView { + public interface Callback { + public void onSurfaceCreated(CameraViewInterface view, Surface surface); + public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height); + public void onSurfaceDestroy(CameraViewInterface view, Surface surface); + } + public void onPause(); + public void onResume(); + public void setCallback(Callback callback); + public SurfaceTexture getSurfaceTexture(); + public Surface getSurface(); + public boolean hasSurface(); + public void setVideoEncoder(final IVideoEncoder encoder); + public Bitmap captureStillImage(int width,int height); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java new file mode 100644 index 0000000000..7e6ca85240 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java @@ -0,0 +1,9 @@ +package com.serenegiant.usb.widget; + +public interface IAspectRatioView { + void setAspectRatio(double var1); + + void setAspectRatio(int var1, int var2); + + double getAspectRatio(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java new file mode 100644 index 0000000000..539a67318e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java @@ -0,0 +1,575 @@ +package com.serenegiant.usb.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Surface; +import android.view.TextureView; + +import com.serenegiant.glutils.EGLBase; +import com.serenegiant.glutils.GLDrawer2D; +import com.serenegiant.glutils.es1.GLHelper; +import com.serenegiant.usb.encoder.IVideoEncoder; +import com.serenegiant.usb.encoder.MediaEncoder; +import com.serenegiant.usb.encoder.MediaVideoEncoder; +import com.serenegiant.utils.FpsCounter; + +/** + * change the view size with keeping the specified aspect ratio. + * if you set this view with in a FrameLayout and set property "android:layout_gravity="center", + * you can show this view in the center of screen and keep the aspect ratio of content + * XXX it is better that can set the aspect ratio as xml property + */ +public class UVCCameraTextureView extends AspectRatioTextureView // API >= 14 + implements TextureView.SurfaceTextureListener, CameraViewInterface { + + private static final boolean DEBUG = true; // TODO set false on release + private static final String TAG = "UVCCameraTextureView"; + + private boolean mHasSurface; + private RenderHandler mRenderHandler; + private final Object mCaptureSync = new Object(); + private Bitmap mTempBitmap; + private boolean mReqesutCaptureStillImage; + private Callback mCallback; + // Camera分辨率宽度 + + + /** for calculation of frame rate */ + private final FpsCounter mFpsCounter = new FpsCounter(); + + public UVCCameraTextureView(final Context context) { + this(context, null, 0); + } + + public UVCCameraTextureView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + public UVCCameraTextureView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + setSurfaceTextureListener(this); + } + + @Override + public void onResume() { + if (DEBUG) Log.v(TAG, "onResume:"); + if (mHasSurface) { + mRenderHandler = RenderHandler.createHandler(mFpsCounter, super.getSurfaceTexture(), getWidth(), getHeight()); + } + } + + @Override + public void onPause() { + if (DEBUG) Log.v(TAG, "onPause:"); + if (mRenderHandler != null) { + mRenderHandler.release(); + mRenderHandler = null; + } + if (mTempBitmap != null) { + mTempBitmap.recycle(); + mTempBitmap = null; + } + } + + @Override + public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) { + if (DEBUG) Log.i(TAG, "onSurfaceTextureAvailable:" + surface); + if (mRenderHandler == null) { + mRenderHandler = RenderHandler.createHandler(mFpsCounter, surface, width, height); + } else { + mRenderHandler.resize(width, height); + } + mHasSurface = true; + if (mCallback != null) { + mCallback.onSurfaceCreated(this, getSurface()); + } + } + + @Override + public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) { + if (DEBUG) Log.i(TAG, "onSurfaceTextureSizeChanged:" + surface); + if (mRenderHandler != null) { + mRenderHandler.resize(width, height); + } + if (mCallback != null) { + mCallback.onSurfaceChanged(this, getSurface(), width, height); + } + } + + @Override + public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) { + if (DEBUG) Log.i(TAG, "onSurfaceTextureDestroyed:" + surface); + if (mRenderHandler != null) { + mRenderHandler.release(); + mRenderHandler = null; + } + mHasSurface = false; + if (mCallback != null) { + mCallback.onSurfaceDestroy(this, getSurface()); + } + if (mPreviewSurface != null) { + mPreviewSurface.release(); + mPreviewSurface = null; + } + return true; + } + + @Override + public void onSurfaceTextureUpdated(final SurfaceTexture surface) { + synchronized (mCaptureSync) { + if (mReqesutCaptureStillImage) { + mReqesutCaptureStillImage = false; + if (mTempBitmap == null) + mTempBitmap = getBitmap(); + else + getBitmap(mTempBitmap); + mCaptureSync.notifyAll(); + } + } + } + + @Override + public boolean hasSurface() { + return mHasSurface; + } + + /** + * capture preview image as a bitmap + * this method blocks current thread until bitmap is ready + * if you call this method at almost same time from different thread, + * the returned bitmap will be changed while you are processing the bitmap + * (because we return same instance of bitmap on each call for memory saving) + * if you need to call this method from multiple thread, + * you should change this method(copy and return) + */ + @Override + public Bitmap captureStillImage(int width,int height) { + synchronized (mCaptureSync) { + mReqesutCaptureStillImage = true; + try { + mCaptureSync.wait(); + } catch (final InterruptedException e) { + } + return mTempBitmap; + } + } + + @Override + public SurfaceTexture getSurfaceTexture() { + return mRenderHandler != null ? mRenderHandler.getPreviewTexture() : null; + } + + private Surface mPreviewSurface; + @Override + public Surface getSurface() { + if (DEBUG) Log.v(TAG, "getSurface:hasSurface=" + mHasSurface); + if (mPreviewSurface == null) { + final SurfaceTexture st = getSurfaceTexture(); + if (st != null) { + mPreviewSurface = new Surface(st); + } + } + return mPreviewSurface; + } + + @Override + public void setVideoEncoder(final IVideoEncoder encoder) { + if (mRenderHandler != null) + mRenderHandler.setVideoEncoder(encoder); + } + + @Override + public void setCallback(final Callback callback) { + mCallback = callback; + } + + public void resetFps() { + mFpsCounter.reset(); + } + + /** update frame rate of image processing */ + public void updateFps() { + mFpsCounter.update(); + } + + /** + * get current frame rate of image processing + * @return + */ + public float getFps() { + return mFpsCounter.getFps(); + } + + /** + * get total frame rate from start + * @return + */ + public float getTotalFps() { + return mFpsCounter.getTotalFps(); + } + + /** + * render camera frames on this view on a private thread + * @author saki + * + */ + private static final class RenderHandler extends Handler + implements SurfaceTexture.OnFrameAvailableListener { + + private static final int MSG_REQUEST_RENDER = 1; + private static final int MSG_SET_ENCODER = 2; + private static final int MSG_CREATE_SURFACE = 3; + private static final int MSG_RESIZE = 4; + private static final int MSG_TERMINATE = 9; + + private RenderThread mThread; + private boolean mIsActive = true; + private final FpsCounter mFpsCounter; + + public static final RenderHandler createHandler(final FpsCounter counter, + final SurfaceTexture surface, final int width, final int height) { + + final RenderThread thread = new RenderThread(counter, surface, width, height); + thread.start(); + return thread.getHandler(); + } + + private RenderHandler(final FpsCounter counter, final RenderThread thread) { + mThread = thread; + mFpsCounter = counter; + } + + public final void setVideoEncoder(final IVideoEncoder encoder) { + if (DEBUG) Log.v(TAG, "setVideoEncoder:"); + if (mIsActive) + sendMessage(obtainMessage(MSG_SET_ENCODER, encoder)); + } + + public final SurfaceTexture getPreviewTexture() { + if (DEBUG) Log.v(TAG, "getPreviewTexture:"); + if (mIsActive) { + synchronized (mThread.mSync) { + sendEmptyMessage(MSG_CREATE_SURFACE); + try { + mThread.mSync.wait(); + } catch (final InterruptedException e) { + } + return mThread.mPreviewSurface; + } + } else { + return null; + } + } + + public void resize(final int width, final int height) { + if (DEBUG) Log.v(TAG, "resize:"); + if (mIsActive) { + synchronized (mThread.mSync) { + sendMessage(obtainMessage(MSG_RESIZE, width, height)); + try { + mThread.mSync.wait(); + } catch (final InterruptedException e) { + } + } + } + } + + public final void release() { + if (DEBUG) Log.v(TAG, "release:"); + if (mIsActive) { + mIsActive = false; + removeMessages(MSG_REQUEST_RENDER); + removeMessages(MSG_SET_ENCODER); + sendEmptyMessage(MSG_TERMINATE); + } + } + + @Override + public final void onFrameAvailable(final SurfaceTexture surfaceTexture) { + if (mIsActive) { + mFpsCounter.count(); + sendEmptyMessage(MSG_REQUEST_RENDER); + } + } + + @Override + public final void handleMessage(final Message msg) { + if (mThread == null) return; + switch (msg.what) { + case MSG_REQUEST_RENDER: + mThread.onDrawFrame(); + break; + case MSG_SET_ENCODER: + mThread.setEncoder((MediaEncoder)msg.obj); + break; + case MSG_CREATE_SURFACE: + mThread.updatePreviewSurface(); + break; + case MSG_RESIZE: + mThread.resize(msg.arg1, msg.arg2); + break; + case MSG_TERMINATE: + Looper.myLooper().quit(); + mThread = null; + break; + default: + super.handleMessage(msg); + } + } + + private static final class RenderThread extends Thread { + private final Object mSync = new Object(); + private final SurfaceTexture mSurface; + private RenderHandler mHandler; + private EGLBase mEgl; + /** IEglSurface instance related to this TextureView */ + private EGLBase.IEglSurface mEglSurface; + private GLDrawer2D mDrawer; + private int mTexId = -1; + /** SurfaceTexture instance to receive video images */ + private SurfaceTexture mPreviewSurface; + private final float[] mStMatrix = new float[16]; + private MediaEncoder mEncoder; + private int mViewWidth, mViewHeight; + private final FpsCounter mFpsCounter; + + /** + * constructor + * @param surface: drawing surface came from TexureView + */ + public RenderThread(final FpsCounter fpsCounter, final SurfaceTexture surface, final int width, final int height) { + mFpsCounter = fpsCounter; + mSurface = surface; + mViewWidth = width; + mViewHeight = height; + setName("RenderThread"); + } + + public final RenderHandler getHandler() { + if (DEBUG) Log.v(TAG, "RenderThread#getHandler:"); + synchronized (mSync) { + // create rendering thread + if (mHandler == null) + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + return mHandler; + } + + public void resize(final int width, final int height) { + if (((width > 0) && (width != mViewWidth)) || ((height > 0) && (height != mViewHeight))) { + mViewWidth = width; + mViewHeight = height; + updatePreviewSurface(); + } else { + synchronized (mSync) { + mSync.notifyAll(); + } + } + } + + public final void updatePreviewSurface() { + if (DEBUG) Log.i(TAG, "RenderThread#updatePreviewSurface:"); + synchronized (mSync) { + if (mPreviewSurface != null) { + if (DEBUG) Log.d(TAG, "updatePreviewSurface:release mPreviewSurface"); + mPreviewSurface.setOnFrameAvailableListener(null); + mPreviewSurface.release(); + mPreviewSurface = null; + } + mEglSurface.makeCurrent(); + if (mTexId >= 0) { + mDrawer.deleteTex(mTexId); + } + // create texture and SurfaceTexture for input from camera + mTexId = mDrawer.initTex(); + if (DEBUG) Log.v(TAG, "updatePreviewSurface:tex_id=" + mTexId); + mPreviewSurface = new SurfaceTexture(mTexId); + mPreviewSurface.setDefaultBufferSize(mViewWidth, mViewHeight); + mPreviewSurface.setOnFrameAvailableListener(mHandler); + // notify to caller thread that previewSurface is ready + mSync.notifyAll(); + } + } + + public final void setEncoder(final MediaEncoder encoder) { + if (DEBUG) Log.v(TAG, "RenderThread#setEncoder:encoder=" + encoder); + if (encoder != null && (encoder instanceof MediaVideoEncoder)) { + ((MediaVideoEncoder)encoder).setEglContext(mEglSurface.getContext(), mTexId); + } + mEncoder = encoder; + } + +/* + * Now you can get frame data as ByteBuffer(as YUV/RGB565/RGBX/NV21 pixel format) using IFrameCallback interface + * with UVCCamera#setFrameCallback instead of using following code samples. + */ +/* // for part1 + private static final int BUF_NUM = 1; + private static final int BUF_STRIDE = 640 * 480; + private static final int BUF_SIZE = BUF_STRIDE * BUF_NUM; + int cnt = 0; + int offset = 0; + final int pixels[] = new int[BUF_SIZE]; + final IntBuffer buffer = IntBuffer.wrap(pixels); */ +/* // for part2 + private ByteBuffer buf = ByteBuffer.allocateDirect(640 * 480 * 4); + */ + /** + * draw a frame (and request to draw for video capturing if it is necessary) + */ + public final void onDrawFrame() { + mEglSurface.makeCurrent(); + // update texture(came from camera) + mPreviewSurface.updateTexImage(); + // get texture matrix + mPreviewSurface.getTransformMatrix(mStMatrix); + // notify video encoder if it exist + if (mEncoder != null) { + // notify to capturing thread that the camera frame is available. + if (mEncoder instanceof MediaVideoEncoder) + ((MediaVideoEncoder)mEncoder).frameAvailableSoon(mStMatrix); + else + mEncoder.frameAvailableSoon(); + } + // draw to preview screen + mDrawer.draw(mTexId, mStMatrix, 0); + mEglSurface.swap(); +/* // sample code to read pixels into Buffer and save as a Bitmap (part1) + buffer.position(offset); + GLES20.glReadPixels(0, 0, 640, 480, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); + if (++cnt == 100) { // save as a Bitmap, only once on this sample code + // if you save every frame as a Bitmap, app will crash by Out of Memory exception... + Log.i(TAG, "Capture image using glReadPixels:offset=" + offset); + final Bitmap bitmap = createBitmap(pixels,offset, 640, 480); + final File outputFile = MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".png"); + try { + final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); + try { + try { + bitmap.compress(CompressFormat.PNG, 100, os); + os.flush(); + bitmap.recycle(); + } catch (IOException e) { + } + } finally { + os.close(); + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + } + offset = (offset + BUF_STRIDE) % BUF_SIZE; +*/ +/* // sample code to read pixels into Buffer and save as a Bitmap (part2) + buf.order(ByteOrder.LITTLE_ENDIAN); // it is enough to call this only once. + GLES20.glReadPixels(0, 0, 640, 480, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); + buf.rewind(); + if (++cnt == 100) { // save as a Bitmap, only once on this sample code + // if you save every frame as a Bitmap, app will crash by Out of Memory exception... + final File outputFile = MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".png"); + BufferedOutputStream os = null; + try { + try { + os = new BufferedOutputStream(new FileOutputStream(outputFile)); + Bitmap bmp = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); + bmp.copyPixelsFromBuffer(buf); + bmp.compress(Bitmap.CompressFormat.PNG, 90, os); + bmp.recycle(); + } finally { + if (os != null) os.close(); + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + } +*/ + } + +/* // sample code to read pixels into IntBuffer and save as a Bitmap (part1) + private static Bitmap createBitmap(final int[] pixels, final int offset, final int width, final int height) { + final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); + paint.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(new float[] { + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + }))); + + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + final Matrix matrix = new Matrix(); + matrix.postScale(1.0f, -1.0f); + matrix.postTranslate(0, height); + canvas.concat(matrix); + + canvas.drawBitmap(pixels, offset, width, 0, 0, width, height, false, paint); + + return bitmap; + } */ + + @Override + public final void run() { + Log.d(TAG, getName() + " started"); + init(); + Looper.prepare(); + synchronized (mSync) { + mHandler = new RenderHandler(mFpsCounter, this); + mSync.notify(); + } + + Looper.loop(); + + Log.d(TAG, getName() + " finishing"); + release(); + synchronized (mSync) { + mHandler = null; + mSync.notify(); + } + } + + private final void init() { + if (DEBUG) Log.v(TAG, "RenderThread#init:"); + // create EGLContext for this thread + mEgl = EGLBase.createFrom(null, false, false); + mEglSurface = mEgl.createFromSurface(mSurface); + mEglSurface.makeCurrent(); + // create drawing object + mDrawer = new GLDrawer2D(true); + } + + private final void release() { + if (DEBUG) Log.v(TAG, "RenderThread#release:"); + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + if (mPreviewSurface != null) { + mPreviewSurface.release(); + mPreviewSurface = null; + } + if (mTexId >= 0) { + GLHelper.deleteTex(mTexId); + mTexId = -1; + } + if (mEglSurface != null) { + mEglSurface.release(); + mEglSurface = null; + } + if (mEgl != null) { + mEgl.release(); + mEgl = null; + } + } + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java new file mode 100644 index 0000000000..6b17620d0c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java @@ -0,0 +1,24 @@ +package com.serenegiant.utils; + +import android.content.res.AssetManager; + +import androidx.annotation.NonNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class AssetsHelper { + + public static String loadString(@NonNull final AssetManager assets, @NonNull final String name) throws IOException { + final StringBuffer sb = new StringBuffer(); + final char[] buf = new char[1024]; + final BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(name))); + int r = reader.read(buf); + while (r > 0) { + sb.append(buf, 0, r); + r = reader.read(buf); + } + return sb.toString(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java new file mode 100644 index 0000000000..fd789a5e1b --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java @@ -0,0 +1,458 @@ +package com.serenegiant.utils; + +import android.os.Build; + +public final class BuildCheck { + + private static final boolean check(final int value) { + return (Build.VERSION.SDK_INT >= value); + } + + /** + * Magic version number for a current development build, + * which has not yet turned into an official release. API=10000 + * @return + */ + public static boolean isCurrentDevelopment() { + return (Build.VERSION.SDK_INT == Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + /** + * October 2008: The original, first, version of Android. Yay!, API>=1 + * @return + */ + public static boolean isBase() { + return check(Build.VERSION_CODES.BASE); + } + + /** + * February 2009: First Android update, officially called 1.1., API>=2 + * @return + */ + public static boolean isBase11() { + return check(Build.VERSION_CODES.BASE_1_1); + } + + /** + * May 2009: Android 1.5., API>=3 + * @return + */ + public static boolean isCupcake() { + return check(Build.VERSION_CODES.CUPCAKE); + } + + /** + * May 2009: Android 1.5., API>=3 + * @return + */ + public static boolean isAndroid1_5() { + return check(Build.VERSION_CODES.CUPCAKE); + } + + /** + * September 2009: Android 1.6., API>=4 + * @return + */ + public static boolean isDonut() { + return check(Build.VERSION_CODES.DONUT); + } + + /** + * September 2009: Android 1.6., API>=4 + * @return + */ + public static boolean isAndroid1_6() { + return check(Build.VERSION_CODES.DONUT); + } + + /** + * November 2009: Android 2.0, API>=5 + * @return + */ + public static boolean isEclair() { + return check(Build.VERSION_CODES.ECLAIR); + } + + /** + * November 2009: Android 2.0, API>=5 + * @return + */ + public static boolean isAndroid2_0() { + return check(Build.VERSION_CODES.ECLAIR); + } + + /** + * December 2009: Android 2.0.1, API>=6 + * @return + */ + public static boolean isEclair01() { + return check(Build.VERSION_CODES.ECLAIR_0_1); + } + + /** + * January 2010: Android 2.1, API>=7 + * @return + */ + public static boolean isEclairMR1() { + return check(Build.VERSION_CODES.ECLAIR_MR1); + } + + /** + * June 2010: Android 2.2, API>=8 + * @return + */ + public static boolean isFroyo() { + return check(Build.VERSION_CODES.FROYO); + } + + /** + * June 2010: Android 2.2, API>=8 + * @return + */ + public static boolean isAndroid2_2() { + return check(Build.VERSION_CODES.FROYO); + } + + /** + * November 2010: Android 2.3, API>=9 + * @return + */ + public static boolean isGingerBread() { + return check(Build.VERSION_CODES.GINGERBREAD); + } + + /** + * November 2010: Android 2.3, API>=9 + * @return + */ + public static boolean isAndroid2_3() { + return check(Build.VERSION_CODES.GINGERBREAD); + } + + /** + * February 2011: Android 2.3.3., API>=10 + * @return + */ + public static boolean isGingerBreadMR1() { + return check(Build.VERSION_CODES.GINGERBREAD_MR1); + } + + /** + * February 2011: Android 2.3.3., API>=10 + * @return + */ + public static boolean isAndroid2_3_3() { + return check(Build.VERSION_CODES.GINGERBREAD_MR1); + } + + /** + * February 2011: Android 3.0., API>=11 + * @return + */ + public static boolean isHoneyComb() { + return check(Build.VERSION_CODES.HONEYCOMB); + } + + /** + * February 2011: Android 3.0., API>=11 + * @return + */ + public static boolean isAndroid3() { + return check(Build.VERSION_CODES.HONEYCOMB); + } + + /** + * May 2011: Android 3.1., API>=12 + * @return + */ + public static boolean isHoneyCombMR1() { + return check(Build.VERSION_CODES.HONEYCOMB_MR1); + } + + /** + * May 2011: Android 3.1., API>=12 + * @return + */ + public static boolean isAndroid3_1() { + return check(Build.VERSION_CODES.HONEYCOMB_MR1); + } + + /** + * June 2011: Android 3.2., API>=13 + * @return + */ + public static boolean isHoneyCombMR2() { + return check(Build.VERSION_CODES.HONEYCOMB_MR2); + } + + /** + * June 2011: Android 3.2., API>=13 + * @return + */ + public static boolean isAndroid3_2() { + return check(Build.VERSION_CODES.HONEYCOMB_MR2); + } + + /** + * October 2011: Android 4.0., API>=14 + * @return + */ + public static boolean isIcecreamSandwich() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH); + } + + /** + * October 2011: Android 4.0., API>=14 + * @return + */ + public static boolean isAndroid4() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH); + } + + /** + * December 2011: Android 4.0.3., API>=15 + * @return + */ + public static boolean isIcecreamSandwichMR1() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + } + + /** + * December 2011: Android 4.0.3., API>=15 + * @return + */ + public static boolean isAndroid4_0_3() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + } + + /** + * June 2012: Android 4.1., API>=16 + * @return + */ + public static boolean isJellyBean() { + return check(Build.VERSION_CODES.JELLY_BEAN); + } + + /** + * June 2012: Android 4.1., API>=16 + * @return + */ + public static boolean isAndroid4_1() { + return check(Build.VERSION_CODES.JELLY_BEAN); + } + + /** + * November 2012: Android 4.2, Moar jelly beans!, API>=17 + * @return + */ + public static boolean isJellyBeanMr1() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR1); + } + + /** + * November 2012: Android 4.2, Moar jelly beans!, API>=17 + * @return + */ + public static boolean isAndroid4_2() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR1); + } + + /** + * July 2013: Android 4.3, the revenge of the beans., API>=18 + * @return + */ + public static boolean isJellyBeanMR2() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * July 2013: Android 4.3, the revenge of the beans., API>=18 + * @return + */ + public static boolean isAndroid4_3() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * October 2013: Android 4.4, KitKat, another tasty treat., API>=19 + * @return + */ + public static boolean isKitKat() { + return check(Build.VERSION_CODES.KITKAT); + } + + /** + * October 2013: Android 4.4, KitKat, another tasty treat., API>=19 + * @return + */ + public static boolean isAndroid4_4() { + return check(Build.VERSION_CODES.KITKAT); + } + + /** + * Android 4.4W: KitKat for watches, snacks on the run., API>=20 + * @return + */ + public static boolean isKitKatWatch() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isL() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isLollipop() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isAndroid5() { + return check(Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop with an extra sugar coating on the outside!, API>=22 + * @return + */ + public static boolean isLollipopMR1() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isM() { + return check(Build.VERSION_CODES.M); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isMarshmallow() { + return check(Build.VERSION_CODES.M); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isAndroid6() { + return check(Build.VERSION_CODES.M); + } + + /** + * 虫歯の元, API >= 24 + * @return + */ + public static boolean isN() { + return check(Build.VERSION_CODES.N); + } + + /** + * 歯にくっつくやつ, API >= 24 + * @return + */ + public static boolean isNougat() { + return check(Build.VERSION_CODES.N); + } + /** + * API >= 24 + * @return + */ + public static boolean isAndroid7() { + return check(Build.VERSION_CODES.N); + } + + /** + * API>=25 + * @return + */ + public static boolean isNMR1() { + return check(Build.VERSION_CODES.N_MR1); + } + + /** + * API>=25 + * @return + */ + public static boolean isNougatMR1() { + return check(Build.VERSION_CODES.N_MR1); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isO() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isOreo() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isAndroid8() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=27 + * @return + */ + public static boolean isOMR1() { + return check(Build.VERSION_CODES.O_MR1); + } + + /** + * おれおれぇー MR1 API>=27 + * @return + */ + public static boolean isOreoMR1() { + return check((Build.VERSION_CODES.O_MR1)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isP() { + return check((Build.VERSION_CODES.P)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isPie() { + return check((Build.VERSION_CODES.P)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isAndroid9() { + return check((Build.VERSION_CODES.P)); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java new file mode 100644 index 0000000000..a60d4f770b --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java @@ -0,0 +1,44 @@ +package com.serenegiant.utils; + +public class FpsCounter { + private int cnt, prevCnt; + private long startTime, prevTime; + private float fps, totalFps; + public FpsCounter() { + reset(); + } + + public synchronized FpsCounter reset() { + cnt = prevCnt = 0; + startTime = prevTime = Time.nanoTime() - 1; + return this; + } + + /** + * フレームをカウント + */ + public synchronized void count() { + cnt++; + } + + /** + * FPSの値を更新, 1秒程度毎に呼び出す + * @return + */ + public synchronized FpsCounter update() { + final long t = Time.nanoTime(); + fps = (cnt - prevCnt) * 1000000000.0f / (t - prevTime); + prevCnt = cnt; + prevTime = t; + totalFps = cnt * 1000000000.0f / (t - startTime); + return this; + } + + public synchronized float getFps() { + return fps; + } + + public synchronized float getTotalFps() { + return totalFps; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java new file mode 100644 index 0000000000..81dcd39159 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java @@ -0,0 +1,41 @@ +package com.serenegiant.utils; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class HandlerThreadHandler extends Handler { + private static final String TAG = "HandlerThreadHandler"; + + public static final HandlerThreadHandler createHandler() { + return createHandler(TAG); + } + + public static final HandlerThreadHandler createHandler(final String name) { + final HandlerThread thread = new HandlerThread(name); + thread.start(); + return new HandlerThreadHandler(thread.getLooper()); + } + + public static final HandlerThreadHandler createHandler(@Nullable final Callback callback) { + return createHandler(TAG, callback); + } + + public static final HandlerThreadHandler createHandler(final String name, @Nullable final Callback callback) { + final HandlerThread thread = new HandlerThread(name); + thread.start(); + return new HandlerThreadHandler(thread.getLooper(), callback); + } + + private HandlerThreadHandler(@NonNull final Looper looper) { + super(looper); + } + + private HandlerThreadHandler(@NonNull final Looper looper, @Nullable final Callback callback) { + super(looper, callback); + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java new file mode 100644 index 0000000000..852d432025 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java @@ -0,0 +1,515 @@ +package com.serenegiant.utils; + +import android.util.Log; + +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; + +public abstract class MessageTask implements Runnable { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageTask.class.getSimpleName(); + + public static class TaskBreak extends RuntimeException { + } + + protected static final class Request { + int request; + int arg1; + int arg2; + Object obj; + int request_for_result; + Object result; + + private Request() { + request = request_for_result = REQUEST_TASK_NON; + } + + /** + * @param _request minus value is reserved internal use + * @param _arg1 + * @param _arg2 + * @param _obj + */ + public Request(final int _request, final int _arg1, final int _arg2, final Object _obj) { + request = _request; + arg1 = _arg1; + arg2 = _arg2; + obj = _obj; + request_for_result = REQUEST_TASK_NON; + } + + public void setResult(final Object result) { + synchronized (this) { + this.result = result; + request = request_for_result = REQUEST_TASK_NON; + notifyAll(); + } + } + + @Override + public boolean equals(final Object o) { + return (o instanceof Request) + ? (request == ((Request) o).request) + && (request_for_result == ((Request)o).request_for_result) + && (arg1 == ((Request) o).arg1) + && (arg2 == ((Request) o).arg2) + && (obj == ((Request) o).obj) + : super.equals(o); + } + } + + // minus values and zero are reserved for internal use + protected static final int REQUEST_TASK_NON = 0; + protected static final int REQUEST_TASK_RUN = -1; + protected static final int REQUEST_TASK_RUN_AND_WAIT = -2; + protected static final int REQUEST_TASK_START = -8; + protected static final int REQUEST_TASK_QUIT = -9; + + private final Object mSync = new Object(); + /** プール/キューのサイズ, -1なら無制限 */ + private final int mMaxRequest; + private final LinkedBlockingQueue mRequestPool; // FIXME これはArrayListにした方が速いかも + private final LinkedBlockingDeque mRequestQueue; + private volatile boolean mIsRunning, mFinished; + private Thread mWorkerThread; + + /** + * コンストラクタ + * プール&キューのサイズは無制限 + * プールは空で生成 + */ + public MessageTask() { + mMaxRequest = -1; + mRequestPool = new LinkedBlockingQueue(); + mRequestQueue = new LinkedBlockingDeque(); + } + + /** + * コンストラクタ + * プール&キューのサイズは無制限 + * @param init_num プールするRequestの初期数を指定 + */ + public MessageTask(final int init_num) { + mMaxRequest = -1; + mRequestPool = new LinkedBlockingQueue(); + mRequestQueue = new LinkedBlockingDeque(); + for (int i = 0; i < init_num; i++) { + if (!mRequestPool.offer(new Request())) break; + } + } + + /** + * コンストラクタ + * プール及びキュー可能な最大サイズを指定して初期化 + * @param max_request キューの最大サイズを指定 + * @param init_num プールするRequestの初期数を指定, max_requestよりも大きければ切り捨てる + */ + public MessageTask(final int max_request, final int init_num) { + mMaxRequest = max_request; + mRequestPool = new LinkedBlockingQueue(max_request); + mRequestQueue = new LinkedBlockingDeque(max_request); + for (int i = 0; i < init_num; i++) { + if (!mRequestPool.offer(new Request())) break; + } + } + + /** + * 初期化要求。継承クラスのコンストラクタから呼び出すこと + * パラメータはonInitに引き渡される + * @param arg1 + * @param arg2 + * @param obj + */ + protected void init(final int arg1, final int arg2, final Object obj) { + mFinished = false; + mRequestQueue.offer(obtain(REQUEST_TASK_START, arg1, arg2, obj)); +// offer(REQUEST_TASK_START, arg1, arg2, obj); + } + + /** 初期化処理 */ + protected abstract void onInit(final int arg1, final int arg2, final Object obj); + + /** 要求処理ループ開始直前に呼ばれる */ + protected abstract void onStart(); + + /** onStopの直前に呼び出される, interruptされた時は呼び出されない */ + protected void onBeforeStop() {} + + /** 停止処理, interruptされた時は呼び出されない */ + protected abstract void onStop(); + + /** onStop後に呼び出される。onStopで例外発生しても呼ばれる */ + protected abstract void onRelease(); + + /** + * メッセージ処理ループ中でのエラー発生時の処理 + * デフフォルトはtrueを返しメッセージ処理ループを終了する + * @return trueを返すとメッセージ処理ループを終了する + */ + protected boolean onError(final Exception e) { +// if (DEBUG) Log.w(TAG, e); + return true; + } + + /** 要求メッセージの処理(内部メッセージは来ない) + * TaskBreakをthrowすると要求メッセージ処理ループを終了する */ + protected abstract Object processRequest(final int request, final int arg1, final int arg2, final Object obj) throws TaskBreak; + + /** 要求メッセージを取り出す処理(要求メッセージがなければブロックされる) */ + protected Request takeRequest() throws InterruptedException { + return mRequestQueue.take(); + } + + public boolean waitReady() { + synchronized (mSync) { + for ( ; !mIsRunning && !mFinished ; ) { + try { + mSync.wait(500); + } catch (final InterruptedException e) { + break; + } + } + return mIsRunning; + } + } + + public boolean isRunning() { + return mIsRunning; + } + + public boolean isFinished() { + return mFinished; + } + + @Override + public void run() { + Request request = null; + mIsRunning = true; + try { + request = mRequestQueue.take(); + } catch (final InterruptedException e) { + mIsRunning = false; + mFinished = true; + } + synchronized (mSync) { + if (mIsRunning) { + mWorkerThread = Thread.currentThread(); + try { + onInit(request.arg1, request.arg2, request.obj); + } catch (final Exception e) { + Log.w(TAG, e); + mIsRunning = false; + mFinished = true; + } + } + mSync.notifyAll(); + } + if (mIsRunning) { + try { + onStart(); + } catch (final Exception e) { + if (callOnError(e)) { + mIsRunning = false; + mFinished = true; + } + } + } +LOOP: for (; mIsRunning; ) { + try { + request = takeRequest(); + switch (request.request) { + case REQUEST_TASK_NON: + break; + case REQUEST_TASK_QUIT: + break LOOP; + case REQUEST_TASK_RUN: + if (request.obj instanceof Runnable) + try { + ((Runnable)request.obj).run(); + } catch (final Exception e) { + if (callOnError(e)) + break LOOP; + } + break; + case REQUEST_TASK_RUN_AND_WAIT: + try { + request.setResult(processRequest(request.request_for_result, request.arg1, request.arg2, request.obj)); + } catch (final TaskBreak e) { + request.setResult(null); + break LOOP; + } catch (final Exception e) { + request.setResult(null); + if (callOnError(e)) + break LOOP; + } + break; + default: + try { + processRequest(request.request, request.arg1, request.arg2, request.obj); + } catch (final TaskBreak e) { + break LOOP; + } catch (final Exception e) { + if (callOnError(e)) + break LOOP; + } + break; + } + request.request = request.request_for_result = REQUEST_TASK_NON; + // プールへ返却する + mRequestPool.offer(request); + } catch (final InterruptedException e) { + break; + } + } + final boolean interrupted = Thread.interrupted(); + synchronized (mSync) { + mWorkerThread = null; + mIsRunning = false; + mFinished = true; + } + if (!interrupted) { + try { + onBeforeStop(); + onStop(); + } catch (final Exception e) { + callOnError(e); + } + } + try { + onRelease(); + } catch (final Exception e) { + // callOnError(e); + } + synchronized (mSync) { + mSync.notifyAll(); + } + } + + /** + * エラー処理。onErrorを呼び出す。 + * trueを返すと要求メッセージ処理ループを終了する + * @param e + * @return + */ + protected boolean callOnError(final Exception e) { + try { + return onError(e); + } catch (final Exception e2) { +// if (DEBUG) Log.e(TAG, "exception occurred in callOnError", e); + } + return true; + } + + /** + * RequestプールからRequestを取得する + * プールが空の場合は新規に生成する + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @param obj + * @return Request + */ + protected Request obtain(final int request, final int arg1, final int arg2, final Object obj) { + Request req = mRequestPool.poll(); + if (req != null) { + req.request = request; + req.arg1 = arg1; + req.arg2 = arg2; + req.obj = obj; + } else { + req = new Request(request, arg1, arg2, obj); + } + return req; + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final int arg2, final Object obj) { + return !mFinished && mRequestQueue.offer(obtain(request, arg1, arg2, obj)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final Object obj) { + return !mFinished && mRequestQueue.offer(obtain(request, arg1, 0, obj)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final int arg2) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, arg2, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @return true if success offer + */ + public boolean offer(final int request, final int arg1) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, 0, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @return true if success offer + */ + public boolean offer(final int request) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final Object obj) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, obj)); + } + + /** + * offer request to run on worker thread on top of the request queue + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + */ + public boolean offerFirst(final int request, final int arg1, final int arg2, final Object obj) { + return !mFinished && mIsRunning && mRequestQueue.offerFirst(obtain(request, arg1, arg2, obj)); + } + + /** + * offer request to run on worker thread and wait for result + * caller thread is blocked until the request finished running on worker thread + * FIXME このメソッドはMessageTaskを実行中のスレッド上で呼び出すとデッドロックする + * @param request + * @param arg1 + * @param arg2 + * @param obj + * @return + */ + public Object offerAndWait(final int request, final int arg1, final int arg2, final Object obj) { + if (!mFinished && (request > REQUEST_TASK_NON)) { + final Request req = obtain(REQUEST_TASK_RUN_AND_WAIT, arg1, arg2, obj); + synchronized (req) { + req.request_for_result = request; + req.result = null; + mRequestQueue.offer(req); + for (; mIsRunning && (req.request_for_result != REQUEST_TASK_NON); ) { + try { + req.wait(100); + } catch (final InterruptedException e) { + break; + } + } + } + return req.result; + } else { + return null; + } + } + + /** + * request to run on worker thread + * @param task + * @return true if success queue + */ + public boolean queueEvent(final Runnable task) { + return !mFinished && (task != null) && offer(REQUEST_TASK_RUN, task); + } + + public void removeRequest(final Request request) { + for (final Request req: mRequestQueue) { + if (!mIsRunning || mFinished) break; + if (req.equals(request)) { + mRequestQueue.remove(req); + mRequestPool.offer(req); + } + } + } + + public void removeRequest(final int request) { + for (final Request req: mRequestQueue) { + if (!mIsRunning || mFinished) break; + if (req.request == request) { + mRequestQueue.remove(req); + mRequestPool.offer(req); + } + } + } + + /** + * request terminate worker thread and release all related resources + */ + public void release() { + release(false); + } + + /** + * request terminate worker thread and release all related resources + * @param interrupt trueなら実行中のタスクをinterruptする + */ + public void release(final boolean interrupt) { + final boolean b = mIsRunning; + mIsRunning = false; + if (!mFinished) { + mRequestQueue.clear(); + mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null)); + synchronized (mSync) { + if (b) { + final long current = Thread.currentThread().getId(); + final long id = mWorkerThread != null ? mWorkerThread.getId() : current; + if (id != current) { + if (interrupt && (mWorkerThread != null)) { + mWorkerThread.interrupt(); + } + for ( ; !mFinished ; ) { + try { + mSync.wait(300); + } catch (final InterruptedException e) { + // ignore + } + } + } + } + } + } + } + + /** + * 実行中のタスクが終了後開放する + */ + public void releaseSelf() { + mIsRunning = false; + if (!mFinished) { + mRequestQueue.clear(); + mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null)); + } + } + + /** + * processRequest内でメッセージループを非常終了させるためのヘルパーメソッド + * 単にTaskBreakをthrowするだけ + * @throws TaskBreak + */ + public void userBreak() throws TaskBreak { + throw new TaskBreak(); + } +} 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/java/com/serenegiant/utils/Time.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/Time.java new file mode 100644 index 0000000000..6d7ecb3894 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/Time.java @@ -0,0 +1,40 @@ +package com.serenegiant.utils; + +import android.annotation.SuppressLint; +import android.os.SystemClock; + +public class Time { + + public static boolean prohibitElapsedRealtimeNanos = true; + + private static Time sTime; + static { + reset(); + } + + public static long nanoTime() { + return sTime.timeNs(); + } + + public static void reset() { + if (!prohibitElapsedRealtimeNanos && BuildCheck.isJellyBeanMr1()) { + sTime = new TimeJellyBeanMr1(); + } else { + sTime = new Time(); + } + } + + private Time() { + } + + @SuppressLint("NewApi") + private static class TimeJellyBeanMr1 extends Time { + public long timeNs() { + return SystemClock.elapsedRealtimeNanos(); + } + } + + protected long timeNs() { + return System.nanoTime(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/JNIUtil.java b/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/JNIUtil.java new file mode 100644 index 0000000000..8e5c48e658 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/JNIUtil.java @@ -0,0 +1,61 @@ +package org.easydarwin.sw; + +/** + */ +public class JNIUtil { + + static { + System.loadLibrary("Utils"); + } + + /** + * 都是Y:U:V = 4:1:1但 U与 V顺序相反。变换可逆 + * + * @param buffer + * @param width + * @param height + */ + public static void yV12ToYUV420P(byte[] buffer, int width, int height) { + callMethod("YV12ToYUV420P", null, buffer, width, height); + } + + /** + * 都是Y:U+V = 4:2,但是这两者U、V方向相反。变换可逆 + * + * @param buffer + * @param width + * @param height + */ + public static void nV21To420SP(byte[] buffer, int width, int height) { + callMethod("NV21To420SP", null, buffer, width, height); + } + + /** + * 旋转1个字节为单位的矩阵 + * + * @param data 要旋转的矩阵 + * @param offset 偏移量 + * @param width 宽度 + * @param height 高度 + * @param degree 旋转度数 + */ + public static void rotateMatrix(byte[] data, int offset, int width, int height, int degree) { + callMethod("RotateByteMatrix", null, data, offset, width, height, degree); + } + + /** + * 旋转2个字节为单位的矩阵 + * + * @param data 要旋转的矩阵 + * @param offset 偏移量 + * @param width 宽度 + * @param height 高度 + * @param degree 旋转度数 + */ + public static void rotateShortMatrix(byte[] data, int offset, int width, int height, int degree) { + callMethod("RotateShortMatrix", null, data, offset, width, height, degree); + } + + private static native void callMethod(String methodName, Object[] returnValue, Object... params); + +} diff --git a/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/TxtOverlay.java b/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/TxtOverlay.java new file mode 100644 index 0000000000..60f9e9de96 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/org/easydarwin/sw/TxtOverlay.java @@ -0,0 +1,93 @@ +package org.easydarwin.sw; + +import android.content.Context; +import android.content.res.AssetManager; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by jiangdg on 2020/1/11. + */ + +public class TxtOverlay { + + static { + System.loadLibrary("TxtOverlay"); + } + + private static TxtOverlay instance; + private final Context context; + private long ctx; + + private TxtOverlay(Context context){ + this.context = context; + } + + public static TxtOverlay getInstance() { + if(instance == null) { + throw new IllegalArgumentException("please call install in your application!"); + } + return instance; + } + + public static void install(Context context) { + if(instance == null) { + instance = new TxtOverlay(context.getApplicationContext()); + + File youyuan = context.getFileStreamPath("SIMYOU.ttf"); + if (!youyuan.exists()){ + AssetManager am = context.getAssets(); + try { + InputStream is = am.open("zk/SIMYOU.ttf"); + FileOutputStream os = context.openFileOutput("SIMYOU.ttf", Context.MODE_PRIVATE); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + os.close(); + is.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void init(int width, int height) { + File youyuan = context.getFileStreamPath("SIMYOU.ttf"); + if (!youyuan.exists()){ + throw new IllegalArgumentException("the font file must be exists,please call install before!"); + } + ctx = txtOverlayInit(width, height,youyuan.getAbsolutePath()); + } + + public void overlay(byte[] data, + String txt) { +// txt = "drawtext=fontfile="+context.getFileStreamPath("SIMYOU.ttf")+": text='EasyPusher 2017':x=(w-text_w)/2:y=H-60 :fontcolor=white :box=1:boxcolor=0x00000000@0.3"; +// txt = "movie=/sdcard/qrcode.png [logo];[in][logo] " +// + "overlay=" + 0 + ":" + 0 +// + " [out]"; +// if (ctx == 0) throw new RuntimeException("init should be called at first!"); + if (ctx == 0) return; + txtOverlay(ctx, data, txt); + } + + public void release() { + if (ctx == 0) return; + txtOverlayRelease(ctx); + ctx = 0; + } + + + private static native long txtOverlayInit(int width, int height, String fonts); + + private static native void txtOverlay(long ctx, byte[] data, String txt); + + private static native void txtOverlayRelease(long ctx); + +} diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libTxtOverlay.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libTxtOverlay.so new file mode 100644 index 0000000000..f466716ce6 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libTxtOverlay.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so new file mode 100644 index 0000000000..6b4d9fdfc4 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUtils.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUtils.so new file mode 100644 index 0000000000..f0c3822a1d Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libUtils.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so new file mode 100644 index 0000000000..7965444d8e Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so new file mode 100644 index 0000000000..c27d595219 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so new file mode 100644 index 0000000000..1f2486a5f6 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/arm64-v8a/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libTxtOverlay.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libTxtOverlay.so new file mode 100644 index 0000000000..a1139c47b8 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libTxtOverlay.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so new file mode 100644 index 0000000000..b6bf9769a5 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUtils.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUtils.so new file mode 100644 index 0000000000..681a9385d3 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libUtils.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so new file mode 100644 index 0000000000..97408c6d46 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so new file mode 100644 index 0000000000..4f2603950e Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so new file mode 100644 index 0000000000..ba68f84d05 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so new file mode 100644 index 0000000000..df770e4e3a Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so new file mode 100644 index 0000000000..d4b67c637c Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so new file mode 100644 index 0000000000..59d8f256cb Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so new file mode 100644 index 0000000000..fdf4318185 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so new file mode 100644 index 0000000000..4c1ea8ab15 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libUVCCamera.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so new file mode 100644 index 0000000000..ee0ff7afe8 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so new file mode 100644 index 0000000000..0c7cf3a9ae Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libusb100.so differ diff --git a/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so new file mode 100644 index 0000000000..1c4eb3ecb8 Binary files /dev/null and b/libraries/map-usbcamera/src/main/jniLibs/x86_64/libuvc.so differ diff --git a/libraries/map-usbcamera/src/main/res/layout/dialog_camera.xml b/libraries/map-usbcamera/src/main/res/layout/dialog_camera.xml new file mode 100644 index 0000000000..3d2564346e --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/layout/dialog_camera.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/res/layout/listitem_device.xml b/libraries/map-usbcamera/src/main/res/layout/listitem_device.xml new file mode 100644 index 0000000000..903baff653 --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/layout/listitem_device.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/res/raw/camera_click.ogg b/libraries/map-usbcamera/src/main/res/raw/camera_click.ogg new file mode 100644 index 0000000000..52512ac0a0 Binary files /dev/null and b/libraries/map-usbcamera/src/main/res/raw/camera_click.ogg differ diff --git a/libraries/map-usbcamera/src/main/res/values/dimens.xml b/libraries/map-usbcamera/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..997fea8543 --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/values/dimens.xml @@ -0,0 +1,30 @@ + + + + 16dp + 16dp + 48dp + 18sp + 32dp + diff --git a/libraries/map-usbcamera/src/main/res/values/strings.xml b/libraries/map-usbcamera/src/main/res/values/strings.xml new file mode 100644 index 0000000000..c7a70e8a0b --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + libusbcamera + 请选择USB摄像头 + 刷新 + Camera + 未搜索到可用USB Camera设备 + diff --git a/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml b/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml new file mode 100644 index 0000000000..9d7e54bfd7 --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/values/strings_permissions.xml @@ -0,0 +1,32 @@ + + + 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/libraries/map-usbcamera/src/main/res/xml/device_filter.xml b/libraries/map-usbcamera/src/main/res/xml/device_filter.xml new file mode 100644 index 0000000000..3c195781a0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/res/xml/device_filter.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/libraries/mogo-adas/.gitignore b/libraries/mogo-adas/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/libraries/mogo-adas/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libraries/mogo-adas/README.md b/libraries/mogo-adas/README.md new file mode 100644 index 0000000000..d8f81fc2da --- /dev/null +++ b/libraries/mogo-adas/README.md @@ -0,0 +1,14 @@ +#### 说明 +# ADAS LIB +## 与工控机交互LIB +RSA密钥 +privateBase64=MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA127FFvuPolaxYTBg5wkyDIrEbcNKXoIjf0bK7QUQ991Lsbv5Ktv/XM2F6qJFssVF1KTkStVBSQGxJB1eZLwJEwIDAQABAkBM39AgpV/Z1Amz3qmVh+h+JT521ItDMjksf7yF25r0dzGjOfGLQmMAqQXT68B+cQX5HSoFMwk/rE1hnXgifyNBAiEA6uNE2TfPj51mPOZztGy1Q8p4exWohfotiy64g/CyVeMCIQDqy9e7bGeZEi6p1zemEgFVtwgZZvLn/BOP4UO7NjJnEQIhAJBnprUwha/SYb+BIpNC3fHOcWGigBfWJdfSomejO9BnAiEApfojLqKbOWHZCsbQ19yyhN02JH7aB5PyYCtlrdnKF4ECIHR1P2LOQLddXO8PwgoF6gtCTO2sxIqdvu8fUruWyouQ +publicBase64=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANduxRb7j6JWsWEwYOcJMgyKxG3DSl6CI39Gyu0FEPfdS7G7+Srb/1zNheqiRbLFRdSk5ErVQUkBsSQdXmS8CRMCAwEAAQ== + +工控机SSH +账号:titan +密码:mogo@ZHIDAO10 + +加密后的密码:Lz71dnr3eEbcXbuRqSBuLPTk9N7vfqL1lkV1FvAzrLE45OslTvAmm0ekjXIuvOz3jw4hh/saV5pxc3ywNubnjQ== + + diff --git a/libraries/mogo-adas/build.gradle b/libraries/mogo-adas/build.gradle new file mode 100644 index 0000000000..1ad426e62a --- /dev/null +++ b/libraries/mogo-adas/build.gradle @@ -0,0 +1,114 @@ +plugins { + id 'com.android.library' + id 'com.google.protobuf' +// id 'maven' +} +ext { + //自动驾驶产品版本号 + AP_VERSION = "2.2.1" +} +android { + compileSdkVersion rootProject.ext.android.compileSdkVersion + // buildToolsVersion rootProject.ext.android.buildToolsVersion + + + defaultConfig { + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode Integer.valueOf(VERSION_CODE) + def name = getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION") + versionName name +// buildConfigField "String", "VERSION_NAME", "\"${name}\"" + buildConfigField "String", "AP_VERSION", "\"${AP_VERSION}\"" + + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main { + java { + srcDir 'src/main/java' + } + + proto { + srcDir 'src/main/proto' + include '**/*.proto' + } + } + } + + protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.6.1' + } + + generateProtoTasks { + all().each { task -> + task.builtins { + remove java + } + task.builtins { + java {} + } + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation rootProject.ext.dependencies.mogochainbase + //okhttp3的依赖 + implementation 'com.squareup.okhttp3:okhttp:3.12.3' + // parser + implementation rootProject.ext.dependencies.gson + // logger + //implementation 'com.orhanobut:logger:2.2.0' + //ProtoBuf + implementation 'com.google.protobuf:protobuf-java:3.6.1' + implementation 'com.google.protobuf:protoc:3.6.1' + implementation 'com.google.protobuf:protobuf-java-util:3.6.1' + implementation 'com.jcraft:jsch:0.1.55' +// api project(path: ':lib_recorder') + api "com.zhidao.support.recorder:recorder:1.0.0.3" + + if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { + implementation rootProject.ext.dependencies.mogo_core_data + } else { + implementation project(':core:mogo-core-data') + } +} + +//task androidSourcesJar(type: Jar) { +// classifier = 'sources' +// from android.sourceSets.main.java.srcDirs +//} +////配置需要上传到maven仓库的文件 +//artifacts { +// archives androidSourcesJar +//} +// +//uploadArchives { +// repositories.mavenDeployer { +// repository(url: RELEASE_REPO_URL) { +// authentication(userName: NAME, password: PASSWORD) +// } +// snapshotRepository(url: SNAPSHOT_REPOSITORY_URL) { +// authentication(userName: NAME, password: PASSWORD) +// } +// pom.groupId = GROUP +// pom.artifactId = POM_ARTIFACT_ID +// pom.version = VERSION +// } +//} \ No newline at end of file diff --git a/libraries/mogo-adas/consumer-rules.pro b/libraries/mogo-adas/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/mogo-adas/gradle.properties b/libraries/mogo-adas/gradle.properties new file mode 100644 index 0000000000..0d60ce4aee --- /dev/null +++ b/libraries/mogo-adas/gradle.properties @@ -0,0 +1,3 @@ +GROUP=com.mogo.adas +POM_ARTIFACT_ID=mogo-adas +VERSION_CODE=1 \ No newline at end of file diff --git a/libraries/mogo-adas/proguard-rules.pro b/libraries/mogo-adas/proguard-rules.pro new file mode 100644 index 0000000000..f10712073b --- /dev/null +++ b/libraries/mogo-adas/proguard-rules.pro @@ -0,0 +1,26 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +#-----MogoMap----- +-keep class com.mogo.map.MogoNavi{ + private (); +} diff --git a/libraries/mogo-adas/src/main/AndroidManifest.xml b/libraries/mogo-adas/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..d0a6ae62d1 --- /dev/null +++ b/libraries/mogo-adas/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasChannel.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasChannel.java new file mode 100644 index 0000000000..37d697f6e4 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasChannel.java @@ -0,0 +1,754 @@ +package com.zhidao.support.adas.high; + +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_UDP_CONNECT_ADDRESS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_ADAS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_LOG_CONNECT_STATUS; +import static com.zhidao.support.adas.high.common.ActionTypeReceive.ACTION_WS_AUTOPILOT_CONTROL; +import static com.zhidao.support.adas.high.udp.CupidUdpConstract.VIDEO_RENDER_IMAGE_UDP; + +import android.content.Context; +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.bean.AdasConfig; +import com.zhidao.support.adas.high.bean.AutopilotControl; +import com.zhidao.support.adas.high.bean.AutopilotSpeedParam; +import com.zhidao.support.adas.high.bean.BaseInfo; +import com.zhidao.support.adas.high.bean.BasicInfo; +import com.zhidao.support.adas.high.bean.DemoModeInfo; +import com.zhidao.support.adas.high.bean.SSHResult; +import com.zhidao.support.adas.high.bean.record.RecordCauseParam; +import com.zhidao.support.adas.high.bean.record.RecordDataParam; +import com.zhidao.support.adas.high.common.AppPreferenceHelper; +import com.zhidao.support.adas.high.common.Base64; +import com.zhidao.support.adas.high.common.Constants; +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.common.Define; +import com.zhidao.support.adas.high.common.IPCFixationIPHelper; +import com.zhidao.support.adas.high.common.MgContextUtils; +import com.zhidao.support.adas.high.common.RSATool; +import com.zhidao.support.adas.high.common.SSH; +import com.zhidao.support.adas.high.common.ThreadPoolManager; +import com.zhidao.support.adas.high.msg.MyMessageFactory; +import com.zhidao.support.adas.high.queue.UdpQueueManager; +import com.zhidao.support.adas.high.queue.WSByteQueueManager; +import com.zhidao.support.adas.high.queue.WebSocketQueueManager; +import com.zhidao.support.adas.high.socket.FpgaSocket; +import com.zhidao.support.adas.high.socket.read.OriginReadData; +import com.zhidao.support.adas.high.socket.read.SocketReader; +import com.zhidao.support.adas.high.udp.CupidUdpConstract; +import com.zhidao.support.adas.high.udp.IConnectRtpListener; +import com.zhidao.support.adas.high.udp.IGetH264Data; +import com.zhjt.service.chain.ChainLog; +import com.zhjt.service.chain.TracingConstants; + +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.util.HashSet; + +import mogo.webproto.WebsocketHeader; +import okio.ByteString; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high + * @ClassName: AdasChannel + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/7 13:32 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/7 13:32 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class AdasChannel implements IAdasNetCommApi, FpgaSocket.IWebSocketConnectListener, IConnectRtpListener, IGetH264Data, IPCFixationIPHelper.IIPCFixationIPListener { + private static final String TAG = AdasChannel.class.getSimpleName(); + + private FpgaSocket mSocket; + private SocketReader socketReader; + /** + * udp server + */ + private CupidUdpConstract udpConstract; + /** + * 与工控机链接状态 + */ + @Define.IPCConnectionStatus + private int ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.DISCONNECTED; + + + /** + * 是否使用队列处理数据 + */ + public static final boolean isUseQueue = false; + /** + * 录包信息 + */ + private RecordDataParam recordDataParam = null; + + /** + * 序列化rect + */ + private final Gson gson = new Gson(); + /** + * 消息工厂 + */ + private MyMessageFactory myMessageFactory; + private AutopilotSpeedParam speedParam; + /** + * 工控机连接配置 + */ + private final AdasOptions adasOptions; + private RecordCauseParam recordCauseParam; + private SSH ssh; + /** + * 已经链接成功的工控机IP 未连接未null + */ + private String ipcConnectedIP; + private OnMultiDeviceListener onMultiDeviceListener; + /** + * IPC固定IP查询工具类 + */ + private IPCFixationIPHelper ipcFixationIPHelper; + + public void setOnMultiDeviceListener(OnMultiDeviceListener onMultiDeviceListener) { + this.onMultiDeviceListener = onMultiDeviceListener; + } + + @Override + public void onStartFindIP() { + if (adasConnectStatusListener != null) { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.SEARCH_ADDRESS; + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, null); + } + } + + /** + * 可连通的IP + * + * @param ip 返回可用IP 当为null时表示无可用IP + */ + @Override + public void onAvailableIP(String ip) { + getOnConnectionAddress(ip); + } + + AdasChannel(Context context, AdasOptions options) { + initData(context); + //配置为null默认是乘客屏幕 + if (options == null) { + this.adasOptions = new AdasOptions.Builder().setClient(true).build(); + } else { + this.adasOptions = options; + } + if (!adasOptions.isClient()) { + initSocket(); + } + } + + private void initData(Context context) { + //原始数据解析类 + socketReader = new SocketReader(); + MgContextUtils.setContext(context.getApplicationContext()); + //消息工厂 + myMessageFactory = new MyMessageFactory(); + + } + + /** + * 初始化udp + */ + private void initUdpServer() { + if (isUseQueue) { + UdpQueueManager.getInstance().registerAdasChannel(this); + UdpQueueManager.getInstance().initDector(); + } + udpConstract = new CupidUdpConstract(VIDEO_RENDER_IMAGE_UDP); + udpConstract.setOnReceiverH264Data(this); + udpConstract.setOnConnectListener(this); + } + + /** + * 根据固定IP查询可用IP + */ + private void ipcFixationIPHelper(HashSet ips) { + if (ipcFixationIPHelper == null) + ipcFixationIPHelper = new IPCFixationIPHelper(this); + ipcFixationIPHelper.start(ips); + } + + private void initSocket() { + mSocket = new FpgaSocket(); + mSocket.setWebSocketListener(this); + if (isUseQueue) { + WebSocketQueueManager.getInstance().registerWebSocketListener(this); + WebSocketQueueManager.getInstance().initDector(); + WSByteQueueManager.getInstance().registerWebSocketListener(this); + WSByteQueueManager.getInstance().initDector(); + } + connect(); + } + + @ChainLog(linkCode = CHAIN_LINK_ADAS, + linkChainLog = CHAIN_LINK_LOG_CONNECT_STATUS, + endpoint = TracingConstants.Endpoint.PAD, + nodeAliasCode = CHAIN_ALIAS_CODE_UDP_CONNECT_ADDRESS, + paramIndexes = {0}, + clientPkFileName = "sn") + private void connectSocket(String address) { + if (mSocket != null) { + mSocket.connectWebSocket(address); + } + } + + /** + * 基础信息 + * + * @param basicInfo + */ + public void sendWsBasicInfo(BasicInfo basicInfo) { + if (basicInfo == null) { + return; + } + String msg = gson.toJson(basicInfo); + sendWsMessage(msg); + } + + /** + * 向工控机发送数据 + * + * @param info + */ + @Override + public void sendBaseInfo(BaseInfo info) { + if (info == null) { + return; + } + String msg = gson.toJson(info); + sendWsMessage(msg); + } + + private void sendIPCCmd(final String cmd) { + if (ssh == null) { +// String ip = ipcIp; + if (TextUtils.isEmpty(ipcConnectedIP)) { + //尝试从缓存中获取工控机IP +// String ip = AppPreferenceHelper.getInstance(MgContextUtils.getContext()).getUdpClientAddress(); +// if (TextUtils.isEmpty(ip)) { + if (mAdasListener != null) { + mAdasListener.onSSHResult(new SSHResult(SSHResult.RESULT_CODE.IP_UNKNOWN, cmd, "IPC IP未知")); + } + return; +// } + } + ThreadPoolManager.getsInstance().execute(new Runnable() { + @Override + public void run() { + ssh = new SSH(); + String encodePwd = "Lz71dnr3eEbcXbuRqSBuLPTk9N7vfqL1lkV1FvAzrLE45OslTvAmm0ekjXIuvOz3jw4hh/saV5pxc3ywNubnjQ=="; + String privateKey = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA127FFvuPolaxYTBg5wkyDIrEbcNKXoIjf0bK7QUQ991Lsbv5Ktv/XM2F6qJFssVF1KTkStVBSQGxJB1eZLwJEwIDAQABAkBM39AgpV/Z1Amz3qmVh+h+JT521ItDMjksf7yF25r0dzGjOfGLQmMAqQXT68B+cQX5HSoFMwk/rE1hnXgifyNBAiEA6uNE2TfPj51mPOZztGy1Q8p4exWohfotiy64g/CyVeMCIQDqy9e7bGeZEi6p1zemEgFVtwgZZvLn/BOP4UO7NjJnEQIhAJBnprUwha/SYb+BIpNC3fHOcWGigBfWJdfSomejO9BnAiEApfojLqKbOWHZCsbQ19yyhN02JH7aB5PyYCtlrdnKF4ECIHR1P2LOQLddXO8PwgoF6gtCTO2sxIqdvu8fUruWyouQ"; + try { + String pwd = RSATool.decodeByPrivateKey(encodePwd, Base64.decodeBase64(privateKey)); + ssh.connect(ipcConnectedIP, 22, "titan", pwd); + SSHResult result = ssh.exec(cmd, pwd); + if (mAdasListener != null) + mAdasListener.onSSHResult(result); + } catch (Exception e) { + if (mAdasListener != null) + mAdasListener.onSSHResult(new SSHResult(SSHResult.RESULT_CODE.ERROR, cmd, e.getMessage())); + e.printStackTrace(); + } + ssh.disConnect(); + ssh = null; + } + }); + } + } + + @Override + public void shutdownIPC() { + sendIPCCmd("sudo -S shutdown -h now"); + } + + @Override + public void rebootIPC() { + sendIPCCmd("sudo -S reboot"); + } + + @Override + public void rebootAPDocker() { + sendIPCCmd("docker restart autocar_default_1"); + } + + + @Override + public void enableDemoMode() { + sendBaseInfo(DemoModeInfo.enable()); + } + + @Override + public void disableDemoMode() { + sendBaseInfo(DemoModeInfo.disable()); + } + + @Override + public String getIPCConnectedIP() { + return ipcConnectedIP; + } + + + /** + * 发送ws消息 + * + * @param msg 消息 + */ + public boolean sendWsMessage(String msg) { + return mSocket != null && mSocket.sendDataWebSocket(msg); + } + + + /** + * 初始化webSocket + */ + public void setUdpIsFirstInit() { + if (udpConstract != null) + udpConstract.setUdpIsFirstInit(); + } + + /** + * 处理webSocket text数据 + * + * @param text + */ + private void handlerWSMsg(String text) { + try { + JSONObject jsonObjectWs = new JSONObject(text); + String action = jsonObjectWs.optString("action"); + if (TextUtils.isEmpty(action)) { + CupidLogUtils.w("--->action is null"); + return; + } + myMessageFactory.createMessage(action).handlerMsg(gson, mAdasListener, text); + } catch (Exception e) { + e.printStackTrace(); + } + // TODO 临时接续Json数据传递添加的Header 0x00表示PB数据 0x01表示Json数据 + if (!adasOptions.isClient() && onMultiDeviceListener != null) { + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + byte[] temp = new byte[bytes.length + 1]; + System.arraycopy(bytes, 0, temp, 1, bytes.length); + temp[0] = 0x01; + onMultiDeviceListener.onForwardingIPCMessage(temp); + } + + } + + /** + * 解析工控机发送过来PB的数据 + * + * @param bytes 数据 + */ + @Override + public void parseIPCData(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return; + } + //TODO 临时将Json数据转发并解析 0x00表示PB数据 0x01表示Json数据 + if (adasOptions.isClient()) { + byte header = bytes[0]; + byte[] temp = new byte[bytes.length - 1]; + System.arraycopy(bytes, 1, temp, 0, temp.length); + bytes = temp; + if (header != 0x00) { + //Json 解析 + handlerWSMsg(new String(bytes, StandardCharsets.UTF_8)); + return; + } + } + //PB解析 + ByteString byteString = ByteString.of(bytes); + try { + if (socketReader != null) { + OriginReadData read = socketReader.read(byteString); + if (read == null) { + //read 解析为空 默认解析view + CupidLogUtils.w("--->websocket byte read null, analysis view"); + myMessageFactory.createMessage(0x101).handlerMsg(gson, mAdasListener, bytes); + return; + } + WebsocketHeader.Header_websock header = WebsocketHeader.Header_websock.parseFrom(read.getHeader()); + int headerType = header.getMsgType(); + CupidLogUtils.w("--->websocket byte read header = " + headerType); + myMessageFactory.createMessage(headerType).handlerMsg(gson, mAdasListener, read.getPayload()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public AdasOptions getAdasOptions() { + return adasOptions; + } + + /** + * 连接工控机 + */ + @Override + public void connect() { + //当是司机屏才启用 + if (!adasOptions.isClient()) { + if (ipcConnectionStatus == Constants.IPC_CONNECTION_STATUS.DISCONNECTED) { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.SEARCH_ADDRESS; + if (adasConnectStatusListener != null) { + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, null); + } + switch (adasOptions.getIpcConnectionMode()) { + case AdasOptions.IPC_CONNECTION_MODE.FIXATION: + if (mSocket != null) { + mSocket.setIsReconnect(true); + } + ipcFixationIPHelper(adasOptions.getIpcFixationIP()); + break; + case AdasOptions.IPC_CONNECTION_MODE.ASSIGN: + if (mSocket != null) { + mSocket.setIsReconnect(true); + } + getOnConnectionAddress(adasOptions.getIpcAssignIP()); + break; + case AdasOptions.IPC_CONNECTION_MODE.UDP: + if (mSocket != null) { + mSocket.setIsReconnect(false); + } + initUdpServer(); + break; + } + } + } + + } + + /** + * 断开与工控机的链接 + */ + @Override + public void disconnect() { + if (!adasOptions.isClient()) { +// ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.DISCONNECTED; +// if (adasConnectStatusListener != null) { +// adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, null); +// } + if (udpConstract != null) + udpConstract.release(); + if (mSocket != null) + mSocket.closeWebSocket(); + if (isUseQueue) { + UdpQueueManager.getInstance().release(); + } + } + } + + private void handlerWSMsg(ByteString bytes) { + byte[] bytes1 = bytes.toByteArray(); + parseIPCData(bytes1); + if (!adasOptions.isClient() && onMultiDeviceListener != null) { + // TODO 临时接续Json数据传递添加的Header 0x00表示PB数据 0x01表示Json数据 + byte[] temp = new byte[bytes1.length + 1]; + System.arraycopy(bytes1, 0, temp, 1, bytes1.length); + temp[0] = 0x00; + onMultiDeviceListener.onForwardingIPCMessage(temp); + } + } + + + @Override + public void onConnecting() { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.CONNECTING; + if (adasConnectStatusListener != null) { + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, null); + } + } + + @Override + public void onWebSocketConnectSuccess() { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.CONNECTED; + if (adasConnectStatusListener != null) { + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, null); + queryCarConfig(); + } + } + + @Override + public void onWebSocketConnectFailed(String t) { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.DISCONNECTED; + setUdpIsFirstInit(); + if (adasConnectStatusListener != null) { + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, t); + } + } + + @Override + public void onMessage(String message) { + handlerWSMsg(message); + } + + @Override + public void onMessage(ByteString bytes) throws InvalidProtocolBufferException { + handlerWSMsg(bytes); + } + + @Override + public void onConnectionSuccessRtp() { + CupidLogUtils.e(TAG, "--->onConnectionSuccessRtp"); + } + + @Override + public void onConnectionFailedRtp(String reason) { + CupidLogUtils.e(TAG, "--->onConnectionFailedRtp:" + reason); + // TODO 暂时注释 测试完成添加 +// closeSocket(); + setUdpIsFirstInit(); + /*if (mRenderImageListener != null) { + mRenderImageListener.onConnectionFailedRtp(reason); + }*/ + } + + @Override + public void getOnConnectionAddress(String address) { + CupidLogUtils.e(TAG, "要连接的工控机IP=" + address); + if (TextUtils.isEmpty(address)) { + if (mSocket != null) { + mSocket.setIsReconnect(false); + } + if (adasConnectStatusListener != null) { + ipcConnectionStatus = Constants.IPC_CONNECTION_STATUS.NOT_FOUND_ADDRESS; + adasConnectStatusListener.onConnectionIPCStatus(ipcConnectionStatus, "找不到可用的IP"); + } + return; + } + ipcConnectedIP = address; + AppPreferenceHelper.getInstance(MgContextUtils.getContext()).saveUdpClientAddress(address); + connectSocket(address); + } + + @Override + public void onDisconnectRtp() { + CupidLogUtils.e(TAG, "--->onDisconnectRtp"); + setUdpIsFirstInit(); + } + + @Override + public void getH264Data(byte[] data, long receiverDataTimeUdp) { + //处理数据 + String content = new String(data).trim(); + CupidLogUtils.e("--->udp数据回调:" + content); + if (isUseQueue) { + UdpQueueManager.getInstance().addQueueData(content); + } else { + udpDataManage(content); + } + } + + /** + * 处理udp数据 + * + * @param content + */ + public void udpDataManage(String content) { + if (!TextUtils.isEmpty(content)) { + try { + JSONObject jsonObject = new JSONObject(content); + String udpRenderAction = jsonObject.getString("action"); + if (!TextUtils.isEmpty(udpRenderAction)) { + //udp消息处理 + myMessageFactory.createMessage(udpRenderAction).handlerMsg(gson, mAdasListener, content); + } + } catch (Exception e) { + CupidLogUtils.e("--->json数据解析异常-换用protoBuf:" + e.toString()); + myMessageFactory.createMessage("view").handlerMsg(gson, mAdasListener, content); + e.printStackTrace(); + } + } + } + + private OnAdasListener mAdasListener; + //连接状态listener + private OnAdasConnectStatusListener adasConnectStatusListener; + + void setOnAdasListener(OnAdasListener adasListener) { + mAdasListener = adasListener; + } + + void setOnAdasMsgConnectStatusListener(OnAdasConnectStatusListener adasMsgConnectStatusListener) { + adasConnectStatusListener = adasMsgConnectStatusListener; + } + + @Override + public boolean controlAutopilotCarAuto() { + AutopilotControl autopilotControl = new AutopilotControl(); + autopilotControl.setAction(ACTION_WS_AUTOPILOT_CONTROL.getmActionType()); + AutopilotControl.ValuesBean autopilotControlValues = new AutopilotControl.ValuesBean(); + autopilotControlValues.setMode(1); + autopilotControl.setValues(autopilotControlValues); + return sendWsMessage(gson.toJson(autopilotControl)); + } + + @Override + public boolean controlAutopilotCarHead() { + AutopilotControl autopilotControl = new AutopilotControl(); + autopilotControl.setAction(ACTION_WS_AUTOPILOT_CONTROL.getmActionType()); + AutopilotControl.ValuesBean autopilotControlValues = new AutopilotControl.ValuesBean(); + autopilotControlValues.setMode(0); + autopilotControl.setValues(autopilotControlValues); + return sendWsMessage(gson.toJson(autopilotControl)); + } + + @Override + public int getIpcConnectionStatus() { + return ipcConnectionStatus; + } + + @Override + public AdasConfig getAdasConfig() { + String address = AppPreferenceHelper.getInstance(MgContextUtils.getContext()).getUdpClientAddress(); + String dockConfig = AppPreferenceHelper.getInstance(MgContextUtils.getContext()).getDockConfig(); + if (TextUtils.isEmpty(dockConfig)) { + queryCarConfig(); + } + AdasConfig adasConfig = new AdasConfig(); + adasConfig.setAddress(address); + adasConfig.setDockVersion(dockConfig); + return adasConfig; + } + + /** + * 查询config信息 + */ + private void queryCarConfig() { + sendWsMessage(Constants.QUERY_CAR_CONFIG); + } + + @Override + public boolean aiCloudToAdasData(String msg) { + //位置信息 action是aiCloudToStartAutopilot + if (!TextUtils.isEmpty(msg)) { + return sendWsMessage(msg); + } + return false; + } + + @Override + public boolean queryAutopilotRoute() { + //查询自动驾驶路径 + return sendWsMessage(Constants.QUERY_GLOBAL_PATH); + } + + @Override + public boolean queryAutopilotGuardian() { + //查询节点信息 + return sendWsMessage(Constants.QUERY_GUARDIAN); + } + + @Override + public boolean recordPackage(int type, int id) { + return recordBag(true, type, id, 0); + } + + @Override + public boolean recordPackage(int type, int id, int duration) { + return recordBag(true, type, id, duration); + } + + @Override + public boolean stopRecord(int type, int id) { + return recordBag(false, type, id, 0); + } + + @Override + public boolean recordCause(String key, String name, String id, String reason) { + if (recordCauseParam == null) { + recordCauseParam = new RecordCauseParam(); + } + RecordCauseParam.Result result = new RecordCauseParam.Result(); + result.setKey(key); + result.setFilename(name); + result.setReason(reason); + result.setId(id); + recordCauseParam.setResult(result); + return sendWsMessage(gson.toJson(recordCauseParam)); + } + + @Override + public boolean setSpeed(int speed) { + if (speedParam == null) { + speedParam = new AutopilotSpeedParam(); + } + speedParam.setSpeed(speed); + String speedJson = gson.toJson(speedParam); + return sendWsMessage(speedJson); + } +// TODO 需求暂停 待讨论 +// @Override +// public boolean getRoutes() { +// return sendWsMessage(Constants.QUERY_ROUTES); +// } + + /** + * 主动录制Bag包 + * + * @param isRecord + * @param type + * @param id + * @param duration + * @return + */ + private boolean recordBag(boolean isRecord, int type, int id, int duration) { + if (recordDataParam == null) { + recordDataParam = new RecordDataParam(); + } + RecordDataParam.Result result = new RecordDataParam.Result(); + result.setIsRecord(isRecord); + result.setType(type); + result.setId(id); + if (isRecord == true) { + if (duration > 0) { + result.setSustain(false); + result.setDuration(duration); + } else { + result.setSustain(true); + } + } + recordDataParam.setResult(result); + String param = gson.toJson(recordDataParam); + return sendWsMessage(param); + + } + + /** + * 接管原因 + */ + + + /** + * log是否显示 + * + * @param isEnableLog + */ + public void setEnableLog(boolean isEnableLog) { + CupidLogUtils.setEnableLog(isEnableLog); + } + + /** + * log是否写入本地 + * + * @param isWriteLog + */ + public void setIsWriteLog(boolean isWriteLog) { + CupidLogUtils.setIsWriteLog(isWriteLog); + } + + +} + diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasManager.java new file mode 100644 index 0000000000..3b1d7604ad --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasManager.java @@ -0,0 +1,345 @@ +package com.zhidao.support.adas.high; + +import android.content.Context; + +import com.zhidao.support.adas.high.bean.AdasConfig; +import com.zhidao.support.adas.high.bean.BaseInfo; +import com.zhidao.support.adas.high.bean.BasicInfo; +import com.zhidao.support.adas.high.common.AppPreferenceHelper; +import com.zhidao.support.adas.high.common.Constants; + +import java.util.HashSet; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high + * @ClassName: AdasManager + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/7 13:13 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/7 13:13 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class AdasManager implements IAdasNetCommApi { + private static volatile AdasManager ourInstance; + + + public static AdasManager getInstance() { + if (ourInstance == null) { + synchronized (AdasManager.class) { + if (ourInstance == null) { + ourInstance = new AdasManager(); + } + } + } + return ourInstance; + } + + private AdasChannel mChannel; + + private AdasManager() { + + } + + public void setOnMultiDeviceListener(OnMultiDeviceListener l) { + if (mChannel != null) { + mChannel.setOnMultiDeviceListener(l); + } + } + + public void setOnAdasListener(OnAdasListener l) { + if (mChannel != null) { + mChannel.setOnAdasListener(l); + } + } + + /** + * 获取 adas 连接状态 + * + * @param onAdasConnectStatusListener + */ + public void setOnAdasConnectStatusListener(OnAdasConnectStatusListener onAdasConnectStatusListener) { + if (mChannel != null) { + mChannel.setOnAdasMsgConnectStatusListener(onAdasConnectStatusListener); + } + } + + public synchronized void create(Context context, AdasOptions options) { + if (mChannel == null) { + mChannel = new AdasChannel(context, options); + } + } + + + /** + * 发送信息 + * + * @param msg + */ + public void sendMessage(String msg) { + if (mChannel != null) { + mChannel.sendWsMessage(msg); + } + } + + /** + * 发送基础信息 + * + * @param basicInfo + */ + public void setBasicInfo(BasicInfo basicInfo) { + if (mChannel != null) { + mChannel.sendWsBasicInfo(basicInfo); + } + } + + + + public synchronized void destory() { + if (mChannel != null) { + mChannel.onDisconnectRtp(); + mChannel.disconnect(); + mChannel = null; + } + } + + + @Override + public void connect() { + if (mChannel != null) { + mChannel.connect(); + } + } + + @Override + public void disconnect() { + if (mChannel != null) { + mChannel.disconnect(); + } + } + + @Override + public boolean controlAutopilotCarAuto() { + if (mChannel != null) { + return mChannel.controlAutopilotCarAuto(); + } + return false; + } + + @Override + public boolean controlAutopilotCarHead() { + if (mChannel != null) { + return mChannel.controlAutopilotCarHead(); + } + return false; + } + + @Override + public int getIpcConnectionStatus() { + if (mChannel != null) { + return mChannel.getIpcConnectionStatus(); + } + return Constants.IPC_CONNECTION_STATUS.DISCONNECTED; + } + + @Override + public AdasConfig getAdasConfig() { + if (mChannel != null) { + return mChannel.getAdasConfig(); + } + return new AdasConfig(); + } + + @Override + public boolean aiCloudToAdasData(String msg) { + if (mChannel != null) { + return mChannel.aiCloudToAdasData(msg); + } + return false; + } + + @Override + public boolean queryAutopilotRoute() { + if (mChannel != null) { + return mChannel.queryAutopilotRoute(); + } + return false; + } + + @Override + public boolean queryAutopilotGuardian() { + if (mChannel != null) { + return mChannel.queryAutopilotGuardian(); + } + return false; + } + + @Override + public boolean recordPackage(int type, int id) { + if (mChannel != null) { + return mChannel.recordPackage(type, id); + } + return false; + } + + @Override + public boolean recordPackage(int type, int id, int duration) { + if (mChannel != null) { + return mChannel.recordPackage(type, id, duration); + } + return false; + } + + @Override + public boolean stopRecord(int type, int id) { + if (mChannel != null) { + return mChannel.stopRecord(type, id); + } + return false; + } + + @Override + public boolean recordCause(String key, String name, String id, String reason) { + return mChannel.recordCause(key, name, id, reason); + } + + @Override + public boolean setSpeed(int speed) { + if (mChannel != null) { + return mChannel.setSpeed(speed); + } + return false; + } +// TODO 需求暂停 待讨论 获取车辆轨迹文件 +// @Override +// public boolean getRoutes() { +// if (mChannel!=null){ +// return mChannel.getRoutes(); +// } +// return false; +// } + + @Override + public void setEnableLog(boolean isEnableLog) { + if (mChannel != null) { + mChannel.setEnableLog(isEnableLog); + } + } + + @Override + public void setIsWriteLog(boolean isWriteLog) { + if (mChannel != null) { + mChannel.setIsWriteLog(isWriteLog); + } + } + + + /** + * 向工控机发送数据 + * + * @param info + */ + @Override + public void sendBaseInfo(BaseInfo info) { + if (mChannel != null) { + mChannel.sendBaseInfo(info); + } + } + + @Override + public void shutdownIPC() { + if (mChannel != null) { + mChannel.shutdownIPC(); + } + } + + @Override + public void rebootIPC() { + if (mChannel != null) { + mChannel.rebootIPC(); + } + } + + @Override + public void rebootAPDocker() { + if (mChannel != null) { + mChannel.rebootAPDocker(); + } + } + + @Override + public void parseIPCData(byte[] bytes) { + if (mChannel != null) { + mChannel.parseIPCData(bytes); + } + } + + @Override + public AdasOptions getAdasOptions() { + if (mChannel != null) { + return mChannel.getAdasOptions(); + } + return null; + } + + + @Override + public void enableDemoMode() { + if (mChannel != null) { + mChannel.enableDemoMode(); + } + } + + @Override + public void disableDemoMode() { + if (mChannel != null) { + mChannel.disableDemoMode(); + } + } + + @Override + public String getIPCConnectedIP() { + if (mChannel != null) { + return mChannel.getIPCConnectedIP(); + } + return null; + } + + /** + * 获取工控机固定IP列表 + * + * @return + */ + public HashSet getIPCFixationIPList(Context context) { + return AppPreferenceHelper.getInstance(context).getIPCFixationIPList(); + } + + /** + * 增加工控机固定IP + * + * @param ipcIP + */ + public void addIPCFixationIP(Context context, String ipcIP) { + AppPreferenceHelper.getInstance(context).addIPCFixationIP(ipcIP); + } + + /** + * 删除指定的工控机固定IP + * + * @param ipcIP + */ + public void delIPCFixationIP(Context context, String ipcIP) { + AppPreferenceHelper.getInstance(context).delIPCFixationIP(ipcIP); + } + + /** + * 删除所有工控机固定IP + */ + public void delIPCFixationIP(Context context) { + AppPreferenceHelper.getInstance(context).delIPCFixationIP(); + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasOptions.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasOptions.java new file mode 100644 index 0000000000..beca852d0a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/AdasOptions.java @@ -0,0 +1,163 @@ +package com.zhidao.support.adas.high; + +import java.util.HashSet; + +/** + * Date:2019/5/31。 + * Note: Adas相关配置。 + * 更换链接方式必须先断开与工控机的连接 + */ +public class AdasOptions { + /** + * 链接方式 + */ + public interface IPC_CONNECTION_MODE { + /** + * 固定IP 根据存储的IP进行轮询 + */ + int FIXATION = 0; + + /** + * 指定IP + */ + int ASSIGN = 1; + /** + * UDP方式 + */ + int UDP = 2; + } + + /** + * 是否是客户端 true:客户度(乘客屏) false:服务端(司机屏) + * 当是乘客端的情况下 ipcConnectionMode ipcAssignIP ipcFixationIPSet 均无效 + */ + private boolean isClient; + + /** + * 链接工控机方式 + * 如果是司机屏默认 固定IP方式 + */ + private int ipcConnectionMode = IPC_CONNECTION_MODE.FIXATION; + /** + * 指定工控机IP + */ + private String ipcAssignIP; + /** + * 工控机固定IP集合 通过Ping方式尝试 + */ + private HashSet ipcFixationIP; + + private AdasOptions() { + } + + /** + * 静态内部类 + */ + public static class Builder { + AdasOptions options; + + // 首先获得一个默认的配置 + public Builder() { + this(getDefaultOptions()); + } + + public Builder(AdasOptions defaultOptions) { + options = defaultOptions; + } + + /** + * 设置当前是客户端还是服务端 + * + * @param isClient true:客户度 false:服务端 + * @return + */ + public Builder setClient(boolean isClient) { + options.isClient = isClient; + return this; + } + + /** + * 设置IPC主机地址 + * + * @param ipcAssignIP + * @return + */ + public Builder setIpcAssignIP(String ipcAssignIP) { + options.ipcAssignIP = ipcAssignIP; + return this; + } + + /** + * 设置IPC 固定IP + * 通过ping方式逐一检查连通性 + * + * @param ipcFixationIP + * @return + */ + public Builder setIpcFixationIP(HashSet ipcFixationIP) { + options.ipcFixationIP = ipcFixationIP; + return this; + } + + /** + * 配置连接方式 + * + * @param ipcConnectionMode + * @return + */ + public Builder setIpcConnectionMode(int ipcConnectionMode) { + options.ipcConnectionMode = ipcConnectionMode; + return this; + } + + public AdasOptions build() { + return options; + } + } + + /** + * 获取默认的配置 + * 默认配置是乘客端 + * + * @return + */ + public static AdasOptions getDefaultOptions() { + AdasOptions options = new AdasOptions(); + options.isClient = true; + options.ipcAssignIP = null; + options.ipcFixationIP = null; + return options; + } + + public boolean isClient() { + return isClient; + } + + public String getIpcAssignIP() { + return ipcAssignIP; + } + + public int getIpcConnectionMode() { + return ipcConnectionMode; + } + + public HashSet getIpcFixationIP() { + return ipcFixationIP; + } + + public void setIpcAssignIP(String ipcAssignIP) { + this.ipcAssignIP = ipcAssignIP; + } + + public void setClient(boolean client) { + isClient = client; + } + + public void setIpcConnectionMode(int ipcConnectionMode) { + this.ipcConnectionMode = ipcConnectionMode; + } + + public void setIpcFixationIP(HashSet ipcFixationIP) { + this.ipcFixationIP = ipcFixationIP; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/IAdasNetCommApi.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/IAdasNetCommApi.java new file mode 100644 index 0000000000..201c6dfcd1 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/IAdasNetCommApi.java @@ -0,0 +1,157 @@ +package com.zhidao.support.adas.high; + +import com.zhidao.support.adas.high.bean.AdasConfig; +import com.zhidao.support.adas.high.bean.BaseInfo; + +/** + * @author nie yunlong + * @des adas 网络通信api + * @date 2020/7/14 + */ +public interface IAdasNetCommApi { + /** + * 链接工控机 + */ + void connect(); + + /** + * 与工控机断开连接 + */ + void disconnect(); + + /** + * 控制自动驾驶车辆 自动模式 自动驾驶 + */ + boolean controlAutopilotCarAuto(); + + /** + * 控制自动驾驶车辆 手动模式 + */ + boolean controlAutopilotCarHead(); + + /** + * 获取当前工控机的链接状态 + * + * @return + */ + int getIpcConnectionStatus(); + + /** + * 基础信息 + */ + AdasConfig getAdasConfig(); + + /** + * ai云 + * + * @param msg + */ + boolean aiCloudToAdasData(String msg); + + /** + * 查询自动驾驶路径 + */ + boolean queryAutopilotRoute(); + + /** + * 查询节点监控信息 + */ + boolean queryAutopilotGuardian(); + + /** + * 数据采集 + */ + boolean recordPackage(int type, int id); + + boolean recordPackage(int type, int id, int duration); + + boolean stopRecord(int type, int id); + + /** + * 采集类型 + * + * @param key 工控机返回的 + * @param name 文件名称 也是工控机返回 + * @param id 接管原因id + * @param reason 接管原因 + * @return + */ + boolean recordCause(String key, String name, String id, String reason); + + /** + * 设置车速 + * speed km/h + */ + boolean setSpeed(int speed); + + /** + * 查询轨迹文件 + * + */ +// TODO 需求暂停 待讨论 +// boolean getRoutes(); + + /** + * log是否显示 + */ + void setEnableLog(boolean isEnableLog); + + /** + * log是否写入 + */ + void setIsWriteLog(boolean isWriteLog); + + + /** + * 向工控机发送数据 + * + * @param info + */ + void sendBaseInfo(BaseInfo info); + + /** + * 向工控机发送关机命令 + */ + void shutdownIPC(); + + /** + * 向工控机发送重启命令 + */ + void rebootIPC(); + + /** + * 向工控机发送重启自动驾驶Docker命令 + */ + void rebootAPDocker(); + + /** + * 解析工控机发送过来的数据 + * + * @param bytes 数据 + */ + void parseIPCData(byte[] bytes); + + /** + * 获取工控机链接配置 + * + * @return + */ + AdasOptions getAdasOptions(); + + /** + * 打开演示模式 + */ + void enableDemoMode(); + + /** + * 关闭演示模式 + */ + void disableDemoMode(); + + /** + * 获取已经链接成功的工控机IP 未连接未null + * + * @return ip null:表示未连接 + */ + String getIPCConnectedIP(); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasConnectStatusListener.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasConnectStatusListener.java new file mode 100644 index 0000000000..1e0df218d0 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasConnectStatusListener.java @@ -0,0 +1,26 @@ +package com.zhidao.support.adas.high; + +import android.text.TextUtils; + +import com.zhidao.support.adas.high.common.Define; + +/** + * @author nie yunlong + * @des 消息通信连接状态 回掉 + * @date 2020/3/20 + */ +public interface OnAdasConnectStatusListener { + /** + * 指定IP或固定IP(轮询ping集合中的IP) 找不到可用的IP时会被调用 + * 规则: TextUtils.isEmpty() + */ +// void onAddressDisabled(); + + /** + * 与工控机链接状态变化 + * + * @param ipcConnectionStatus + */ + void onConnectionIPCStatus(@Define.IPCConnectionStatus int ipcConnectionStatus, String failedMsg); + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasListener.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasListener.java new file mode 100644 index 0000000000..37529065f5 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnAdasListener.java @@ -0,0 +1,149 @@ +package com.zhidao.support.adas.high; + +import com.zhidao.support.adas.high.bean.AutopilotRoute; +import com.zhidao.support.adas.high.bean.AutopilotStatus; +import com.zhidao.support.adas.high.bean.AutopilotWayArrive; +import com.zhidao.support.adas.high.bean.CarLaneInfo; +import com.zhidao.support.adas.high.bean.CarStateInfo; +import com.zhidao.support.adas.high.bean.SSHResult; +import com.zhidao.support.adas.high.bean.IPCUpgradeStateInfo; +import com.zhidao.support.adas.high.bean.LightStatueInfo; +import com.zhidao.support.adas.high.bean.ObstaclesInfo; +import com.zhidao.support.adas.high.bean.RectInfo; +import com.zhidao.support.adas.high.bean.TrajectoryInfo; +import com.zhidao.support.adas.high.bean.WarnMessageInfo; +import com.zhidao.support.adas.high.bean.guardian.AutopilotGuardianInfo; +import com.zhidao.support.adas.high.bean.record.AutopilotRecordResult; + +import java.util.List; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high + * @ClassName: OnAdasListener + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/9 20:28 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/9 20:28 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public interface OnAdasListener { + /** + * 报警信息 + * + * @param warnMessageInfo + */ + void onWarnMessage(WarnMessageInfo warnMessageInfo); + + /** + * 视频宽高 + * + * @param width + * @param height + */ + void onVideoSize(int width, int height); + + /** + * 车框 + * + * @param rectInfo + */ + void onRectData(RectInfo rectInfo); + + /** + * 车辆状态数据 + * + * @param carStateInfo + */ + void onCarStateData(CarStateInfo carStateInfo); + + /** + * 红绿灯状态 + * + * @param lightStatueInfo + */ + void onLightStateData(LightStatueInfo lightStatueInfo); + + /** + * 周边渲染 + * + * @param obstaclesInfo + */ + void onObstaclesInfo(ObstaclesInfo obstaclesInfo); + + /** + * 车道线渲染 + * + * @param carLaneInfo + */ + void onCarLaneInfo(CarLaneInfo carLaneInfo); + + /** + * 自动驾驶状态 + * + * @param autopilotStatus + */ + void autopilotStatus(AutopilotStatus autopilotStatus); + + /** + * 自动驾驶到站 + * + * @param autopilotWayArrive + */ + void autopilotArrive(AutopilotWayArrive autopilotWayArrive); + /** + * 接收到FPGA端发来的数据 + * + * @param msg json格式 + */ + //void onMessage(String msg); + + /** + * 自动驾驶路径 + * + * @param route + */ + void onAutopilotRoute(AutopilotRoute route); + + /** + * 自动驾驶局部轨迹 + * + * @param trajectoryList + */ + void onAutopilotTrajectory(List trajectoryList); + + /** + * 工控机获取SN + * + * @param + */ + void onAutopilotSNRequest(); + + /** + * 工控机监控节点 + */ + void onAutopilotGuardian(AutopilotGuardianInfo guardianInfo); + + /** + * 数据采集结果 + */ + void onAutopilotRecord(AutopilotRecordResult result); + + /** + * 向IPC发送命令返回结果 + * + * @param info + */ + void onSSHResult(SSHResult info); + + /** + * 升级状态 + * + * @param info + */ + void onUpgradeStateInfo(IPCUpgradeStateInfo info); + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnMultiDeviceListener.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnMultiDeviceListener.java new file mode 100644 index 0000000000..1206f837d6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/OnMultiDeviceListener.java @@ -0,0 +1,16 @@ +package com.zhidao.support.adas.high; + +/** + * 多设备监听 + * 服务端实现此监听,将工控机发送过来的数据通过其他方式传递出去 + */ +public interface OnMultiDeviceListener { + + /** + * 转发工控机消息 + * 如果是客户端此回调不会被调用 + * + * @param bytes 数据 + */ + void onForwardingIPCMessage(byte[] bytes); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AdasConfig.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AdasConfig.java new file mode 100644 index 0000000000..7184cd7732 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AdasConfig.java @@ -0,0 +1,49 @@ +package com.zhidao.support.adas.high.bean; + +import com.zhidao.support.adas.high.BuildConfig; + +/** + * @author song kenan + * @des + * @date 2021/10/13 + */ +public class AdasConfig { + private static final String VERSION = BuildConfig.VERSION_NAME; + private static final String AP_VERSION = BuildConfig.AP_VERSION; + private String address; + private String dockVersion; + + /** + * MoGo-AP 版本 + * + * @return + */ + public String getAPVersion() { + return AP_VERSION; + } + + /** + * SDK版本 + * + * @return + */ + public String getVersion() { + return VERSION; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getDockVersion() { + return dockVersion; + } + + public void setDockVersion(String dockVersion) { + this.dockVersion = dockVersion; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotConfig.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotConfig.java new file mode 100644 index 0000000000..e8a2e76925 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotConfig.java @@ -0,0 +1,46 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des + * @date 2020/7/14 + */ + +public class AutopilotConfig implements Serializable { + + private String action; + private ResultBean result; + + public String getAction() { + return action; + } + + public ResultBean getResult() { + return result; + } + + @Override + public String toString() { + return "AutopilotWayArrive{" + + "action='" + action + '\'' + + ", result=" + result + + '}'; + } + + public static class ResultBean { + private String dock_version; + + public String getDock_version() { + return dock_version; + } + + @Override + public String toString() { + return "ResultBean{" + + "dock_version='" + dock_version + '\'' + + '}'; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotControl.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotControl.java new file mode 100644 index 0000000000..f7e6ec1136 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotControl.java @@ -0,0 +1,53 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des mode 0是手动 1是自动 + * @date 2020/7/14 + */ + +public class AutopilotControl implements Serializable { + + /** + * action : autopilotmode + * values : {"mode":0} + */ + + private String action; + private ValuesBean values; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ValuesBean getValues() { + return values; + } + + public void setValues(ValuesBean values) { + this.values = values; + } + + public static class ValuesBean { + /** + * mode : 0 + */ + + private int mode; + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + } +} + diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotRoute.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotRoute.java new file mode 100644 index 0000000000..1de9990c8b --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotRoute.java @@ -0,0 +1,75 @@ +package com.zhidao.support.adas.high.bean; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/6/21 + */ + +public class AutopilotRoute { + + @SerializedName("action") + private String action; + @SerializedName("models") + private List models; + + public static class RouteModels { + @SerializedName("lat") + private Double lat; + @SerializedName("lon") + private Double lon; + + public Double getLat() { + return lat; + } + + public void setLat(Double lat) { + this.lat = lat; + } + + public Double getLon() { + return lon; + } + + public void setLon(Double lon) { + this.lon = lon; + } + + @Override + public String toString() { + return "RouteModels{" + + "lat=" + lat + + ", lon=" + lon + + '}'; + } + + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public List getModels() { + return models; + } + + public void setModels(List models) { + this.models = models; + } + + @Override + public String toString() { + return "AutopilotRoute{" + + "action='" + action + '\'' + + ", models=" + models + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSnRequest.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSnRequest.java new file mode 100644 index 0000000000..1ad4199bd5 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSnRequest.java @@ -0,0 +1,29 @@ +package com.zhidao.support.adas.high.bean; + +/** + * @author song kenan + * @des + * @date 2021/7/2 + */ +public class AutopilotSnRequest { + private String action; + private String models; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getModels() { + return models; + } + + public void setModels(String models) { + this.models = models; + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSpeedParam.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSpeedParam.java new file mode 100644 index 0000000000..ec4e8c0c16 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotSpeedParam.java @@ -0,0 +1,39 @@ +package com.zhidao.support.adas.high.bean; + +import com.zhidao.support.adas.high.common.CupidLogUtils; + +/** + * @author song kenan + * @des + * @date 2021/12/6 + */ +public class AutopilotSpeedParam { + private final String action = "autospeed"; + private SpeedParam result; + + public String getAction() { + return action; + } + + public void setSpeed(int speed){ + if (result==null){ + result = new SpeedParam(); + } + result.setSpeed(speed); + } + + public static class SpeedParam { + //车辆速度 m/s + private double speed; + + public double getSpeed() { + return speed; + } + + public void setSpeed(int speed) { + // speed km/h + // this.speed m/s + this.speed = speed/3.6; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotStatus.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotStatus.java new file mode 100644 index 0000000000..6339c25f09 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotStatus.java @@ -0,0 +1,139 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des + * @date 2020/7/14 + */ +public class AutopilotStatus implements Serializable { + + /** + * action : autopilotstate + * values : {"state":0,"reason":""} + */ + + private String action; + private ValuesBean values; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ValuesBean getValues() { + return values; + } + + public void setValues(ValuesBean values) { + this.values = values; + } + + public static class ValuesBean { + /** + * 0是不可用 1是ready 2是自动驾驶start + * + * @return + */ + private int state; + /** + * 车速 m/s + */ + private float speed; + /** + * 不可用原因 + */ + private String reason; + /** + * 摄像头状态 1代表开启,0代表关闭 + */ + private int camera; + /** + * 雷达状态 1代表开启,0代表关闭 + */ + private int radar; + /** + * RTK状态 1代表开启,0代表关闭 + */ + private int rtk; + /** + * 自动驾驶状态 0非自动驾驶,1自动驾驶 + */ + private int pilotmode; + + /** + * 自动驾驶车控状态 + */ + private int control_pilotmode; + + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public float getSpeed() { + return speed; + } + + public void setSpeed(float speed) { + this.speed = speed; + } + public int getCamera() { + return camera; + } + + public int getRadar() { + return radar; + } + + public int getRtk() { + return rtk; + } + + public int getPilotmode() { + return pilotmode; + } + + public int getControl_pilotmode() { + return control_pilotmode; + } + + @Override + public String toString() { + return "ValuesBean{" + + "state=" + state + + ", speed=" + speed + + ", reason='" + reason + '\'' + + ", camera=" + camera + + ", radar=" + radar + + ", rtk=" + rtk + + ", pilotmode=" + pilotmode + + ", control_pilotmode=" + control_pilotmode + + '}'; + } + } + + @Override + public String toString() { + return "AutopilotStatus{" + + "action='" + action + '\'' + + ", values=" + values + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotTrajectory.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotTrajectory.java new file mode 100644 index 0000000000..079db7b0bc --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotTrajectory.java @@ -0,0 +1,34 @@ +package com.zhidao.support.adas.high.bean; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/10/19 + */ +public class AutopilotTrajectory { + + @SerializedName("action") + private String action; + @SerializedName("models") + private List models; + + public String getAction() { + return action; + } + + public List getModels() { + return models; + } + + @Override + public String toString() { + return "AutopilotTrajectory{" + + "action='" + action + '\'' + + ", models=" + models + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotWayArrive.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotWayArrive.java new file mode 100644 index 0000000000..a81c22ddf5 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/AutopilotWayArrive.java @@ -0,0 +1,115 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des + * @date 2020/7/14 + */ + +public class AutopilotWayArrive implements Serializable { + + + /** + * action : autopilotArrive + * result : {"carType":0,"endLatLon":{"lat":116.7559675438664,"lon":116.7559675438664}} + */ + + private String action; + private ResultBean result; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ResultBean getResult() { + return result; + } + + public void setResult(ResultBean result) { + this.result = result; + } + + @Override + public String toString() { + return "AutopilotWayArrive{" + + "action='" + action + '\'' + + ", result=" + result + + '}'; + } + + public static class ResultBean { + /** + * carType : 0 + * endLatLon : {"lat":116.7559675438664,"lon":116.7559675438664} + */ + + private int carType; + private EndLatLonBean endLatLon; + + + public int getCarType() { + return carType; + } + + public void setCarType(int carType) { + this.carType = carType; + } + + public EndLatLonBean getEndLatLon() { + return endLatLon; + } + + public void setEndLatLon(EndLatLonBean endLatLon) { + this.endLatLon = endLatLon; + } + + @Override + public String toString() { + return "ResultBean{" + + "carType=" + carType + + ", endLatLon=" + endLatLon + + '}'; + } + + public static class EndLatLonBean { + /** + * lat : 116.7559675438664 + * lon : 116.7559675438664 + */ + + private double lat; + private double lon; + + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + @Override + public String toString() { + return "EndLatLonBean{" + + "lat=" + lat + + ", lon=" + lon + + '}'; + } + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BaseInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BaseInfo.java new file mode 100644 index 0000000000..4e660e1f69 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BaseInfo.java @@ -0,0 +1,23 @@ +package com.zhidao.support.adas.high.bean; + +public abstract class BaseInfo { + protected String action; + protected T values; + + + public BaseInfo(String action) { + this.action = action; + } + + public String getAction() { + return action; + } + + public T getValues() { + return values; + } + + public void setValues(T values) { + this.values = values; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BasicInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BasicInfo.java new file mode 100644 index 0000000000..f9d08ced0c --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/BasicInfo.java @@ -0,0 +1,61 @@ +package com.zhidao.support.adas.high.bean; + +/** + * 7.5. 自动驾驶设备基础信息 + */ +public class BasicInfo { + private String action = "basicinfo"; + private Values values; + + public void setSn(String sn) { + if (values == null) { + values = new Values(); + } + values.setSn(sn); + } + + public void setTileid(String tileid) { + if (values == null) { + values = new Values(); + } + values.setTileid(tileid); + } + + public void setNetEnvironment(int environment) { + if (values == null) { + values = new Values(); + } + values.setNetEnvironment(environment); + } + + private static class Values { + private String sn; + private String tileid; + //研发环境 1;测试环境 2;生产环境 3;演示环境 4; + private int netEnvironment; + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public String getTileid() { + return tileid; + } + + public void setTileid(String tileid) { + this.tileid = tileid; + } + + public int getNetEnvironment() { + return netEnvironment; + } + + public void setNetEnvironment(int environment) { + this.netEnvironment = environment; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarLaneInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarLaneInfo.java new file mode 100644 index 0000000000..0a4aa775db --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarLaneInfo.java @@ -0,0 +1,63 @@ +package com.zhidao.support.adas.high.bean; + +import java.util.List; + +/** + * @author nie yunlong + * @des 车道线 + * @date 2020/4/24 + */ +public class CarLaneInfo { + + private String action; + + private List models; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public List getModels() { + return models; + } + + public void setModels(List models) { + this.models = models; + } + + public static class CarLaneDetailInfo { + private String id; //50~55, 50左黄,51左红,(52左白),53右黄,54右红,(55右白) + private String type; + private int color; //0白色,1黄色 + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarStateInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarStateInfo.java new file mode 100644 index 0000000000..928a17aa3a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/CarStateInfo.java @@ -0,0 +1,279 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des 车辆状态 + * @date 2020/3/12 + */ +public class CarStateInfo implements Serializable { + + + /** + * action : “state” + * values : {"lon":116.8,"lat":39.4,"alt":22.3,"heading":87.5,"acceleration":0.5,"yaw_rate":0.3} + */ + + private String action; + private ValuesBean values; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ValuesBean getValues() { + return values; + } + + public void setValues(ValuesBean values) { + this.values = values; + } + + public static class ValuesBean { + /** + * lon : 116.8 + * lat : 39.4 + * alt : 22.3 + * heading : 87.5 + * acceleration : 0.5 + * yaw_rate : 0.3 + */ + + private double lon; + private double lat; + private double alt; + private double heading; + private double acceleration; + private double yaw_rate; + //惯导车速 m/s + private float gnss_speed; + //车辆车速 m/s + private float vehicle_speed; + //gps时间 + private String satelliteTime; + //UTC时间差 + private long utcTimeDiffer; + //系统时间 + private String systemTime; + //接收到数据的时间 + private long receiverDataTime; + //接收到gps时间 + private String adasSatelliteTime; + //开始接收数据时间 + private String startReceiverDataTime; + //时间延迟 + private long timeDiff = 0; + private int turn_light; //转向灯状态 0是正常 1是左转 2是右转 + private int flash_light; //双闪灯状态 + private int brake_light; //刹车灯状态 + private int frame_num;//统计发包个数 + /** + * 常开 常关 转向灯 + * 0 --关 + * 1 --左转 + * 2 --右转 + */ + private int turnLightOften = 0; + + + public int getFrame_num() { + return frame_num; + } + + public void setFrame_num(int frame_num) { + this.frame_num = frame_num; + } + + public int getTurnLightOften() { + return turnLightOften; + } + + public void setTurnLightOften(int turnLightOften) { + this.turnLightOften = turnLightOften; + } + + public long getTimeDiff() { + return timeDiff; + } + + public void setTimeDiff(long timeDiff) { + this.timeDiff = timeDiff; + } + + public long getReceiverDataTime() { + return receiverDataTime; + } + + public void setReceiverDataTime(long receiverDataTime) { + this.receiverDataTime = receiverDataTime; + } + + public String getAdasSatelliteTime() { + return adasSatelliteTime; + } + + public void setAdasSatelliteTime(String adasSatelliteTime) { + this.adasSatelliteTime = adasSatelliteTime; + } + + public String getSystemTime() { + return systemTime; + } + + public void setSystemTime(String systemTime) { + this.systemTime = systemTime; + } + + public float getGnss_speed() { + return gnss_speed; + } + + public void setGnss_speed(float gnss_speed) { + this.gnss_speed = gnss_speed; + } + + public float getVehicle_speed() { + return vehicle_speed; + } + + public void setVehicle_speed(float vehicle_speed) { + this.vehicle_speed = vehicle_speed; + } + + public int getTurn_light() { + return turn_light; + } + + public void setTurn_light(int turn_light) { + this.turn_light = turn_light; + } + + public int getFlash_light() { + return flash_light; + } + + public void setFlash_light(int flash_light) { + this.flash_light = flash_light; + } + + public int getBrake_light() { + return brake_light; + } + + public void setBrake_light(int brake_light) { + this.brake_light = brake_light; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getAlt() { + return alt; + } + + public void setAlt(double alt) { + this.alt = alt; + } + + public double getHeading() { + return heading; + } + + public void setHeading(double heading) { + this.heading = heading; + } + + public double getAcceleration() { + return acceleration; + } + + public void setAcceleration(double acceleration) { + this.acceleration = acceleration; + } + + public double getYaw_rate() { + return yaw_rate; + } + + public void setYaw_rate(double yaw_rate) { + this.yaw_rate = yaw_rate; + } + + public String getSatelliteTime() { + return satelliteTime; + } + + public void setSatelliteTime(String satelliteTime) { + this.satelliteTime = satelliteTime; + } + + public long getUtcTimeDiffer() { + return utcTimeDiffer; + } + + public void setUtcTimeDiffer(long utcTimeDiffer) { + this.utcTimeDiffer = utcTimeDiffer; + } + + public String getStartReceiverDataTime() { + return startReceiverDataTime; + } + + public void setStartReceiverDataTime(String startReceiverDataTime) { + this.startReceiverDataTime = startReceiverDataTime; + } + + + @Override + public String toString() { + return "ValuesBean{" + + "lon=" + lon + + ", lat=" + lat + + ", alt=" + alt + + ", heading=" + heading + + ", acceleration=" + acceleration + + ", yaw_rate=" + yaw_rate + + ", gnss_speed=" + gnss_speed + + ", vehicle_speed=" + vehicle_speed + + ", satelliteTime='" + satelliteTime + '\'' + + ", utcTimeDiffer=" + utcTimeDiffer + + ", systemTime='" + systemTime + '\'' + + ", receiverDataTime='" + receiverDataTime + '\'' + + ", adasSatelliteTime='" + adasSatelliteTime + '\'' + + ", startReceiverDataTime='" + startReceiverDataTime + '\'' + + ", timeDiff=" + timeDiff + + ", turn_light=" + turn_light + + ", flash_light=" + flash_light + + ", brake_light=" + brake_light + + ", frame_num=" + frame_num + + ", turnLightOften=" + turnLightOften + + '}'; + } + } + + @Override + public String toString() { + return "CarStateInfo{" + + "action='" + action + '\'' + + ", values=" + values + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/DemoModeInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/DemoModeInfo.java new file mode 100644 index 0000000000..04deffb0ee --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/DemoModeInfo.java @@ -0,0 +1,30 @@ +package com.zhidao.support.adas.high.bean; + +/** + * 演示模式 + */ +public class DemoModeInfo extends BaseInfo { + public DemoModeInfo(String action) { + super(action); + } + + /** + * 开启演示模式 + * + * @return + */ + public static DemoModeInfo enable() { + return new DemoModeInfo("enable_demo_pilot"); + } + + /** + * 关闭演示模式 + * + * @return + */ + public static DemoModeInfo disable() { + return new DemoModeInfo("disable_demo_pilot"); + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeInfo.java new file mode 100644 index 0000000000..8772a2f105 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeInfo.java @@ -0,0 +1,50 @@ +package com.zhidao.support.adas.high.bean; + +/** + * 工控机升级 + */ +public class IPCUpgradeInfo extends BaseInfo { + public IPCUpgradeInfo() { + super("ipc_upgrade"); + } + + /** + * 用户不同意升级 + * + * @return + */ + public static IPCUpgradeInfo cancel() { + IPCUpgradeInfo info = new IPCUpgradeInfo(); + info.values = new IPCUpgradeInfo.Values(); + info.values.state = 0; + return info; + } + + /** + * 用户同意升级 + * + * @return + */ + public static IPCUpgradeInfo affirm() { + IPCUpgradeInfo info = new IPCUpgradeInfo(); + info.values = new IPCUpgradeInfo.Values(); + info.values.state = 1; + return info; + } + + public static class Values { + /** + * 0 不同意升级 + * 1 同意升级 + */ + private int state; + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeStateInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeStateInfo.java new file mode 100644 index 0000000000..4e82b55e0f --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/IPCUpgradeStateInfo.java @@ -0,0 +1,282 @@ +package com.zhidao.support.adas.high.bean; + +import com.google.gson.annotations.SerializedName; + +/** + * 工控机升级 + */ +public class IPCUpgradeStateInfo extends BaseInfo { + /** + * 升级模式 + */ + public enum UpgradeMode { + /** + * 0:静默升级 + * 3:提示升级 + */ + DATA_NULL(-1, "数据为NULL"), + QUIET(0, "静默升级"), + HINT(3, "提示升级"); + public final int code; + public final String describe; + + private UpgradeMode(int code, String describe) { + this.code = code; + this.describe = describe; + } + + public static UpgradeMode getStatus(int code) { + switch (code) { + default: + case -1: + return DATA_NULL; + case 0: + return QUIET; + case 3: + return HINT; + + } + } + + @Override + public String toString() { + return "{" + + "code=" + code + + ", describe='" + describe + '\'' + + '}'; + } + } + + /** + * 下载状态 + */ + public enum DownloadStatus { + /** + * 30:开始下载 + * 31:下载完成 + * 32:下载失败 + */ + DATA_NULL(-1, "数据为NULL"), + START(30, "开始下载"), + FINISH(31, "下载完成"), + FAILED(32, "下载失败"); + public final int code; + public final String describe; + + private DownloadStatus(int code, String describe) { + this.code = code; + this.describe = describe; + } + + public static DownloadStatus getStatus(int code) { + switch (code) { + default: + case -1: + return DATA_NULL; + case 30: + return START; + case 31: + return FINISH; + case 32: + return FAILED; + } + } + + @Override + public String toString() { + return "{" + + "code=" + code + + ", describe='" + describe + '\'' + + '}'; + } + } + + /** + * 下载状态 + */ + public enum UpgradeStatus { + /** + * 60:是否升级 + * 61:升级成功 + * 62:升级失败 + * 63:用户确认 + * 64:用户取消 + */ + DATA_NULL(-1, "数据为NULL"), + AFFIRM(60, "是否升级"), + SUCCEED(61, "升级成功"), + FAILED(62, "升级失败"), + USER_AFFIRM(63, "用户确认"), + USER_CANCEL(64, "用户取消"); + public final int code; + public final String describe; + + private UpgradeStatus(int code, String describe) { + this.code = code; + this.describe = describe; + } + + public static UpgradeStatus getStatus(int code) { + switch (code) { + default: + case -1: + return DATA_NULL; + case 60: + return AFFIRM; + case 61: + return SUCCEED; + case 62: + return FAILED; + case 63: + return USER_AFFIRM; + case 64: + return USER_CANCEL; + } + } + + @Override + public String toString() { + return "{" + + "code=" + code + + ", describe='" + describe + '\'' + + '}'; + } + } + + public IPCUpgradeStateInfo(String action) { + super(action); + } + + /** + * 获取升级包下载进度 + * + * @return + */ + public Values.Progress getProgress() { + if (this.values == null) return null;//数据或解析异常 + return this.values.progress; + } + + /** + * 获取下载状态 + * + * @return + */ + public int getDownloadStatus() { + if (this.values == null) return -1;//数据或解析异常 + return this.values.downloadStatus; + } + + /** + * 获取升级状态 + * + * @return + */ + public int getUpgradeStatus() { + if (this.values == null) return -1;//数据或解析异常 + return this.values.upgradeStatus; + } + + /** + * 获取升级模式 + * + * @return + */ + public int getUpgradeMode() { + if (this.values == null) return -1;//数据或解析异常 + return this.values.upgradeMode; + } + + /** + * 获取版本号 + * + * @return + */ + public String getImages() { + if (this.values == null) return null;//数据或解析异常 + return this.values.images; + } + + public static class Values { + /** + * int 类型, 升级模式 静默升级不需要前端通知,工控机会自动升级 + */ + @SerializedName("upgrade_mode") + private int upgradeMode = -1; + private Progress progress; + /** + * int 类型, 下载状态, 如果下载完成,下载进度会为空 + */ + @SerializedName("download_status") + private int downloadStatus = -1; + /** + * int 类型, 升级状态 + */ + @SerializedName("upgrade_status") + private int upgradeStatus = -1; + /** + * string 类型,版本号 + */ + private String images; + + public static class Progress { + /** + * total : 307405010 + * current : 1071793 + */ + /** + * int 类型 ,包总大小,字节 + */ + private int total; + /** + * int 类型 ,包已经下载大小,字节 + */ + private int current; + + public int getTotal() { + return total; + } + + public int getCurrent() { + return current; + } + + @Override + public String toString() { + return "{" + + "total=" + total + + ", current=" + current + + '}'; + } + } + } +} + +/** + * upgrade_mode: + * 0-静默升级 + * 1-提示升级 + * download_status: + * 30:开始下载 + * 31:下载完成 + * 32:下载失败 + * upgrade_status: + * 60:是否升级 + * 61:升级成功 + * 62:升级失败 + * 63:用户确认 + * 64:用户取消 + * *{ + * * "action": "ipc_upgrade_status", + * * "values": { + * * "progress": { + * * "total": 307405010, //int 类型 ,包总大小,字节 + * * "current": 1071793 //int 类型 ,包已经下载大小,字节 + * * }, + * * "download_status": 31, //int 类型, 下载状态, 如果下载完成,下载进度会为空 + * * "upgrade_status": 61, //int 类型, 升级状态 + * * "upgrade_mode": 1, //int 类型, 升级模式 + * * "images": "mogohub.tencentcloudcr.com/autocar/df:RoboTaxi_MAP-taxi_2.1.3.1_20211214_huzhengming"* } //string 类型,版本号 + * * + * * } + */ diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/LightStatueInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/LightStatueInfo.java new file mode 100644 index 0000000000..4d8df164d3 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/LightStatueInfo.java @@ -0,0 +1,143 @@ +package com.zhidao.support.adas.high.bean; + +import java.io.Serializable; + +/** + * @author nie yunlong + * @des 红绿灯状态 + * @date 2020/3/12 + */ +public class LightStatueInfo implements Serializable { + + /** + * action : light + * values : {"left":1,"right ":0,"straight":3,"u_turn":0,"left_time_remains":3,"right_time_remains":3,"straight_time_remains":3,"u_turn_time_remains":3} + */ + + private String action; + private ValuesBean values; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ValuesBean getValues() { + return values; + } + + public void setValues(ValuesBean values) { + this.values = values; + } + + @Override + public String toString() { + return "LightStatueInfo{" + + "action='" + action + '\'' + + ", values=" + values + + '}'; + } + + public static class ValuesBean { + /** + * left : 1 + * right : 0 + * straight : 3 + * u_turn : 0 + * left_time_remains : 3 + * right_time_remains : 3 + * straight_time_remains : 3 + * u_turn_time_remains : 3 + */ + + private int left; + private int right; + private int straight; + private int u_turn; + private int left_time_remains; + private int right_time_remains; + private int straight_time_remains; + private int u_turn_time_remains; + + public int getLeft() { + return left; + } + + public void setLeft(int left) { + this.left = left; + } + + public int getRight() { + return right; + } + + public void setRight(int right) { + this.right = right; + } + + public int getStraight() { + return straight; + } + + public void setStraight(int straight) { + this.straight = straight; + } + + public int getU_turn() { + return u_turn; + } + + public void setU_turn(int u_turn) { + this.u_turn = u_turn; + } + + public int getLeft_time_remains() { + return left_time_remains; + } + + public void setLeft_time_remains(int left_time_remains) { + this.left_time_remains = left_time_remains; + } + + public int getRight_time_remains() { + return right_time_remains; + } + + public void setRight_time_remains(int right_time_remains) { + this.right_time_remains = right_time_remains; + } + + public int getStraight_time_remains() { + return straight_time_remains; + } + + public void setStraight_time_remains(int straight_time_remains) { + this.straight_time_remains = straight_time_remains; + } + + public int getU_turn_time_remains() { + return u_turn_time_remains; + } + + public void setU_turn_time_remains(int u_turn_time_remains) { + this.u_turn_time_remains = u_turn_time_remains; + } + + @Override + public String toString() { + return "ValuesBean{" + + "left=" + left + + ", right=" + right + + ", straight=" + straight + + ", u_turn=" + u_turn + + ", left_time_remains=" + left_time_remains + + ", right_time_remains=" + right_time_remains + + ", straight_time_remains=" + straight_time_remains + + ", u_turn_time_remains=" + u_turn_time_remains + + '}'; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/ObstaclesInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/ObstaclesInfo.java new file mode 100644 index 0000000000..2af375c32b --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/ObstaclesInfo.java @@ -0,0 +1,82 @@ +package com.zhidao.support.adas.high.bean; + +import java.util.List; + +/** + * @author nie yunlong + * @des 周边物体渲染 + * @date 2020/4/24 + */ +public class ObstaclesInfo { + + private String action; + + private List models; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public List getModels() { + return models; + } + + public void setModels(List models) { + this.models = models; + } + + public static class ObstaclesModelInfo { + + private String id; //50~55, 50左黄,51左红,(52左白),53右黄,54右红,(55右白) + private String type; + private double distance; //距离/米 + private double direction; //正前为0,左为90,后为180,右为270 + private double heading; //物体的朝向角,90~270属于逆向车辆 + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public double getDirection() { + return direction; + } + + public void setDirection(double direction) { + this.direction = direction; + } + + public double getHeading() { + return heading; + } + + public void setHeading(double heading) { + this.heading = heading; + } + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RectInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RectInfo.java new file mode 100644 index 0000000000..ab710e82a6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RectInfo.java @@ -0,0 +1,344 @@ +package com.zhidao.support.adas.high.bean; + +import java.util.List; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high.bean + * @ClassName: RectInfo + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/9 20:22 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/9 20:22 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class RectInfo { + private String type; + private String action; + private List models; + private float fps; + + public float getFps() { + return fps; + } + + public void setFps(float fps) { + this.fps = fps; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getModels() { + return models; + } + + public void setModels(List models) { + this.models = models; + } + + public static class RectBean { + private int id; + private double xl; + private double xr; + private double yb; + private double yt; + //距离x轴值 + private double distance_x; + //距离y轴值 + private double distance_y; + //type="car" 是车辆 + private String type; + //纬度 + private double lat; + //经度 + private double lon; + //朝向 + private double heading; + //系统时间 + private String systemTime; + //gps 时间 + private String satelliteTime; + //海拔高度 + private double alt; + //车牌id + private String carId; + //uuid + private String uuid; + //color + private String color; + //speed + private double speed; + //危险等级 1 绿,2 黄,3 红 + private int drawlevel; + //长 + private float length; + //宽 + private float width; + //高 + private float height; + //驱动时间 + private String driverTime; + /** + * 数据来源精度 0 - 普通定位、1 - 高精定位 + */ + public int dataAccuracy = 1; + + /** + * 实际距离,使用 distanceX 和 distanceY 计算 + */ + public double distance; + + public int getDataAccuracy() { + return dataAccuracy; + } + + public void setDataAccuracy(int dataAccuracy) { + this.dataAccuracy = dataAccuracy; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public String getSystemTime() { + return systemTime; + } + + public void setSystemTime(String systemTime) { + this.systemTime = systemTime; + } + + public String getSatelliteTime() { + return satelliteTime; + } + + public void setSatelliteTime(String satelliteTime) { + this.satelliteTime = satelliteTime; + } + + public double getAlt() { + return alt; + } + + public void setAlt(double alt) { + this.alt = alt; + } + + public String getCarId() { + return carId; + } + + public void setCarId(String carId) { + this.carId = carId; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public float getLength() { + return length; + } + + public void setLength(float length) { + this.length = length; + } + + public float getWidth() { + return width; + } + + public void setWidth(float width) { + this.width = width; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; + } + + public int getDrawlevel() { + return drawlevel; + } + + public void setDrawlevel(int drawlevel) { + this.drawlevel = drawlevel; + } + + public double getSpeed() { + return speed; + } + + public void setSpeed(double speed) { + this.speed = speed; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + public double getDistance_x() { + return distance_x; + } + + public void setDistance_x(double distance_x) { + this.distance_x = distance_x; + } + + public double getDistance_y() { + return distance_y; + } + + public void setDistance_y(double distance_y) { + this.distance_y = distance_y; + } + + public double getHeading() { + return heading; + } + + public void setHeading(double heading) { + this.heading = heading; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public double getXl() { + return xl; + } + + public void setXl(double xl) { + this.xl = xl; + } + + public double getXr() { + return xr; + } + + public void setXr(double xr) { + this.xr = xr; + } + + public double getYb() { + return yb; + } + + public void setYb(double yb) { + this.yb = yb; + } + + public double getYt() { + return yt; + } + + public void setYt(double yt) { + this.yt = yt; + } + + public String getDriverTime() { + return driverTime; + } + + public void setDriverTime(String driverTime) { + this.driverTime = driverTime; + } + + @Override + public String toString() { + return "RectBean{" + + "id=" + id + + ", xl=" + xl + + ", xr=" + xr + + ", yb=" + yb + + ", yt=" + yt + + ", distance_x=" + distance_x + + ", distance_y=" + distance_y + + ", type='" + type + '\'' + + ", lat=" + lat + + ", lon=" + lon + + ", heading=" + heading + + ", systemTime='" + systemTime + '\'' + + ", satelliteTime='" + satelliteTime + '\'' + + ", alt=" + alt + + ", carId='" + carId + '\'' + + ", uuid='" + uuid + '\'' + + ", color='" + color + '\'' + + ", speed=" + speed + + ", drawlevel=" + drawlevel + + ", length=" + length + + ", width=" + width + + ", height=" + height + + ", driverTime=" + driverTime + + '}'; + } + } + + @Override + public String toString() { + return "RectInfo{" + + ", action='" + action + '\'' + + ", models=" + models + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RemoteControlAutoPilotParameters.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RemoteControlAutoPilotParameters.java new file mode 100644 index 0000000000..4208c9d617 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RemoteControlAutoPilotParameters.java @@ -0,0 +1,139 @@ +package com.zhidao.support.adas.high.bean; + +import java.util.List; + +public +/** + * @author congtaowang + * @since 2020/10/16 + * + * 自动驾驶参数 + */ +class RemoteControlAutoPilotParameters { + + private String action; + + private RemoteControlAutoPilotParameters.ValuesBean result; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public ValuesBean getResult() { + return result; + } + + public void setResult(ValuesBean result) { + this.result = result; + } + + @Override + public String toString() { + return "RemoteControlAutoPilotParameters{" + + "action='" + action + '\'' + + ", result=" + result + + '}'; + } + + public static class ValuesBean { + public AutoPilotLonLat startLatLon; + public List< AutoPilotLonLat > wayLatLons; + public AutoPilotLonLat endLatLon; + public float speedLimit; + public int vehicleType;// 运营类型 + + public static class AutoPilotLonLat { + public double lat; + public double lon; + + public AutoPilotLonLat() { + } + + public AutoPilotLonLat( double lat, double lon ) { + this.lat = lat; + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + @Override + public String toString() { + return "AutoPilotLonLat{" + + "lat=" + lat + + ", lon=" + lon + + '}'; + } + } + + + public AutoPilotLonLat getStartLatLon() { + return startLatLon; + } + + public void setStartLatLon(AutoPilotLonLat startLatLon) { + this.startLatLon = startLatLon; + } + + public List getWayLatLons() { + return wayLatLons; + } + + public void setWayLatLons(List wayLatLons) { + this.wayLatLons = wayLatLons; + } + + public AutoPilotLonLat getEndLatLon() { + return endLatLon; + } + + public void setEndLatLon(AutoPilotLonLat endLatLon) { + this.endLatLon = endLatLon; + } + + public float getSpeedLimit() { + return speedLimit; + } + + public void setSpeedLimit(float speedLimit) { + this.speedLimit = speedLimit; + } + + public int getVehicleType() { + return vehicleType; + } + + public void setVehicleType(int vehicleType) { + this.vehicleType = vehicleType; + } + + @Override + public String toString() { + return "RemoteControlAutoPilotParameters{" + + "startLatLon=" + startLatLon + + ", wayLatLons=" + wayLatLons + + ", endLatLon=" + endLatLon + + ", speedLimit=" + speedLimit + + ", vehicleType=" + vehicleType + + '}'; + } + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RouteInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RouteInfo.java new file mode 100644 index 0000000000..5229eca2f5 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/RouteInfo.java @@ -0,0 +1,23 @@ +package com.zhidao.support.adas.high.bean; + +/** + * @author song kenan + * @des + * @date 2021/12/7 + */ +public class RouteInfo { + + private String id; + + private double startLat; + + private double startLon; + + private double endLat; + + private double endLon; + + private String path; + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/SSHResult.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/SSHResult.java new file mode 100644 index 0000000000..d9f16e9219 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/SSHResult.java @@ -0,0 +1,46 @@ +package com.zhidao.support.adas.high.bean; + +/** + * SSH返回结果 + */ +public class SSHResult { + + public interface RESULT_CODE { + /** + * 工控机IP未知 + */ + int IP_UNKNOWN = -10001; + /** + * 网络异常或连接异常或IO异常 + */ + int ERROR = -10000; + /** + * 命令下发成功 可能存在-1的情况 + * 在虚拟机的ubuntu系统发送reboot或shutdown或ls 命令返回的是0,在工控机发送reboot或shutdown返回-1 ls返回0 + */ + int SEND_SUCCEED = 0; + /** + * 其他值根据Shell返回结果而定 >0 + */ + + } + + public final int code; + public final String cmd; + public final String msg; + + public SSHResult(int code, String cmd, String msg) { + this.code = code; + this.cmd = cmd; + this.msg = msg; + } + + @Override + public String toString() { + return "SSHResult{" + + "code=" + code + + ", cmd='" + cmd + '\'' + + ", msg='" + msg + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/TrajectoryInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/TrajectoryInfo.java new file mode 100644 index 0000000000..027a1e2cb0 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/TrajectoryInfo.java @@ -0,0 +1,125 @@ +package com.zhidao.support.adas.high.bean; + +import com.google.gson.annotations.SerializedName; + +/** + * @author song kenan + * @des + * @date 2021/10/21 + */ +public class TrajectoryInfo { + //经度 + @SerializedName("lon") + private Double lon; + //纬度 + @SerializedName("lat") + private Double lat; + //高度 + @SerializedName("alt") + private Double alt; + //时间 秒s + @SerializedName("time") + private Double time; + //速度 m/s + @SerializedName("velocity") + private Double velocity; + //加速度 + @SerializedName("acceleration") + private Double acceleration; + //速度方向 + @SerializedName("theta") + private Double theta; + //曲率 + @SerializedName("kappa") + private Double kappa; + //从起点到目前的总距离 + @SerializedName("accumulated_dis") + private Double accumulatedDis; + + public void setLon(Double lon) { + this.lon = lon; + } + + public void setLat(Double lat) { + this.lat = lat; + } + + public void setAlt(Double alt) { + this.alt = alt; + } + + public void setTime(Double time) { + this.time = time; + } + + public void setVelocity(Double velocity) { + this.velocity = velocity; + } + + public void setAcceleration(Double acceleration) { + this.acceleration = acceleration; + } + + public void setTheta(Double theta) { + this.theta = theta; + } + + public void setKappa(Double kappa) { + this.kappa = kappa; + } + + public void setAccumulatedDis(Double accumulatedDis) { + this.accumulatedDis = accumulatedDis; + } + + public Double getLon() { + return lon; + } + + public Double getLat() { + return lat; + } + + public Double getAlt() { + return alt; + } + + public Double getTime() { + return time; + } + + public Double getVelocity() { + return velocity; + } + + public Double getAcceleration() { + return acceleration; + } + + public Double getTheta() { + return theta; + } + + public Double getKappa() { + return kappa; + } + + public Double getAccumulatedDis() { + return accumulatedDis; + } + + @Override + public String toString() { + return "TrajectoryModels{" + + "lon=" + lon + + ", lat=" + lat + + ", alt=" + alt + + ", time='" + time + '\'' + + ", velocity=" + velocity + + ", acceleration=" + acceleration + + ", theta=" + theta + + ", kappa=" + kappa + + ", accumulatedDis=" + accumulatedDis + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/WarnMessageInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/WarnMessageInfo.java new file mode 100644 index 0000000000..d79071f189 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/WarnMessageInfo.java @@ -0,0 +1,80 @@ +package com.zhidao.support.adas.high.bean; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high.bean + * @ClassName: WarnMessageInfo + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/9 20:40 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/9 20:40 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class WarnMessageInfo { + /** + * content : 小心前车 + * level : 2 + * type : 20 + * value : + */ + + private String content; + private String level; + private String type; + private String value; + + public WarnMessageInfo() { + } + + public WarnMessageInfo(String type, String content, String value, String level) { + this.type = type; + this.content = content; + this.value = value; + this.level = level; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + + @Override + public String toString() { + return "WarnMessageInfo{" + + "content='" + content + '\'' + + ", level='" + level + '\'' + + ", type='" + type + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/AutopilotGuardianInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/AutopilotGuardianInfo.java new file mode 100644 index 0000000000..88a05c38dd --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/AutopilotGuardianInfo.java @@ -0,0 +1,113 @@ +package com.zhidao.support.adas.high.bean.guardian; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.List; + +/** + * @author songkenan + * @des 节点监控 + * @date 2021/7/7 + */ +public class AutopilotGuardianInfo implements Serializable { + //接口名称 + @SerializedName("action") + private String action; + //监控项名称 + @SerializedName("agentName") + private String agentName; + //车牌号 + @SerializedName("carNum") + private String carNum; + //车型 + @SerializedName("carType") + private String carType; + //monitorType + @SerializedName("monitorType") + private String monitorType; + //sn + @SerializedName("sn") + private String sn; + //时间 + @SerializedName("time") + private long time; + @SerializedName("modules") + private GuardianModule modules; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getAgentName() { + return agentName; + } + + public void setAgentName(String agentName) { + this.agentName = agentName; + } + + public String getCarNum() { + return carNum; + } + + public void setCarNum(String carNum) { + this.carNum = carNum; + } + + public String getCarType() { + return carType; + } + + public void setCarType(String carType) { + this.carType = carType; + } + + public String getMonitorType() { + return monitorType; + } + + public void setMonitorType(String monitorType) { + this.monitorType = monitorType; + } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public GuardianModule getModules() { + return modules; + } + + public void setModules(GuardianModule modules) { + this.modules = modules; + } + + @Override + public String toString() { + return "AutopilotGuardianInfo{" + + "action='" + action + '\'' + + ", agentName='" + agentName + '\'' + + ", carNum='" + carNum + '\'' + + ", carType='" + carType + '\'' + + ", monitorType='" + monitorType + '\'' + + ", modules=" + modules + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItem.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItem.java new file mode 100644 index 0000000000..4b2a04b761 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItem.java @@ -0,0 +1,39 @@ +package com.zhidao.support.adas.high.bean.guardian; + +import com.google.gson.annotations.SerializedName; + +/** + * @author song kenan + * @des + * @date 2021/12/10 + */ +public class GuardianItem { + @SerializedName("name") + private String name; + @SerializedName("value") + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "GuardianItem{" + + "name='" + name + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItemsName.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItemsName.java new file mode 100644 index 0000000000..1dfe098a6e --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianItemsName.java @@ -0,0 +1,41 @@ +package com.zhidao.support.adas.high.bean.guardian; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/12/10 + */ +public class GuardianItemsName { + @SerializedName("name") + private String name; + @SerializedName("items") + private List items; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + @Override + public String toString() { + return "GuardianItemsName{" + + "name='" + name + '\'' + + ", items=" + items + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianModule.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianModule.java new file mode 100644 index 0000000000..4815e7b367 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianModule.java @@ -0,0 +1,41 @@ +package com.zhidao.support.adas.high.bean.guardian; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/12/10 + */ +public class GuardianModule { + @SerializedName("itemsname") + private List itemsname; + @SerializedName("submodules") + private List submodules; + + public List getItemsname() { + return itemsname; + } + + public void setItemsname(List itemsname) { + this.itemsname = itemsname; + } + + public List getSubmodules() { + return submodules; + } + + public void setSubmodules(List submodules) { + this.submodules = submodules; + } + + @Override + public String toString() { + return "GuardianModule{" + + "itemsname=" + itemsname + + ", submodules=" + submodules + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianSubmodule.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianSubmodule.java new file mode 100644 index 0000000000..79e4648acc --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/guardian/GuardianSubmodule.java @@ -0,0 +1,84 @@ +package com.zhidao.support.adas.high.bean.guardian; + + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/12/10 + */ +public class GuardianSubmodule { + @SerializedName("modulename") + private String modulename; + @SerializedName("subnodes") + private List subnodes; + + public String getModulename() { + return modulename; + } + + public void setModulename(String modulename) { + this.modulename = modulename; + } + + public List getSubnodes() { + return subnodes; + } + + public void setSubnodes(List subnodes) { + this.subnodes = subnodes; + } + + public static class Subnode { + @SerializedName("nodename") + private String nodename; + @SerializedName("nodeitems") + private List nodeitems; + @SerializedName("topicitems") + private List topicitems; + + public String getNodename() { + return nodename; + } + + public void setNodename(String nodename) { + this.nodename = nodename; + } + + public List getNodeitems() { + return nodeitems; + } + + public void setNodeitems(List nodeitems) { + this.nodeitems = nodeitems; + } + + public List getTopicitems() { + return topicitems; + } + + public void setTopicitems(List topicitems) { + this.topicitems = topicitems; + } + + @Override + public String toString() { + return "Subnode{" + + "nodename='" + nodename + '\'' + + ", nodeitems=" + nodeitems + + ", topicitems=" + topicitems + + '}'; + } + } + + @Override + public String toString() { + return "GuardianSubmodule{" + + "modulename='" + modulename + '\'' + + ", subnodes=" + subnodes + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotIdentifyInfo.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotIdentifyInfo.java new file mode 100644 index 0000000000..4091d0ceb3 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotIdentifyInfo.java @@ -0,0 +1,75 @@ +package com.zhidao.support.adas.high.bean.record; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @des + * @date 2021/8/26 + */ +public class AutopilotIdentifyInfo { + + @SerializedName("action") + private String action; + @SerializedName("result") + private Result result; + + public static class Result { + @SerializedName("panel") + private AutopilotRecordResult panel; + @SerializedName("passwd") + private String passwd; + @SerializedName("user") + private String user; + + public AutopilotRecordResult getPanel() { + return panel; + } + + public void setPanel(AutopilotRecordResult panel) { + this.panel = panel; + } + + public String getPasswd() { + return passwd; + } + + public void setPasswd(String passwd) { + this.passwd = passwd; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + @Override + public String toString() { + return "AutopilotIdentifyInfo{" + + "action='" + action + '\'' + + ", result=" + result + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotRecordResult.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotRecordResult.java new file mode 100644 index 0000000000..144b480fb5 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/AutopilotRecordResult.java @@ -0,0 +1,171 @@ +package com.zhidao.support.adas.high.bean.record; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @author song kenan + * @ 自动驾驶数据采集结果 + * @date 2021/11/8 + */ +public class AutopilotRecordResult { + //磁盘可用空间(M) + @SerializedName("disk_free") + private Long diskFree; + //采集时长(秒) + @SerializedName("duration") + private double duration; + //保存的文件名 + @SerializedName("filename") + private String filename; + //其他信息,包含错误信息等 + @SerializedName("note") + private String note; + //域控制器定义的bag key + @SerializedName("key") + private String key; + //采集状态:100 - 采集成功,自动结束 + //101 - 采集成功,收到结束指令 + //102 -- 达到最大采集时长 + //103 --达到最大采集空间 + //200 - 采集失败 + //201 - 采集中 + //300 - 开始采集 + @SerializedName("stat") + private int stat; + //任务类型:1-badcase采集任务,2-地图数据采集任务 + @SerializedName("type") + private int type; + //任务id + @SerializedName("id") + private int id; + //时间戳,格式:YYYY-MM-DD-hh-mm-ss + @SerializedName("timestamp") + private String timestamp; + //此次数据总大小(M) + @SerializedName("total_size") + private Long totalSize; + //触发指令 + @SerializedName("triggerinfo") + private Triggerinfo triggerinfo; + //各topic数据量 + @SerializedName("records") + private List records; + + public static class Triggerinfo { + //是否为人工接管时自动采集:true - 自动采集 false - 手动触发 + @SerializedName("auto_trig") + private Boolean autoTrig; + //触发采集附带的信息,如任务ID,时间戳等。若为自动采集则此项忽略 + @SerializedName("trig_msg") + private String trigMsg; + + public Boolean getAutoTrig() { + return autoTrig; + } + + public String getTrigMsg() { + return trigMsg; + } + + @Override + public String toString() { + return "Triggerinfo{" + + "autoTrig=" + autoTrig + + ", trigMsg='" + trigMsg + '\'' + + '}'; + } + } + + public static class Records { + //数据记录数 + @SerializedName("nums") + private Integer nums; + //数据来源的topic名 + @SerializedName("topic") + private String topic; + + public Integer getNums() { + return nums; + } + + public String getTopic() { + return topic; + } + + @Override + public String toString() { + return "Records{" + + "nums=" + nums + + ", topic='" + topic + '\'' + + '}'; + } + } + + public Long getDiskFree() { + return diskFree; + } + + public double getDuration() { + return duration; + } + + public String getFilename() { + return filename; + } + + public String getNote() { + return note; + } + + public String getKey() { + return key; + } + + public List getRecords() { + return records; + } + + public int getStat() { + return stat; + } + + public int getType() { + return type; + } + + public int getId() { + return id; + } + + public String getTimestamp() { + return timestamp; + } + + public Long getTotalSize() { + return totalSize; + } + + public Triggerinfo getTriggerinfo() { + return triggerinfo; + } + + @Override + public String toString() { + return "AutopilotRecordResult{" + + "diskFree=" + diskFree + + ", duration=" + duration + + ", filename='" + filename + '\'' + + ", note='" + note + '\'' + + ", key='" + key + '\'' + + ", stat=" + stat + + ", type=" + type + + ", id=" + id + + ", timestamp='" + timestamp + '\'' + + ", totalSize=" + totalSize + + ", triggerinfo=" + triggerinfo + + ", records=" + records + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordCauseParam.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordCauseParam.java new file mode 100644 index 0000000000..9190a5fa01 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordCauseParam.java @@ -0,0 +1,75 @@ +package com.zhidao.support.adas.high.bean.record; + +/** + * @author song kenan + * @des 采集类型 + * @date 2021/8/25 + */ + +public class RecordCauseParam { + private final String action = "record_cause"; + private Result result; + + public String getAction() { + return action; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public static class Result{ + //bag key 唯一标识 + private String key; + //文件路径 + private String filename; + //接管原因 + private String reason; + //接管原因id + private String id; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @Override + public String toString() { + return "RecordDataParam{" + + "action='" + action + '\'' + + ", result='" + result + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordDataParam.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordDataParam.java new file mode 100644 index 0000000000..f3ff1966a9 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/bean/record/RecordDataParam.java @@ -0,0 +1,113 @@ +package com.zhidao.support.adas.high.bean.record; + +/** + * @author song kenan + * @des + * @date 2021/8/25 + */ + +public class RecordDataParam { + private final String action = "record_data"; + private Result result; + + public String getAction() { + return action; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public static class Result{ + //采集时间 + private String time; + //采集指令 true 采集 , false 停止采集 + private boolean isRecord; + //采集类型 1 badcase; 2 map; 3 rests + private int type; + //采集id + private int id; + //是否持续采集 + private boolean sustain; + //采集时间长 + private int duration; + //余留字段 + private String note; + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public boolean getIsRecord() { + return isRecord; + } + + public void setIsRecord(boolean isRecord) { + this.isRecord = isRecord; + } + + public boolean isRecord() { + return isRecord; + } + + public void setRecord(boolean record) { + isRecord = record; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public boolean isSustain() { + return sustain; + } + + public void setSustain(boolean sustain) { + this.sustain = sustain; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + } + + @Override + public String toString() { + return "RecordDataParam{" + + "action='" + action + '\'' + + ", result='" + result + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ActionTypeReceive.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ActionTypeReceive.java new file mode 100644 index 0000000000..edf9426be7 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ActionTypeReceive.java @@ -0,0 +1,144 @@ +package com.zhidao.support.adas.high.common; + +/** + * 接收的Action + * + * @author nie yunlong + * @description 请求值 + * @date 2018/7/3 + */ +public enum ActionTypeReceive { + + /** + * udp 消息渲染 action + */ + ACTION_WS_MSG_RENDER_TYPE("view", 0x101, "渲染流 渲染图像"), + /** + * 周边物体的状态 + */ + ACTION_WS_OBSTACLES_MESSAGE_TYPE("obstacles", -1, "周边物体的状态"), + /** + * 车道线渲染 + */ + ACTION_WS_LANES_MESSAGE_TYPE("lanes", -1, "车道线渲染"), + /** + * 车辆状态 + */ + ACTION_WS_CAR_STATE_TYPE("state", 0x102, "车辆状态"), + /** + * ws 报警消息 action + */ + ACTION_WS_MSG_WARNING_TYPE("warn", -1, "ws 报警消息"), + /** + * 红绿灯状态 + */ + ACTION_UDP_LIGHT_STATE_TYPE("light", -1, "udp 红绿灯状态"), + /** + * 自动驾驶状态 + */ + ACTION_WS_AUTOPILOT_STATUE("autopilotstate", -1, "自动驾驶状态"), + /** + * 自动驾驶控制 + */ + ACTION_WS_AUTOPILOT_CONTROL("autopilotmode", -1, "自动驾驶控制"), + /** + * obu红绿灯数据 + */ + ACTION_WS_AI_CLOUD_OBU_RED_GREEN_LIGHT_TYPE("obuTrafficLight", -1, "obu红绿灯数据"), + /** + * ai云 + */ + ACTION_WS_AI_CLOUD_TYPE("aiCloudToStartAutopilot", -1, "云端控制自动驾驶"), + /** + * 图片大小 + */ + ACTION_WS_MSG_IMAGE_SIZE_TYPE("imagesize", -1, "ws 图片大小"), + /** + * ai云推送 在线车辆 + */ + ACTION_WS_AI_CLOUD_ONLINE_CAR_TYPE("aiCloudToOnLineCar", -1, "云端推送在线车辆"), + /** + * 自动驾驶站点通信 + */ + ACTION_WS_AUTOPILOT_WAY_ARRIVE("autopilotArrive", -1, "自动驾驶站点"), + /** + * 获取自动驾驶路径 + */ + ACTION_WS_AUTOPILOT_ROUTE("global_path", -1, "自动驾驶路径"), + /** + * 查询自动驾驶路径 + */ + ACTION_WS_AUTOPILOT_QUERY_ROUTE("query_global_path", -1, "查询自动驾驶路径"), + /** + * 工控机请求SN + */ + ACTION_WS_AUTOPILOT_SN_REQUEST("autopilotSN", -1, "工控机请求SN"), + /** + * 监控车辆节点 + */ + ACTION_WS_AUTOPILOT_GUARDIAN("guardian", 0x105, "监控车辆节点"), + /** + * BadCase数据采集 + */ + ACTION_WS_AUTOPILOT_IDENTIFY("identify", -1, "BadCase数据采集"), + /** + * 局部轨迹 车前引导钱 + */ + ACTION_WS_AUTOPILOT_TRAJECTORY("trajectory", 0x100, "局部轨迹 车前引导钱"), + /** + * car dock 基础信息 + */ + ACTION_WS_AUTOPILOT_CAR_CONFIG("car_config", -1, "car_config"), + /** + * 轨迹列表 routes + */ + ACTION_WS_AUTOPILOT_ROUTES("route_list", 0x104, "轨迹列表"), + /** + * 工控机升级状态 + */ + ACTION_WS_AUTOPILOT_UPGRADE_STATUS("ipc_upgrade_status", -1, "IPC升级状态"); + + /** + * 消息action 类型 + */ + String mActionType; + + /** + * 消息action code + */ + int mActionCode; + /** + * 描述 + */ + String mMsgDesc; + + ActionTypeReceive(String actionType, int actionCode, String msgDesc) { + this.mActionType = actionType; + this.mActionCode = actionCode; + this.mMsgDesc = msgDesc; + } + + public String getmActionType() { + return mActionType; + } + + public void setmActionType(String mActionType) { + this.mActionType = mActionType; + } + + public int getmActionCode() { + return mActionCode; + } + + public void setmActionCode(int mActionCode) { + this.mActionCode = mActionCode; + } + + public String getmMsgDesc() { + return mMsgDesc; + } + + public void setmMsgDesc(String mMsgDesc) { + this.mMsgDesc = mMsgDesc; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/AppPreferenceHelper.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/AppPreferenceHelper.java new file mode 100644 index 0000000000..7fc5c55a14 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/AppPreferenceHelper.java @@ -0,0 +1,102 @@ +package com.zhidao.support.adas.high.common; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.google.gson.reflect.TypeToken; + +import java.util.HashSet; + +/** + * Created by nie yunlong on 2018/4/13. + * sharePreference 操作 + * 根据 具体情况来写 + */ + +public class AppPreferenceHelper implements IPreferencesHelper { + + public static AppPreferenceHelper appPreferenceHelper; + /** + * 保存deviceId 文件名称 + */ + private final String UDP_RECEIVER_ADDRESS_FILE_NAME = "upd_receive"; + + /** + * 手机Id + */ + private SharedPreferences mDevicePre; + + /** + * 默认data + */ + private String defaultData = "{\"configInfoHistory\":[{\"kCode\":\"d_fcw_touch\",\"kName\":\"前车碰撞预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_fcw_start\",\"kName\":\"前车启动提醒\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_hdw_driver\",\"kName\":\"驾驶员疲劳/危险预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_fcw_person\",\"kName\":\"行人碰撞预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_hdw_sliding\",\"kName\":\"溜车碰撞预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_ldw_out\",\"kName\":\"车道线偏离预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_hmw_follow\",\"kName\":\"跟车距离预警\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0},{\"kCode\":\"d_volume_size\",\"kName\":\"音量大小\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"60\",\"userId\":0},{\"kCode\":\"is_use_ar_navBar\",\"kName\":\"是否使用AR导航\",\"kOrder\":1,\"kType\":\"aided_driving_dic\",\"kVal\":\"5\",\"userId\":0}]}"; + + private HashSet ipcFixationIPSet;//工控机固定IP列表 + + public static AppPreferenceHelper getInstance(Context context) { + if (appPreferenceHelper == null) { + appPreferenceHelper = new AppPreferenceHelper(context.getApplicationContext()); + } + return appPreferenceHelper; + } + + private AppPreferenceHelper(Context context) { + mDevicePre = context.getSharedPreferences(UDP_RECEIVER_ADDRESS_FILE_NAME, Context.MODE_PRIVATE); + getIPCFixationIPList(); + } + + @Override + public void saveUdpClientAddress(String address) { + mDevicePre.edit().putString("upd_client_address", address).apply(); + } + + @Override + public String getUdpClientAddress() { + return mDevicePre.getString("upd_client_address", null); + } + + @Override + public void saveDockConfig(String dockConfig) { + mDevicePre.edit().putString("dock_version", dockConfig).apply(); + } + + @Override + public String getDockConfig() { + return mDevicePre.getString("dock_version", null); + } + + + @Override + public HashSet getIPCFixationIPList() { + if (ipcFixationIPSet == null || ipcFixationIPSet.isEmpty()) { + String json = mDevicePre.getString("ipc_fixation_ip", Constants.DEFAULT_IPC_FIXATION_IP); + ipcFixationIPSet = JsonUtil.fromJson(json, new TypeToken>() { + }.getType()); + + } + return ipcFixationIPSet; + } + + @Override + public void addIPCFixationIP(String ipcIP) { + if (ipcFixationIPSet == null) + ipcFixationIPSet = new HashSet<>(); + if (!ipcFixationIPSet.contains(ipcIP)) { + ipcFixationIPSet.add(ipcIP); + mDevicePre.edit().putString("ipc_fixation_ip", JsonUtil.toJson(ipcFixationIPSet)).apply(); + } + } + + @Override + public void delIPCFixationIP(String ipcIP) { + if (ipcFixationIPSet != null && ipcFixationIPSet.contains(ipcIP)) { + ipcFixationIPSet.remove(ipcIP); + mDevicePre.edit().putString("ipc_fixation_ip", JsonUtil.toJson(ipcFixationIPSet)).apply(); + } + } + + @Override + public void delIPCFixationIP() { + mDevicePre.edit().remove("ipc_fixation_ip").apply(); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Base64.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Base64.java new file mode 100644 index 0000000000..16d63dfef1 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Base64.java @@ -0,0 +1,1033 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package com.zhidao.support.adas.high.common; + + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + *

+ * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

+ *

+ * The class can be parameterized in the following manner with various constructors: + *

    + *
  • URL-safe mode: Default off.
  • + *
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of + * 4 in the encoded data. + *
  • Line separator: Default is CRLF ("\r\n")
  • + *
+ *

+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

+ * + * @see RFC 2045 + * @since 2.2 + */ +public class Base64 { + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + /** + * An empty immutable {@code byte} array. + */ + private static final byte[] EMPTY_BTYE_ARRAY = new byte[0]; + /** + * Chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 2045 section 6.8 + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * Byte used to pad output. + */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in + * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + * + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + /** Mask used to extract 8 bits, used in decoding base64 bytes */ + private static final int MASK_8BITS = 0xff; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + /** + * Line length for encoding. Not used when decoding. A value of zero or less implies no chunking of the base64 + * encoded data. + */ + private final int lineLength; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Buffer for streaming. + */ + private byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + private int pos; + + /** + * Position where next character should be read from the buffer. + */ + private int readPos; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to + * make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + private int currentLinePos; + + /** + * Writes to the buffer only occur after every 3 reads when encoding, an every 4 reads when decoding. This variable + * helps track that. + */ + private int modulus; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this Base64 object becomes useless, + * and must be thrown away. + */ + private boolean eof; + + /** + * Place holder for the 3 bytes we're dealing with for our base64 logic. Bitwise operations store and extract the + * base64 encoding or decoding from this variable. + */ + private int x; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ */ + public Base64() { + this(false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + *

+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ * + * @param urlSafe + * if true, URL-safe encoding is used. In most cases this should be set to + * false. + * @since 1.4 + */ + public Base64(final boolean urlSafe) { + this(CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @since 1.4 + */ + public Base64(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException + * Thrown when the provided lineSeparator included some base64 characters. + * @since 1.4 + */ + public Base64(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param urlSafe + * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * @throws IllegalArgumentException + * The provided lineSeparator included some base64 characters. That's not going to work! + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator, final boolean urlSafe) { + if (lineSeparator == null) { + lineLength = 0; // disable chunk-separating + lineSeparator = EMPTY_BTYE_ARRAY; // this just gets ignored + } + this.lineLength = lineLength > 0 ? (lineLength / 4) * 4 : 0; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + if (lineLength > 0) { + this.encodeSize = 4 + lineSeparator.length; + } else { + this.encodeSize = 4; + } + this.decodeSize = this.encodeSize - 1; + if (containsBase64Byte(lineSeparator)) { + final String sep = newStringUtf8(lineSeparator); + throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); + } + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + * Returns true if this Base64 object has buffered data for reading. + * + * @return true if there is Base64 object still available for reading. + */ + boolean hasData() { + return this.buffer != null; + } + + /** + * Returns the amount of buffered data available for reading. + * + * @return The amount of buffered data available for reading. + */ + int avail() { + return buffer != null ? pos - readPos : 0; + } + + /** Doubles our buffer. */ + private void resizeBuffer() { + if (buffer == null) { + buffer = new byte[DEFAULT_BUFFER_SIZE]; + pos = 0; + readPos = 0; + } else { + final byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(buffer, 0, b, 0, buffer.length); + buffer = b; + } + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail + * bytes. Returns how many bytes were actually extracted. + * + * @param b + * byte[] array to extract the buffered data into. + * @param bPos + * position in byte[] array to start extraction at. + * @param bAvail + * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(final byte[] b, final int bPos, final int bAvail) { + if (buffer != null) { + final int len = Math.min(avail(), bAvail); + if (buffer != b) { + System.arraycopy(buffer, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buffer = null; + } + } else { + // Re-using the original consumer's output array is only + // allowed for one round. + buffer = null; + } + return len; + } + return eof ? -1 : 0; + } + + /** + * Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output + * array for one round (if the consumer calls this method first) instead of starting our own buffer. + * + * @param out + * byte[] array to buffer directly to. + * @param outPos + * Position to start buffering into. + * @param outAvail + * Amount of bytes available for direct buffering. + */ + void setInitialBuffer(final byte[] out, final int outPos, final int outAvail) { + // We can re-use consumer's original output array under + // special circumstances, saving on some System.arraycopy(). + if (out != null && out.length == outAvail) { + buffer = out; + pos = outPos; + readPos = outPos; + } + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last + * remaining bytes (if not multiple of 3). + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of binary data to base64 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void encode(final byte[] in, int inPos, final int inAvail) { + if (eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + eof = true; + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + switch (modulus) { + case 1 : + buffer[pos++] = encodeTable[(x >> 2) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + buffer[pos++] = PAD; + } + break; + + case 2 : + buffer[pos++] = encodeTable[(x >> 10) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 4) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + } + break; + default: + break; // other values ignored + } + if (lineLength > 0 && pos > 0) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + modulus = (++modulus) % 3; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + x = (x << 8) + b; + if (0 == modulus) { + buffer[pos++] = encodeTable[(x >> 18) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 12) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 6) & MASK_6BITS]; + buffer[pos++] = encodeTable[x & MASK_6BITS]; + currentLinePos += 4; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } + } + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of ascii data to base64 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void decode(final byte[] in, int inPos, final int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + } + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < decodeSize) { + resizeBuffer(); + } + final byte b = in[inPos++]; + if (b == PAD) { + // We're done. + eof = true; + break; + } + if (b >= 0 && b < DECODE_TABLE.length) { + final int result = DECODE_TABLE[b]; + if (result >= 0) { + modulus = (++modulus) % 4; + x = (x << 6) + result; + if (modulus == 0) { + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + buffer[pos++] = (byte) (x & MASK_8BITS); + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (eof && modulus != 0) { + x = x << 6; + switch (modulus) { + case 2 : + x = x << 6; + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + break; + case 3 : + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + break; + default: + break; // other values ignored + } + } + } + + /** + * Returns whether or not the octet is in the base 64 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the base 64 alphabet, false otherwise. + * @since 1.4 + */ + public static boolean isBase64(final byte octet) { + return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the + * method treats whitespace as valid. + * + * @param arrayOctet + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; + * false, otherwise + */ + public static boolean isArrayByteBase64(final byte[] arrayOctet) { + for (final byte element : arrayOctet) { + if (!isBase64(element) && !isWhiteSpace(element)) { + return false; + } + } + return true; + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * + * @param arrayOctet + * byte array to test + * @return true if any byte is a valid character in the Base64 alphabet; false herwise + */ + private static boolean containsBase64Byte(final byte[] arrayOctet) { + for (final byte element : arrayOctet) + { + if (isBase64(element)) { + return true; + } + } + return false; + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(final byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm into 76 character blocks separated by CRLF. + *

+ * For a non-chunking version, see {@link #encodeBase64StringUnChunked(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 1.4 + */ + public static String encodeBase64String(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, true)); + } + + /** + * Encodes binary data using the base64 algorithm, without using chunking. + *

+ * For a chunking version, see {@link #encodeBase64String(byte[])}. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64StringUnChunked(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using the base64 algorithm. + * + * @param binaryData + * binary data to encode + * @param useChunking whether to split the output into chunks + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64String(final byte[] binaryData, final boolean useChunking) { + return newStringUtf8(encodeBase64(binaryData, useChunking)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(final byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The + * url-safe variation emits - and _ instead of + and / characters. + * + * @param binaryData + * binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData + * binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(final byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Decodes a String containing containing characters in the Base64 alphabet. + * + * @param pArray + * A String containing Base64 character data + * @return a byte array containing binary data + * @since 1.4 + */ + public byte[] decode(final String pArray) { + return decode(getBytesUtf8(pArray)); + } + + private byte[] getBytesUtf8(final String pArray) { + return pArray.getBytes(StandardCharsets.UTF_8); + } + + /** + * Decodes a byte[] containing containing characters in the Base64 alphabet. + * + * @param pArray + * A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public byte[] decode(final byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + final long len = (pArray.length * 3) / 4; + final byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + decode(pArray, 0, pArray.length); + decode(pArray, 0, -1); // Notify decoder of EOF. + + // Would be nice to just return buf (like we sometimes do in the encode + // logic), but we have no idea what the line-length was (could even be + // variable). So we cannot determine ahead of time exactly how big an + // array is necessary. Hence the need to construct a 2nd byte array to + // hold the final result: + + final byte[] result = new byte[pos]; + readResults(result, 0, result.length); + return result; + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @param maxResultSize + * The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe, + final int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + final long len = getEncodeLength(binaryData, isChunked ? CHUNK_SIZE : 0, + isChunked ? CHUNK_SEPARATOR : EMPTY_BTYE_ARRAY); + if (len > maxResultSize) { + throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + len + + ") than the specified maxium size of " + maxResultSize); + } + + final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + return b64.encode(binaryData); + } + + /** + * Decodes a Base64 String into octets. + * + * @param base64String + * String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(final String base64String) { + return new Base64().decode(base64String); + } + + /** + * Decodes Base64 data into octets. + * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(final byte[] base64Data) { + return new Base64().decode(base64Data); + } + + /** + * Checks if a byte value is whitespace or not. + * + * @param byteToCheck + * the byte to check + * @return true if byte is whitespace, false otherwise + */ + private static boolean isWhiteSpace(final byte byteToCheck) { + switch (byteToCheck) { + case ' ' : + case '\n' : + case '\r' : + case '\t' : + return true; + default : + return false; + } + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A String containing only Base64 character data + * @since 1.4 + */ + public String encodeToString(final byte[] pArray) { + return newStringUtf8(encode(pArray)); + } + + private static String newStringUtf8(final byte[] encode) { + return new String(encode, StandardCharsets.UTF_8); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public byte[] encode(final byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + final long len = getEncodeLength(pArray, lineLength, lineSeparator); + byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + encode(pArray, 0, pArray.length); + encode(pArray, 0, -1); // Notify encoder of EOF. + // Encoder might have resized, even though it was unnecessary. + if (buffer != buf) { + readResults(buf, 0, buf.length); + } + // In URL-SAFE mode we skip the padding characters, so sometimes our + // final length is a bit smaller. + if (isUrlSafe() && pos < buf.length) { + final byte[] smallerBuf = new byte[pos]; + System.arraycopy(buf, 0, smallerBuf, 0, pos); + buf = smallerBuf; + } + return buf; + } + + /** + * Pre-calculates the amount of space needed to base64-encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @param chunkSize line-length of the output (<= 0 means no chunking) between each + * chunkSeparator (e.g. CRLF). + * @param chunkSeparator the sequence of bytes used to separate chunks of output (e.g. CRLF). + * + * @return amount of space needed to encoded the supplied array. Returns + * a long since a max-len array will require Integer.MAX_VALUE + 33%. + */ + private static long getEncodeLength(final byte[] pArray, int chunkSize, final byte[] chunkSeparator) { + // base64 always encodes to multiples of 4. + chunkSize = (chunkSize / 4) * 4; + + long len = (pArray.length * 4) / 3; + final long mod = len % 4; + if (mod != 0) { + len += 4 - mod; + } + if (chunkSize > 0) { + final boolean lenChunksPerfectly = len % chunkSize == 0; + len += (len / chunkSize) * chunkSeparator.length; + if (!lenChunksPerfectly) { + len += chunkSeparator.length; + } + } + return len; + } + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param pArray + * a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(final byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param bigInt + * a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException + * if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(final BigInteger bigInt) { + if (bigInt == null) { + throw new NullPointerException("encodeInteger called with null parameter"); + } + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Returns a byte-array representation of a BigInteger without sign bit. + * + * @param bigInt + * BigInteger to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(final BigInteger bigInt) { + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + final byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + final byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Resets this Base64 object to its initial newly constructed state. + */ + private void reset() { + buffer = null; + pos = 0; + readPos = 0; + currentLinePos = 0; + modulus = 0; + eof = false; + } + + // Getters for use in testing + + int getLineLength() { + return lineLength; + } + + byte[] getLineSeparator() { + return lineSeparator.clone(); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseIoUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseIoUtils.java new file mode 100644 index 0000000000..c5cbd962b6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseIoUtils.java @@ -0,0 +1,84 @@ +package com.zhidao.support.adas.high.common; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Created by nie yunlong on 2018/4/9. + * IO 操作 + */ + +public class BaseIoUtils { + + public static final int DEFAULT_IMAGE_TOTAL_SIZE = 500 * 1024; // 500 Kb + + /** + * 从输入流中获取数据 + * + * @param inStream 输入流 + * @return + * @throws Exception + */ + public static byte[] readInputStreamToByte(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toByteArray(); + } + + /** + * 从输入流中获取数据 + * + * @param inStream 输入流 + * @return + * @throws Exception + */ + public static String readInputStreamToString(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + inStream.close(); + return outStream.toString("utf-8"); + } + + public static void closeSilently(Closeable closeable) { + + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + } + + public static boolean copyStream(InputStream is, OutputStream os, int bufferSize) + throws IOException { + int current = 0; + int total = is.available(); + if (total <= 0) { + total = DEFAULT_IMAGE_TOTAL_SIZE; + } + + final byte[] bytes = new byte[bufferSize]; + int count; + while ((count = is.read(bytes, 0, bufferSize)) != -1) { + os.write(bytes, 0, count); + current += count; + } + os.flush(); + return true; + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseSDCardHelper.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseSDCardHelper.java new file mode 100644 index 0000000000..1fdceec991 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseSDCardHelper.java @@ -0,0 +1,276 @@ +package com.zhidao.support.adas.high.common; + +import android.content.Context; +import android.os.Environment; +import android.os.StatFs; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BaseSDCardHelper { + + // 判断SD卡是否被挂载 + public static boolean isSDCardMounted() { + // return Environment.getExternalStorageState().equals("mounted"); + return Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED); + } + + // 获取SD卡的根目录 + public static String getSDCardBaseDir() { + if (isSDCardMounted()) { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } + return null; + } + + + // 获取SD卡的完整空间大小,返回MB + public static long getSDCardSize() { + if (isSDCardMounted()) { + StatFs fs = new StatFs(getSDCardBaseDir()); + int count = fs.getBlockCount(); + int size = fs.getBlockSize(); + return count * size / 1024 / 1024; + } + return 0; + } + + // 获取SD卡的剩余空间大小 + public static long getSDCardFreeSize() { + if (isSDCardMounted()) { + StatFs fs = new StatFs(getSDCardBaseDir()); + int count = fs.getFreeBlocks(); + int size = fs.getBlockSize(); + return count * size / 1024 / 1024; + } + return 0; + } + + // 获取SD卡的可用空间大小 + public static long getSDCardAvailableSize() { + if (isSDCardMounted()) { + StatFs fs = new StatFs(getSDCardBaseDir()); + int count = fs.getAvailableBlocks(); + int size = fs.getBlockSize(); + return count * size / 1024 / 1024; + } + return 0; + } + + // 往SD卡的公有目录下保存文件 + public static boolean saveFileToSDCardPublicDir(byte[] data, String type, + String fileName) { + BufferedOutputStream bos = null; + if (isSDCardMounted()) { + File file = Environment.getExternalStoragePublicDirectory(type); + try { + bos = new BufferedOutputStream(new FileOutputStream(new File( + file, fileName))); + bos.write(data); + bos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + bos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return false; + } + + // 往SD卡的自定义目录下保存文件 + public static boolean saveFileToSDCardCustomDir(byte[] data, String dir, + String fileName) { + BufferedOutputStream bos = null; + if (isSDCardMounted()) { + File file = new File(getSDCardBaseDir() + File.separator + dir); + if (!file.exists()) { + file.mkdirs();// 递归创建自定义目录 + } + try { + bos = new BufferedOutputStream(new FileOutputStream(new File( + file, fileName))); + bos.write(data); + bos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + bos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return false; + } + + // 往SD卡的私有Files目录下保存文件 + public static boolean saveFileToSDCardPrivateFilesDir(byte[] data, + String type, String fileName, Context context) { + BufferedOutputStream bos = null; + if (isSDCardMounted()) { + File file = context.getExternalFilesDir(type); + try { + bos = new BufferedOutputStream(new FileOutputStream(new File( + file, fileName))); + bos.write(data); + bos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + bos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return false; + } + + // 往SD卡的私有Cache目录下保存文件 + public static boolean saveFileToSDCardPrivateCacheDir(byte[] data, + String fileName, Context context) { + BufferedOutputStream bos = null; + if (isSDCardMounted()) { + File file = context.getExternalCacheDir(); + try { + bos = new BufferedOutputStream(new FileOutputStream(new File( + file, fileName))); + bos.write(data); + bos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + bos.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return false; + } + + // 从SD卡获取文件 + public static byte[] loadFileFromSDCard(String fileDir) { + BufferedInputStream bis = null; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try { + bis = new BufferedInputStream( + new FileInputStream(new File(fileDir))); + byte[] buffer = new byte[8 * 1024]; + int c = 0; + while ((c = bis.read(buffer)) != -1) { + baos.write(buffer, 0, c); + baos.flush(); + } + return baos.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + baos.close(); + if (bis != null) { + bis.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + // 获取SD卡公有目录的路径 + public static String getSDCardPublicDir(String type) { + return Environment.getExternalStoragePublicDirectory(type).toString(); + } + + // 获取SD卡私有Cache目录的路径 + public static String getSDCardPrivateCacheDir(Context context) { + return context.getExternalCacheDir().getAbsolutePath(); + } + + // 获取SD卡私有Files目录的路径 + public static String getSDCardPrivateFilesDir(Context context, String type) { + return context.getExternalFilesDir(type).getAbsolutePath(); + } + + // 获取SD卡文件目录的路径 + public static String getSDCardPrivateFilesDirOne(Context context, + String type) { + return Environment.getExternalStorageDirectory().getAbsolutePath() + + File.separator + type; + } + + /* + * 获取sdcard绝对物理路径 + */ + public static String getSDCardPath() { + if (isSDCardMounted()) { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } else { + return null; + } + } + + /** + * 删除文件夹 + * @param path + */ + public static boolean deleteAllFilesOfDir(File path) { + if (!path.exists()){ + return false; + } + if (path.isFile()) { + path.delete(); + return true; + } + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) { + deleteAllFilesOfDir(files[i]); + } + path.delete(); + System.out.println("删除文件夹成功"); + return true; + } + /** + * 删除文件夹2 + * @param path + */ + public static void deleteAllFilesOfDir2(File path) { + if (!path.exists()){ + return ; + } + if (path.isFile()) { + path.delete(); + return ; + } + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) { + deleteAllFilesOfDir(files[i]); + } + path.delete(); + System.out.println("删除文件夹成功"); + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseTimeUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseTimeUtils.java new file mode 100644 index 0000000000..19d015f249 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/BaseTimeUtils.java @@ -0,0 +1,139 @@ +package com.zhidao.support.adas.high.common; + +import android.text.TextUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * @author nie yunlong + * @description + * @date 2018/7/25 + */ +public class BaseTimeUtils { + + + /** + * 格式化日期 + * + * @return + */ + public static String[] formatTimeDate() { + Date d = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd-HH:mm"); + String time = sdf.format(d); + if (!TextUtils.isEmpty(time) && time.contains("-")) { + String[] timeSplit = time.split("-"); + return timeSplit; + } + return null; + } + + /** + * 格式化时间 + * + * @param time + * @return + */ + public static String formatTimeDate1(long time) { + Date date = new Date(); + date.setTime(time); + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); + return sdf.format(date); + } + + /** + * 格式化时间 + * + * @param time + * @return + */ + public static String formatTimeDate(long time, String format) { + Date date = new Date(); + date.setTime(time); + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(date); + } + + /** + * 格式化时间 + * + * @return + */ + public static String formatTimeDate2() { + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(date); + } + + public static String formatTimeDate3() { + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss"); + return sdf.format(date); + } + + + /** + * + *

Description: 本地时间转化为UTC时间

+ * @param localTime + * @return + * @author wgs + * @date 2018年10月19日 下午2:23:43 + * + */ + public static Date localToUTC(String localTime) { +// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); +// Date localDate= null; +// try { +// localDate = sdf.parse(localTime); +// } catch (ParseException e) { +// e.printStackTrace(); +// } + long localTimeInMillis=Long.parseLong(localTime); + /** long时间转换成Calendar */ + Calendar calendar= Calendar.getInstance(); + calendar.setTimeInMillis(localTimeInMillis); + /** 取得时间偏移量 */ + int zoneOffset = calendar.get(java.util.Calendar.ZONE_OFFSET); + /** 取得夏令时差 */ + int dstOffset = calendar.get(java.util.Calendar.DST_OFFSET); + /** 从本地时间里扣除这些差量,即可以取得UTC时间*/ + calendar.add(java.util.Calendar.MILLISECOND, -(zoneOffset + dstOffset)); + /** 取得的时间就是UTC标准时间 */ + Date utcDate=new Date(calendar.getTimeInMillis()); + return utcDate; + } + + /** + * + *

Description:UTC时间转化为本地时间

+ * @param utcTime + * @return + * @author wgs + * @date 2018年10月19日 下午2:23:24 + * + */ + public static Date utcToLocal(String utcTime){ + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + Date utcDate = null; + try { + utcDate = sdf.parse(utcTime); + } catch (ParseException e) { + e.printStackTrace(); + } + sdf.setTimeZone(TimeZone.getDefault()); + Date locatlDate = null; + String localTime = sdf.format(utcDate.getTime()); + try { + locatlDate = sdf.parse(localTime); + } catch (ParseException e) { + e.printStackTrace(); + } + return locatlDate; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Constants.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Constants.java new file mode 100644 index 0000000000..50f64645ee --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Constants.java @@ -0,0 +1,74 @@ +package com.zhidao.support.adas.high.common; + +/** + * @ProjectName: lib-adas-fpga + * @Package: PACKAGE_NAME + * @ClassName: com.zhidao.lib.adas.high.common.Constants + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/7 15:53 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/7 15:53 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class Constants { + /** + * ws 主机地址 需要从udp里面获取 + */ + public static final String WS_IP_HOST_HEAD = "ws:"; + /** + * ws 端口以及资源名称 + */ + public static final String WS_PORT = ":4110/xvideo-detector"; + + /** + * 发送位置信息 action + */ + public static final int WS_SEND_LOCATION = 2; + + /** + * 查询自动驾驶轨迹 + */ + public static final String QUERY_GLOBAL_PATH = "{\"action\":\"query_global_path\",\"result\":null}"; + /** + * 查询节点信息 + */ + public static final String QUERY_GUARDIAN = "{\"action\":\"query_guardian\",\"result\":null}"; + + /** + * 查询docker版本 + */ + public static final String QUERY_CAR_CONFIG = "{\"action\":\"car_config\",\"result\":null}"; + + /** + * 查询轨迹 + */ + public static final String QUERY_ROUTES = "{\"action\":\"query_routes\",\"result\":null}"; + + + public static final String DEFAULT_IPC_FIXATION_IP = "[\"192.168.1.102\", \"192.168.8.102\"]"; + + public interface IPC_CONNECTION_STATUS { + /** + * 已连接 + */ + int CONNECTED = 0x00; + /** + * 未连接 + */ + int DISCONNECTED = 0x01; + /** + * 正在连接 + */ + int CONNECTING = 0x02; + /** + * 正在搜索IP + */ + int SEARCH_ADDRESS = 0x03; + /** + * 找不到可用IP 传入的IP不可用或固定IP列表中所有IP不可用 + */ + int NOT_FOUND_ADDRESS = 0x04; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/CupidLogUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/CupidLogUtils.java new file mode 100644 index 0000000000..1b3c9d2217 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/CupidLogUtils.java @@ -0,0 +1,85 @@ +package com.zhidao.support.adas.high.common; + +import android.util.Log; + +import com.zhidao.support.adas.high.BuildConfig; + +/** + * @author nie yunlong + * @description video-lib + * @date 2018/6/15 + */ +public class CupidLogUtils { + /** + * 默认是打开日志 + */ +// private static boolean mIsEnableLog = BuildConfig.DEBUG; + private static boolean mIsEnableLog = true; + /** + * 是否写日志 + */ + private static boolean isWriteLog = true; + + /** + * @param isEnableLog true开启 false关闭 + */ + public static void setEnableLog(boolean isEnableLog) { + mIsEnableLog = isEnableLog; + } + + + public static boolean isIsWriteLog() { + return isWriteLog; + } + + public static void setIsWriteLog(boolean isWriteLog) { + CupidLogUtils.isWriteLog = isWriteLog; + } + + public static void i(String tag, String msg) { + if (!mIsEnableLog) { + return; + } + Log.i(tag, msg); + } + + public static void w(String tag, String msg) { + if (!mIsEnableLog) { + return; + } + Log.w(tag, msg); + } + + public static void e(String tag, String msg) { + if (!mIsEnableLog) { + return; + } + Log.e(tag, msg); + } + + public static void e(String msg) { + if (!mIsEnableLog) { + return; + } + Log.e("elita_lib", msg); + } + + public static void w(String msg) { + if (!mIsEnableLog) { + return; + } + Log.e("elita_lib", msg); + } + + /** + * 写文件 + * + * @param msg + */ + public static void writeOwnerCarStateLog(String msg) { + if (!isWriteLog) { + return; + } + LogSaveManage.getInstance().saveWrite(msg); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Define.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Define.java new file mode 100644 index 0000000000..612fda44a1 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/Define.java @@ -0,0 +1,18 @@ +package com.zhidao.support.adas.high.common; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public final class Define { + + @IntDef(flag = true, value = {Constants.IPC_CONNECTION_STATUS.CONNECTED, + Constants.IPC_CONNECTION_STATUS.DISCONNECTED, + Constants.IPC_CONNECTION_STATUS.CONNECTING, + Constants.IPC_CONNECTION_STATUS.SEARCH_ADDRESS, + Constants.IPC_CONNECTION_STATUS.NOT_FOUND_ADDRESS}) + @Retention(RetentionPolicy.SOURCE) + public @interface IPCConnectionStatus { + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/DigitalTrans.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/DigitalTrans.java new file mode 100644 index 0000000000..fe344bee47 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/DigitalTrans.java @@ -0,0 +1,516 @@ +package com.zhidao.support.adas.high.common; + +public class DigitalTrans { + + /** + * 数字字符串转ASCII码字符串 + * + * @param content 字符串 + * @return ASCII字符串 + */ + public static String StringToAsciiString(String content) { + String result = ""; + int max = content.length(); + for (int i = 0; i < max; i++) { + char c = content.charAt(i); + String b = Integer.toHexString(c); + result = result + b; + } + return result; + } + + /** + * 十六进制转字符串 + * + * @param hexString 十六进制字符串 + * @param encodeType 编码类型4:Unicode,2:普通编码 + * @return 字符串 + */ + public static String hexStringToString(String hexString, int encodeType) { + String result = ""; + int max = hexString.length() / encodeType; + for (int i = 0; i < max; i++) { + char c = (char) DigitalTrans.hexStringToAlgorism(hexString + .substring(i * encodeType, (i + 1) * encodeType)); + result += c; + } + return result; + } + + /** + * 十六进制字符串装十进制 + * + * @param hex 十六进制字符串 + * @return 十进制数值 + */ + public static int hexStringToAlgorism(String hex) { + hex = hex.toUpperCase(); + int max = hex.length(); + int result = 0; + for (int i = max; i > 0; i--) { + char c = hex.charAt(i - 1); + int algorism = 0; + if (c >= '0' && c <= '9') { + algorism = c - '0'; + } else { + algorism = c - 55; + } + result += Math.pow(16, max - i) * algorism; + } + return result; + } + + /** + * 十六转二进制 + * + * @param hex 十六进制字符串 + * @return 二进制字符串 + */ + public static String hexStringToBinary(String hex) { + hex = hex.toUpperCase(); + String result = ""; + int max = hex.length(); + for (int i = 0; i < max; i++) { + char c = hex.charAt(i); + switch (c) { + case '0': + result += "0000"; + break; + case '1': + result += "0001"; + break; + case '2': + result += "0010"; + break; + case '3': + result += "0011"; + break; + case '4': + result += "0100"; + break; + case '5': + result += "0101"; + break; + case '6': + result += "0110"; + break; + case '7': + result += "0111"; + break; + case '8': + result += "1000"; + break; + case '9': + result += "1001"; + break; + case 'A': + result += "1010"; + break; + case 'B': + result += "1011"; + break; + case 'C': + result += "1100"; + break; + case 'D': + result += "1101"; + break; + case 'E': + result += "1110"; + break; + case 'F': + result += "1111"; + break; + } + } + return result; + } + + /** + * ASCII码字符串转数字字符串 + * + * @param content ASCII字符串 + * @return 字符串 + */ + public static String AsciiStringToString(String content) { + String result = ""; + int length = content.length() / 2; + for (int i = 0; i < length; i++) { + String c = content.substring(i * 2, i * 2 + 2); + int a = hexStringToAlgorism(c); + char b = (char) a; + String d = String.valueOf(b); + result += d; + } + return result; + } + + /** + * 将十进制转换为指定长度的十六进制字符串 + * + * @param algorism int 十进制数字 + * @param maxLength int 转换后的十六进制字符串长度 + * @return String 转换后的十六进制字符串 + */ + public static String algorismToHEXString(int algorism, int maxLength) { + String result = ""; + result = Integer.toHexString(algorism); + + if (result.length() % 2 == 1) { + result = "0" + result; + } + return patchHexString(result.toUpperCase(), maxLength); + } + + /** + * 字节数组转为普通字符串(ASCII对应的字符) + * + * @param bytearray byte[] + * @return String + */ + public static String bytetoString(byte[] bytearray) { + String result = ""; + char temp; + + int length = bytearray.length; + for (int i = 0; i < length; i++) { + temp = (char) bytearray[i]; + result += temp; + } + return result; + } + + /** + * 二进制字符串转十进制 + * + * @param binary 二进制字符串 + * @return 十进制数值 + */ + public static int binaryToAlgorism(String binary) { + int max = binary.length(); + int result = 0; + for (int i = max; i > 0; i--) { + char c = binary.charAt(i - 1); + int algorism = c - '0'; + result += Math.pow(2, max - i) * algorism; + } + return result; + } + + /** + * 十进制转换为十六进制字符串 + * + * @param algorism int 十进制的数字 + * @return String 对应的十六进制字符串 + */ + public static String algorismToHEXString(int algorism) { + String result = ""; + result = Integer.toHexString(algorism); + + if (result.length() % 2 == 1) { + result = "0" + result; + + } + result = result.toUpperCase(); + + return result; + } + + /** + * HEX字符串前补0,主要用于长度位数不足。 + * + * @param str String 需要补充长度的十六进制字符串 + * @param maxLength int 补充后十六进制字符串的长度 + * @return 补充结果 + */ + static public String patchHexString(String str, int maxLength) { + String temp = ""; + for (int i = 0; i < maxLength - str.length(); i++) { + temp = "0" + temp; + } + str = (temp + str).substring(0, maxLength); + return str; + } + + /** + * 将一个字符串转换为int + * + * @param s String 要转换的字符串 + * @param defaultInt int 如果出现异常,默认返回的数字 + * @param radix int 要转换的字符串是什么进制的,如16 8 10. + * @return int 转换后的数字 + */ + public static int parseToInt(String s, int defaultInt, int radix) { + int i = 0; + try { + i = Integer.parseInt(s, radix); + } catch (NumberFormatException ex) { + i = defaultInt; + } + return i; + } + + /** + * 将一个十进制形式的数字字符串转换为int + * + * @param s String 要转换的字符串 + * @param defaultInt int 如果出现异常,默认返回的数字 + * @return int 转换后的数字 + */ + public static int parseToInt(String s, int defaultInt) { + int i = 0; + try { + i = Integer.parseInt(s); + } catch (NumberFormatException ex) { + i = defaultInt; + } + return i; + } + + /** + * 十六进制字符串转为Byte数组,每两个十六进制字符转为一个Byte + * + * @param hex 十六进制字符串 + * @return byte 转换结果 + */ + public static byte[] hexStringToByte(String hex) { + int max = hex.length() / 2; + byte[] bytes = new byte[max]; + String binarys = DigitalTrans.hexStringToBinary(hex); + for (int i = 0; i < max; i++) { + bytes[i] = (byte) DigitalTrans.binaryToAlgorism(binarys.substring( + i * 8 + 1, (i + 1) * 8)); + if (binarys.charAt(8 * i) == '1') { + bytes[i] = (byte) (0 - bytes[i]); + } + } + return bytes; + } + + /** + * 十六进制串转化为byte数组 + * + * @return the array of byte + */ + public static final byte[] hex2byte(String hex) + throws IllegalArgumentException { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException(); + } + char[] arr = hex.toCharArray(); + byte[] b = new byte[hex.length() / 2]; + for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) { + String swap = "" + arr[i++] + arr[i]; + int byteint = Integer.parseInt(swap, 16) & 0xFF; + b[j] = new Integer(byteint).byteValue(); + } + return b; + } + + /** + * 字节数组转换为十六进制字符串 + * + * @param b byte[] 需要转换的字节数组 + * @return String 十六进制字符串 + */ + public static final String byte2hex(byte b[]) { + if (b == null) { + throw new IllegalArgumentException( + "Argument b ( byte array ) is null! "); + } + String hs = ""; + String stmp = ""; + for (int n = 0; n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0xff); + if (stmp.length() == 1) { + hs = hs + "0" + stmp; + } else { + hs = hs + stmp; + } + } + return hs.toUpperCase(); + } + + /** + * 字符串转换成十六进制字符串 + * + * @param String str 待转换的ASCII字符串 + * @return String 每个Byte之间空格分隔,如: [61 6C 6B] + */ + public static String str2HexStr(String str) { + + char[] chars = "0123456789ABCDEF".toCharArray(); + StringBuilder sb = new StringBuilder(""); + byte[] bs = str.getBytes(); + int bit; + + for (int i = 0; i < bs.length; i++) { + bit = (bs[i] & 0x0f0) >> 4; + sb.append(chars[bit]); + bit = bs[i] & 0x0f; + sb.append(chars[bit]); + sb.append(' '); + } + return sb.toString().trim(); + } + + /** + * 十六进制转换字符串 + * + * @param String str Byte字符串(Byte之间无分隔符 如:[616C6B]) + * @return String 对应的字符串 + */ + public static String hexStr2Str(String hexStr) { + String str = "0123456789ABCDEF"; + char[] hexs = hexStr.toCharArray(); + byte[] bytes = new byte[hexStr.length() / 2]; + int n; + + for (int i = 0; i < bytes.length; i++) { + n = str.indexOf(hexs[2 * i]) * 16; + n += str.indexOf(hexs[2 * i + 1]); + bytes[i] = (byte) (n & 0xff); + } + return new String(bytes); + } + + /** + * bytes转换成十六进制字符串 + * + * @param byte[] b byte数组 + * @return String 每个Byte值之间空格分隔 + */ + public static String byte2HexStr(byte[] b) { + String stmp = ""; + StringBuilder sb = new StringBuilder(""); + for (int n = 0; n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0xFF); + sb.append((stmp.length() == 1) ? "0" + stmp : stmp); + sb.append(" "); + } + return sb.toString().toUpperCase().trim(); + } + + /** + * bytes字符串转换为Byte值 + * + * @param String src Byte字符串,每个Byte之间没有分隔符 + * @return byte[] + */ + public static byte[] hexStr2Bytes(String src) { + int m = 0, n = 0; + int l = src.length() / 2; + System.out.println(l); + byte[] ret = new byte[l]; + for (int i = 0; i < l; i++) { + m = i * 2 + 1; + n = m + 1; + ret[i] = Byte.decode("0x" + src.substring(i * 2, m) + src.substring(m, n)); + } + return ret; + } + + /** + * String的字符串转换成unicode的String + * + * @param String strText 全角字符串 + * @return String 每个unicode之间无分隔符 + * @throws Exception + */ + public static String strToUnicode(String strText) + throws Exception { + char c; + StringBuilder str = new StringBuilder(); + int intAsc; + String strHex; + for (int i = 0; i < strText.length(); i++) { + c = strText.charAt(i); + intAsc = (int) c; + strHex = Integer.toHexString(intAsc); + if (intAsc > 128) + str.append("\\u" + strHex); + else // 低位在前面补00 + str.append("\\u00" + strHex); + } + return str.toString(); + } + + /** + * unicode的String转换成String的字符串 + * + * @param String hex 16进制值字符串 (一个unicode为2byte) + * @return String 全角字符串 + */ + public static String unicodeToString(String hex) { + int t = hex.length() / 6; + StringBuilder str = new StringBuilder(); + for (int i = 0; i < t; i++) { + String s = hex.substring(i * 6, (i + 1) * 6); + // 高位需要补上00再转 + String s1 = s.substring(2, 4) + "00"; + // 低位直接转 + String s2 = s.substring(4); + // 将16进制的string转为int + int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16); + // 将int转换为字符 + char[] chars = Character.toChars(n); + str.append(new String(chars)); + } + return str.toString(); + } + + /** + * 整型数据拆分为长度为2的字节数组,低8位存放在序号小的元素,高8位存放在序号大的元素中(大端存储) + * + * @param data + * @return + */ + public static byte[] intTo2Byte(int data) { + byte[] byteArray = new byte[2]; + byteArray[0] = (byte) (data >> 8); + byteArray[1] = (byte) data; + return byteArray; + } + + /** + * 将两个字节拼接还原成无符号的整型数据 + * 数据域中的数据都按小端存储,示例:数据0x1234,则Byte0为0x34,Byte1为0x12 + * + * @param byte1 + * @param byte2 + * @return + */ + public static int joint2ByteToInt(byte byte1, byte byte2) { + int result = byte1 & 0xFF; + result = (result << 8) | (0x00FF & byte2); + return result; + } + + /** + * 拼接两个byte[] + * + * @param + * @param + * @return + */ + public static byte[] concatBytes(byte[] bt1, byte[] bt2) { + if (bt1 == null) { + return bt2; + } + if (bt2 == null) { + return bt1; + } + byte[] bt3 = new byte[bt1.length + bt2.length]; + System.arraycopy(bt1, 0, bt3, 0, bt1.length); + System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length); + return bt3; + } + + //获得本机cpu大小端 + public static boolean isBigendian() { + short i = 0x1; + boolean bRet = ((i >> 8) == 0x1); + return bRet; + } + +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/FileUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/FileUtils.java new file mode 100644 index 0000000000..4376c74e9a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/FileUtils.java @@ -0,0 +1,1543 @@ +package com.zhidao.support.adas.high.common; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import javax.net.ssl.HttpsURLConnection; + +/** + *
+ *     author: Blankj
+ *     blog  : http://blankj.com
+ *     time  : 2016/05/03
+ *     desc  : utils about file
+ * 
+ */ +public final class FileUtils { + + private static final String LINE_SEP = System.getProperty("line.separator"); + + private FileUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * Return the file by path. + * + * @param filePath The path of file. + * @return the file + */ + public static File getFileByPath(final String filePath) { + return isSpace(filePath) ? null : new File(filePath); + } + + /** + * Return whether the file exists. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final String filePath) { + return isFileExists(getFileByPath(filePath)); + } + + /** + * Return whether the file exists. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + /** + * Rename the file. + * + * @param filePath The path of file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final String filePath, final String newName) { + return rename(getFileByPath(filePath), newName); + } + + /** + * Rename the file. + * + * @param file The file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final File file, final String newName) { + // file is null then return false + if (file == null) return false; + // file doesn't exist then return false + if (!file.exists()) return false; + // the new name is space then return false + if (isSpace(newName)) return false; + // the new name equals old name then return true + if (newName.equals(file.getName())) return true; + File newFile = new File(file.getParent() + File.separator + newName); + // the new name of file exists then return false + return !newFile.exists() + && file.renameTo(newFile); + } + + /** + * Return whether it is a directory. + * + * @param dirPath The path of directory. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final String dirPath) { + return isDir(getFileByPath(dirPath)); + } + + /** + * Return whether it is a directory. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final File file) { + return file != null && file.exists() && file.isDirectory(); + } + + /** + * Return whether it is a file. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final String filePath) { + return isFile(getFileByPath(filePath)); + } + + /** + * Return whether it is a file. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final File file) { + return file != null && file.exists() && file.isFile(); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param dirPath The path of directory. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final String dirPath) { + return createOrExistsDir(getFileByPath(dirPath)); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final File file) { + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param filePath The path of file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final String filePath) { + return createOrExistsFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final File file) { + if (file == null) return false; + if (file.exists()) return file.isFile(); + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final String filePath) { + return createFileByDeleteOldFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final File file) { + if (file == null) return false; + // file exists and unsuccessfully delete then return false + if (file.exists() && !file.delete()) return false; + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, false); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, false); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, false); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, false); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, true); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, true); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, true); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, true); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final boolean isMove) { + return copyOrMoveDir(srcDir, destDir, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener, + final boolean isMove) { + if (srcDir == null || destDir == null) return false; + // destDir's path locate in srcDir's path then return false + String srcPath = srcDir.getPath() + File.separator; + String destPath = destDir.getPath() + File.separator; + if (destPath.contains(srcPath)) return false; + if (!srcDir.exists() || !srcDir.isDirectory()) return false; + if (destDir.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old directory + if (!deleteAllInDir(destDir)) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destDir)) return false; + File[] files = srcDir.listFiles(); + for (File file : files) { + File oneDestFile = new File(destPath + file.getName()); + if (file.isFile()) { + if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; + } else if (file.isDirectory()) { + if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; + } + } + return !isMove || deleteDir(srcDir); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final boolean isMove) { + return copyOrMoveFile(srcFile, destFile, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener, + final boolean isMove) { + if (srcFile == null || destFile == null) return false; + // srcFile equals destFile then return false + if (srcFile.equals(destFile)) return false; + // srcFile doesn't exist or isn't a file then return false + if (!srcFile.exists() || !srcFile.isFile()) return false; + if (destFile.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old file + if (!destFile.delete()) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destFile.getParentFile())) return false; + try { + return writeFileFromIS(destFile, new FileInputStream(srcFile)) + && !(isMove && !deleteFile(srcFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Delete the directory. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final String filePath) { + return delete(getFileByPath(filePath)); + } + + /** + * Delete the directory. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final File file) { + if (file == null) return false; + if (file.isDirectory()) { + return deleteDir(file); + } + return deleteFile(file); + } + + /** + * Delete the directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final String dirPath) { + return deleteDir(getFileByPath(dirPath)); + } + + /** + * Delete the directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final File dir) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + return dir.delete(); + } + + /** + * Delete the file. + * + * @param srcFilePath The path of source file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final String srcFilePath) { + return deleteFile(getFileByPath(srcFilePath)); + } + + /** + * Delete the file. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final File file) { + return file != null && (!file.exists() || file.isFile() && file.delete()); + } + + /** + * Delete the all in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final String dirPath) { + return deleteAllInDir(getFileByPath(dirPath)); + } + + /** + * Delete the all in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }); + } + + /** + * Delete all files in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final String dirPath) { + return deleteFilesInDir(getFileByPath(dirPath)); + } + + /** + * Delete all files in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile(); + } + }); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + } + return true; + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath) { + return listFilesInDir(dirPath, false); + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @return the files in directory + */ + public static List listFilesInDir(final File dir) { + return listFilesInDir(dir, false); + } + + /** + * Return the files in directory. + * + * @param dirPath The path of directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath, final boolean isRecursive) { + return listFilesInDir(getFileByPath(dirPath), isRecursive); + } + + /** + * Return the files in directory. + * + * @param dir The directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final File dir, final boolean isRecursive) { + return listFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter) { + return listFilesInDirWithFilter(dir, filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter, + final boolean isRecursive) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter, + final boolean isRecursive) { + if (!isDir(dir)) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + list.add(file); + } + if (isRecursive && file.isDirectory()) { + //noinspection ConstantConditions + list.addAll(listFilesInDirWithFilter(file, filter, true)); + } + } + } + return list; + } + + /** + * Return the time that the file was last modified. + * + * @param filePath The path of file. + * @return the time that the file was last modified + */ + + public static long getFileLastModified(final String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + /** + * Return the time that the file was last modified. + * + * @param file The file. + * @return the time that the file was last modified + */ + public static long getFileLastModified(final File file) { + if (file == null) return -1; + return file.lastModified(); + } + + /** + * Return the charset of file simply. + * + * @param filePath The path of file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final String filePath) { + return getFileCharsetSimple(getFileByPath(filePath)); + } + + /** + * Return the charset of file simply. + * + * @param file The file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final File file) { + int p = 0; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + p = (is.read() << 8) + is.read(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + switch (p) { + case 0xefbb: + return "UTF-8"; + case 0xfffe: + return "Unicode"; + case 0xfeff: + return "UTF-16BE"; + default: + return "GBK"; + } + } + + /** + * Return the number of lines of file. + * + * @param filePath The path of file. + * @return the number of lines of file + */ + public static int getFileLines(final String filePath) { + return getFileLines(getFileByPath(filePath)); + } + + /** + * Return the number of lines of file. + * + * @param file The file. + * @return the number of lines of file + */ + public static int getFileLines(final File file) { + int count = 1; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[1024]; + int readChars; + if (LINE_SEP.endsWith("\n")) { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\n') ++count; + } + } + } else { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\r') ++count; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return count; + } + + /** + * Return the size of directory. + * + * @param dirPath The path of directory. + * @return the size of directory + */ + public static String getDirSize(final String dirPath) { + return getDirSize(getFileByPath(dirPath)); + } + + /** + * Return the size of directory. + * + * @param dir The directory. + * @return the size of directory + */ + public static String getDirSize(final File dir) { + long len = getDirLength(dir); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static String getFileSize(final String filePath) { + long len = getFileLength(filePath); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static String getFileSize(final File file) { + long len = getFileLength(file); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of directory. + * + * @param dirPath The path of directory. + * @return the length of directory + */ + public static long getDirLength(final String dirPath) { + return getDirLength(getFileByPath(dirPath)); + } + + /** + * Return the length of directory. + * + * @param dir The directory. + * @return the length of directory + */ + public static long getDirLength(final File dir) { + if (!isDir(dir)) return -1; + long len = 0; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static long getFileLength(final String filePath) { + boolean isURL = filePath.matches("[a-zA-z]+://[^\\s]*"); + if (isURL) { + try { + HttpsURLConnection conn = (HttpsURLConnection) new URL(filePath).openConnection(); + conn.setRequestProperty("Accept-Encoding", "identity"); + conn.connect(); + if (conn.getResponseCode() == 200) { + return conn.getContentLength(); + } + return -1; + } catch (IOException e) { + e.printStackTrace(); + } + } + return getFileLength(getFileByPath(filePath)); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static long getFileLength(final File file) { + if (!isFile(file)) return -1; + return file.length(); + } + + /** + * 获取指定文件大小 + * @return + * @throws Exception + */ + public static long getFileSizeLong(File file) throws Exception + { + long size = 0; + if (file.exists()){ + FileInputStream fis = null; + fis = new FileInputStream(file); + size = fis.available(); + } + else{ + CupidLogUtils.e("获取文件大小","文件不存在!"); + } + return size; + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final String filePath) { + File file = isSpace(filePath) ? null : new File(filePath); + return getFileMD5ToString(file); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final File file) { + return bytes2HexString(getFileMD5(file)); + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final String filePath) { + return getFileMD5(getFileByPath(filePath)); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, md); + byte[] buffer = new byte[1024 * 256]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + md = dis.getMessageDigest(); + return md.digest(); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } finally { + try { + if (dis != null) { + dis.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * Return the file's path of directory. + * + * @param file The file. + * @return the file's path of directory + */ + public static String getDirName(final File file) { + if (file == null) return ""; + return getDirName(file.getAbsolutePath()); + } + + /** + * Return the file's path of directory. + * + * @param filePath The path of file. + * @return the file's path of directory + */ + public static String getDirName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); + } + + /** + * Return the name of file. + * + * @param file The file. + * @return the name of file + */ + public static String getFileName(final File file) { + if (file == null) return ""; + return getFileName(file.getAbsolutePath()); + } + + /** + * Return the name of file. + * + * @param filePath The path of file. + * @return the name of file + */ + public static String getFileName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + + /** + * Return the name of file without extension. + * + * @param file The file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final File file) { + if (file == null) return ""; + return getFileNameNoExtension(file.getPath()); + } + + /** + * Return the name of file without extension. + * + * @param filePath The path of file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + /** + * Return the extension of file. + * + * @param file The file. + * @return the extension of file + */ + public static String getFileExtension(final File file) { + if (file == null) return ""; + return getFileExtension(file.getPath()); + } + + /** + * Return the extension of file. + * + * @param filePath The path of file. + * @return the extension of file + */ + public static String getFileExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + /** + * Notify system to scan the file. + * + * @param file The file. + */ + public static void notifySystemToScan(Context context, final File file) { + if (file == null || !file.exists()) return; + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + Uri uri = Uri.fromFile(file); + intent.setData(uri); + context.sendBroadcast(intent); + } + + /** + * Notify system to scan the file. + * + * @param filePath The path of file. + */ + public static void notifySystemToScan(final Context context, final String filePath) { + notifySystemToScan(context, getFileByPath(filePath)); + } + + /////////////////////////////////////////////////////////////////////////// + // interface + /////////////////////////////////////////////////////////////////////////// + + public interface OnReplaceListener { + boolean onReplace(); + } + + /////////////////////////////////////////////////////////////////////////// + // other utils methods + /////////////////////////////////////////////////////////////////////////// + + private static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static String bytes2HexString(final byte[] bytes) { + if (bytes == null) return ""; + int len = bytes.length; + if (len <= 0) return ""; + char[] ret = new char[len << 1]; + for (int i = 0, j = 0; i < len; i++) { + ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f]; + ret[j++] = HEX_DIGITS[bytes[i] & 0x0f]; + } + return new String(ret); + } + + private static String byte2FitMemorySize(final long byteNum) { + if (byteNum < 0) { + return "shouldn't be less than zero!"; + } else if (byteNum < 1024) { + return String.format(Locale.getDefault(), "%.3fB", (double) byteNum); + } else if (byteNum < 1048576) { + return String.format(Locale.getDefault(), "%.3fKB", (double) byteNum / 1024); + } else if (byteNum < 1073741824) { + return String.format(Locale.getDefault(), "%.3fMB", (double) byteNum / 1048576); + } else { + return String.format(Locale.getDefault(), "%.3fGB", (double) byteNum / 1073741824); + } + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean writeFileFromIS(final File file, + final InputStream is) { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + byte data[] = new byte[8192]; + int len; + while ((len = is.read(data, 0, 8192)) != -1) { + os.write(data, 0, len); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + if (os != null) { + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 写文件 string + * + * @param fullFileName + * @param logContent + * @param isAppend + */ + public static void writeFile(String fullFileName, String logContent, boolean isAppend) { + FileWriter fileWriter = null; + try { + fileWriter = new FileWriter(new File(fullFileName), isAppend); + fileWriter.write(logContent); + fileWriter.write("\n"); + fileWriter.flush(); + fileWriter.close(); + } catch (Exception e) { + } finally { + BaseIoUtils.closeSilently(fileWriter); + } + } + + /** + * 获取文件路径 + * + * @param baseDir + * @param fileName + * @return + */ + public static String getFilePath(Context context, String baseDir, String fileName) { + String basePath = saveVideoBasePathDir(context, baseDir); + File file = new File(basePath + File.separator + fileName); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + if (!file.exists()) { + try { + file.createNewFile(); + return file.getAbsolutePath(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return file.getAbsolutePath(); + } + + /** + * 保存图片的路径 + * + * @return + */ + public static String saveVideoBasePathDir(Context context, String dirName) { + String basePath = null; + //有SD卡 + if (BaseSDCardHelper.isSDCardMounted()) { + basePath = BaseSDCardHelper.getSDCardBaseDir(); + } else { + basePath = context.getCacheDir().getAbsolutePath(); + } + + return basePath + File.separator + dirName; + + } + + /** + * 删除文件夹 + * + * @param context + * @param baseDir + */ + public static void removeAllFileDir(Context context, String baseDir) { + String basePath = saveVideoBasePathDir(context, baseDir); + File file = new File(basePath); + if (file.exists()) { + delAllFile(file.getAbsolutePath()); + } + } + + + public static boolean delAllFile(String path) { + boolean flag = false; + try { + File file = new File(path); + if (!file.exists()) { + return flag; + } + if (!file.isDirectory()) { + return flag; + } + String[] tempList = file.list(); + File temp = null; + for (int i = 0; i < tempList.length; i++) { + if (path.endsWith(File.separator)) { + temp = new File(path + tempList[i]); + } else { + temp = new File(path + File.separator + tempList[i]); + } + if (temp.isFile()) { + temp.delete(); + } + if (temp.isDirectory()) { + delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件 + delFolder(path + "/" + tempList[i]);// 再删除空文件夹 + flag = true; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return flag; + } + + private static void delFolder(String folderPath) { + try { + delAllFile(folderPath); // 删除完里面所有内容 + String filePath = folderPath; + filePath = filePath.toString(); + File myFilePath = new File(filePath); + myFilePath.delete(); // 删除空文件夹 + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 根据byte数组生成文件 + * + * @param bytes 生成文件用到的byte数组 + */ + public static void createFileWithByte(byte[] bytes, String filePath) { + /** + * 创建File对象,其中包含文件所在的目录以及文件的命名 + */ + File file = new File(filePath); + // 创建FileOutputStream对象 + FileOutputStream outputStream = null; + // 创建BufferedOutputStream对象 + BufferedOutputStream bufferedOutputStream = null; + try { + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + if (!file.exists()) { + // 在文件系统中根据路径创建一个新的空文件 + file.createNewFile(); + } + // 获取FileOutputStream对象 + outputStream = new FileOutputStream(file); + // 获取BufferedOutputStream对象 + bufferedOutputStream = new BufferedOutputStream(outputStream); + // 往文件所在的缓冲输出流中写byte数据 + bufferedOutputStream.write(bytes); + // 刷出缓冲输出流,该步很关键,要是不执行flush()方法,那么文件的内容是空的。 + bufferedOutputStream.flush(); + } catch (Exception e) { + // 打印异常信息 + e.printStackTrace(); + } finally { + // 关闭创建的流对象 + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (bufferedOutputStream != null) { + try { + bufferedOutputStream.close(); + } catch (Exception e2) { + e2.printStackTrace(); + } + } + } + } + + /** + * Mapped File way MappedByteBuffer 可以在处理大文件时,提升性能 + * + * @param filename + * @return + * @throws IOException + */ + public static byte[] file2ByteArray(String filename) throws IOException { + + FileChannel fc = null; + try { + fc = new RandomAccessFile(filename, "r").getChannel(); + MappedByteBuffer byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, + fc.size()).load(); + System.out.println(byteBuffer.isLoaded()); + byte[] result = new byte[(int) fc.size()]; + if (byteBuffer.remaining() > 0) { + // System.out.println("remain"); + byteBuffer.get(result, 0, byteBuffer.remaining()); + } + return result; + } catch (IOException e) { + e.printStackTrace(); + throw e; + } finally { + try { + fc.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static String getNowString(final java.text.DateFormat format) { + return millis2String(System.currentTimeMillis(), format); + } + + /** + * Milliseconds to the formatted time string. + * + * @param millis The milliseconds. + * @param format The format. + * @return the formatted time string + */ + public static String millis2String(final long millis, final java.text.DateFormat format) { + return format.format(new Date(millis)); + } + +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/HandlerThreadManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/HandlerThreadManager.java new file mode 100644 index 0000000000..94cfee8328 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/HandlerThreadManager.java @@ -0,0 +1,63 @@ +package com.zhidao.support.adas.high.common; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class HandlerThreadManager { + private volatile static HandlerThread sBackgroundHandlerThread; + private volatile static HandlerThread sIOHandlerThread; + private volatile static Handler sBackgroundHandler; + private volatile static Handler sMainHandler; + + public static Handler getBackgroundHandler() { + if (sBackgroundHandler == null) { + sBackgroundHandler = new Handler(getBackgroundHandlerThread().getLooper()); + } + return sBackgroundHandler; + } + + public static HandlerThread getBackgroundHandlerThread() { + synchronized (HandlerThreadManager.class) { + if (sBackgroundHandlerThread == null) { + sBackgroundHandlerThread = new HandlerThread("autopilot_bgd_thread"); + sBackgroundHandlerThread.start(); + } + } + return sBackgroundHandlerThread; + } + + private static HandlerThread getVideoHandlerThread() { + synchronized (HandlerThreadManager.class) { + if (sIOHandlerThread == null) { + sIOHandlerThread = new HandlerThread("autopilot_video_thread"); + sIOHandlerThread.start(); + } + } + return sIOHandlerThread; + } + + + public static Handler getMainHandler() { + if (sMainHandler == null) { + sMainHandler = new Handler(Looper.getMainLooper()); + } + return sMainHandler; + } + + public static final Executor EXECUTOR = new ThreadPoolExecutor(2, 5, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(30), + new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "autopilot-thread-pool" + mCount.getAndIncrement()); + } + }); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPCFixationIPHelper.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPCFixationIPHelper.java new file mode 100644 index 0000000000..4d9668e08a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPCFixationIPHelper.java @@ -0,0 +1,146 @@ +package com.zhidao.support.adas.high.common; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Future; + +/** + * 工控机固定IP查询 + */ +public class IPCFixationIPHelper { + private static final String TAG = IPCFixationIPHelper.class.getSimpleName(); + private final IIPCFixationIPListener listener; + private final List futures = new ArrayList<>(); + private volatile boolean isCallListener = false;//是否已经调用了回调 + private volatile int unavailableCount = 0;//不可用IP个数 + private volatile int queryCount;//要查询的IP个数 + private Timer retryTimer; + + public IPCFixationIPHelper(IIPCFixationIPListener listener) { + if (listener == null) + throw new RuntimeException("工控机固定IP查询回调不能为空"); + this.listener = listener; + } + + public interface IIPCFixationIPListener { + /** + * 开始查找可用IP + */ + void onStartFindIP(); + + /** + * 轮询结果可连通的IP + * + * @param ip 返回可用IP 当为null时表示无可用IP + */ + void onAvailableIP(String ip); + } + + private void interrupted() { + if (!futures.isEmpty()) { + for (Future future : futures) { + if (!future.isCancelled()) { + future.cancel(true); + } + } + futures.clear(); + } + } + + /** + * 开始根据固定IP列表惊醒PING 方式查找可用工控机 + */ + public void start(final HashSet ips) { + listener.onStartFindIP(); + if (retryTimer != null) { + retryTimer.cancel(); + retryTimer = null; + } + if (ips == null || ips.isEmpty()) { + listener.onAvailableIP(null); + } else { + interrupted(); + queryCount = ips.size(); + unavailableCount = 0; + isCallListener = false; + for (final String temp : ips) { + Runnable runnable = new Runnable() { + @Override + public void run() { + CupidLogUtils.i(TAG, "ip=" + temp); + boolean isAvailable = ping(temp); + if (isAvailable) { + if (!isCallListener && !Thread.currentThread().isInterrupted()) { + isCallListener = true; + listener.onAvailableIP(temp); + CupidLogUtils.i(TAG, "可用IP=" + temp); + interrupted(); + } + } else { + allNotAvailable(ips); + } + } + }; + Future future = ThreadPoolManager.getsInstance().submit(runnable); + futures.add(future); + } + } + } + + private synchronized void allNotAvailable(final HashSet ips) { + unavailableCount++; + CupidLogUtils.i(TAG, "unavailableCount=" + unavailableCount); + if (queryCount == unavailableCount) { + if (!isCallListener) { + isCallListener = true; + listener.onAvailableIP(null); + CupidLogUtils.i(TAG, "所有IP均不可用"); + //当配置中的所有IP都ping不通时,需要继续ping + if (retryTimer == null) { + retryTimer = new Timer(); + retryTimer.schedule(new TimerTask() { + @Override + public void run() { + start(ips); + } + }, 5 * 1000L);//延时 + } + } + } + } + + private boolean ping(String str) { + String resault; + Process p; + try { + //ping -c 3 -w 100 中 ,-c 是指ping的次数 3是指ping 3次 ,-w 100 以秒为单位指定超时间隔,是指超时时间为100秒 + p = Runtime.getRuntime().exec("ping -c 2 -w 5 " + str); + int status = p.waitFor(); + InputStream input = p.getInputStream(); + BufferedReader in = new BufferedReader(new InputStreamReader(input)); + StringBuilder builder = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + builder.append(line).append("\n"); + } + if (status == 0) { + resault = "是"; + } else { + resault = "否"; + } + builder.append("工控机IP:").append(str).append(" 是否可以连通:").append(resault); + CupidLogUtils.i(TAG, builder.toString()); + return status == 0; + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPreferencesHelper.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPreferencesHelper.java new file mode 100644 index 0000000000..bcaaa74f35 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/IPreferencesHelper.java @@ -0,0 +1,68 @@ +package com.zhidao.support.adas.high.common; + + +import java.util.HashSet; + +/** + * Created by nie yunlong on 2018/4/13. + */ + +public interface IPreferencesHelper { + /** + * 保存udp client address + * + * @param address + */ + void saveUdpClientAddress(String address); + + /** + * 获取client address + * + * @return + */ + String getUdpClientAddress(); + + /** + * 保存dock config + * + * @param dockConfig + */ + void saveDockConfig(String dockConfig); + + /** + * 获取dock config + * + * @return + */ + String getDockConfig(); + + + + + /** + * 获取工控机固定IP列表 + * + * @return + */ + HashSet getIPCFixationIPList(); + + /** + * 增加工控机固定IP + * + * @param ipcIP + */ + void addIPCFixationIP(String ipcIP); + + /** + * 删除指定的工控机固定IP + * + * @param ipcIP + */ + void delIPCFixationIP(String ipcIP); + + /** + * 删除所有工控机固定IP + */ + void delIPCFixationIP(); + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/JsonUtil.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/JsonUtil.java new file mode 100644 index 0000000000..f9ca0e6566 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/JsonUtil.java @@ -0,0 +1,66 @@ +package com.zhidao.support.adas.high.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.lang.reflect.Type; + +/** + * Java对象和JSON字符串相互转化工具类 + * 格式化 + * + * @author xfk + */ +public final class JsonUtil { + + + /** + * 对象转换成json字符串 + * + * @param obj + * @return + */ + public static String toJson(Object obj) { + + return toJson(false, obj); + } + + public static String toJson(boolean isSerializeNulls, Object obj) { + Gson gson; + if (isSerializeNulls) { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.serializeNulls(); //重点 + gson = gsonBuilder.create(); + } else { + gson = new Gson(); + } + return gson.toJson(obj); + } + + + /** + * json字符串转成对象 + * + * @param str + * @param type + * @return + */ + public static T fromJson(String str, Type type) { + Gson gson = new Gson(); + return gson.fromJson(str, type); + } + + /** + * json字符串转成对象 + * + * @param str + * @param type + * @return + */ + public static T fromJson(String str, Class type) { + Gson gson = new Gson(); + return gson.fromJson(str, type); + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/LogSaveManage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/LogSaveManage.java new file mode 100644 index 0000000000..48e2df1031 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/LogSaveManage.java @@ -0,0 +1,70 @@ +package com.zhidao.support.adas.high.common; + +import android.text.TextUtils; + +import com.zhidao.support.adas.high.thread.QueuedWork; + +import java.io.File; + +public +/** + * @author nie yunlong + * @des + * @date 2021/3/4 + */ +class LogSaveManage { + private static final LogSaveManage ourInstance = new LogSaveManage(); + //当前保存的文件路径 + private String currentSaveFilePath; + //6M + private final long FILE_LIMIT_SIZE = 6 * 1024 * 1024; + + static LogSaveManage getInstance() { + return ourInstance; + } + + private LogSaveManage() { + } + + /** + * 保存文件 + * + * @param msg + */ + public synchronized void saveWrite(final String msg) { + QueuedWork.runInBack(new Runnable() { + @Override + public void run() { + try { + String logFilePath = createFileReturnPath(); + FileUtils.writeFile(logFilePath, msg, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + } + + /** + * 创建文件 + * + * @return + */ + private synchronized String createFileReturnPath() throws Exception { + if (!TextUtils.isEmpty(currentSaveFilePath)) { + long fileSize = FileUtils.getFileSizeLong(new File(currentSaveFilePath)); + if (fileSize <= FILE_LIMIT_SIZE) { + return currentSaveFilePath; + } + } + //大于6M + String filePath = BaseSDCardHelper.getSDCardPrivateFilesDir(MgContextUtils.getContext(), "adas-log") + File.separator + BaseTimeUtils.formatTimeDate3() + "-log.txt"; + //把日志保存到文件 + boolean isCreateFileSuccess = FileUtils.createOrExistsFile(filePath); + if (isCreateFileSuccess) { + currentSaveFilePath = filePath; + } + return currentSaveFilePath; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/MgContextUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/MgContextUtils.java new file mode 100644 index 0000000000..fcc310a381 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/MgContextUtils.java @@ -0,0 +1,33 @@ +package com.zhidao.support.adas.high.common; + +import android.content.Context; + +/** + * @author nie yunlong + * @des 上下文管理 + * @date 2019/5/13 + */ +public class MgContextUtils { + /** + * 上下文 + */ + private static Context mContext; + + /** + * 设置上下文 + * + * @param context + */ + public static void setContext(Context context) { + mContext = context.getApplicationContext(); + } + + /** + * 获取上下文 + * + * @return + */ + public static Context getContext() { + return mContext; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RSATool.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RSATool.java new file mode 100644 index 0000000000..7f112faf19 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RSATool.java @@ -0,0 +1,107 @@ +package com.zhidao.support.adas.high.common; + +import android.util.Log; + + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + +public class RSATool { + private static final String KEY_ALGORITHM = "RSA"; + private static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding"; + + public static Map initKey() throws Exception { + //实例化密钥生成器 + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); //加密方式 + //初始化密钥生成器 + keyPairGenerator.initialize(512, new SecureRandom()); //长度 + //生成密钥对 + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + //甲方公钥 + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + //甲方私钥 + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + //将密钥存储在map中 + Map keyMap = new HashMap(); + keyMap.put("PUBLIC_KEY", publicKey); + keyMap.put("PRIVATE_KEY", privateKey); + return keyMap; + } + + public static void getBase64() { + try { + Map map = initKey(); + byte[] privateKey = ((Key) map.get("PRIVATE_KEY")).getEncoded(); + byte[] publicKey = ((Key) map.get("PUBLIC_KEY")).getEncoded(); + // 将公私钥转为base64-法1 +// String privateBase64 = new String(Base64.encodeBase64(privateKey)); +// String publicBase64 = new String(Base64.encodeBase64(publicKey)); + // 将公私钥转为base64-法2 + String privateBase64 = Base64.encodeBase64String(privateKey); + String publicBase64 = Base64.encodeBase64String(publicKey); + Log.i("RSATool", "privateBase64=" + privateBase64); + Log.i("RSATool", "publicBase64=" + publicBase64); + String data = "mogo@ZHIDAO10"; + String encryptData = encryptByPublicKey(data, publicKey); + Log.i("RSATool", "encryptData=" + encryptData); + String decodeData = decodeByPrivateKey(encryptData, Base64.decodeBase64(privateBase64)); + Log.i("RSATool", "decodeData=" + decodeData); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 公钥加密 + * + * @param data 待加密数据 + * @param key 密钥 + * @return String 加密数据 Base64类型 + */ + public static String encryptByPublicKey(String data, byte[] key) throws Exception { + //实例化密钥工厂 + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + //初始化公钥 + //密钥材料转换 + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key); + //产生公钥 + PublicKey pubKey = keyFactory.generatePublic(x509KeySpec); + //数据加密 + Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING); + cipher.init(Cipher.ENCRYPT_MODE, pubKey); + return Base64.encodeBase64String(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8))); + } + + /** + * 私钥解密 + * + * @param data 待解密数据 Base64类型 + * @param key 密钥 + * @return String 解密数据 + */ + public static String decodeByPrivateKey(String data, byte[] key) throws Exception { + //取得私钥 + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + //生成私钥 + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec); + //数据解密 + Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return new String(cipher.doFinal(Base64.decodeBase64(data))); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RequestWsMsgType.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RequestWsMsgType.java new file mode 100644 index 0000000000..6eb1ffa5b2 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/RequestWsMsgType.java @@ -0,0 +1,52 @@ +package com.zhidao.support.adas.high.common; + +/** + * @author nie yunlong + * @description 请求值 + * @date 2018/7/3 + */ +public enum RequestWsMsgType { + + /** + * 获取视频信息 + */ + MSG_GET_VIDEO_INFO(1, "get video info"), + /** + * 视频分辨率 + */ + MSG_SEND_LOCATION(2, "视频分辨率"), + /** + * 上传配置 + */ + MSG_SEND_UPLOAD_CONFIG(3, "上传配置"), + + MSG_SEND_CLOSE(5, "关闭socket"); + int mMsgType; + /** + * 描述 + */ + String mMsgDesc; + + RequestWsMsgType(int msgType, String msgDesc) { + this.mMsgType = msgType; + this.mMsgDesc = msgDesc; + } + + /** + * 获取msgType + * + * @return + */ + public int getMsgType() { + return mMsgType; + } + + /** + * 获取描述 + * + * @return + */ + public String getmMsgDesc() { + return mMsgDesc; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/SSH.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/SSH.java new file mode 100644 index 0000000000..481a369729 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/SSH.java @@ -0,0 +1,149 @@ +package com.zhidao.support.adas.high.common; + +import android.text.TextUtils; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.zhidao.support.adas.high.bean.SSHResult; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Properties; + +public class SSH { + private static final String TAG = SSH.class.getSimpleName(); + + private Session session = null; + private ChannelExec channelExec = null; + ByteArrayOutputStream err = null; + StringBuffer outBuf = null; + + public ByteArrayOutputStream getErr() { + return err; + } + + public StringBuffer getOutBuf() { + return outBuf; + } + + public Session getSession() { + return session; + } + + public ChannelExec getChannelExec() { + return channelExec; + } + + /** + * 使用用户名、密码连接 + * + * @param host 主机ip + * @param port 主机端口 + * @param username 主机用户名 + * @param password 主机密码 + * @throws JSchException + */ + public void connect(String host, int port, String username, String password) throws JSchException { + JSch jsch = new JSch(); + session = jsch.getSession(username, host, port); + session.setPassword(password); + Properties config = new Properties(); + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + session.setTimeout(10000); + session.connect(); + CupidLogUtils.i(TAG, "Connected to " + host + "."); + } + + /** + * 使用授信连接 + * + * @param host 主机ip + * @param username 主机用户名 + * @param privateKey 私钥路径 + * @throws JSchException + */ + public void connect(String host, String username, String privateKey) throws JSchException { + JSch jsch = new JSch(); + jsch.addIdentity(privateKey); + session = jsch.getSession(username, host); + + Properties config = new Properties(); + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + session.setTimeout(30000); + session.connect(); + CupidLogUtils.i(TAG, "Connected to " + host + "."); + } + + /** + * 执行 + * + * @param cmd 命令 + * @return 状态 + * @throws JSchException + * @throws IOException + */ + public SSHResult exec(String cmd) throws JSchException, IOException { + return exec(cmd, null); + } + + public SSHResult exec(String cmd, String suPwd) throws JSchException, IOException { + int exitStatus = 0; + channelExec = (ChannelExec) session.openChannel("exec"); + channelExec.setInputStream(null); + err = new ByteArrayOutputStream(); + channelExec.setErrStream(err); + channelExec.setCommand(cmd); + InputStream in = channelExec.getInputStream(); + OutputStream out = channelExec.getOutputStream(); + channelExec.connect(); + if (!TextUtils.isEmpty(suPwd) && (cmd.contains("sudo") || cmd.contains("su"))) { + out.write((suPwd + "\n").getBytes()); //这里是密码后跟了一个换行符 + out.flush(); + } + outBuf = new StringBuffer(); + byte[] tmp = new byte[1024]; + while (true) { + while (in.available() > 0) { + int i = in.read(tmp, 0, 1024); + if (i < 0) break; + outBuf.append(new String(tmp, 0, i)); + } + if (channelExec.isClosed()) { + if (in.available() > 0) continue; + exitStatus = channelExec.getExitStatus(); + break; + } + try { + Thread.sleep(1000L); + } catch (Exception e) { + } + } + channelExec.disconnect(); + if (exitStatus == 0) { + String outInfo = this.getOutBuf().toString(); + return new SSHResult(exitStatus, cmd, outInfo); + } else { + String errInfo = this.getErr().toString(); + return new SSHResult(exitStatus, cmd, errInfo); + } + } + + /** + * 断开连接 + */ + public void disConnect() { + if (channelExec != null) { + channelExec.disconnect(); + } + if (session != null) { + session.disconnect(); + } + } + +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ThreadPoolManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ThreadPoolManager.java new file mode 100644 index 0000000000..08c0774b95 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/common/ThreadPoolManager.java @@ -0,0 +1,152 @@ +package com.zhidao.support.adas.high.common; + + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 线程池 + * Created by xfk on 2018/9/30. + */ + +public class ThreadPoolManager { + + /** + * 根据cpu的数量动态的配置核心线程数和最大线程数 + */ + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + /** + * 核心线程数 = CPU核心数 + 1 + */ + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + /** + * 线程池最大线程数 = CPU核心数 * 2 + 1 + */ + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + /** + * 非核心线程闲置时超时1s + */ + private static final int KEEP_ALIVE = 1; + /** + * 线程池的对象 + */ + private ThreadPoolExecutor executor; + + /** + * 要确保该类只有一个实例对象,避免产生过多对象消费资源,所以采用单例模式 + */ + private ThreadPoolManager() { + } + + private volatile static ThreadPoolManager INSTANCE; + + public static ThreadPoolManager getsInstance() { + if (INSTANCE == null) { + synchronized (ThreadPoolManager.class) { + if (INSTANCE == null) { + INSTANCE = new ThreadPoolManager(); + } + } + + } + return INSTANCE; + } + + /** + * 开启一个无返回结果的线程 + * + * @param r + */ + public void execute(Runnable r) { + if (executor == null) { + /** + * corePoolSize:核心线程数 + * maximumPoolSize:线程池所容纳最大线程数(workQueue队列满了之后才开启) + * keepAliveTime:非核心线程闲置时间超时时长 + * unit:keepAliveTime的单位 + * workQueue:等待队列,存储还未执行的任务 + * threadFactory:线程创建的工厂 + * handler:异常处理机制 + * + */ + executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, + KEEP_ALIVE, TimeUnit.SECONDS, new ArrayBlockingQueue(200), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); + } + // 把一个任务丢到了线程池中 + try { + executor.execute(r); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + /** + * 开启一个有返回结果的线程 + * + * @param r + * @return + */ + public Future submit(Callable r) { + if (executor == null) { + /** + * corePoolSize:核心线程数 + * maximumPoolSize:线程池所容纳最大线程数(workQueue队列满了之后才开启) + * keepAliveTime:非核心线程闲置时间超时时长 + * unit:keepAliveTime的单位 + * workQueue:等待队列,存储还未执行的任务 + * threadFactory:线程创建的工厂 + * handler:异常处理机制 + * + */ + executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, + KEEP_ALIVE, TimeUnit.SECONDS, new ArrayBlockingQueue(20), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); + } + // 把一个任务丢到了线程池中 + return executor.submit(r); + } + + /** + * 开启一个无返回结果的线程 + * + * @param r + */ + public Future submit(Runnable r) { + if (executor == null) { + /** + * corePoolSize:核心线程数 + * maximumPoolSize:线程池所容纳最大线程数(workQueue队列满了之后才开启) + * keepAliveTime:非核心线程闲置时间超时时长 + * unit:keepAliveTime的单位 + * workQueue:等待队列,存储还未执行的任务 + * threadFactory:线程创建的工厂 + * handler:异常处理机制 + * + */ + executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, + KEEP_ALIVE, TimeUnit.SECONDS, new ArrayBlockingQueue(200), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); + } + return executor.submit(r); + + } + + /** + * 把任务移除等待队列 + * + * @param r + */ + public void cancel(Runnable r) { + if (r != null) { + executor.getQueue().remove(r); + } + } + +} + diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMsg.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMsg.java new file mode 100644 index 0000000000..acdc45de7d --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMsg.java @@ -0,0 +1,22 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des + * @date 2020/3/12 + */ +public interface IMsg { + /** + * 处理消息 + * + * @param msg + */ + void handlerMsg(Gson gson, OnAdasListener adasListener, String msg); + + void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg); + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMyMessageFactory.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMyMessageFactory.java new file mode 100644 index 0000000000..d85aeee531 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/IMyMessageFactory.java @@ -0,0 +1,20 @@ +package com.zhidao.support.adas.high.msg; + +/** + * 生产产品的工厂 + */ +public interface IMyMessageFactory { + /** + * + * @param messageType 消息类型 + * @return + */ + public IMsg createMessage(String messageType); + + /** + * + * @param messageCode 消息类型 + * @return + */ + public IMsg createMessage(int messageCode); +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyAbstractMessageHandler.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyAbstractMessageHandler.java new file mode 100644 index 0000000000..59dcf3d8c3 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyAbstractMessageHandler.java @@ -0,0 +1,11 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.protobuf.util.JsonFormat; + +/** + * @author nie yunlong + * @des 消息处理中心 + * @date 2020/3/12 + */ +public abstract class MyAbstractMessageHandler implements IMsg { +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyMessageFactory.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyMessageFactory.java new file mode 100644 index 0000000000..055d002223 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/MyMessageFactory.java @@ -0,0 +1,233 @@ +package com.zhidao.support.adas.high.msg; + +import android.text.TextUtils; + +import com.zhidao.support.adas.high.common.ActionTypeReceive; + +/** + * @author nie yunlong + * @des 简易工厂 + * @date 2020/3/12 + */ +public class MyMessageFactory implements IMyMessageFactory { + /** + * udp 渲染 image + */ + private IMsg udpRenderImageMsg; + /** + * ws 报警 消息 + */ + private IMsg wsWarnMessage; + /** + * ws 图像size + */ + private IMsg wsImageSizeMessage; + /** + * 空实现 + */ + private IMsg nuImplMessage; + /** + * udp 车辆状态消息 + */ + private IMsg udpCarStateMessage; + /** + * 红绿灯状态 + */ + private IMsg udpLightStateMessage; + /** + * 周边物体的状态 + */ + private IMsg udpObstaclesMessage; + + /** + * 局部轨迹消息 + */ + private IMsg trajectoryMessage; + + /** + * 监控信息 + */ + private IMsg guardianMessage; + /** + * 车道线渲染 + */ + private IMsg lanesMessage; + /** + * 自动驾驶状态 + */ + private IMsg autopilotStatusMessage; + /** + * 自动驾驶到站 + */ + private IMsg autopilotArriveMessage; + /** + * 自动驾驶路径 + */ + private IMsg autopilotRouteMessage; + /** + * 工控机获取SN + */ + private IMsg autopilotSNMessage; + /** + * 数据采集信息 + */ + private IMsg autopilotIdentify; + /** + * 工控机升级状态 + */ + private IMsg autopilotUpgradeStatusMessage; + /** + * car dock 基础信息 + */ + private IMsg autopilotConfig; + + + + @Override + public IMsg createMessage(String messageType) { + if (TextUtils.isEmpty(messageType)) { + if (nuImplMessage == null) { + nuImplMessage = new NuImplMessage(); + } + return nuImplMessage; + } + if (messageType.equals(ActionTypeReceive.ACTION_WS_MSG_RENDER_TYPE.getmActionType())) { + //udp render + if (udpRenderImageMsg == null) { + udpRenderImageMsg = new UdpRenderImageMessage(); + } + return udpRenderImageMsg; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_MSG_WARNING_TYPE.getmActionType())) { + //ws 报警消息 + if (wsWarnMessage == null) { + wsWarnMessage = new WsWarnMessage(); + } + return wsWarnMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_MSG_IMAGE_SIZE_TYPE.getmActionType())) { + //ws 报警消息 + if (wsImageSizeMessage == null) { + wsImageSizeMessage = new WsImageSizeMessage(); + } + return wsImageSizeMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_CAR_STATE_TYPE.getmActionType())) { + //udp 车辆消息 + if (udpCarStateMessage == null) { + udpCarStateMessage = new UdpCarStateMessage(); + } + return udpCarStateMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_UDP_LIGHT_STATE_TYPE.getmActionType())) { + //udp 红绿灯状态 + if (udpLightStateMessage == null) { + udpLightStateMessage = new UdpLightStateMessage(); + } + return udpLightStateMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_OBSTACLES_MESSAGE_TYPE.getmActionType())) { + //udp 周边物体的状态 + if (udpObstaclesMessage == null) { + udpObstaclesMessage = new UdpObstaclesMessage(); + } + return udpObstaclesMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_LANES_MESSAGE_TYPE.getmActionType())) { + // 车道线渲染 + if (lanesMessage == null) { + lanesMessage = new UdpLanesMessage(); + } + return lanesMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_STATUE.getmActionType())) { + //ws 自动驾驶状态 + if (autopilotStatusMessage == null) { + autopilotStatusMessage = new WsAutopilotStatusMessage(); + } + return autopilotStatusMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_WAY_ARRIVE.getmActionType())) { + //自送驾驶到站 + if (autopilotArriveMessage == null) { + autopilotArriveMessage = new WsAutopilotArriveMessage(); + } + return autopilotArriveMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_ROUTE.getmActionType())) { + //自动驾驶路径 + if (autopilotRouteMessage == null) { + autopilotRouteMessage = new WsAutopilotRouteMessage(); + } + return autopilotRouteMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_SN_REQUEST.getmActionType())) { + //工控机获取sn + if (autopilotSNMessage == null) { + autopilotSNMessage = new WsAutopilotSNMessage(); + } + return autopilotSNMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_IDENTIFY.getmActionType())) { + //数据采集信息 + if (autopilotIdentify == null) { + autopilotIdentify = new WsAutopilotIdentify(); + } + return autopilotIdentify; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_TRAJECTORY.getmActionType())) { + //局部轨迹 + if (trajectoryMessage == null) { + trajectoryMessage = new WsAutopilotTrajectoryMessage(); + } + return trajectoryMessage; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_CAR_CONFIG.getmActionType())) { + //dock config + if (autopilotConfig == null) { + autopilotConfig = new WsAutopilotConfig(); + } + return autopilotConfig; + } else if (messageType.equals(ActionTypeReceive.ACTION_WS_AUTOPILOT_UPGRADE_STATUS.getmActionType())) { + //ws 工控机升级状态 + if (autopilotUpgradeStatusMessage == null) { + autopilotUpgradeStatusMessage = new WsAutopilotUpgradeStatusMessage(); + } + return autopilotUpgradeStatusMessage; + } + + if (nuImplMessage == null) { + nuImplMessage = new NuImplMessage(); + } + return nuImplMessage; + } + + @Override + public IMsg createMessage(int messageCode) { + if (messageCode == 0) { + if (nuImplMessage == null) { + nuImplMessage = new NuImplMessage(); + } + return nuImplMessage; + } + if (messageCode == ActionTypeReceive.ACTION_WS_MSG_RENDER_TYPE.getmActionCode()) { + //感知数据 + if (udpRenderImageMsg == null) { + udpRenderImageMsg = new UdpRenderImageMessage(); + } + return udpRenderImageMsg; + } else if (messageCode == ActionTypeReceive.ACTION_WS_CAR_STATE_TYPE.getmActionCode()) { + //定位数据 + if (udpCarStateMessage == null) { + udpCarStateMessage = new UdpCarStateMessage(); + } + return udpCarStateMessage; + } else if (messageCode == (ActionTypeReceive.ACTION_WS_AUTOPILOT_TRAJECTORY.getmActionCode())) { + //局部轨迹 + if (trajectoryMessage == null) { + trajectoryMessage = new WsAutopilotTrajectoryMessage(); + } + return trajectoryMessage; + } else if (messageCode == (ActionTypeReceive.ACTION_WS_AUTOPILOT_ROUTES.getmActionCode())) { + //轨迹列表 + + } else if (messageCode == (ActionTypeReceive.ACTION_WS_AUTOPILOT_GUARDIAN.getmActionCode())) { + //监控信息 + if (guardianMessage == null) { + guardianMessage = new WsAutopilotGuardian(); + } + return guardianMessage; + } + if (nuImplMessage == null) { + nuImplMessage = new NuImplMessage(); + } + return nuImplMessage; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/NuImplMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/NuImplMessage.java new file mode 100644 index 0000000000..31e816b59c --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/NuImplMessage.java @@ -0,0 +1,23 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des 空实现防止空指针 + * @date 2020/3/12 + */ +public class NuImplMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpCarStateMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpCarStateMessage.java new file mode 100644 index 0000000000..10a8cb7efe --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpCarStateMessage.java @@ -0,0 +1,100 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.CarStateInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import mogo.status.CarStatus; + +/** + * @author nie yunlong + * @des udp 车辆状态 + * @date 2020/3/12 + */ +public class UdpCarStateMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + CupidLogUtils.w("CarStateData", "===>carStateInfo" + adasListener); + CarStateInfo carStateInfo = gson.fromJson(msg, CarStateInfo.class); +// String satelliteTime = carStateInfo.getValues().getSatelliteTime(); +// long systemTime = System.currentTimeMillis(); +// Date date = BaseTimeUtils.localToUTC(String.valueOf(System.currentTimeMillis())); +// long time = date.getTime(); +// long l = Long.parseLong(satelliteTime)-time; +// CupidLogUtils.w("CarStateData", "carStateInfo2 pad system time="+systemTime); +// CupidLogUtils.w("CarStateData", "carStateInfo2 pad utc time=" + time); +// CupidLogUtils.w("CarStateData", "carStateInfo2 gkj utc time=" + satelliteTime); +// CupidLogUtils.w("CarStateData", "carStateInfo2 time difference=" + l); + if (adasListener != null) { + adasListener.onCarStateData(carStateInfo); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + try { + long time = System.currentTimeMillis(); + CarStatus.Status status = CarStatus.Status.parseFrom(msg); + if (status != null) { + CarStateInfo carStateInfo = new CarStateInfo(); + carStateInfo.setAction("state"); + CarStateInfo.ValuesBean values = new CarStateInfo.ValuesBean(); + values.setLon(status.getLon()); + values.setLat(status.getLat()); + values.setAlt(status.getAlt()); + values.setHeading(status.getHeading()); + values.setAcceleration(status.getAcceleration()); + values.setYaw_rate(status.getYawRate()); + values.setGnss_speed(status.getGnssSpeed()); + values.setVehicle_speed(status.getVehicleSpeed()); + values.setSatelliteTime(status.getSatelliteTime()); + values.setSystemTime(status.getSystemTime()); + values.setTurn_light(status.getTurnLight()); + values.setFlash_light(status.getFlashLight()); + values.setBrake_light(status.getBrakeLight()); + values.setFrame_num(status.getFrameNum()); + values.setReceiverDataTime(time); + carStateInfo.setValues(values); + setTurnLightState(values); + if (adasListener != null) { + adasListener.onCarStateData(carStateInfo); + CupidLogUtils.w("UdpCarStateMessage", "" + carStateInfo.toString()); + } + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + /** + * 解析转向灯 + */ + private int turnLightTimes = 0; + private boolean isOnTurnLight = false; + private int turnLight = 0; + + public void setTurnLightState(CarStateInfo.ValuesBean values) { + int turn_light = values.getTurn_light(); + if (turn_light == 0) { + if (isOnTurnLight) { + if (turnLightTimes >= 10) { + isOnTurnLight = false; + turnLight = 0; + } + turnLightTimes++; + } + } else if (turn_light == 1) { + turnLightTimes = 0; + isOnTurnLight = true; + turnLight = 1; + } else if (turn_light == 2) { + turnLightTimes = 0; + isOnTurnLight = true; + turnLight = 2; + } + values.setTurnLightOften(turnLight); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLanesMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLanesMessage.java new file mode 100644 index 0000000000..16cea5dc1d --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLanesMessage.java @@ -0,0 +1,28 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.CarLaneInfo; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des udp 车道线渲染 + * @date 2020/3/12 + */ +public class UdpLanesMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //udp 车道线渲染 + CarLaneInfo carLaneInfo = gson.fromJson(msg, CarLaneInfo.class); + if (adasListener != null) { + adasListener.onCarLaneInfo(carLaneInfo); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLightStateMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLightStateMessage.java new file mode 100644 index 0000000000..d77597908e --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpLightStateMessage.java @@ -0,0 +1,29 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.CarStateInfo; +import com.zhidao.support.adas.high.bean.LightStatueInfo; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des udp 红绿灯状态 + * @date 2020/3/12 + */ +public class UdpLightStateMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //udp 红绿灯状态 + LightStatueInfo lightStatueInfo = gson.fromJson(msg, LightStatueInfo.class); + if (adasListener != null) { + adasListener.onLightStateData(lightStatueInfo); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpObstaclesMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpObstaclesMessage.java new file mode 100644 index 0000000000..5bf41edbcc --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpObstaclesMessage.java @@ -0,0 +1,29 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.LightStatueInfo; +import com.zhidao.support.adas.high.bean.ObstaclesInfo; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des udp 周边物体渲染 + * @date 2020/3/12 + */ +public class UdpObstaclesMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //udp 周边物体渲染 + ObstaclesInfo obstaclesInfo = gson.fromJson(msg, ObstaclesInfo.class); + if (adasListener != null) { + adasListener.onObstaclesInfo(obstaclesInfo); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpRenderImageMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpRenderImageMessage.java new file mode 100644 index 0000000000..84be2140d4 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/UdpRenderImageMessage.java @@ -0,0 +1,101 @@ +package com.zhidao.support.adas.high.msg; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.RectInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import java.util.ArrayList; + +import adas.Adas; + +/** + * @author nie yunlong + * @des udp渲染 + * @date 2020/3/12 + */ +public class UdpRenderImageMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //渲染图像流 + RectInfo rectInfo = gson.fromJson(msg, RectInfo.class); + CupidLogUtils.e("UdpRenderImageMessage--->json:num = " + rectInfo.getModels().size()); + if (adasListener != null) { + adasListener.onRectData(rectInfo); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + Adas.ViwesMsg viewMsg = null; + try { + viewMsg = Adas.ViwesMsg.parseFrom(msg); + } catch (InvalidProtocolBufferException e) { + CupidLogUtils.e("UdpRenderImageMessage--->protoBuf Exception:" + e.toString()); + e.printStackTrace(); + } + if (viewMsg != null) { + if (TextUtils.equals(viewMsg.getAction(), "view")) { + RectInfo rectInfo = new RectInfo(); + rectInfo.setAction(viewMsg.getAction()); + ArrayList rectBeans = new ArrayList<>(); + for (int i = 0; i < viewMsg.getModelsList().size(); i++) { + RectInfo.RectBean rectBean = new RectInfo.RectBean(); + rectBean.setYt(viewMsg.getModels(i).getYt()); + rectBean.setYb(viewMsg.getModels(i).getYb()); + rectBean.setXr(viewMsg.getModels(i).getXr()); + rectBean.setXl(viewMsg.getModels(i).getXl()); + rectBean.setDistance_x(viewMsg.getModels(i).getDistanceX()); + rectBean.setDistance_y(viewMsg.getModels(i).getDistanceY()); + rectBean.setType(viewMsg.getModels(i).getType()); + rectBean.setLat(viewMsg.getModels(i).getLat()); + rectBean.setLon(viewMsg.getModels(i).getLon()); + rectBean.setHeading(viewMsg.getModels(i).getHeading()); + rectBean.setSystemTime(viewMsg.getModels(i).getSystemTime()); + rectBean.setSatelliteTime(viewMsg.getModels(i).getSatelliteTime()); + rectBean.setAlt(viewMsg.getModels(i).getAlt()); + rectBean.setCarId(viewMsg.getModels(i).getCarId()); + rectBean.setUuid(viewMsg.getModels(i).getUuid()); + rectBean.setColor(viewMsg.getModels(i).getColor()); + rectBean.setSpeed(viewMsg.getModels(i).getSpeed()); + rectBean.setDrawlevel(viewMsg.getModels(i).getDrawlevel()); + rectBean.setLength(viewMsg.getModels(i).getLength()); + rectBean.setWidth(viewMsg.getModels(i).getWidth()); + rectBean.setHeight(viewMsg.getModels(i).getHeight()); + rectBean.setDriverTime(viewMsg.getModels(i).getDriverTime()); +// if (viewMsg.getModels(i).getDriverTime()!=null){ +// String s16 = conversionTime(viewMsg.getModels(i).getDriverTime()); +// if (s16!=null){ +// CupidLogUtils.e("UdpRenderImageMessage--->time===>s16=" + s16); +// String s13 = s16.substring(0, 13); +// CupidLogUtils.e("UdpRenderImageMessage--->time===>s13=" + s13); +// rectBean.setDriverTime(s13); +// }else { +// rectBean.setDriverTime(viewMsg.getModels(i).getDriverTime()); +// } +// } + rectBeans.add(rectBean); + } + rectInfo.setModels(rectBeans); + if (adasListener != null) { + adasListener.onRectData(rectInfo); + CupidLogUtils.e("UdpRenderImageMessage--->protoBuf===>:rectInfo = " + rectInfo.toString()); + } + } + } + } + + public String conversionTime(String str) { + StringBuffer stringBuffer = new StringBuffer(""); + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) != '.') { + stringBuffer.append(str.charAt(i)); + } + } + return stringBuffer.toString(); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotArriveMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotArriveMessage.java new file mode 100644 index 0000000000..bb45a115a6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotArriveMessage.java @@ -0,0 +1,29 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotStatus; +import com.zhidao.support.adas.high.bean.AutopilotWayArrive; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des ws自动驾驶到站 + * @date 2020/3/12 + */ +public class WsAutopilotArriveMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //ws 自动驾驶状态 + AutopilotWayArrive autopilotWayArrive = gson.fromJson(msg, AutopilotWayArrive.class); + if (adasListener != null) { + adasListener.autopilotArrive(autopilotWayArrive); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotConfig.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotConfig.java new file mode 100644 index 0000000000..1046cebaf1 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotConfig.java @@ -0,0 +1,34 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AdasConfig; +import com.zhidao.support.adas.high.bean.AutopilotConfig; +import com.zhidao.support.adas.high.bean.guardian.AutopilotGuardianInfo; +import com.zhidao.support.adas.high.common.AppPreferenceHelper; +import com.zhidao.support.adas.high.common.Constants; +import com.zhidao.support.adas.high.common.MgContextUtils; + +/** + * @author song kenan + * @des + * @date 2021/11/17 + */ +public class WsAutopilotConfig extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + AutopilotConfig autopilotConfig = gson.fromJson(msg, AutopilotConfig.class); + AutopilotConfig.ResultBean result = autopilotConfig.getResult(); + if (result!=null){ + String dockVersion = result.getDock_version(); + AppPreferenceHelper.getInstance(MgContextUtils.getContext()).saveDockConfig(dockVersion); + } + + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotGuardian.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotGuardian.java new file mode 100644 index 0000000000..2dc502c8bc --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotGuardian.java @@ -0,0 +1,51 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.guardian.AutopilotGuardianInfo; +import com.zhidao.support.adas.high.bean.guardian.GuardianItemsName; +import com.zhidao.support.adas.high.bean.guardian.GuardianModule; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import java.util.ArrayList; + +import mogo.guardian.MogoGuardian; + +/** + * @author song kenan + * @des + * @date 2021/7/7 + */ +public class WsAutopilotGuardian extends MyAbstractMessageHandler{ + private final JsonFormat.Printer printer = JsonFormat.printer(); + private AutopilotGuardianInfo info; + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { +// CupidLogUtils.w("WsAutopilotGuardian===>"+msg); + AutopilotGuardianInfo autopilotGuardian = gson.fromJson(msg, AutopilotGuardianInfo.class); + if (adasListener != null) { + adasListener.onAutopilotGuardian(autopilotGuardian); + } + + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + try { + MogoGuardian.GuardianProto proto = MogoGuardian.GuardianProto.parseFrom(msg); + String print = printer.print(proto); + info = gson.fromJson(print, AutopilotGuardianInfo.class); + if (adasListener != null) { + adasListener.onAutopilotGuardian(info); + CupidLogUtils.w("WsAutopilotGuardian===>"+ info.toString()); + + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotIdentify.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotIdentify.java new file mode 100644 index 0000000000..82187a79a3 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotIdentify.java @@ -0,0 +1,71 @@ +package com.zhidao.support.adas.high.msg; + +import android.content.Intent; +import android.os.Handler; +import android.os.Message; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.record.AutopilotIdentifyInfo; +import com.zhidao.support.adas.high.bean.record.AutopilotRecordResult; +import com.zhidao.support.adas.high.common.HandlerThreadManager; +import com.zhidao.support.adas.high.common.MgContextUtils; +import com.zhidao.support.recorder.RecordDataManager; +import com.zhidao.support.recorder.activity.LoginSftpActivity; + +/** + * @author song kenan + * @des + * @date 2021/7/7 + */ +public class WsAutopilotIdentify extends MyAbstractMessageHandler { + private static final int SHOW_RECORDER = 0x01; + private boolean isConnect = false; + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + AutopilotIdentifyInfo info = gson.fromJson(msg, AutopilotIdentifyInfo.class); + AutopilotIdentifyInfo.Result result = info.getResult(); + AutopilotRecordResult panel = result.getPanel(); + if (adasListener != null) { + adasListener.onAutopilotRecord(panel); + } +// if (panel!=null){ +// int stat = panel.getStat(); +// if (stat==1){ +// final String user = result.getUser(); +// final String passwd = result.getPasswd(); +// String timestamp = panel.getTimestamp(); +// final String udpClientAddress = AppPreferenceHelper.getInstance(MgContextUtils.getContext()).getUdpClientAddress(); +// if (TextUtils.isEmpty(user) || TextUtils.isEmpty(passwd) || TextUtils.isEmpty(udpClientAddress)) { +// CupidLogUtils.w("ftp message error"); +// return; +// } +// RecordDataManager.getInstance().init(MgContextUtils.getContext(),timestamp , udpClientAddress, 22, user, passwd); +// isConnect = RecordDataManager.getInstance().connectSftp(); +// handler.removeMessages(SHOW_RECORDER); +// handler.sendEmptyMessage(SHOW_RECORDER); +// } +// } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } + + private Handler handler = new Handler(HandlerThreadManager.getMainHandler().getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (isConnect) { + RecordDataManager.getInstance().record(); + } else { + Intent intent = new Intent(MgContextUtils.getContext(), LoginSftpActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MgContextUtils.getContext().startActivity(intent); + } + } + }; + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRouteMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRouteMessage.java new file mode 100644 index 0000000000..fa6398c9a0 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRouteMessage.java @@ -0,0 +1,26 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotRoute; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +/** + * 自动驾驶路径 + */ +public class WsAutopilotRouteMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { +// CupidLogUtils.w("WsAutopilotRouteMessage===>"+msg); + AutopilotRoute autopilotRoute = gson.fromJson(msg, AutopilotRoute.class); + if (adasListener != null) { + adasListener.onAutopilotRoute(autopilotRoute); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRoutesMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRoutesMessage.java new file mode 100644 index 0000000000..c816039bb7 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotRoutesMessage.java @@ -0,0 +1,29 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotTrajectory; +import com.zhidao.support.adas.high.bean.TrajectoryInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import java.util.ArrayList; +import java.util.List; + +import mogo.trajectory.TrajectoryOuterClass; + +/** + * 自动驾驶路径 + */ +public class WsAutopilotRoutesMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotSNMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotSNMessage.java new file mode 100644 index 0000000000..5b3058101f --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotSNMessage.java @@ -0,0 +1,25 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotSnRequest; + +/** + * 自动驾驶路径 + */ +public class WsAutopilotSNMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { +// CupidLogUtils.w("WsAutopilotSNMessage===>"+msg); + AutopilotSnRequest autopilotSn = gson.fromJson(msg, AutopilotSnRequest.class); + if (adasListener != null) { + adasListener.onAutopilotSNRequest(); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotStatusMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotStatusMessage.java new file mode 100644 index 0000000000..5f384202d6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotStatusMessage.java @@ -0,0 +1,28 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotStatus; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +/** + * @author nie yunlong + * @des ws自动驾驶状态 + * @date 2020/3/12 + */ +public class WsAutopilotStatusMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + //ws 自动驾驶状态 + AutopilotStatus autopilotStatus = gson.fromJson(msg, AutopilotStatus.class); + if (adasListener != null) { + adasListener.autopilotStatus(autopilotStatus); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotTrajectoryMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotTrajectoryMessage.java new file mode 100644 index 0000000000..f31d0ee102 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotTrajectoryMessage.java @@ -0,0 +1,58 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.AutopilotTrajectory; +import com.zhidao.support.adas.high.bean.TrajectoryInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import java.util.ArrayList; +import java.util.List; + +import mogo.trajectory.TrajectoryOuterClass; + +/** + * 自动驾驶路径 + */ +public class WsAutopilotTrajectoryMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + CupidLogUtils.w("WsAutopilotTrajectoryMessage===>" + msg); + AutopilotTrajectory trajectory = gson.fromJson(msg, AutopilotTrajectory.class); + if (adasListener != null) { + adasListener.onAutopilotTrajectory(trajectory.getModels()); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + try { + TrajectoryOuterClass.Trajectory trajectory = TrajectoryOuterClass.Trajectory.parseFrom(msg); + List pointsList = trajectory.getPointsList(); + ArrayList trajectoryInfos = new ArrayList<>(); + for (int i = 0; i < pointsList.size(); i++) { + TrajectoryOuterClass.TrajectoryPoint point = pointsList.get(i); + TrajectoryInfo trajectoryInfo = new TrajectoryInfo(); + trajectoryInfo.setLon(point.getX()); + trajectoryInfo.setLat(point.getY()); + trajectoryInfo.setAlt(point.getZ()); + trajectoryInfo.setTime(point.getT()); + trajectoryInfo.setVelocity(point.getV()); + trajectoryInfo.setAcceleration(point.getA()); + trajectoryInfo.setTheta(point.getTheta()); + trajectoryInfo.setKappa(point.getKappa()); + trajectoryInfo.setAccumulatedDis(point.getS()); + trajectoryInfos.add(trajectoryInfo); + } + if (adasListener != null) { + adasListener.onAutopilotTrajectory(trajectoryInfos); + CupidLogUtils.e("WsAutopilotTrajectoryMessage--->" + trajectoryInfos.toString()); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotUpgradeStatusMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotUpgradeStatusMessage.java new file mode 100644 index 0000000000..501077e959 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsAutopilotUpgradeStatusMessage.java @@ -0,0 +1,27 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.IPCUpgradeStateInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; + +/** + * @des 工控机升级状态 + * @date 2020/3/12 + */ +public class WsAutopilotUpgradeStatusMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + CupidLogUtils.i("工控机升级", "msg=" + msg); + IPCUpgradeStateInfo autopilotStatus = gson.fromJson(msg, IPCUpgradeStateInfo.class); + if (adasListener != null) { + adasListener.onUpgradeStateInfo(autopilotStatus); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsImageSizeMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsImageSizeMessage.java new file mode 100644 index 0000000000..994c4384fe --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsImageSizeMessage.java @@ -0,0 +1,40 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * @author nie yunlong + * @des 图像大小 + * @date 2020/3/12 + */ +public class WsImageSizeMessage extends MyAbstractMessageHandler { + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + JSONObject jsonObject = null; + try { + jsonObject = new JSONObject(msg); + JSONObject videoInfo = jsonObject.optJSONObject("values"); + if (videoInfo != null) { + int width = videoInfo.optInt("width"); + int height = videoInfo.optInt("height"); + CupidLogUtils.e("--->width" + width + ",height" + height); + if (adasListener != null) { + adasListener.onVideoSize(width, height); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsWarnMessage.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsWarnMessage.java new file mode 100644 index 0000000000..373c5b9ac1 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/msg/WsWarnMessage.java @@ -0,0 +1,43 @@ +package com.zhidao.support.adas.high.msg; + +import com.google.gson.Gson; +import com.zhidao.support.adas.high.OnAdasListener; +import com.zhidao.support.adas.high.bean.WarnMessageInfo; +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.queue.UdpMsgModel; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * @author nie yunlong + * @des 报警消息 + * @date 2020/3/12 + */ +public class WsWarnMessage extends MyAbstractMessageHandler { + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, String msg) { + try { + JSONObject jsonObject = new JSONObject(msg); + JSONObject warning = jsonObject.optJSONObject("values"); + if (warning != null) { + String type = warning.optString("type"); + String content = warning.optString("content"); + String value = warning.optString("value"); + String level = warning.optString("level"); + CupidLogUtils.e(String.format("--->type:%s, content:%s, value:%s, level:%s", type, content, value, level)); + if (adasListener != null) { + adasListener.onWarnMessage(new WarnMessageInfo(type, content, value, level)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void handlerMsg(Gson gson, OnAdasListener adasListener, byte[] msg) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/PublicQueue.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/PublicQueue.java new file mode 100644 index 0000000000..60476a7d42 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/PublicQueue.java @@ -0,0 +1,96 @@ +package com.zhidao.support.adas.high.queue; + +import java.util.Iterator; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * 公共缓存队列 + * 只做两件事:(1)生产;(2)消费 + */ +public class PublicQueue { + + private BlockingDeque blockingDeque = new LinkedBlockingDeque<>();//缓冲区 + + public static PublicQueue publicQueue; + + public static synchronized PublicQueue getInstance() { + if (publicQueue == null) { + publicQueue = new PublicQueue(); + } + return publicQueue; + } + + public void add(T msg) { + + try { + blockingDeque.put(msg); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 先移除最新的然后添加最新的 + * + * @param msg + */ + public void removeLastPutLast(T msg) { + + try { + //移除最新的 然后把最新的添加到队尾 + blockingDeque.takeFirst(); + blockingDeque.put(msg); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + public T remove() { + + T t = null; + try { + t = blockingDeque.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return t; + } + + /** + * 清空所有数据 + */ + public void clear(){ + if (blockingDeque!=null){ + blockingDeque.clear(); + } + } + /** + * 获取个数 + * + * @return + */ + public int getSize() { + return blockingDeque.size(); + } + + /** + * 移除最后一个 + */ + public void removeLast() { + blockingDeque.removeLast(); + } + + + public String iteratorData(){ + StringBuilder stringBuilder=new StringBuilder(); + Iterator iterater = blockingDeque.iterator(); + while (iterater.hasNext()){ + T data = iterater.next(); + stringBuilder.append(","+data); + } + return stringBuilder.toString(); + } +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpMsgModel.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpMsgModel.java new file mode 100644 index 0000000000..9763e0dbb6 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpMsgModel.java @@ -0,0 +1,62 @@ +package com.zhidao.support.adas.high.queue; + +import okio.ByteString; + +/** + * author nieyunlong + * + * @des + * @date 2021/3/10 + */ +public class UdpMsgModel { + + /** + * 发送数据类型 + * 1 json,2 pb + */ + private int type; + + /** + * 当前接收到的时间 + */ + private long receiverDataTimeUdp; + + /** + * udp 数据 + */ + private String content; + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public long getReceiverDataTimeUdp() { + return receiverDataTimeUdp; + } + + public void setReceiverDataTimeUdp(long receiverDataTimeUdp) { + this.receiverDataTimeUdp = receiverDataTimeUdp; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + + @Override + public String toString() { + return "UdpMsgModel{" + + "receiverDataTimeUdp=" + receiverDataTimeUdp + + ", content='" + content + '\'' + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpQueueManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpQueueManager.java new file mode 100644 index 0000000000..c4ca870879 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/UdpQueueManager.java @@ -0,0 +1,101 @@ +package com.zhidao.support.adas.high.queue; + +import com.zhidao.support.adas.high.AdasChannel; +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.thread.QueuedWork; +import com.zhidao.support.adas.high.thread.callback.NormalCallback; + +import java.util.concurrent.Future; + +/** + * author : peng hongqiu + * e-mail : penghongqiu@163.com + * date : 2020/12/22 11:46 + * desc : + * version: + */ +public class UdpQueueManager { + + private static UdpQueueManager sInstance = new UdpQueueManager(); + //存储dup发送的数据 + PublicQueue publicQueue = new PublicQueue<>(); + //实时消费 + private boolean runConsumerFlag = true; + private AdasChannel adasChannel; + private RealTimeConsumerFrameMission realTimeConsumerFrameMission; + private Future mFutureTask; + + public static UdpQueueManager getInstance() { + return sInstance; + } + + private UdpQueueManager() { + + } + + + public void addQueueData(String data) { + publicQueue.add(data); + } + + public void registerAdasChannel(AdasChannel adasChannel) { + this.adasChannel = adasChannel; + } + + + /** + * 实时消费帧 消费完一帧 立马取最新一帧 + */ + private class RealTimeConsumerFrameMission extends NormalCallback { + //消费者 + private PublicQueue mPublicQueue; + + public RealTimeConsumerFrameMission(PublicQueue publicQueue) { + mPublicQueue = publicQueue; + } + + @Override + public String doInBackground() { + try { + while (runConsumerFlag) { + String data = mPublicQueue.remove(); + if (adasChannel != null) { + adasChannel.udpDataManage(data); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return super.doInBackground(); + } + + public void stop() { + runConsumerFlag = false; + } + } + + public synchronized void initDector() { + CupidLogUtils.w("===>UdpQueueManager initDector"); + release(); + runConsumerFlag = true; + if (realTimeConsumerFrameMission == null){ + realTimeConsumerFrameMission = new RealTimeConsumerFrameMission(publicQueue); + } + mFutureTask = QueuedWork.submit(null, realTimeConsumerFrameMission); + } + + public void release() { + publicQueue.clear(); + stopCallBack(); + } + + private void stopCallBack() { + if (realTimeConsumerFrameMission != null) { + realTimeConsumerFrameMission.stop(); + realTimeConsumerFrameMission = null; + } + if (mFutureTask != null) { + mFutureTask.cancel(true); + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WSByteQueueManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WSByteQueueManager.java new file mode 100644 index 0000000000..b218efb98b --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WSByteQueueManager.java @@ -0,0 +1,107 @@ +package com.zhidao.support.adas.high.queue; + + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.socket.FpgaSocket; +import com.zhidao.support.adas.high.thread.QueuedWork; +import com.zhidao.support.adas.high.thread.callback.NormalCallback; + +import java.util.concurrent.Future; + +import okio.ByteString; + +/** + * author : peng hongqiu + * e-mail : penghongqiu@163.com + * date : 2020/12/22 11:46 + * desc : + * version: + */ +public class WSByteQueueManager { + + private static WSByteQueueManager sInstance = new WSByteQueueManager(); + //存储dup/websocket发送的数据 + PublicQueue publicQueue = new PublicQueue<>(); + private RealTimeConsumerFrameMission realTimeConsumerFrameMission; + + private Future mFutureTask; + + private FpgaSocket.IWebSocketConnectListener webSocketConnectListener; + + public static WSByteQueueManager getInstance() { + return sInstance; + } + + private WSByteQueueManager() { + + } + + + public void addQueueData(ByteString data) { +// CupidLogUtils.w("====>生产者几个数据" + publicQueue.getSize() + ",list" + publicQueue.iteratorData()); + publicQueue.add(data); + } + + public void registerWebSocketListener(FpgaSocket.IWebSocketConnectListener webSocketConnectListener) { + this.webSocketConnectListener = webSocketConnectListener; + } + + + /** + * 实时消费帧 消费完一帧 立马取最新一帧 + */ + private class RealTimeConsumerFrameMission extends NormalCallback{ + //消费者 + private PublicQueue mPublicQueue; + //实时消费 + private boolean runConsumerFlag = true; + public RealTimeConsumerFrameMission(PublicQueue publicQueue) { + mPublicQueue = publicQueue; + } + public ByteString doInBackground() { + try { + while (runConsumerFlag) { +// CupidLogUtils.w("===>消费者==>ConsumerDataMission"); + ByteString data = mPublicQueue.remove(); +// CupidLogUtils.w("===>socket消费者" + data + ",内容个数" + mPublicQueue.getSize() + "\n所有数据" + mPublicQueue.iteratorData()); + if (webSocketConnectListener != null) { + webSocketConnectListener.onMessage(data); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + return super.doInBackground(); + } + + public void stop() { + runConsumerFlag = false; + } + + } + + + public synchronized void initDector() { + release(); + realTimeConsumerFrameMission = new RealTimeConsumerFrameMission(publicQueue); + mFutureTask = QueuedWork.submit(null,realTimeConsumerFrameMission); + + } + + public void release() { + publicQueue.clear(); + stopCallBack(); + } + + private void stopCallBack() { + if (realTimeConsumerFrameMission != null) { + realTimeConsumerFrameMission.stop(); + realTimeConsumerFrameMission = null; + } + if (mFutureTask != null) { + mFutureTask.cancel(true); + } + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WebSocketQueueManager.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WebSocketQueueManager.java new file mode 100644 index 0000000000..b7d8c864f4 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/queue/WebSocketQueueManager.java @@ -0,0 +1,105 @@ +package com.zhidao.support.adas.high.queue; + + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.socket.FpgaSocket; +import com.zhidao.support.adas.high.thread.QueuedWork; +import com.zhidao.support.adas.high.thread.callback.NormalCallback; + +import java.util.concurrent.Future; + +/** + * author : peng hongqiu + * e-mail : penghongqiu@163.com + * date : 2020/12/22 11:46 + * desc : + * version: + */ +public class WebSocketQueueManager { + + private static WebSocketQueueManager sInstance = new WebSocketQueueManager(); + //存储dup/websocket发送的数据 + PublicQueue publicQueue = new PublicQueue<>(); + private RealTimeConsumerFrameMission realTimeConsumerFrameMission; + + private Future mFutureTask; + + private FpgaSocket.IWebSocketConnectListener webSocketConnectListener; + + public static WebSocketQueueManager getInstance() { + return sInstance; + } + + private WebSocketQueueManager() { + + } + + + public void addQueueData(String data) { +// CupidLogUtils.w("====>生产者几个数据" + publicQueue.getSize() + ",list" + publicQueue.iteratorData()); + publicQueue.add(data); + } + + public void registerWebSocketListener(FpgaSocket.IWebSocketConnectListener webSocketConnectListener) { + this.webSocketConnectListener = webSocketConnectListener; + } + + + /** + * 实时消费帧 消费完一帧 立马取最新一帧 + */ + private class RealTimeConsumerFrameMission extends NormalCallback{ + //消费者 + private PublicQueue mPublicQueue; + //实时消费 + private boolean runConsumerFlag = true; + public RealTimeConsumerFrameMission(PublicQueue publicQueue) { + mPublicQueue = publicQueue; + } + public String doInBackground() { + try { + while (runConsumerFlag) { +// CupidLogUtils.w("===>消费者==>ConsumerDataMission"); + String data = mPublicQueue.remove(); +// CupidLogUtils.w("===>socket消费者" + data + ",内容个数" + mPublicQueue.getSize() + "\n所有数据" + mPublicQueue.iteratorData()); + if (webSocketConnectListener != null) { + webSocketConnectListener.onMessage(data); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + return super.doInBackground(); + } + + public void stop() { + runConsumerFlag = false; + } + + } + + + public synchronized void initDector() { + release(); + realTimeConsumerFrameMission = new RealTimeConsumerFrameMission(publicQueue); + mFutureTask = QueuedWork.submit(null,realTimeConsumerFrameMission); + + } + + public void release() { + publicQueue.clear(); + stopCallBack(); + } + + private void stopCallBack() { + if (realTimeConsumerFrameMission != null) { + realTimeConsumerFrameMission.stop(); + realTimeConsumerFrameMission = null; + } + if (mFutureTask != null) { + mFutureTask.cancel(true); + } + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/FpgaSocket.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/FpgaSocket.java new file mode 100644 index 0000000000..2c877b66fb --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/FpgaSocket.java @@ -0,0 +1,306 @@ +package com.zhidao.support.adas.high.socket; + +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_BYTE; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_JSON; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_WEB_SOCKET_OPEN; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_ADAS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_LOG_CONNECT_STATUS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_LOG_WEB_SOCKET_DATA; +import static com.zhidao.support.adas.high.common.Constants.WS_IP_HOST_HEAD; +import static com.zhidao.support.adas.high.common.Constants.WS_PORT; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.zhidao.support.adas.high.AdasChannel; +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.queue.WSByteQueueManager; +import com.zhidao.support.adas.high.queue.WebSocketQueueManager; +import com.zhjt.service.chain.ChainLog; +import com.zhjt.service.chain.TracingConstants; + +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high + * @ClassName: FpgaSocket + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/7 13:36 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/7 13:36 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public class FpgaSocket implements IWebSocket { + private static final String TAG = FpgaSocket.class.getSimpleName(); + private OkHttpClient client; + + private WebSocket mWebSocket; + + private EchoWebSocketListener listener; + private IWebSocketConnectListener mWebSocketConnectListener; + private Request request; + + private OkHttpClient.Builder okBuilder; + + private String wsHost; + /** + * 是否是用户主动关闭socket + */ + private volatile boolean isUserCloseWebSocket = false; + /** + * 是否启用自动重连 + */ + private volatile boolean isReconnect = true; + + public FpgaSocket() { + init(); + } + + private void init() { + if (listener == null) { + listener = new EchoWebSocketListener(); + } + if (okBuilder == null) { + okBuilder = new OkHttpClient.Builder(); + okBuilder.writeTimeout(10, TimeUnit.SECONDS); + okBuilder.readTimeout(10, TimeUnit.SECONDS); + okBuilder.connectTimeout(10, TimeUnit.SECONDS); + } + if (client == null) { + client = okBuilder.build(); + } + } + + @Override + public void connectWebSocket(String address) { + isUserCloseWebSocket = false; + wsHost = WS_IP_HOST_HEAD + address + WS_PORT; + CupidLogUtils.i(TAG, "WebSocket 主动连接= " + wsHost); + init(); + connect(); + } + + private void connect() { + if (client != null && mWebSocket == null) { + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onConnecting(); + if (request == null) + request = new Request.Builder() + .url(wsHost) + .build(); + client.newWebSocket(request, listener); + } + } + + /** + * 重连 + */ + public void reconnect() { + CupidLogUtils.i(TAG, "WebSocket 是否启用自动重连=" + isReconnect); + if (isReconnect) { + if (!isUserCloseWebSocket) { + try { + Thread.sleep(5000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + connect(); + } else { + isUserCloseWebSocket = false; + } + } + } + + @Override + public void closeWebSocket() { + CupidLogUtils.i(TAG, "WebSocket 主动断开连接"); + if (AdasChannel.isUseQueue) { + WebSocketQueueManager.getInstance().release(); + WSByteQueueManager.getInstance().release(); + } + isUserCloseWebSocket = true; + if (mWebSocket != null) { + boolean isClose = mWebSocket.close(1000, null); + CupidLogUtils.i(TAG, "WebSocket 主动断开连接,是否成功= " + isClose); + mWebSocket.cancel(); + mWebSocket = null; + } + listener = null; + client = null; + request = null; + } + + @Override + public boolean sendDataWebSocket(String data) { + if (mWebSocket != null) { + boolean result = mWebSocket.send(data); + CupidLogUtils.i(TAG, "WebSocket send String= " + data + ", result= " + result); + return result; + } + return false; + } + + @Override + public boolean sendDataWebSocket(ByteString data) { + if (mWebSocket != null) { + boolean result = mWebSocket.send(data); + CupidLogUtils.i(TAG, "WebSocket send ByteString= " + data + ", result= " + result); + return result; + } + return false; + } + + @Override + public void setIsReconnect(boolean isReconnect) { + this.isReconnect = isReconnect; + } + + + public interface IWebSocketConnectListener { + /** + * 正在连接 + */ + void onConnecting(); + + void onWebSocketConnectSuccess(); + + void onWebSocketConnectFailed(String t); + + void onMessage(String message); + + void onMessage(ByteString bytes) throws InvalidProtocolBufferException; + } + + public void setWebSocketListener(IWebSocketConnectListener listener) { + mWebSocketConnectListener = listener; + } + + public class EchoWebSocketListener extends WebSocketListener { + + @ChainLog(linkCode = CHAIN_LINK_ADAS, + linkChainLog = CHAIN_LINK_LOG_CONNECT_STATUS, + endpoint = TracingConstants.Endpoint.PAD, + nodeAliasCode = CHAIN_ALIAS_CODE_WEB_SOCKET_OPEN, + paramIndexes = {1}, + clientPkFileName = "sn") + @Override + public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) { + super.onOpen(webSocket, response); + mWebSocket = webSocket; + boolean isConnect = response.code() == 101; + if (!isConnect) { + reconnect(); + } else { + CupidLogUtils.i(TAG, "WebSocket 连接成功"); + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onWebSocketConnectSuccess(); + } + } + + @ChainLog(linkCode = CHAIN_LINK_ADAS, + linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, + endpoint = TracingConstants.Endpoint.PAD, + nodeAliasCode = CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_JSON, + paramIndexes = {1}, + clientPkFileName = "sn") + @Override + public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { + super.onMessage(webSocket, text); + CupidLogUtils.i(TAG, "WebSocket onMessage text= " + text); + if (TextUtils.isEmpty(text)) { + return; + } + if (AdasChannel.isUseQueue) { + WebSocketQueueManager.getInstance().addQueueData(text); + } else { + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onMessage(text); + } + } + + @ChainLog(linkCode = CHAIN_LINK_ADAS, + linkChainLog = CHAIN_LINK_LOG_WEB_SOCKET_DATA, + endpoint = TracingConstants.Endpoint.PAD, + nodeAliasCode = CHAIN_ALIAS_CODE_WEB_SOCKET_MESSAGE_BYTE, + paramIndexes = {-1}, + clientPkFileName = "sn") + @Override + public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) { + super.onMessage(webSocket, bytes); + CupidLogUtils.i(TAG, "WebSocket onMessage bytes= " + bytes.hex()); + if (AdasChannel.isUseQueue) { + WSByteQueueManager.getInstance().addQueueData(bytes); + } else { + try { + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onMessage(bytes); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + } + } + + } + + @Override + public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) { + super.onClosing(webSocket, code, reason); + CupidLogUtils.e(TAG, "WebSocket onClosing= " + reason); + if (mWebSocket != null) { + mWebSocket.close(1000, null); + mWebSocket.cancel(); + mWebSocket = null; + } else { + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onWebSocketConnectFailed(reason); + reconnect(); + } + } + + @Override + public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) { + super.onClosed(webSocket, code, reason); + CupidLogUtils.e(TAG, "WebSocket onClosed= " + reason); + if (mWebSocket != null) { + mWebSocket.close(1000, null); + mWebSocket.cancel(); + mWebSocket = null; + } else { + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onWebSocketConnectFailed(reason); + reconnect(); + } + } + + @Override + public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, Response response) { + super.onFailure(webSocket, t, response); + CupidLogUtils.e(TAG, "WebSocket onFailure= " + t); + if (mWebSocket != null) { + mWebSocket.close(1000, null); + mWebSocket.cancel(); + mWebSocket = null; + } + if (mWebSocketConnectListener != null) + mWebSocketConnectListener.onWebSocketConnectFailed(t.toString()); + reconnect(); + + } + } + + public WebSocket getWebSocket() { + return mWebSocket; + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/IWebSocket.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/IWebSocket.java new file mode 100644 index 0000000000..1c28c5bb2b --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/IWebSocket.java @@ -0,0 +1,46 @@ +package com.zhidao.support.adas.high.socket; + +import okio.ByteString; + +/** + * @ProjectName: lib-adas-fpga + * @Package: com.zhidao.lib.adas.high + * @ClassName: IWebSocket + * @Description: java类作用描述 + * @Author: fenghl + * @CreateDate: 2020/2/7 13:28 + * @UpdateUser: 更新者: + * @UpdateDate: 2020/2/7 13:28 + * @UpdateRemark: 更新说明: + * @Version: 1.0 + */ +public interface IWebSocket { + /** + * 连接socket + */ + void connectWebSocket(String address); + + /** + * 关闭webSocket + */ + void closeWebSocket(); + + /** + * websocket send data + * + * @param data + */ + boolean sendDataWebSocket(String data); + + boolean sendDataWebSocket(ByteString data); + + /** + * 是否启用自动重连 + * + * @param isReconnect + */ + void setIsReconnect(boolean isReconnect); + + +} + diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/SocketMsgType.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/SocketMsgType.java new file mode 100644 index 0000000000..aa4d402a78 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/SocketMsgType.java @@ -0,0 +1,62 @@ +package com.zhidao.support.adas.high.socket; + +/** + * @author nie yunlong + * @description 接收消息type + * @date 2018/7/3 + */ +public enum SocketMsgType { + + /** + * 警告 + */ + MSG_WARNING_TYPE(1, "警告"), + /** + * 视频分辨率 + */ + MSG_VIDEO_INFO_TYPE(2, "视频分辨率"), + + /** + * 控制台开 + */ + MSG_CONSOLE_OPEN_TYPE(3, "控制台开"), + /** + * 控制台关 + */ + MSG_CONSOLE_CLOSE_TYPE(4, "控制台关"), + /** + * 车道线曲率 + */ + MSG_CONSOLE_ROAD_TYPE(11, "车道线曲率"), + /** + * 是否是最左侧车道 + */ + MSG_IS_ROAD_LEFT(10, "获取位置"), + + /** + * 获取通知进度 + */ + MSG_UPGRADE_NOTIFY(12,"通知进度"); + + + int mMsgType; + String mMsgContent; + + SocketMsgType(int msgType, String msgContent) { + this.mMsgType = msgType; + this.mMsgContent = msgContent; + } + + /** + * 获取msgType + * + * @return + */ + public int getMsgType() { + return mMsgType; + } + + public String getmMsgContent() { + return mMsgContent; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/DefaultMessageProtocol.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/DefaultMessageProtocol.java new file mode 100644 index 0000000000..2095861dbc --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/DefaultMessageProtocol.java @@ -0,0 +1,50 @@ +package com.zhidao.support.adas.high.socket.read; + +import com.zhidao.support.adas.high.common.DigitalTrans; + +/** + * @author song kenan + * @des + * @date 2021/10/20 + */ +public class DefaultMessageProtocol implements IMessageProtocol{ + @Override + public int getMagicCodeLength() { + return 2; + } + + @Override + public int getPackageLength() { + return 4; + } + + @Override + public int getOffsetLength() { + return 2; + } + + public int getOutHeader(){ + return getMagicCodeLength()+getPackageLength()+getOffsetLength(); + } + + @Override + public int getHeaderLength(byte[] offset) { + if (offset == null) { + return 0; + } + String lengthStr = DigitalTrans.byte2hex(offset); + int length = DigitalTrans.hexStringToAlgorism(lengthStr); + return length - 8; + } + + @Override + public int getPayloadLength(byte[] readData,byte[] offset) { + if (readData == null){ + return 0; + } + if (offset == null){ + return DigitalTrans.hexStringToAlgorism(DigitalTrans.byte2hex(readData)); + } + return 0; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/IMessageProtocol.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/IMessageProtocol.java new file mode 100644 index 0000000000..e68846c9f9 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/IMessageProtocol.java @@ -0,0 +1,35 @@ +package com.zhidao.support.adas.high.socket.read; + +import java.nio.ByteOrder; + +/** + * @author song kenan + * @des + * @date 2021/10/20 + */ +public interface IMessageProtocol { + /** + * 获取magicCode的长度 + */ + int getMagicCodeLength(); + + /** + * 包长度 + */ + int getPackageLength(); + + /** + * 数据偏移量的长度 + */ + int getOffsetLength(); + + /** + * 获取header数据长度 + */ + int getHeaderLength(byte[] offset); + + /** + * 消息体长度 + */ + int getPayloadLength(byte[] readData,byte[] offset); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/OriginReadData.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/OriginReadData.java new file mode 100644 index 0000000000..b078e4e7c7 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/OriginReadData.java @@ -0,0 +1,103 @@ +package com.zhidao.support.adas.high.socket.read; + +import com.zhidao.support.adas.high.common.DigitalTrans; + +import java.io.Serializable; + +/** + * @author song kenan + * @des + * @date 2021/10/20 + */ +public class OriginReadData implements Serializable { + /** + * 用于检查数据是否为正确数据 + * mg=0x6d67 + */ + private byte[] magicCode; + + /** + * 包长度 + */ + private byte[] packageLength; + + /** + * 偏移量,用于计算header长度 + */ + private byte[] offset; + + /** + * 头部消息,装载消息类型等 + * 长度=offset-out_header.length + */ + private byte[] header; + + /** + * 负载数据:真实上报的数据 + */ + private byte[] payload; + + + /** + * 获取out_header 由魔数、包大小、payload偏移量组成; + * 用于识别连接数据和数据报解码使用。 + * 固定8个字节,否则出错。 + */ + public byte[] getOutHeaderBytes() { + byte[] bytes = DigitalTrans.concatBytes(getMagicCode(), getPackageLength()); + byte[] outHeader = DigitalTrans.concatBytes(bytes, getOffset()); + return outHeader; + } + + /** + * 获取完整的数据 + * + * @return + */ + public byte[] getOriginDataBytes() { + byte[] bytes = DigitalTrans.concatBytes(getOutHeaderBytes(), getHeader()); + byte[] originData = DigitalTrans.concatBytes(bytes, getPayload()); + return originData; + } + + + public byte[] getMagicCode() { + return magicCode; + } + + public void setMagicCode(byte[] magicCode) { + this.magicCode = magicCode; + } + + public byte[] getPackageLength() { + return packageLength; + } + + public void setPackageLength(byte[] packageLength) { + this.packageLength = packageLength; + } + + public byte[] getOffset() { + return offset; + } + + public void setOffset(byte[] offset) { + this.offset = offset; + } + + public byte[] getHeader() { + return header; + } + + public void setHeader(byte[] header) { + this.header = header; + } + + public byte[] getPayload() { + return payload; + } + + public void setPayload(byte[] payload) { + this.payload = payload; + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/SocketReader.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/SocketReader.java new file mode 100644 index 0000000000..007a41f4d2 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/socket/read/SocketReader.java @@ -0,0 +1,69 @@ +package com.zhidao.support.adas.high.socket.read; + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.common.DigitalTrans; +import com.zhidao.support.adas.high.udp.CupidUdpConstract; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.Arrays; + +import okio.ByteString; + +/** + * @author song kenan + * @des + * @date 2021/10/20 + */ +public class SocketReader { + private DefaultMessageProtocol messageProtocol; + private static final byte[] mg = new byte[]{(byte) 0x6d, 0x67}; + + public SocketReader() { + messageProtocol = new DefaultMessageProtocol(); + } + + public OriginReadData read(ByteString bytes) { + OriginReadData originalData = new OriginReadData(); + OriginReadData originReadData = readData(bytes, originalData); + //分发 + return originReadData; + + } + + private synchronized OriginReadData readData(ByteString bytes, OriginReadData originalData) { +// CupidLogUtils.w("--->websocket byte read bytes :"+bytes.hex()); + //读取magicCode + ByteString magicCode = bytes.substring(0, messageProtocol.getMagicCodeLength()); + originalData.setMagicCode(magicCode.toByteArray()); +// CupidLogUtils.w("--->websocket byte read magic :"+magicCode.hex()); + if (Arrays.equals(magicCode.toByteArray(), mg)) { + //读取offset + ByteString offset = bytes.substring(messageProtocol.getMagicCodeLength(), messageProtocol.getMagicCodeLength() + messageProtocol.getOffsetLength()); + originalData.setOffset(offset.toByteArray()); +// CupidLogUtils.w("--->websocket byte read offset :"+offset.hex()+";offset length="+ DigitalTrans.hexStringToAlgorism(offset.hex())); + //读取packageLength + ByteString packageLength = bytes.substring(messageProtocol.getMagicCodeLength() + messageProtocol.getOffsetLength(), messageProtocol.getOutHeader()); + originalData.setPackageLength(packageLength.toByteArray()); +// CupidLogUtils.w("--->websocket byte read packageLength :"+packageLength.hex()+";pl="+ DigitalTrans.hexStringToAlgorism(packageLength.hex())); + //读取header + ByteString header = bytes.substring(messageProtocol.getOutHeader(), messageProtocol.getOutHeader() + messageProtocol.getHeaderLength(offset.toByteArray())); + originalData.setHeader(header.toByteArray()); +// CupidLogUtils.w("--->websocket byte read header :"+header.hex()); + //读取payload + ByteString payload = bytes.substring(messageProtocol.getOutHeader() + messageProtocol.getHeaderLength(offset.toByteArray())); + originalData.setPayload(payload.toByteArray()); +// CupidLogUtils.w("--->websocket byte read payload :"+payload.hex()); + return originalData; + } else { + CupidLogUtils.w("--->websocket byte read magicCode error 丢弃"); + return null; + } + + + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/CallableWrapper.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/CallableWrapper.java new file mode 100644 index 0000000000..1d0508a537 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/CallableWrapper.java @@ -0,0 +1,84 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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. +*/ + +package com.zhidao.support.adas.high.thread; + + + +import com.zhidao.support.adas.high.thread.callback.NormalCallback; +import com.zhidao.support.adas.high.thread.callback.ThreadCallback; + +import java.util.concurrent.Callable; + +/** + * @param + */ +public final class CallableWrapper implements Callable { + + private ThreadCallback callback; + + private Callable proxy; + + /** + * 构造方法 + * + * @param proxy 线程优先级 + */ + public CallableWrapper(Callable proxy, NormalCallback normalCallback) { + this.proxy = proxy; + this.callback = normalCallback; + } + + /** + * 详细可以看我的GitHub:https://github.com/yangchong211 + * 自定义Callable继承Callable类,Callable 是在 JDK1.5 增加的。 + * Callable 的 call() 方法可以返回值和抛出异常 + * + * @return 泛型 + * @throws Exception 异常 + */ + @Override + public T call() { + if (callback != null) { + //开始 + callback.onStart(); + } + T t = null; + try { + t = proxy == null ? null : proxy.call(); + if (callback != null) { + T callInBackground = callback.doInBackground(); + if (callInBackground != null) { + t = callInBackground; + } + } + } catch (Exception e) { + e.printStackTrace(); + //异常错误 + if (callback != null) { + callback.onError(e); + } + } finally { + //完成 + if (callback != null) { + callback.onCompleted(); + } + } + return t; + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/QueuedWork.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/QueuedWork.java new file mode 100644 index 0000000000..595736858d --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/QueuedWork.java @@ -0,0 +1,141 @@ +package com.zhidao.support.adas.high.thread; + +import android.os.Handler; +import android.os.Looper; + + +import com.zhidao.support.adas.high.thread.callback.NormalCallback; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by nieyunlong on 17/8/8. + * 队列 + */ + +public class QueuedWork { + + private static Handler uiHandler; + + /*sdk请求量不是很大*/ + private static final int MAX_MISSION_COUNT = 1; //设置线程池最大线程个数 + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + // We want at least 2 threads and at most 4 threads in the core pool, + // preferring to have 1 less than the CPU count to avoid saturating + // the CPU with background work +// private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); + private static final int CORE_POOL_SIZE = 3; + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + + public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR; + + static { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 30, TimeUnit.SECONDS, + new LinkedBlockingDeque(128)); + threadPoolExecutor.allowCoreThreadTimeOut(true); + THREAD_POOL_EXECUTOR = threadPoolExecutor; + } + + + public static void runInMain(Runnable paramRunnable) { + if (uiHandler == null) + uiHandler = new Handler(Looper.getMainLooper()); + uiHandler.post(paramRunnable); + } + + public static void runInBack(Runnable task) { + THREAD_POOL_EXECUTOR.execute(task); + } + + /** + * 建议停止正在执行的任务 + */ + public static void shutDownNow() { + THREAD_POOL_EXECUTOR.shutdownNow(); + } + + /** + * 建议停止正在执行的任务 + */ + public static void shutDown() { + THREAD_POOL_EXECUTOR.shutdown(); + } + + /** + * 发射任务 + * 提交任务有返回值 + * 当我们使用submit来提交任务时,它会返回一个future,我们就可以通过这个future来判断任务是否执行成功, + * 还可以通过future的get方法来获取返回值。如果子线程任务没有完成,get方法会阻塞住直到任务完成, + * 而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时候有可能任务并没有执行完。 + * + * @param callable callable + * @param type + * @return {@link Future} + */ + public static Future submit(Callable callable, NormalCallback normalCallback) { + callable = new CallableWrapper<>(callable, normalCallback); + Future result = THREAD_POOL_EXECUTOR.submit(callable); + return result; + } + + /** + * 移除队列里面的任务 + */ + public static int getQueueSize() { + if (null != THREAD_POOL_EXECUTOR) { + BlockingQueue queue = THREAD_POOL_EXECUTOR.getQueue(); + return queue.size(); + } + return 0; + } + + + public static abstract class ZSAsyncTask { + + protected Runnable thread; + + protected void onPreExecute() { + } + + protected abstract Result doInBackground(); + + protected void onPostExecute(Result paramResult) { + } + + public final ZSAsyncTask execute() { + this.thread = new Runnable() { + public void run() { + final Result var1 = ZSAsyncTask.this.doInBackground(); + QueuedWork.runInMain(new Runnable() { + public void run() { + ZSAsyncTask.this.onPostExecute(var1); + } + }); + } + }; + QueuedWork.runInMain(new Runnable() { + public void run() { + //pre execute + ZSAsyncTask.this.onPreExecute(); + runInBack(ZSAsyncTask.this.thread); + } + }); + return this; + } + } + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "ZSAsyncTask #" + mCount.getAndIncrement()); + } + }; +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/NormalCallback.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/NormalCallback.java new file mode 100644 index 0000000000..15ba267b0a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/NormalCallback.java @@ -0,0 +1,44 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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. +*/ +package com.zhidao.support.adas.high.thread.callback; + +/** + * + * @param + */ +public class NormalCallback implements ThreadCallback { + + + @Override + public void onError(Throwable t) { + + } + + @Override + public Result doInBackground() { + return null; + } + + @Override + public void onCompleted() { + + } + + @Override + public void onStart() { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/ThreadCallback.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/ThreadCallback.java new file mode 100644 index 0000000000..9e9d0965b9 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/thread/callback/ThreadCallback.java @@ -0,0 +1,47 @@ +/* +Copyright 2017 yangchong211(github.com/yangchong211) + +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. +*/ +package com.zhidao.support.adas.high.thread.callback; + +/** + * + * @param + */ +public interface ThreadCallback { + + /** + * 当线程发生错误时,将调用此方法。 + * @param t 异常 + */ + void onError(Throwable t); + + /** + * 做后台操作 + * 如果没有Callable 代理 直接走这个后台操作 如果有代理会直接走代理 + */ + Result doInBackground(); + + /** + * 通知用户知道它已经完成 + */ + void onCompleted(); + + /** + * 通知用户任务开始运行 + */ + void onStart(); + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl.java new file mode 100644 index 0000000000..b9e0497aec --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl.java @@ -0,0 +1,88 @@ +package com.zhidao.support.adas.high.udp; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +/** + * Created by wangyaofu on 2018/6/14. + */ +public class CupidBufStreamImpl implements IBuffStream { + + // 5M +// public static final int BUF_SIZE = 5242880; + public static final int BUF_SIZE = 2097152; + //3M + public static final int BUF_SIZE_LIMIT = 3145728; + + ByteArrayOutputStream ostream0_; + ByteArrayOutputStream ostream1_; + + volatile Boolean isSwitch; + + @Override + public void init() { + ostream0_ = new ByteArrayOutputStream(BUF_SIZE); + ostream1_ = new ByteArrayOutputStream(BUF_SIZE); + isSwitch = false; + } + + @Override + public OutputStream getOutputStream() { + synchronized (isSwitch) { + if (isSwitch) { + return ostream1_; + } else { + return ostream0_; + } + } + + } + + @Override + public byte[] getData() { + byte[] b = null; + synchronized (isSwitch) { + if (isSwitch) { + b = ostream0_.toByteArray(); + } else { + b = ostream1_.toByteArray(); + } + } + return b; + } + + @Override + public int getLength() { + int size; + synchronized (isSwitch) { + if (isSwitch) { + size = ostream0_.size(); + } else { + size = ostream1_.size(); + } + } + return size; + } + + @Override + public void close() { + if (ostream0_ != null) { + ostream0_.reset(); + } + if (ostream1_ != null) { + ostream1_.reset(); + } + } + + @Override + public void doSwitch() { + synchronized (isSwitch) { + if (isSwitch) { + ostream0_.reset(); + } else { + ostream1_.reset(); + } + isSwitch = !isSwitch; + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl2.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl2.java new file mode 100644 index 0000000000..df33ac8ade --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidBufStreamImpl2.java @@ -0,0 +1,54 @@ +package com.zhidao.support.adas.high.udp; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +/** + * Created by wangyaofu on 2018/6/14. + */ +public class CupidBufStreamImpl2 implements IBuffStream { + + // 5M +// public static final int BUF_SIZE = 5242880; + public static final int BUF_SIZE = 2097152; + //3M + public static final int BUF_SIZE_LIMIT = 3145728; + + ByteArrayOutputStream ostream0_; + + @Override + public void init() { + if (ostream0_ != null) { + ostream0_.reset(); + ostream0_ = null; + } + ostream0_ = new ByteArrayOutputStream(BUF_SIZE); + } + + @Override + public OutputStream getOutputStream() { + return ostream0_; + } + + @Override + public byte[] getData() { + byte[] b = ostream0_.toByteArray(); + return b; + } + + @Override + public int getLength() { + int size = ostream0_.size(); + return size; + } + + @Override + public void close() { + init(); + } + + @Override + public void doSwitch() { + ostream0_.reset(); + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpConstract.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpConstract.java new file mode 100644 index 0000000000..ae386ba84a --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpConstract.java @@ -0,0 +1,144 @@ +package com.zhidao.support.adas.high.udp; + + +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_ALIAS_CODE_UDP_INIT; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_ADAS; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_LOG_CONNECT_STATUS; + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.udp.factory.AbstractUdpImpl; +import com.zhidao.support.adas.high.udp.factory.CupidUdpFactory; +import com.zhjt.service.chain.ChainLog; +import com.zhjt.service.chain.TracingConstants; + +/** + * @author nie yunlong + * @description udp 与 stream + * @date 2018/6/15 + */ +public class CupidUdpConstract { + + + private CupidBufStreamImpl2 buffStreamImpl; + /** + * udp 接收数据线程 + */ + private Thread udpReceiverDataThread; + /** + * 默认的udp端口 + */ + private int defaultVideoUdpPort = 6665; + + /** + * 默认的udp端口 + */ + private int defaultRenderImageUdpPort = 6661; + + private AbstractUdpImpl udpImpl; + /** + * 默认是0 创建h264解析流 + */ + public static final int VIDEO_STREAM_UDP = 0; + /** + * 上层绘制 待开发 + */ + public static final int VIDEO_RENDER_IMAGE_UDP = 1; + + public CupidUdpConstract(int udpType) { + init(udpType); + } + + /** + * 初始化UDPserver 接收数据 + */ + private void init(int udpType) { + if (udpType == VIDEO_STREAM_UDP) { + udpImpl = CupidUdpFactory.createVideoStreamUdp(); + udpImpl.setUdpPort(defaultVideoUdpPort); + // buffStreamImpl = new CupidBufStreamImpl(); + buffStreamImpl = new CupidBufStreamImpl2(); + buffStreamImpl.init(); + udpImpl.setBuffStream(buffStreamImpl); + } else if (udpType == VIDEO_RENDER_IMAGE_UDP) { + udpImpl = CupidUdpFactory.createRenderImageUdp(); + udpImpl.setUdpPort(defaultRenderImageUdpPort); + } + udpImpl.setStop(false); + startReceiverDataThread(); + } + + /** + * 开启线程 在surface准备好之后再进行接收数据 + */ + @ChainLog(linkCode = CHAIN_LINK_ADAS, + linkChainLog = CHAIN_LINK_LOG_CONNECT_STATUS, + endpoint = TracingConstants.Endpoint.PAD, + nodeAliasCode = CHAIN_ALIAS_CODE_UDP_INIT, + paramIndexes = {0}, + clientPkFileName = "sn") + public void startReceiverDataThread() { + if (udpReceiverDataThread != null) { + udpReceiverDataThread.interrupt(); + udpReceiverDataThread = null; + } + udpReceiverDataThread = new Thread(udpImpl); + udpReceiverDataThread.start(); + } + + public void setOnReceiverH264Data(IGetH264Data getH264Data) { + udpImpl.getVideoInputStreamData(getH264Data); + } + + /** + * 连接listener + * + * @param connectListener + */ + public void setOnConnectListener(IConnectRtpListener connectListener) { + udpImpl.setOnConnectListener(connectListener); + } + + public void onPause() { + udpImpl.onPause(); + } + + public void onResume() { + udpImpl.onResume(); + } + + /** + * 初始化webSocket + */ + public void setUdpIsFirstInit() { + if (udpImpl != null) { + udpImpl.setFirstReceiverAddress(false); + } + } + + /** + * 服务端关闭 初始化buffer + */ + public void resetBuffer() { + if (udpImpl != null) { + udpImpl.initBuffer(); + } + } + + + public void release() { + try { + CupidLogUtils.e("---->关闭socket"); + if (udpImpl != null) { + udpImpl.close(); + udpImpl = null; + } + if (udpReceiverDataThread != null && !udpReceiverDataThread.isInterrupted()) { + udpReceiverDataThread.interrupt(); + udpReceiverDataThread = null; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpServer.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpServer.java new file mode 100644 index 0000000000..bc4f6c1a0f --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/CupidUdpServer.java @@ -0,0 +1,275 @@ +package com.zhidao.support.adas.high.udp; + + + +import com.zhidao.support.adas.high.common.CupidLogUtils; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.MulticastSocket; + +/** + * Created by wangyaofu on 18/6/14. + */ +public class CupidUdpServer implements Runnable { + + static final String TAG = CupidUdpServer.class.getSimpleName(); + + private int udpPort; + private boolean isMultiCast; + private String multiCastIp; + private volatile boolean isStop; + + private IBuffStream buffStream; + /** + * 接口 回调一帧 + */ + private IGetH264Data mGetH264Data; + + private IConnectRtpListener mConnectRtpListener; + /** + * 接收的ip地址 + */ + private volatile String mReceiveAddress; + + + public int getUdpPort() { + return udpPort; + } + + public void setUdpPort(int udpPort) { + this.udpPort = udpPort; + } + + public boolean isMultiCast() { + return isMultiCast; + } + + public void setMultiCast(boolean multiCast) { + isMultiCast = multiCast; + } + + public String getMultiCastIp() { + return multiCastIp; + } + + public void setMultiCastIp(String multiCastIp) { + this.multiCastIp = multiCastIp; + } + + public boolean isStop() { + return isStop; + } + + public synchronized void setStop(boolean stop) { + isStop = stop; + } + + public IBuffStream getBuffStream() { + return buffStream; + } + + public void setBuffStream(IBuffStream buffStream) { + this.buffStream = buffStream; + } + + + /** + * + * @param connectRtpListener + */ + public void setOnConnectListener(IConnectRtpListener connectRtpListener) { + this.mConnectRtpListener = connectRtpListener; + } + + /** + * + * @param getH264Data + */ + public void setOnReceiverListener(IGetH264Data getH264Data) { + this.mGetH264Data = getH264Data; + } + + private static final int DATA_LEN = 4096; + byte[] inBuff = new byte[DATA_LEN]; + /** + * 接收的包 + */ + private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); + /** + * udp + */ + private DatagramSocket socket = null; + /** + * 带广播的udp + */ + private MulticastSocket mMulticastSocket = null; + /** + * 第一次获取到接收端的ip + */ + private volatile boolean isFirstReceiverAddress; + + boolean parseRTP() { + int naluType = 0; + int nalunri = 0; + int naluf = 0; + + boolean isEnd = false; + if ((int) (inBuff[1] >> 7 & 0x01) == 1) { + isEnd = true; + } + + byte naluHeader = inBuff[12]; + naluType = naluHeader & 0x1f; + nalunri = naluHeader >> 5 & 0x03; + naluf = naluHeader >> 7 & 0x01; + int len = inPacket.getLength(); + CupidLogUtils.i(TAG, "packet-len:" + len); + + byte[] fu_h_byte = new byte[5]; + fu_h_byte[0] = 0; + fu_h_byte[1] = 0; + fu_h_byte[2] = 0; + fu_h_byte[3] = 1; + + if (28 == naluType) { + // H264 Begin + int fu_type = 0; + int fu_r = 0; + int fu_e = 0; + int fu_s = 0; + + byte fu_header = inBuff[13]; + fu_type = fu_header & 0x1f; + fu_r = fu_header >> 5 & 0x01; + fu_e = fu_header >> 6 & 0x01; + fu_s = fu_header >> 7 & 0x01; + + if (fu_s == 1) { + //buffStream.doSwitch(); + // start of fu-a + fu_h_byte[4] = (byte) ((fu_type & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } else { + // end of fu-a + try { + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + } else { + fu_h_byte[4] = (byte) ((naluType & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 13, len - 13); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + if (isEnd) { + buffStream.doSwitch(); + if (mGetH264Data != null) { + mGetH264Data.getH264Data(buffStream.getData(),System.currentTimeMillis()); + } + } + return true; + } + + @Override + public void run() { + CupidLogUtils.e(TAG, "---->开始接收数据"); + try { + if (isMultiCast) { + mMulticastSocket = new MulticastSocket(udpPort); + InetAddress bcAddr = InetAddress.getByName(multiCastIp); + mMulticastSocket.setReuseAddress(true); + mMulticastSocket.joinGroup(bcAddr); + mMulticastSocket.setLoopbackMode(false); + } else { + CupidLogUtils.e(TAG, "---->开始连接端口" + udpPort); + socket = new DatagramSocket(udpPort); + socket.setReuseAddress(true); + CupidLogUtils.e(TAG, "---->连接成功" + udpPort); + } + } catch (Exception e) { + e.printStackTrace(); + if (mConnectRtpListener != null) { + mConnectRtpListener.onConnectionFailedRtp(e.getMessage()); + } + return; + } + + while (!isStop) { + try { + if (isMultiCast && mMulticastSocket != null) { + mMulticastSocket.receive(inPacket); + } else { + if (socket != null) { + socket.receive(inPacket); + } + } + if (!isFirstReceiverAddress) { + mReceiveAddress = inPacket.getAddress().getHostAddress(); + CupidLogUtils.e("mReceiveAddress--->mReceiveAddress" + mReceiveAddress); + isFirstReceiverAddress = true; + if (mConnectRtpListener != null) { + mConnectRtpListener.getOnConnectionAddress(mReceiveAddress); + } + } + + + if (inPacket.getLength() > 12) { + parseRTP(); + } + } catch (Exception e) { + e.printStackTrace(); + if (mConnectRtpListener != null) { + mConnectRtpListener.onConnectionFailedRtp(e.getMessage()); + } + } + } + } + + /** + * 获取到连接ip地址 + * + * @return + */ + public String getmReceiveAddress() { + return mReceiveAddress; + } + + + /** + * 关闭流 + */ + public synchronized void close() { + setStop(true); + if (mMulticastSocket != null) { + mMulticastSocket.close(); + } + if (socket != null) { + socket.close(); + } + } + + public boolean isFirstReceiverAddress() { + return isFirstReceiverAddress; + } + + public void setFirstReceiverAddress(boolean firstReceiverAddress) { + isFirstReceiverAddress = firstReceiverAddress; + } +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IBuffStream.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IBuffStream.java new file mode 100644 index 0000000000..07b065ab5b --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IBuffStream.java @@ -0,0 +1,20 @@ +package com.zhidao.support.adas.high.udp; + +import java.io.OutputStream; + +/** + * Created by wangyaofu on 2018/6/14. + */ +public interface IBuffStream { + void init(); + + OutputStream getOutputStream(); + + void doSwitch(); + + byte[] getData(); + + int getLength(); + + void close(); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IConnectRtpListener.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IConnectRtpListener.java new file mode 100644 index 0000000000..ecccba0bb9 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IConnectRtpListener.java @@ -0,0 +1,25 @@ +package com.zhidao.support.adas.high.udp; + +/** + * @author nie yunlong + * @description + * @date 2018/6/15 + */ +public interface IConnectRtpListener { + + /** + * 连接成功 指的是 client发送数据包 + */ + void onConnectionSuccessRtp(); + + void onConnectionFailedRtp(String reason); + + /** + * 获取到client的ip地址 + * @param address + */ + void getOnConnectionAddress(String address); + + void onDisconnectRtp(); + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IGetH264Data.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IGetH264Data.java new file mode 100644 index 0000000000..d32a9f0351 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IGetH264Data.java @@ -0,0 +1,16 @@ +package com.zhidao.support.adas.high.udp; + +/** + * @author nie yunlong + * @description h264数据 + * @date 2018/6/15 + */ +public interface IGetH264Data { + + /** + * 获取h264数据 + * @param data h264 + */ + void getH264Data(byte[] data,long receiverDataUdpTime); + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IRenderImageListener.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IRenderImageListener.java new file mode 100644 index 0000000000..88b4495452 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/IRenderImageListener.java @@ -0,0 +1,81 @@ +package com.zhidao.support.adas.high.udp; + +import android.view.SurfaceHolder; + +/** + * @author nie yunlong + * @description 回调获取数据时候 ui变化 + * @date 2018/6/26 + */ +public interface IRenderImageListener { + /** + * 获取udp包的数据 用来告知展示什么ui + * + * @param data + */ + void getUdpData(String data); + + /** + * surface create + * + * @param holder + */ + void surfaceCreated(SurfaceHolder holder); + + /** + * 改变尺寸 + * + * @param holder + * @param format + * @param width + * @param height + */ + void surfaceChanged(SurfaceHolder holder, int format, int width, int height); + + /** + * 销毁 + * + * @param holder + */ + void surfaceDestroyed(SurfaceHolder holder); + + /** + * udp 连接失败 + */ + void onConnectionFailedRtp(String reason); + + /** + * 连接udpok + * @param address + */ + void getOnConnectionAddress(String address); + + + /** + * 所有的都是模拟 贴图模式 + * + * @param canvas + * @param width + * @param height + * @param scaleWidth + * @param scaleHeight + * @param isChange 是否改变元数据坐标 + */ + //boolean draw(Canvas canvas, int width, int height, float scaleWidth, float scaleHeight, List drawUiInfoList, boolean isChange); + + /** + * 绘制 与帧绑定 + * + * @param canvas + * @param width 展示UI层 宽度 + */ + // void draw(Canvas canvas, int width, int height, float scaleWidth, float scaleHeight, DrawUiInfo drawUiInfo); + + /** + * @param canvas + * @param picWidth 图片的宽度 + * @param picHeight 图片的高度 + * @param drawAlarmAnimationList 报警位置的集合 + */ +// void drawAlarm(Canvas canvas, float picWidth, float picHeight, List drawAlarmAnimationList); +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/RTPUtils.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/RTPUtils.java new file mode 100644 index 0000000000..34bfaf260d --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/RTPUtils.java @@ -0,0 +1,392 @@ +package com.zhidao.support.adas.high.udp; + +import java.util.Comparator; + +/** + * RTP-related static utility methods. + * + * @deprecated + * + * @author Boris Grozev + */ +public class RTPUtils +{ + /** + * Hex characters for converting bytes to readable hex strings + */ + private final static char[] HEXES = new char[] + { + '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + /** + * Returns the delta between two RTP sequence numbers, taking into account + * rollover. This will return the 'shortest' delta between the two + * sequence numbers in the form of the number you'd add to b to get a. e.g.: + * getSequenceNumberDelta(1, 10) -> -9 (10 + -9 = 1) + * getSequenceNumberDelta(1, 65530) -> 7 (65530 + 7 = 1) + * @return the delta between two RTP sequence numbers (modulo 2^16). + */ + public static int getSequenceNumberDelta(int a, int b) + { + int diff = a - b; + + if (diff < -(1<<15)) + { + diff += 1 << 16; + } + else if (diff > 1<<15) + { + diff -= 1 << 16; + } + + return diff; + } + + /** + * Returns whether or not seqNumOne is 'older' than seqNumTwo, taking + * rollover into account + * @param seqNumOne + * @param seqNumTwo + * @return true if seqNumOne is 'older' than seqNumTwo + */ + public static boolean isOlderSequenceNumberThan(int seqNumOne, int seqNumTwo) + { + return getSequenceNumberDelta(seqNumOne, seqNumTwo) < 0; + } + + /** + * Returns result of the subtraction of one RTP sequence number from another + * (modulo 2^16). + * @return result of the subtraction of one RTP sequence number from another + * (modulo 2^16). + */ + public static int subtractNumber(int a, int b) + { + return as16Bits(a - b); + } + + + /** + * Apply a delta to a given sequence number and return the result (taking + * rollover into account) + * @param startingSequenceNumber the starting sequence number + * @param delta the delta to be applied + * @return the sequence number result from doing + * startingSequenceNumber + delta + */ + public static int applySequenceNumberDelta( + int startingSequenceNumber, int delta) + { + return (startingSequenceNumber + delta) & 0xFFFF; + } + + /** + * Set an integer at specified offset in network order. + * + * @param off Offset into the buffer + * @param data The integer to store in the packet + */ + public static int writeInt(byte[] buf, int off, int data) + { + if (buf == null || buf.length < off + 4) + { + return -1; + } + + buf[off++] = (byte)(data>>24); + buf[off++] = (byte)(data>>16); + buf[off++] = (byte)(data>>8); + buf[off] = (byte)data; + return 4; + } + + /** + * Writes the least significant 24 bits from the given integer into the + * given byte array at the given offset. + * @param buf the buffer into which to write. + * @param off the offset at which to write. + * @param data the integer to write. + * @return 3 + */ + public static int writeUint24(byte[] buf, int off, int data) + { + if (buf == null || buf.length < off + 3) + { + return -1; + } + + buf[off++] = (byte)(data>>16); + buf[off++] = (byte)(data>>8); + buf[off] = (byte)data; + return 3; + } + + /** + * Set an integer at specified offset in network order. + * + * @param off Offset into the buffer + * @param data The integer to store in the packet + */ + public static int writeShort(byte[] buf, int off, short data) + { + buf[off++] = (byte)(data>>8); + buf[off] = (byte)data; + return 2; + } + + /** + * Read an integer from a buffer at a specified offset. + * + * @param buf the buffer. + * @param off start offset of the integer to be read. + */ + public static int readInt(byte[] buf, int off) + { + return + ((buf[off++] & 0xFF) << 24) + | ((buf[off++] & 0xFF) << 16) + | ((buf[off++] & 0xFF) << 8) + | (buf[off] & 0xFF); + } + + /** + * Reads a 32-bit unsigned integer from the given buffer at the given + * offset and returns its {@link long} representation. + * @param buf the buffer. + * @param off start offset of the integer to be read. + */ + public static long readUint32AsLong(byte[] buf, int off) + { + return readInt(buf, off) & 0xFFFF_FFFFL; + } + + /** + * Read an unsigned short at a specified offset as an int. + * + * @param buf the buffer from which to read. + * @param off start offset of the unsigned short + * @return the int value of the unsigned short at offset + */ + public static int readUint16AsInt(byte[] buf, int off) + { + int b1 = (0xFF & (buf[off + 0])); + int b2 = (0xFF & (buf[off + 1])); + int val = b1 << 8 | b2; + return val; + } + + /** + * Read a signed short at a specified offset as an int. + * + * @param buf the buffer from which to read. + * @param off start offset of the unsigned short + * @return the int value of the unsigned short at offset + */ + public static int readInt16AsInt(byte[] buf, int off) + { + int ret = ((0xFF & (buf[off])) << 8) + | (0xFF & (buf[off + 1])); + if ((ret & 0x8000) != 0) + { + ret = ret | 0xFFFF_0000; + } + + return ret; + } + + /** + * Read an unsigned short at specified offset as a int + * + * @param buf + * @param off start offset of the unsigned short + * @return the int value of the unsigned short at offset + */ + public static int readUint24AsInt(byte[] buf, int off) + { + int b1 = (0xFF & (buf[off + 0])); + int b2 = (0xFF & (buf[off + 1])); + int b3 = (0xFF & (buf[off + 2])); + return b1 << 16 | b2 << 8 | b3; + } + + /** + * Returns the given integer masked to 16 bits + * @param value the integer to mask + * @return the value, masked to only keep the lower + * 16 bits + */ + public static int as16Bits(int value) + { + return value & 0xFFFF; + } + + /** + * Returns the given integer masked to 32 bits + * @param value the integer to mask + * @return the value, masked to only keep the lower + * 32 bits + */ + public static long as32Bits(long value) + { + return value & 0xFFFF_FFFFL; + } + + /** + * A {@link Comparator} implementation for unsigned 16-bit {@link Integer}s. + * Compares {@code a} and {@code b} inside the [0, 2^16] ring; + * {@code a} is considered smaller than {@code b} if it takes a smaller + * number to reach from {@code a} to {@code b} than the other way round. + * + * IMPORTANT: This is a valid {@link Comparator} implementation only when + * used for subsets of [0, 2^16) which don't span more than 2^15 elements. + * + * E.g. it works for: [0, 2^15-1] and ([50000, 2^16) u [0, 10000]) + * Doesn't work for: [0, 2^15] and ([0, 2^15-1] u {2^16-1}) and [0, 2^16) + */ + public static final Comparator sequenceNumberComparator + = new Comparator() { + @Override + public int compare(Integer a, Integer b) + { + if (a.equals(b)) + { + return 0; + } + else if (a > b) + { + if (a - b < 0x10000) + { + return 1; + } + else + { + return -1; + } + } + else //a < b + { + if (b - a < 0x10000) + { + return -1; + } + else + { + return 1; + } + } + } + }; + + /** + * Returns the difference between two RTP timestamps. + * @return the difference between two RTP timestamps. + */ + public static long rtpTimestampDiff(long a, long b) + { + long diff = a - b; + if (diff < -(1L<<31)) + { + diff += 1L << 32; + } + else if (diff > 1L<<31) + { + diff -= 1L << 32; + } + + return diff; + } + + /** + * Returns whether or not the first given timestamp is newer than the second + * @param a + * @param b + * @return true if a is newer than b, false otherwise + */ + public static boolean isNewerTimestampThan(long a, long b) + { + return rtpTimestampDiff(a, b) > 0; + } + + /** + * Return a string containing the hex string version of the given byte + * @param b + * @return + */ + private static String toHexString(byte b) + { + + StringBuilder hexStringBuilder = new StringBuilder(2); + + hexStringBuilder.append(HEXES[(b & 0xF0) >> 4]); + hexStringBuilder.append(HEXES[b & 0x0F]); + + return hexStringBuilder.toString(); + } + + /** + * Return a string containing the hex string version of the given bytes + * @param buf + * @return + */ + public static String toHexString(byte[] buf) + { + return toHexString(buf, 0, buf.length, true); + } + + /** + * Return a string containing the hex string version of the given byte + * @param buf + * @param off + * @param len + * @return + */ + public static String toHexString(byte[] buf, int off, int len) + { + return toHexString(buf, off, len, true); + } + + /** + * Return a string containing the hex string version of the given byte + * @param buf + * @param off + * @param len + * @param format a boolean that indicates whether or not to format the hex + * string. + * @return + */ + public static String toHexString(byte[] buf, int off, int len, boolean format) + { + if (buf == null) + { + return null; + } + else + { + StringBuilder hexStringBuilder + = new StringBuilder(2 * buf.length); + + for (int i = 0; i < len; i++) + { + if (format) + { + if (i % 16 == 0) + { + hexStringBuilder.append("\n") + .append(toHexString((byte) i)) + .append(" "); + } + else if (i % 8 == 0) + { + hexStringBuilder.append(" "); + } + } + byte b = buf[off + i]; + + hexStringBuilder.append(toHexString(b)); + hexStringBuilder.append(" "); + } + return hexStringBuilder.toString(); + } + } +} \ No newline at end of file diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/AbstractUdpImpl.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/AbstractUdpImpl.java new file mode 100644 index 0000000000..69401db025 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/AbstractUdpImpl.java @@ -0,0 +1,217 @@ +package com.zhidao.support.adas.high.udp.factory; + + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.udp.IConnectRtpListener; +import com.zhidao.support.adas.high.udp.IGetH264Data; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.MulticastSocket; + +/** + * @author nie yunlong + * @description + * @date 2018/7/11 + */ +public abstract class AbstractUdpImpl implements IUdpAction, Runnable { + + + static final String TAG = AbstractUdpImpl.class.getSimpleName(); + /** + * udp端口 + */ + private int udpPort; + /** + * 是否组播 + */ + private boolean isMultiCast; + /** + * 组播IP + */ + private String multiCastIp; + + public static final int DATA_LEN = 8 * 1024; + byte[] inBuff; + /** + * 接收的包 + */ + private DatagramPacket inPacket; + /** + * udp + */ + private DatagramSocket socket = null; + /** + * 带广播的udp + */ + private MulticastSocket mMulticastSocket = null; + /** + * 第一次获取到接收端的ip + */ + private volatile boolean isFirstReceiverAddress; + + /** + * 接口 回调一帧 + */ + protected IGetH264Data mGetH264Data; + + private IConnectRtpListener mConnectRtpListener; + /** + * stop + */ + private volatile boolean isStop; + + /** + * 接收的ip地址 + */ + private volatile String mReceiveAddress; + /** + * 暂停状态 + */ + protected volatile boolean isPause = false; + + public AbstractUdpImpl(int byteSize) { + inBuff = new byte[byteSize]; + inPacket = new DatagramPacket(inBuff, inBuff.length); + + + } + + @Override + public void onPause() { + isPause = true; + } + + @Override + public void onResume() { + isPause = false; + } + + @Override + public void close() { + try { + setStop(true); + if (mMulticastSocket != null) { + mMulticastSocket.close(); + mMulticastSocket = null; + } + if (socket != null) { + socket.close(); + socket = null; + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (mConnectRtpListener != null) { + mConnectRtpListener = null; + } + if (mGetH264Data != null) { + mGetH264Data = null; + } + inPacket = null; + inBuff = null; + } + } + + @Override + public void setUdpPort(int udpPort) { + this.udpPort = udpPort; + } + + @Override + public void setMultiCast(boolean multiCast) { + isMultiCast = multiCast; + } + + @Override + public synchronized void setStop(boolean stop) { + isStop = stop; + } + + @Override + public void setOnConnectListener(IConnectRtpListener connectRtpListener) { + this.mConnectRtpListener = connectRtpListener; + } + + @Override + public void getVideoInputStreamData(IGetH264Data getH264Data) { + this.mGetH264Data = getH264Data; + } + + public void setFirstReceiverAddress(boolean firstReceiverAddress) { + isFirstReceiverAddress = firstReceiverAddress; + } + + @Override + public void initBuffer() { + + } + + @Override + public void run() { + // CupidLogUtils.e(TAG, "---->开始接收数据"); + try { + if (isMultiCast) { + mMulticastSocket = new MulticastSocket(udpPort); + InetAddress bcAddr = InetAddress.getByName(multiCastIp); + mMulticastSocket.setReuseAddress(true); + mMulticastSocket.joinGroup(bcAddr); + mMulticastSocket.setLoopbackMode(false); + } else { + CupidLogUtils.e(TAG, "---->开始连接端口" + udpPort + ",stop" + isStop); + if (socket == null) { + socket = new DatagramSocket(udpPort); + socket.setReuseAddress(true); + socket.setReceiveBufferSize(32 * 1024); + CupidLogUtils.e("--->setReceiveBufferSize:" + socket.getReceiveBufferSize()); + CupidLogUtils.e(TAG, "---->连接成功" + udpPort); + } + } + } catch (Exception e) { + e.printStackTrace(); + CupidLogUtils.e(TAG, "---->udp" + e.getMessage()); + if (mConnectRtpListener != null) { + mConnectRtpListener.onConnectionFailedRtp(e.getMessage()); + } + } + + while (!isStop) { + try { + if (isMultiCast && mMulticastSocket != null) { + mMulticastSocket.receive(inPacket); + } else { + if (socket != null) { + //inPacket.setLength(0); + socket.receive(inPacket); + } + } + long receiverDataTimeUdp = System.currentTimeMillis(); + if (!isFirstReceiverAddress) { + if (inPacket.getAddress() != null) { + mReceiveAddress = inPacket.getAddress().getHostAddress(); + CupidLogUtils.e("mReceiveAddress--->mReceiveAddress" + mReceiveAddress); + isFirstReceiverAddress = true; + if (mConnectRtpListener != null) { + mConnectRtpListener.getOnConnectionAddress(mReceiveAddress); + } + } + + } + if (inPacket.getAddress() != null) { + parseData(inPacket, receiverDataTimeUdp); + } + } catch (Exception e) { + e.printStackTrace(); + CupidLogUtils.e("UdpException" + e.getMessage()); + if (mConnectRtpListener != null) { + mConnectRtpListener.onConnectionFailedRtp(e.getMessage()); + } + } + } + + } + + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidRenderImageServer.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidRenderImageServer.java new file mode 100644 index 0000000000..f35b10635c --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidRenderImageServer.java @@ -0,0 +1,55 @@ +package com.zhidao.support.adas.high.udp.factory; + +import com.zhidao.support.adas.high.udp.IBuffStream; + +import java.net.DatagramPacket; + +/** + * @author nie yunlong + * @description 绘制上层数据 + * @date 2018/7/11 + */ +public class CupidRenderImageServer extends AbstractUdpImpl { + + +// StringBuilder stringBuilder = new StringBuilder(); +// +// StringBuilder normalDataBuilder = new StringBuilder(); + /** + * 内容 + */ + private String content; + + public CupidRenderImageServer() { + super(8*1024); + } + + + /** + * 回调真实数据 + * + * @param realData + */ + public void callBackRealData(String realData) { + if (realData.contains("<")) { + realData.replace("<", ""); + } + if (realData.contains(">")) { + realData.replace(">", ""); + } + mGetH264Data.getH264Data(realData.getBytes(),System.currentTimeMillis()); + } + + @Override + public void parseData(DatagramPacket inPacket, long receiverDataTimeUdp) { + if (mGetH264Data != null && !isPause) { + content = new String(inPacket.getData(), 0, inPacket.getLength()); + mGetH264Data.getH264Data(content.getBytes(),receiverDataTimeUdp); + } + } + + @Override + public void setBuffStream(IBuffStream buffStream) { + + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidUdpFactory.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidUdpFactory.java new file mode 100644 index 0000000000..a5c8c22ca8 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidUdpFactory.java @@ -0,0 +1,37 @@ +package com.zhidao.support.adas.high.udp.factory; + +/** + * @author nie yunlong + * @description 生产udp对象 + * @date 2018/7/11 + */ +public class CupidUdpFactory { + + /** + * 创建视频流解析对象 + * + * @return + */ + public static AbstractUdpImpl createVideoStreamUdp() { + return new CupidVideoUdpServer(); + } + + /** + * 优化后的接收udp buffer + * + * @return + */ + public static AbstractUdpImpl createVideoStreamUdp2() { + return new CupidVideoUdpServer2(); + } + + /** + * 上层绘制 接收udp 给的数据 解析展示数据 + * + * @return + */ + public static AbstractUdpImpl createRenderImageUdp() { + return new CupidRenderImageServer(); + } + +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer.java new file mode 100644 index 0000000000..a579e76b5f --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer.java @@ -0,0 +1,243 @@ +package com.zhidao.support.adas.high.udp.factory; + + + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.udp.IBuffStream; + +import java.io.IOException; +import java.net.DatagramPacket; + +import static com.zhidao.support.adas.high.udp.CupidBufStreamImpl.BUF_SIZE_LIMIT; + + +/** + * @author nie yunlong + * @description 视频流 + * @date 2018/7/11 + */ +public class CupidVideoUdpServer extends AbstractUdpImpl { + + private IBuffStream buffStream; + + private byte[] fu_h_byte; + + /** + * 是否是开始第一帧 + */ + boolean isBegin = false; + /** + * 对照h264帧 + */ + private int mIndexSeqNo = 0; + + /** + * 容错次数 + */ + private int mConcernedNumber = 0; + /** + * 容错常量 2次 + */ + private final int CONCERNED_NUMBER_RTP = 5; + + public CupidVideoUdpServer() { + super(DATA_LEN); + } + + + @Override + public void setBuffStream(IBuffStream buffStream) { + this.buffStream = buffStream; + } + + /** + * 替换rtp头 暴露h264 + * + * @param inPacket + * @return + */ + boolean parseRTP(DatagramPacket inPacket) { + int naluType = 0; + int nalunri = 0; + int naluf = 0; + + boolean isEnd = false; + //最高位是否是F 1111 **** + if ((int) (inBuff[1] >> 7 & 0x01) == 1) { + isEnd = true; + } + //inBuff[3] 是啥数据 就是啥 0xFF 都是1 1111 1111 + int seq_no = inBuff[3] & 0xFF; + //左移 补0 结果是把inBuffer[3]inBuffer[2] 得到数据 获取到的长度 + seq_no |= (((int) inBuff[2] << 8) & 0xFF00); + +// CupidLogUtils.e("seq_no" + seq_no + ",begin=>" + isBegin + ",isEnd=>" + isEnd); + + if (seq_no == 1) { //0001 开始位置 + isBegin = true; + mIndexSeqNo = 1; + } else if (seq_no == 65530) { //FFFF 结束一帧 + isEnd = true; + isBegin = false; + mIndexSeqNo = 0; + } + +// boolean isSkip = isCheckConcernedNumer(isEnd); +//// if (isSkip) { +//// return false; +//// } + + if (buffStream.getLength() > BUF_SIZE_LIMIT) { + buffStream.close(); + return false; + } + byte naluHeader = inBuff[12]; + naluType = naluHeader & 0x1f; // + nalunri = naluHeader >> 5 & 0x03; + naluf = naluHeader >> 7 & 0x01; + int len = inPacket.getLength(); +// CupidLogUtils.i(TAG, "packet-len:" + len); + + fu_h_byte = new byte[5]; + fu_h_byte[0] = 0; + fu_h_byte[1] = 0; + fu_h_byte[2] = 0; + fu_h_byte[3] = 1; + + if (28 == naluType) { //0x1C ‭0001 1100‬ + // H264 Begin + int fu_type = 0; + int fu_r = 0; + int fu_e = 0; + int fu_s = 0; + + byte fu_header = inBuff[13]; + fu_type = fu_header & 0x1f; + fu_r = fu_header >> 5 & 0x01; + fu_e = fu_header >> 6 & 0x01; + fu_s = fu_header >> 7 & 0x01; + + if (fu_s == 1) { + //buffStream.doSwitch(); + // start of fu-a + fu_h_byte[4] = (byte) ((fu_type & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } else if (1 == fu_e) { + // end of fu-a + try { + if (isBegin || isEnd) { + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } else { + // middle of fu-a + try { + if (isBegin) { + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } + + } else { + //TODO 没用上 + fu_h_byte[4] = (byte) ((naluType & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 13, len - 13); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + if (isEnd) { + if (mGetH264Data != null) { + try { + mGetH264Data.getH264Data(buffStream.getData(),System.currentTimeMillis()); + } catch (OutOfMemoryError outOfMemoryError) { + CupidLogUtils.e("===>内存溢出"); + buffStream.close(); + System.gc(); + } + } + buffStream.doSwitch(); + } + return true; + } + + /** + * 是否跳过 后边代码 + * + * @param isEnd + * @return + */ + private boolean isCheckConcernedNumer(boolean isEnd) { + int seq_no = inBuff[3] & 0xFF; + seq_no |= (((int) inBuff[2] << 8) & 0xFF00); + + CupidLogUtils.e("seq_no" + seq_no + ",begin=>" + isBegin + ",isEnd=>" + isEnd); + + if (seq_no == 1) { + isBegin = true; + mIndexSeqNo = 1; + } else if (seq_no == 65530) { + isEnd = true; + isBegin = false; + mIndexSeqNo = 0; + } else { + mIndexSeqNo++; + if (mIndexSeqNo != seq_no && mConcernedNumber <= CONCERNED_NUMBER_RTP) { + mConcernedNumber++; + } + if (mConcernedNumber > CONCERNED_NUMBER_RTP) { + CupidLogUtils.e("===>超过次数==>mConcernedNumber" + mConcernedNumber); + mConcernedNumber = 0; + buffStream.doSwitch(); + return true; + } + } + return false; + } + + @Override + public void close() { + super.close(); + if (buffStream != null) { + buffStream.close(); + buffStream = null; + } + } + + @Override + public void parseData(DatagramPacket inPacket, long receiverDataTimeUdp) { + if (isPause) { + buffStream.close(); + } + if (inPacket.getLength() > 0 && !isPause) { + CupidLogUtils.w("===》数据包" + inPacket.getLength() + ",string" + new String(inPacket.getData())); + parseRTP(inPacket); + } + } + + @Override + public void initBuffer() { + super.initBuffer(); + if (buffStream != null) { + buffStream.close(); + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer2.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer2.java new file mode 100644 index 0000000000..9d2c434049 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/CupidVideoUdpServer2.java @@ -0,0 +1,247 @@ +package com.zhidao.support.adas.high.udp.factory; + + + +import com.zhidao.support.adas.high.common.CupidLogUtils; +import com.zhidao.support.adas.high.udp.IBuffStream; + +import java.io.IOException; +import java.net.DatagramPacket; + +import static com.zhidao.support.adas.high.udp.CupidBufStreamImpl2.BUF_SIZE_LIMIT; + + +/** + * @author nie yunlong + * @description 视频流 + * @date 2018/7/11 + */ +public class CupidVideoUdpServer2 extends AbstractUdpImpl { + + private IBuffStream buffStream; + + private byte[] fu_h_byte; + + /** + * 是否是开始第一帧 + */ + boolean isBegin = false; + /** + * 对照h264帧 + */ + private int mIndexSeqNo = 0; + + /** + * 容错次数 + */ + private int mConcernedNumber = 0; + /** + * 容错常量 2次 + */ + private final int CONCERNED_NUMBER_RTP = 5; + + public CupidVideoUdpServer2() { + super(DATA_LEN); + } + + + @Override + public void setBuffStream(IBuffStream buffStream) { + this.buffStream = buffStream; + } + + /** + * 替换rtp头 暴露h264 + * + * @param inPacket + * @return + */ + boolean parseRTP(DatagramPacket inPacket) { + int naluType = 0; + int nalunri = 0; + int naluf = 0; + + boolean isEnd = false; + //最高位是否是F 1111 **** + if ((int) (inBuff[1] >> 7 & 0x01) == 1) { + isEnd = true; + } + //inBuff[3] 是啥数据 就是啥 0xFF 都是1 1111 1111 + int seq_no = inBuff[3] & 0xFF; + //左移 补0 结果是把inBuffer[3]inBuffer[2] 得到数据 获取到的长度 + seq_no |= (((int) inBuff[2] << 8) & 0xFF00); + +// CupidLogUtils.e("seq_no" + seq_no + ",begin=>" + isBegin + ",isEnd=>" + isEnd); + + if (seq_no == 1) { //0001 开始位置 + //如果在开始有问题 reset + if (buffStream.getLength() > 0) { + buffStream.doSwitch(); + } + isBegin = true; + mIndexSeqNo = 1; + } else if (seq_no == 65530) { //FFFF 结束一帧 + isEnd = true; + isBegin = false; + mIndexSeqNo = 0; + } + +// boolean isSkip = isCheckConcernedNumer(isEnd); +//// if (isSkip) { +//// return false; +//// } + + if (buffStream.getLength() > BUF_SIZE_LIMIT) { + buffStream.close(); + return false; + } + byte naluHeader = inBuff[12]; + naluType = naluHeader & 0x1f; // + nalunri = naluHeader >> 5 & 0x03; + naluf = naluHeader >> 7 & 0x01; + int len = inPacket.getLength(); +// CupidLogUtils.i(TAG, "packet-len:" + len); + + fu_h_byte = new byte[5]; + fu_h_byte[0] = 0; + fu_h_byte[1] = 0; + fu_h_byte[2] = 0; + fu_h_byte[3] = 1; + + if (28 == naluType) { //0x1C ‭0001 1100‬ + // H264 Begin + int fu_type = 0; + int fu_r = 0; + int fu_e = 0; + int fu_s = 0; + + byte fu_header = inBuff[13]; + fu_type = fu_header & 0x1f; + fu_r = fu_header >> 5 & 0x01; + fu_e = fu_header >> 6 & 0x01; + fu_s = fu_header >> 7 & 0x01; + + if (fu_s == 1) { + //buffStream.doSwitch(); + // start of fu-a + fu_h_byte[4] = (byte) ((fu_type & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } else if (1 == fu_e) { + // end of fu-a + try { + if (isBegin || isEnd) { + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } else { + // middle of fu-a + try { + if (isBegin) { + buffStream.getOutputStream().write(inBuff, 14, len - 14); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + } + + } else { + //TODO 没用上 + fu_h_byte[4] = (byte) ((naluType & 0x1f) | (nalunri << 5 & 0x60) | (naluf << 7 & 0x80)); + try { + //buffStream.getOutputStream().write(fu_h_byte, 0, 5); + buffStream.getOutputStream().write(inBuff, 13, len - 13); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + if (isEnd) { + if (mGetH264Data != null) { + try { + mGetH264Data.getH264Data(buffStream.getData(),System.currentTimeMillis()); + } catch (OutOfMemoryError outOfMemoryError) { + CupidLogUtils.e("===>内存溢出"); + buffStream.close(); + System.gc(); + } + } + buffStream.doSwitch(); + } + return true; + } + + /** + * 是否跳过 后边代码 + * + * @param isEnd + * @return + */ + private boolean isCheckConcernedNumer(boolean isEnd) { + int seq_no = inBuff[3] & 0xFF; + seq_no |= (((int) inBuff[2] << 8) & 0xFF00); + + CupidLogUtils.e("seq_no" + seq_no + ",begin=>" + isBegin + ",isEnd=>" + isEnd); + + if (seq_no == 1) { + isBegin = true; + mIndexSeqNo = 1; + } else if (seq_no == 65530) { + isEnd = true; + isBegin = false; + mIndexSeqNo = 0; + } else { + mIndexSeqNo++; + if (mIndexSeqNo != seq_no && mConcernedNumber <= CONCERNED_NUMBER_RTP) { + mConcernedNumber++; + } + if (mConcernedNumber > CONCERNED_NUMBER_RTP) { + CupidLogUtils.e("===>超过次数==>mConcernedNumber" + mConcernedNumber); + mConcernedNumber = 0; + buffStream.doSwitch(); + return true; + } + } + return false; + } + + @Override + public void close() { + super.close(); + if (buffStream != null) { + buffStream.close(); + buffStream = null; + } + } + + @Override + public void parseData(DatagramPacket inPacket, long receiverDataTimeUdp) { + if (isPause) { + buffStream.close(); + } + if (inPacket.getLength() > 12 && !isPause) { + CupidLogUtils.w("===》数据包" + inPacket.getLength() + ",string" + new String(inPacket.getData())); + parseRTP(inPacket); + } + } + + @Override + public void initBuffer() { + super.initBuffer(); + if (buffStream != null) { + buffStream.close(); + } + } +} diff --git a/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/IUdpAction.java b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/IUdpAction.java new file mode 100644 index 0000000000..bfff270c14 --- /dev/null +++ b/libraries/mogo-adas/src/main/java/com/zhidao/support/adas/high/udp/factory/IUdpAction.java @@ -0,0 +1,88 @@ +package com.zhidao.support.adas.high.udp.factory; + + + +import com.zhidao.support.adas.high.udp.IBuffStream; +import com.zhidao.support.adas.high.udp.IConnectRtpListener; +import com.zhidao.support.adas.high.udp.IGetH264Data; + +import java.net.DatagramPacket; + +/** + * @author nie yunlong + * @description udp 抽象方法 + * @date 2018/7/11 + */ +public interface IUdpAction { + + /** + * udp 关闭释放资源 + */ + void close(); + + /** + * set udp port + * + * @param udpPort + */ + void setUdpPort(int udpPort); + + /** + * 设置广播模式 + * + * @param multiCast + */ + void setMultiCast(boolean multiCast); + + /** + * 停止udp 线程 + * + * @param stop + */ + void setStop(boolean stop); + + /** + * 设置socket 连接状态监听 + * + * @param connectRtpListener + */ + void setOnConnectListener(IConnectRtpListener connectRtpListener); + + /** + * 获取udp数据 + * + * @param getH264Data + */ + void getVideoInputStreamData(IGetH264Data getH264Data); + + /** + * 解析接收到数据包 + * + * @param inPacket + */ + void parseData(DatagramPacket inPacket,long receiverDataTimeUdp); + + + /** + * 设置buffer + * + * @param buffStream + */ + void setBuffStream(IBuffStream buffStream); + + /** + * onPause + */ + void onPause(); + + /** + * onResume + */ + void onResume(); + + /** + * init buffer 服务器关闭 初始化数据 + */ + void initBuffer(); + +} diff --git a/libraries/mogo-adas/src/main/proto/adas.proto b/libraries/mogo-adas/src/main/proto/adas.proto new file mode 100644 index 0000000000..f8b5478853 --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/adas.proto @@ -0,0 +1,87 @@ +syntax = "proto2"; +package adas; + +enum CarLocation +{ + Same_LINE = 0; + Left_LINE = 1; + Right_LINE = 2; + left2_LINE = 3; + Right2_LINE = 4; +} + +enum ActionType +{ + action_type_view = 1; + action_type_obstacles = 2; + action_type_lanes = 3; + action_type_state = 4; + action_type_warn = 5; + action_type_light = 6; + action_type_config = 7; + action_type_gdgps = 8; + action_type_auto_pilot_state = 9; + action_type_auto_pilot_mode = 10; + action_type_obu_traffic_light = 11; + action_type_ai_cloud_to_start_autopilot = 12; +} + +message View +{ + optional int32 xl = 1; + optional int32 yt = 2; + optional int32 xr = 3; + optional int32 yb = 4; + //物体类型 + optional string type = 5; + optional CarLocation showImageLocation = 6; + //距离x轴值 + optional double distance_x = 7; + //距离y轴值 + optional double distance_y = 8; + //经度 + optional double lon = 9; + //纬度 + optional double lat = 10; + //海拔 + optional double alt = 11; + //系统时间 + optional string systemTime = 12; + //gps时间 + optional string satelliteTime = 13; + //车辆id + optional string uuid = 14; + //车牌id + optional string carId = 15; + //车辆颜色 + optional string color = 16; + //车辆朝向 + optional double heading = 17; + //车辆速度 + optional double speed = 18; + //长 + optional float length = 19; + //宽 + optional float width = 20; + //高 + optional float height = 21; + //危险等级 1 绿,2 黄,3 红 + optional int32 drawlevel = 22; + //驱动感知时间 + optional string driverTime = 23; +} + + +// 渲染流消息 +message ViwesMsg +{ + optional string action = 1; + repeated View models = 2; +} + + + + + + + diff --git a/libraries/mogo-adas/src/main/proto/car_status.proto b/libraries/mogo-adas/src/main/proto/car_status.proto new file mode 100644 index 0000000000..6f4f477051 --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/car_status.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; +package mogo.status; + + +message Status +{ + //经度 + double lon = 1; + //纬度 + double lat = 2; + //海拔 + double alt = 3; + //航向角 + double heading = 4; + //加速度 + double acceleration = 5; + //曲率 + double yaw_rate = 6; + //惯导车速 m/s + float gnss_speed = 7; + //车辆车速 m/s + float vehicle_speed = 8; + //gps时间 + string satelliteTime = 9; + //系统时间 + string systemTime = 10; + //转向灯状态 + int32 turn_light = 11; + //双闪灯状态 + int32 flash_light = 12; + //刹车灯状态 + int32 brake_light = 13; + //统计包个数 + int32 frame_num = 14; +} + + + + + + + diff --git a/libraries/mogo-adas/src/main/proto/geometry.proto b/libraries/mogo-adas/src/main/proto/geometry.proto new file mode 100644 index 0000000000..9910b32a10 --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/geometry.proto @@ -0,0 +1,84 @@ +syntax = "proto2"; +package geometry; + +message Vector3 { + optional double x = 1; + optional double y = 2; + optional double z = 3; +} + +message Vector3f { + optional float x = 1; + optional float y = 2; + optional float z = 3; +} + +message Point2D { + optional double x = 1 [default = nan]; + optional double y = 2 [default = nan]; +} + +message Point3D { + optional double x = 1 [default = nan]; + optional double y = 2 [default = nan]; + optional double z = 3 [default = nan]; +} + +message Trace { + optional double timestamp = 1; + optional Point3D position = 2; + optional Point3D velocity = 3; + optional Point3D acceleration = 4; + optional Point3D heading = 5; +} + +message PointLLH { + // Longitude in degrees, ranging from -180 to 180. + optional double lon = 1 [default = nan]; + // Latitude in degrees, ranging from -90 to 90. + optional double lat = 2 [default = nan]; + // WGS-84 ellipsoid height in meters. + optional double height = 3 [default = 0.0]; +} + +message Point { + optional double x = 1; + optional double y = 2; + optional double z = 3; +} + +message Quaternion { + optional double x = 1 [default = nan]; + optional double y = 2 [default = nan]; + optional double z = 3 [default = nan];; + optional double w = 4 [default = nan]; +} + +message Polygon { + repeated Point points = 1; +} + +message Transform { + optional Vector3 translation = 1; + optional Quaternion rotation = 2; +} + +//pose in free space, composed of position and orientation +message Pose { + optional Point position = 1; + optional Quaternion orientation = 2; +} + +//acceleration in free space broken into its linear and angular parts +message Accel { + optional Vector3 linear = 1; + optional Vector3 angular = 2; +} + +//velocity in free space broken into its linear and angular parts +message Twist { + optional Vector3 linear = 1; + optional Vector3 angular = 2; +} + + diff --git a/libraries/mogo-adas/src/main/proto/header.proto b/libraries/mogo-adas/src/main/proto/header.proto new file mode 100644 index 0000000000..2a50a1702d --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/header.proto @@ -0,0 +1,23 @@ +syntax = "proto2"; + +package common; + +message Time { + optional uint32 sec = 1; + optional uint32 nsec = 2; +} + +message Header { + // Sequence number for each message. Each module maintains its own counter for + // sequence_num, always starting from 1 on boot. + optional uint32 seq = 1; + + // Message publishing time in seconds. + optional Time stamp = 2; + + // frame id + optional string frame_id = 3; + + // Module name. + optional string module_name = 4; +} diff --git a/libraries/mogo-adas/src/main/proto/mogo_guardian.proto b/libraries/mogo-adas/src/main/proto/mogo_guardian.proto new file mode 100644 index 0000000000..27e5946a47 --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/mogo_guardian.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; +package mogo.guardian; +/* +message xxx { + // 字段规则:required -> 字段只能也必须出现 1 次. proto3中移除了required + // 字段规则:optional -> 字段可出现 0 次或1次 + // 字段规则:repeated -> 字段可出现任意多次(包括 0) + // 类型:int32、int64、sint32、sint64、string、32-bit .... + // 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字) + 字段规则 类型 名称 = 字段编号; +} +*/ +/* 监控实体 */ +message GuardianProto { +/* i接口名成*/ + string action = 1; +/* 监控项名称*/ + string agentName = 2; +/* 车牌号*/ + string carNum = 3; +/* 工控机mac地址*/ + string macAddr = 4; +/* 车型*/ + string carType = 5; +/* 监控类型*/ + string monitorType = 6; +/* sn*/ + string sn = 7; +/* 时间*/ + uint64 time = 8; +/* 模块*/ + Modules modules = 9; +} + +/* 模块*/ +message Modules { +/* 子项*/ + repeated ItemsName itemsname = 1; +/* 子模块*/ + repeated SubModules submodules = 2; +} + +/* 子项*/ +message ItemsName { +/* 子项名称*/ + string name = 1; +/* 子项键值属性*/ + repeated Items items = 2; +} +/* 键值对*/ +message Items { +/* key*/ + string name = 1; +/* value*/ + string value = 2; + +} +/* 子模块*/ +message SubModules { +/* 子模块名称*/ + string modulename = 1; +/* 子节点*/ + repeated SubNode subnodes = 2; + +} + +/* 模块中的节点*/ +message SubNode{ +/* 节点名称*/ + string nodename = 1; +/* 节点项*/ + repeated Items nodeitems = 2; +/* *topic项*/ + repeated ItemsName topicitems = 3; +} + diff --git a/libraries/mogo-adas/src/main/proto/route_list.proto b/libraries/mogo-adas/src/main/proto/route_list.proto new file mode 100644 index 0000000000..407ca0563c --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/route_list.proto @@ -0,0 +1,27 @@ +syntax = "proto2"; + +package hadmap; + +import "geometry.proto"; +import "header.proto"; + +message RouteInfo { + optional string id = 1; + optional geometry.Point start = 2;//经纬度坐标 + optional string start_eng = 3; //起点拼音大写 + optional string start_chn = 4; //起点中文名 + optional geometry.Point end = 5;//经纬度坐标 + optional string end_eng = 6; //终点拼音大写 + optional string end_chn = 7; //终点中文名 + optional string traj_file_path = 8; // file path+file name + optional int32 vehicle_type = 9; + optional string time = 10; + optional string comment = 11; +} + +message RouteList { + optional common.Header header = 1; + optional uint32 routes_count = 2; + repeated RouteInfo routes_info = 3; +} + diff --git a/libraries/mogo-adas/src/main/proto/trajectory.proto b/libraries/mogo-adas/src/main/proto/trajectory.proto new file mode 100644 index 0000000000..afd61c757c --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/trajectory.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package mogo.trajectory; + +message TrajectoryPoint { + double x = 1; + double y = 2; + double z = 3; + double t = 4; //time in seconds + double v = 5; //velocity in m/s + double a = 6; //acceleration in m/s^26766 + double theta = 7; //direction of v + double kappa = 8; //curvature + double s = 9; //accumulated distance in meters from beginning +} + +message Trajectory { + repeated TrajectoryPoint points = 1; +} diff --git a/libraries/mogo-adas/src/main/proto/websocket_header.proto b/libraries/mogo-adas/src/main/proto/websocket_header.proto new file mode 100644 index 0000000000..909787d1d3 --- /dev/null +++ b/libraries/mogo-adas/src/main/proto/websocket_header.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package mogo.webproto; + +enum Constants { + DEFAULT = 0x0000; //0 + Magic = 0x6d67; //"mg" +} + +/************************Head Def***********************/ +/*******************************************************/ + +message Header_websock { + uint32 msgType = 1; //payload type, enum @PacketType +} + + +enum MsgType_websock { + mogo_DEFAULT = 0x000; // 0 + mogoTrajectory = 0x100; // 轨迹规划 + mogoAdas = 0x101; //adas数据 + mogoCar = 0x102; //自车数据 + mogoDataCollect = 0x103; //数据采集文件 + mogoRoutList = 0x104; //maplist + mogoGuardian = 0x105; //监控 +} diff --git a/libraries/mogo-adas/src/main/res/values/strings.xml b/libraries/mogo-adas/src/main/res/values/strings.xml new file mode 100644 index 0000000000..9e8787cc20 --- /dev/null +++ b/libraries/mogo-adas/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + adas-high + diff --git a/libraries/mogo-map-api/src/main/java/com/mogo/map/uicontroller/VisualAngleMode.java b/libraries/mogo-map-api/src/main/java/com/mogo/map/uicontroller/VisualAngleMode.java index 3d6462fab2..2d76d530f1 100644 --- a/libraries/mogo-map-api/src/main/java/com/mogo/map/uicontroller/VisualAngleMode.java +++ b/libraries/mogo-map-api/src/main/java/com/mogo/map/uicontroller/VisualAngleMode.java @@ -15,9 +15,25 @@ public enum VisualAngleMode implements IMogoMapVisualAngle { /** * 视距远景 */ - MODE_LONG_SIGHT(2); + MODE_LONG_SIGHT(2), - private int code; + /** + * 后方来车300视角 + */ + MAP_STYLE_VR_ANGLE_300(3), + + /** + * 顶视角 + */ + MAP_STYLE_VR_ANGLE_TOP(4), + + /** + * 十字路口视角 + */ + MAP_STYLE_VR_ANGLE_CROSS(5); + + + private final int code; VisualAngleMode(int code) { this.code = code; diff --git a/modules.txt b/modules.txt index a969ffe1d8..ac24aa7e14 100644 --- a/modules.txt +++ b/modules.txt @@ -4,21 +4,21 @@ :core:mogo-core-network :core:mogo-core-function-api :core:mogo-core-function-call -:foudations:mogo-utils :tts:tts-base :tts:tts-pad :libraries:mogo-map-api :services:mogo-service-api :foudations:mogo-aicloud-services-sdk :foudations:mogo-commons +:libraries:map-usbcamera :libraries:map-custom :libraries:mogo-map +:libraries:mogo-adas :modules:mogo-module-carchattingprovider :modules:mogo-module-common :modules:mogo-module-chat :modules:mogo-module-carchatting :test:crashreport -:test:crashreport-bugly :test:crashreport-apm :test:crashreport-noop :test:crashreport-upgrade diff --git a/modules/mogo-module-common/build.gradle b/modules/mogo-module-common/build.gradle index 47db9c2ec2..49b6b6cbf6 100644 --- a/modules/mogo-module-common/build.gradle +++ b/modules/mogo-module-common/build.gradle @@ -73,23 +73,21 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { api rootProject.ext.dependencies.mogomap api rootProject.ext.dependencies.mogomapapi - api rootProject.ext.dependencies.mogo_core_utils api rootProject.ext.dependencies.mogocommons api rootProject.ext.dependencies.mogoserviceapi implementation rootProject.ext.dependencies.callchatprovider - - implementation rootProject.ext.dependencies.mogo_core_utils + + api rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_function_call } else { api project(":libraries:mogo-map") api project(":libraries:mogo-map-api") - api project(':core:mogo-core-utils') api project(":foudations:mogo-commons") api project(':services:mogo-service-api') implementation project(':modules:mogo-module-carchattingprovider') - implementation project(':core:mogo-core-utils') + api project(':core:mogo-core-utils') implementation project(':core:mogo-core-data') implementation project(':core:mogo-core-function-call') } diff --git a/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/AdasRecognizedResultDrawer.java b/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/AdasRecognizedResultDrawer.java index f02e748b39..57d5e2d4ab 100644 --- a/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/AdasRecognizedResultDrawer.java +++ b/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/AdasRecognizedResultDrawer.java @@ -211,7 +211,6 @@ public class AdasRecognizedResultDrawer extends BaseDrawer { return; } Iterator iterator = mLastPositions.values().iterator(); - Log.d("EmArrow", "removeUselessLastRecord size : " + mLastPositions.size()); while (iterator.hasNext()) { TrafficData result = iterator.next(); long internal = result.getSatelliteTime() - getCurSatelliteTime(); diff --git a/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/SnapshotSetDataDrawer.java b/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/SnapshotSetDataDrawer.java index c6fa8939fc..d5e3a69162 100644 --- a/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/SnapshotSetDataDrawer.java +++ b/modules/mogo-module-common/src/main/java/com/mogo/module/common/drawer/SnapshotSetDataDrawer.java @@ -1,5 +1,6 @@ package com.mogo.module.common.drawer; +import static com.mogo.eagle.core.data.chain.ChainConstant.CHAIN_LINK_CLOUD_SHOW; import static com.mogo.module.common.constants.DataTypes.TYPE_MARKER_CLOUD_DATA; import android.os.Build; @@ -140,11 +141,11 @@ public class SnapshotSetDataDrawer extends BaseDrawer implements IMogoMarkerClic * * @param data 自车周边数据 */ - @ChainLog(linkCode = 0, - endpoint = TracingConstants.Endpoint.PAD, - nodeAliasCode = "PAD_YINGYAN_SHOW", - paramIndexes = {0}, - clientPkFileName = "sn") +// @ChainLog(linkCode = CHAIN_LINK_CLOUD_SHOW, +// endpoint = TracingConstants.Endpoint.PAD, +// nodeAliasCode = "PAD_YINGYAN_SHOW", +// paramIndexes = {0}, +// clientPkFileName = "sn") public void renderSnapshotData(SocketDownData.LauncherSnapshotProto data) { final long start = System.nanoTime(); if (clear(data)) { @@ -252,7 +253,6 @@ public class SnapshotSetDataDrawer extends BaseDrawer implements IMogoMarkerClic return; } Iterator iterator = mLastPositions.values().iterator(); - Log.d("EmArrow", "removeUselessLastRecord size : " + mLastPositions.size()); while (iterator.hasNext()) { SocketDownData.CloudRoadDataProto result = iterator.next(); long internal = Long.parseLong(adasControllerApi.getSatelliteTime()) - result.getSatelliteTime(); diff --git a/modules/mogo-module-common/src/main/java/com/mogo/module/common/kt/ScopeManager.kt b/modules/mogo-module-common/src/main/java/com/mogo/module/common/kt/ScopeManager.kt deleted file mode 100644 index 8909e4213b..0000000000 --- a/modules/mogo-module-common/src/main/java/com/mogo/module/common/kt/ScopeManager.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.mogo.module.common.kt - -import androidx.annotation.RestrictTo -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch - -/** - * @author congtaowang - * @since 2021/3/3 - * - * 描述 - */ -object ScopeManager { - - private var mScope = MainScope() - private var mAdasScope = MainScope() - private var mThreadScope: RestrictTo.Scope? = null - - fun mainScope(runnable: Runnable?) { - mScope.launch(Dispatchers.Default) { - runnable?.apply { - run() - } - } - } - - fun adasScope(runnable: Runnable?) { - mAdasScope.launch(Dispatchers.Default) { - runnable?.apply { - run() - } - } - } -} \ No newline at end of file diff --git a/modules/mogo-module-extensions/build.gradle b/modules/mogo-module-extensions/build.gradle index 3380212888..4b63e7ffee 100644 --- a/modules/mogo-module-extensions/build.gradle +++ b/modules/mogo-module-extensions/build.gradle @@ -64,25 +64,23 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { api rootProject.ext.dependencies.mogomapapi - api rootProject.ext.dependencies.mogo_core_utils api rootProject.ext.dependencies.mogocommons api rootProject.ext.dependencies.mogoserviceapi implementation rootProject.ext.dependencies.modulecommon implementation rootProject.ext.dependencies.moduleservice - implementation rootProject.ext.dependencies.mogo_core_utils + api rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_res implementation rootProject.ext.dependencies.mogo_core_function_call } else { api project(":libraries:mogo-map-api") - api project(':core:mogo-core-utils') api project(":foudations:mogo-commons") api project(':services:mogo-service-api') implementation project(':modules:mogo-module-common') implementation project(':modules:mogo-module-service') - implementation project(':core:mogo-core-utils') + api project(':core:mogo-core-utils') implementation project(':core:mogo-core-data') implementation project(':core:mogo-core-res') implementation project(':core:mogo-core-function-call') diff --git a/modules/mogo-module-main/build.gradle b/modules/mogo-module-main/build.gradle index c4e3370763..b209b14507 100644 --- a/modules/mogo-module-main/build.gradle +++ b/modules/mogo-module-main/build.gradle @@ -53,7 +53,6 @@ dependencies { compileOnly rootProject.ext.dependencies.adasapi compileOnly rootProject.ext.dependencies.adasconfigapi if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { - api rootProject.ext.dependencies.mogo_core_utils api rootProject.ext.dependencies.mogocommons api rootProject.ext.dependencies.modulecommon api rootProject.ext.dependencies.mogoserviceapi @@ -63,8 +62,8 @@ 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(':core:mogo-core-utils') api project(":foudations:mogo-commons") api project(':modules:mogo-module-common') api project(':services:mogo-service-api') @@ -74,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..b423200a68 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/modules/mogo-module-service/src/main/java/com/mogo/module/service/marker/MapMarkerManager.java b/modules/mogo-module-service/src/main/java/com/mogo/module/service/marker/MapMarkerManager.java index e453747ba7..192a6f7345 100644 --- a/modules/mogo-module-service/src/main/java/com/mogo/module/service/marker/MapMarkerManager.java +++ b/modules/mogo-module-service/src/main/java/com/mogo/module/service/marker/MapMarkerManager.java @@ -142,11 +142,6 @@ public class MapMarkerManager implements IMogoMarkerClickListener, @Override public void onMsgReceived(SocketDownData.LauncherSnapshotProto mogoSnapshotSetData) { DebugConfig.setStatus(DebugConfig.sDownloadSnapshot, true); -// SnapshotSetDataDrawer.getInstance().renderSnapshotData(mogoSnapshotSetData); - // Message msg = mSnapshotHandler.obtainMessage(); - // msg.obj = mogoSnapshotSetData; - // msg.what = MSG_SNAPSHOT; - // msg.sendToTarget(); } }); } @@ -528,10 +523,6 @@ public class MapMarkerManager implements IMogoMarkerClickListener, int radius, boolean fitBounds) { - // if ( DebugConfig.isNeedUploadCoordinatesDurationInTime() ) {//todo 实时在线车辆需要注释,否则在2D模式下不能展示 - // return; - // } - if (DebugConfig.isDebug()) { if (!DebugConfig.isRequestOnlineCarData()) { return; diff --git a/modules/mogo-module-service/src/main/java/com/mogo/module/service/routeoverlay/MogoRouteOverlayManager.java b/modules/mogo-module-service/src/main/java/com/mogo/module/service/routeoverlay/MogoRouteOverlayManager.java index 3cd2ddf2a9..d580ac9aa1 100644 --- a/modules/mogo-module-service/src/main/java/com/mogo/module/service/routeoverlay/MogoRouteOverlayManager.java +++ b/modules/mogo-module-service/src/main/java/com/mogo/module/service/routeoverlay/MogoRouteOverlayManager.java @@ -96,7 +96,7 @@ public class MogoRouteOverlayManager implements RouteOverlayDrawer.getInstance(mContext).drawTrajectoryList(mogoLatLngs); } builder.append("}"); - Log.d(TAG,builder.toString()); + //Log.d(TAG,builder.toString()); } @Override diff --git a/services/mogo-service/build.gradle b/services/mogo-service/build.gradle index 76d36c3203..f6c6e92e28 100644 --- a/services/mogo-service/build.gradle +++ b/services/mogo-service/build.gradle @@ -51,7 +51,6 @@ dependencies { implementation rootProject.ext.dependencies.adasapi implementation rootProject.ext.dependencies.adasconfigapi if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { - implementation rootProject.ext.dependencies.moduleADAS api rootProject.ext.dependencies.mogomap implementation rootProject.ext.dependencies.mogomapapi implementation rootProject.ext.dependencies.mogo_core_utils diff --git a/settings.gradle b/settings.gradle index d06a6c0260..0e8e0f5ad8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,6 +40,8 @@ include ':core:function-impl:mogo-core-function-notice' include ':core:function-impl:mogo-core-function-autopilot' // 行车超视距服务,路测摄像头、前车摄像头 include ':core:function-impl:mogo-core-function-monitoring' +// USB 行车记录仪 +include ':core:function-impl:mogo-core-function-carcorder' // 服务 @@ -51,9 +53,11 @@ include ':foudations:mogo-aicloud-services-sdk' include ':foudations:mogo-commons' // 基础库 +include ':libraries:map-usbcamera' include ':libraries:map-custom' include ':libraries:mogo-map-api' include ':libraries:mogo-map' +include ':libraries:mogo-adas' // OLD业务模块 include ':modules:mogo-module-common' diff --git a/test/crashreport-apmbyte/build.gradle b/test/crashreport-apmbyte/build.gradle index 825f58a947..d3c9f93c1e 100644 --- a/test/crashreport-apmbyte/build.gradle +++ b/test/crashreport-apmbyte/build.gradle @@ -52,7 +52,7 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { api rootProject.ext.dependencies.crashreport - implementation rootProject.ext.dependencies.mogoutils + implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogocommons } else { api project(":test:crashreport") 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 0512916291..86e15da72e 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 @@ -8,11 +8,13 @@ import com.apm.insight.CrashType; import com.apm.insight.MonitorCrash; import com.apm.insight.log.VLog; import com.bytedance.apm.insight.ApmInsight; +import com.bytedance.apm.insight.ApmInsightAgent; import com.bytedance.apm.insight.ApmInsightInitConfig; import com.mogo.cloud.passport.MoGoAiCloudClientConfig; import com.mogo.eagle.core.utilcode.mogo.logger.Logger; import com.mogo.eagle.core.utilcode.util.AppUtils; import com.mogo.eagle.core.utilcode.util.CommonUtils; +import com.mogo.eagle.core.utilcode.util.DeviceIdUtils; import com.mogo.test.crashreport.CrashReportConstants; import com.mogo.test.crashreport.ITestCrashReportProvider; @@ -23,7 +25,7 @@ import java.util.Map; /** * @author congtaowang * @since 2020/9/9 - * + *

* 描述 */ @Route(path = CrashReportConstants.PATH) @@ -39,7 +41,8 @@ public class ApmCrashReportProvider implements ITestCrashReportProvider { initCrash(context); initApmInsight(context); } - private void initCrash(final Context context) { + + private void initCrash(final Context context) { MonitorCrash crash = MonitorCrash.init(context, BYTEAMP_APPID, CommonUtils.getVersionCode(context), CommonUtils.getVersionName(context)) .setCustomDataCallback(new AttachUserData() { @@ -55,9 +58,17 @@ public class ApmCrashReportProvider implements ITestCrashReportProvider { crash.config().setChannel("eagle"); crash.config().setDeviceId(MoGoAiCloudClientConfig.getInstance().getSn());//可选,可以设置自定义did,不设置会使用内部默认的 String mapSDKVersion = AppUtils.getCustomMapSDKVersion(context); - crash.addTags(MAP_SDK_VERSION,mapSDKVersion); + crash.addTags(MAP_SDK_VERSION, mapSDKVersion); // crash.setReportUrl("www.xxx.com"); // 私有化部署:私有化部署才配置上报地址 // crash.addTags("key", "value"); // 自定义筛选tag, 按需添加、可多次覆盖 + + HashMap dimension = new HashMap<>(); + //维度值 + dimension.put("Devices_ID", DeviceIdUtils.getWidevineID(context)); + HashMap metric = new HashMap<>(); + //指标值 + metric.put("Devices_ID_metric", (double) 100); + ApmInsightAgent.monitorEvent("Devices_ID_EVENT", dimension, metric); } /** @@ -69,17 +80,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把平台数据和自己用户关联起来,可以不配置