diff --git a/libraries/map-usbcamera/build.gradle b/libraries/map-usbcamera/build.gradle
index 5fb2f29276..776c9580e3 100644
--- a/libraries/map-usbcamera/build.gradle
+++ b/libraries/map-usbcamera/build.gradle
@@ -25,6 +25,10 @@ android {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
+
+ ndk {
+ abiFilters "armeabi-v7a", "arm64-v8a"
+ }
}
buildTypes {
@@ -38,6 +42,18 @@ android {
sourceCompatibility 1.8
targetCompatibility 1.8
}
+
+
+ repositories {
+ flatDir {
+ dirs 'libs'
+ }
+ }
+ sourceSets{
+ main{
+ jniLibs.srcDir(['libs'])
+ }
+ }
}
dependencies {
@@ -45,21 +61,12 @@ dependencies {
implementation rootProject.ext.dependencies.arouter
kapt rootProject.ext.dependencies.aroutercompiler
-
+ implementation(name: 'common-4.1.1', ext: 'aar')
+
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
- implementation rootProject.ext.dependencies.mogoutils
- implementation rootProject.ext.dependencies.mogomapapi
- implementation rootProject.ext.dependencies.mogocommons
- implementation rootProject.ext.dependencies.mogomapapi
- implementation rootProject.ext.dependencies.mogo_core_data
} else {
- implementation project(':foudations:mogo-utils')
- implementation project(':libraries:mogo-map-api')
- implementation project(':foudations:mogo-commons')
- implementation project(':services:mogo-service-api')
- implementation project(':core:mogo-core-data')
}
}
diff --git a/libraries/map-usbcamera/gradle.properties b/libraries/map-usbcamera/gradle.properties
index b57696e52a..86f5eb86de 100644
--- a/libraries/map-usbcamera/gradle.properties
+++ b/libraries/map-usbcamera/gradle.properties
@@ -1,3 +1,3 @@
-GROUP=com.mogo.map
-POM_ARTIFACT_ID=map-autonavi
+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/src/main/AndroidManifest.xml b/libraries/map-usbcamera/src/main/AndroidManifest.xml
index 74341ccd56..6173006460 100644
--- a/libraries/map-usbcamera/src/main/AndroidManifest.xml
+++ b/libraries/map-usbcamera/src/main/AndroidManifest.xml
@@ -1,13 +1,7 @@
+ package="com.mogo.usbcamera">
-
-
-
-
-
+
\ 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/map/impl/automap/navi/AutoNaviClient.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/AutoNaviClient.java
deleted file mode 100644
index 87e8d8a351..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/AutoNaviClient.java
+++ /dev/null
@@ -1,229 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.location.Location;
-
-import com.mogo.eagle.core.data.map.MogoLatLng;
-import com.mogo.map.navi.IMogoCarLocationChangedListener2;
-import com.mogo.map.navi.IMogoNavi;
-import com.mogo.map.navi.MogoCalculatePath;
-import com.mogo.map.navi.MogoNaviConfig;
-import com.mogo.map.navi.OnCalculatePathItemClickInteraction;
-import com.mogo.utils.logger.Logger;
-
-import java.util.List;
-
-/**
- * @author congtaowang
- * @since 2020/6/2
- *
- * 使用高德车机版导航
- */
-public class AutoNaviClient implements IMogoNavi {
-
- private static final String TAG = "NaviClient";
-
- public static final String ACTION_AUTO_MAP = "AUTONAVI_STANDARD_BROADCAST_RECV";
-
- public static final String KEY_TYPE = "KEY_TYPE";
- public static final String SOURCE_APP = "SOURCE_APP";
- public static final String LAT = "LAT";
- public static final String LON = "LON";
- public static final String ENTRY_LAT = "ENTRY_LAT";
- public static final String ENTRY_LON = "ENTRY_LON";
- public static final String DEV = "DEV"; // (int)是否偏移(0:lat 和 lon 是已经加密后的,不需要国测加密; 1:需要国测加密)
-
- /**
- * (必填)(int)导航方式
- * =1(避免收费)
- * =2(多策略算路)
- * =3 (不走高速)
- * =4(躲避拥堵)
- * =5(不走高速且避免收费)
- * =6(不走高速且躲避拥堵)
- * =7(躲避收费且躲避拥堵)
- * =8(不走高速躲避收费和拥堵)
- * =20 (高速优先)
- * =24(高速优先且躲避拥堵)
- * =-1(地图内部设置默认规则)
- */
- public static final String STYLE = "STYLE";
-
- private static volatile AutoNaviClient sInstance;
- private final Context mContext;
-
- private AutoNaviClient( Context context ) {
- mContext = context.getApplicationContext();
- }
-
- public static AutoNaviClient getInstance( Context context ) {
- if ( sInstance == null ) {
- synchronized ( AutoNaviClient.class ) {
- if ( sInstance == null ) {
- sInstance = new AutoNaviClient( context );
- }
- }
- }
- return sInstance;
- }
-
- public synchronized void release() {
- sInstance = null;
- }
-
- @Override
-
- public void naviTo( MogoLatLng endPoint ) {
- Intent intent = new Intent();
- intent.putExtra( KEY_TYPE, 10038 );
- intent.putExtra( LAT, endPoint.lat );
- intent.putExtra( LON, endPoint.lon );
-// intent.putExtra( ENTRY_LAT, endPoint.lat );
-// intent.putExtra( ENTRY_LON, endPoint.lon );
- intent.putExtra( DEV, 0 );
- intent.putExtra( STYLE, -1 );
- intent.putExtra( SOURCE_APP, "Third App" );
- sendByIntent( intent );
- }
-
- @Override
- public void naviTo( MogoLatLng endPoint, MogoNaviConfig config ) {
- naviTo( endPoint );
- }
-
- @Override
- public void naviTo( MogoLatLng endPoint, List< MogoLatLng > wayPoints ) {
- naviTo( endPoint );
- }
-
- @Override
- public void naviTo( MogoLatLng endPoint, List< MogoLatLng > wayPoints, MogoNaviConfig config ) {
- naviTo( endPoint );
- }
-
- @Override
- public void reCalculateRoute( MogoNaviConfig config ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void stopNavi() {
- Intent intent = new Intent();
- intent.putExtra( "KEY_TYPE", 10010 );
- sendByIntent( intent );
- }
-
- @Override
- public void startNavi( boolean isRealNavi ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- private void sendByIntent( Intent intent ) {
- intent.setAction( ACTION_AUTO_MAP );
- intent.addFlags( Intent.FLAG_INCLUDE_STOPPED_PACKAGES );
- mContext.sendBroadcast( intent );
- }
-
- @Override
- public boolean isNaviing() {
- return MapState.getInstance().isNaving();
- }
-
- @Override
- public List< MogoCalculatePath > getCalculatedStrategies() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return null;
- }
-
- @Override
- public List< MogoLatLng > getCalculatedPathPos() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return null;
- }
-
- @Override
- public OnCalculatePathItemClickInteraction getItemClickInteraction() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return null;
- }
-
- @Override
- public void setLineClickInteraction( OnCalculatePathItemClickInteraction itemClickInteraction ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void clearCalculatePaths() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void setCalculatePathDisplayBounds( Rect bounds ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public MogoNaviConfig getNaviConfig() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return MogoMapApi.getApiBuilder().getNavi( mContext ).getNaviConfig();
- }
-
- @Override
- public boolean setBroadcastMode( int mode ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return false;
- }
-
- @Override
- public List< MogoLatLng > getNaviPathCoordinates() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- return null;
- }
-
- @Override
- public MogoLatLng getCarLocation() {
- return MogoMapApi.getApiBuilder().getNavi( mContext ).getCarLocation();
- }
-
- @Override
- public Location getCarLocation2() {
- return MogoMapApi.getApiBuilder().getNavi( mContext ).getCarLocation2();
- }
-
- @Override
- public void registerCarLocationChangedListener( IMogoCarLocationChangedListener2 listener ) {
- //do not impl
- }
-
- @Override
- public void startAimlessMode() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void stopAimlessMode() {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void setAimlessModeStatus( boolean open ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void displayOverview( Rect bounds ) {
- Logger.w( TAG, "高德车机导航,不支持此设置" );
- }
-
- @Override
- public void setUseExtraGPSData( boolean use ) {
- MogoMapApi.getApiBuilder().getNavi( mContext ).setUseExtraGPSData( use );
- }
-
- @Override
- public void setExtraGPSData( double lon, double lat, float speed, float accuracy, float bearing, long timestamp ) {
- MogoMapApi.getApiBuilder().getNavi( mContext ).setExtraGPSData( lon, lat, speed, accuracy, bearing, timestamp );
- }
-}
diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/AutoNaviReceiver.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/AutoNaviReceiver.java
deleted file mode 100644
index ecf250781d..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/AutoNaviReceiver.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.text.TextUtils;
-
-import com.mogo.commons.storage.SpStorage;
-import com.mogo.map.navi.MogoNaviInfo;
-import com.mogo.map.navi.MogoNaviListenerHandler;
-import com.mogo.map.navi.MogoTraffic;
-import com.mogo.utils.logger.Logger;
-
-/**
- * @author congtaowang
- * @since 2020/6/2
- *
- * 高德公版地图透出信息广播接收者
- */
-public class AutoNaviReceiver extends BroadcastReceiver {
-
- private static final String TAG = "AutoNaviReceiver";
-
- public static final String ACTION_AUTONAVI_SEND = "AUTONAVI_STANDARD_BROADCAST_SEND";
- private static AutoNaviReceiver autoNaviReceiver;
- private static boolean sRegisterFlag = false;
-
- private static MogoNaviInfo sNaviInfo;
- private static MogoTraffic sTraffic;
-
-
- public static void register( Context context ) {
- IntentFilter filter = new IntentFilter();
- filter.addAction( ACTION_AUTONAVI_SEND );
- autoNaviReceiver = new AutoNaviReceiver();
- context.registerReceiver( autoNaviReceiver, filter );
- sRegisterFlag = true;
- }
-
- public static void unregister( Context context ) {
- if ( autoNaviReceiver != null && sRegisterFlag ) {
- try {
- context.unregisterReceiver( autoNaviReceiver );
- } catch ( Exception e ) {
- Logger.e( TAG, e, "error. " );
- }
- }
- }
-
- @Override
- public void onReceive( Context context, Intent intent ) {
- String action = intent.getAction();
-
- if ( !TextUtils.equals( ACTION_AUTONAVI_SEND, action ) ) {
- return;
- }
-
- int keyType = intent.getIntExtra( "KEY_TYPE", 0 );
- switch ( keyType ) {
- case 10001:
- handleAutoNaviInfo( context, intent );
- break;
- case 10019:
- int state = intent.getIntExtra( "EXTRA_STATE", -1 );
- handleMapStatusChanged( state );
- break;
- case 10056:
- String json = intent.getStringExtra( "EXTRA_ROAD_INFO" );
- SpStorage.setNavigationTarget( json );
- Logger.d( TAG, json );
- break;
- }
- }
-
- /**
- * 在导航/巡航/模拟导航中auto主动将变化的引导信息发送给第三方系统
- *
- * @param intent
- */
- private void handleAutoNaviInfo( Context context, Intent intent ) {
-
- int type = intent.getIntExtra( GuideInfoExtraKey.TYPE, -1 );
-
- int cameraSpeed = intent.getIntExtra( GuideInfoExtraKey.CAMERA_SPEED, 0 );
- int cameraDisc = intent.getIntExtra( GuideInfoExtraKey.CAMERA_DIST, 0 );
- int cameraType = intent.getIntExtra( GuideInfoExtraKey.CAMERA_TYPE, 0 );
-
- if ( type == 0 || type == 1 ) {
- if ( !MapState.getInstance().isNaving()
- && MogoNaviListenerHandler.getInstance().hasDelegateListener() ) {
- MapState.getInstance().setNaving( true );
- MogoNaviListenerHandler.getInstance().onStartNavi();
- }
- if ( sNaviInfo == null ) {
- sNaviInfo = new MogoNaviInfo();
- }
- sNaviInfo.setCurrentLimitSpeed( cameraSpeed );
- sNaviInfo.setCurrentRoadName( intent.getStringExtra( GuideInfoExtraKey.CUR_ROAD_NAME ) );
- sNaviInfo.setCurrentSpeed( intent.getIntExtra( GuideInfoExtraKey.CUR_SPEED, 0 ) );
- sNaviInfo.setCurStepRetainDistance( intent.getIntExtra( GuideInfoExtraKey.SEG_REMAIN_DIS, 0 ) );
- sNaviInfo.setCurStepRetainTime( intent.getIntExtra( GuideInfoExtraKey.SEG_REMAIN_TIME, 0 ) );
- sNaviInfo.setIconResId( MogoMapApi.getApiBuilder().getResIdByIconType( context, intent.getIntExtra( GuideInfoExtraKey.NEW_ICON, 0 ) ) );
- sNaviInfo.setNextRoadName( intent.getStringExtra( GuideInfoExtraKey.NEXT_ROAD_NAME ) );
- sNaviInfo.setPathRetainDistance( intent.getIntExtra( GuideInfoExtraKey.ROUTE_REMAIN_DIS, 0 ) );
- sNaviInfo.setPathRetainTime( intent.getIntExtra( GuideInfoExtraKey.ROUTE_REMAIN_TIME, 0 ) );
- MogoNaviListenerHandler.getInstance().onNaviInfoUpdate( sNaviInfo );
- }
- if ( sTraffic == null ) {
- sTraffic = new MogoTraffic( MapState.getInstance().isAimless() ? MogoTraffic.TYPE_AIM : MogoTraffic.TYPE_NAVI );
- }
- sTraffic.setFromType( MapState.getInstance().isAimless() ? MogoTraffic.TYPE_AIM : MogoTraffic.TYPE_NAVI );
- sTraffic.setDistance( cameraDisc );
- sTraffic.setSpeedLimit( cameraSpeed );
- sTraffic.setTrafficType( cameraType );
- MogoNaviListenerHandler.getInstance().onUpdateTraffic2( sTraffic );
- }
-
- /**
- * 当导航发生状态变更时,将相应的状态通知给系统。
- *
- * @param state
- */
- private void handleMapStatusChanged( int state ) {
- if ( state == -1 ) {
- return;
- }
- switch ( state ) {
- case MapStateValue.START_NAVI:
- case MapStateValue.START_EMULATOR_NAVI:
- if ( MapState.getInstance().isNaving() ) {
- Logger.w( TAG, "naving..." );
- return;
- }
- MapState.getInstance().setNaving( true );
- MogoNaviListenerHandler.getInstance().onStartNavi();
- break;
- case MapStateValue.STOP_NAVI:
- case MapStateValue.STOP_EMULATOR_NAVI:
- case MapStateValue.APP_START: // 语音退出导航,感觉是杀掉了高德APP了
- if ( MapState.getInstance().isNaving() ) {
- MapState.getInstance().setNaving( false );
- MogoNaviListenerHandler.getInstance().onStopNavi();
- }
- break;
- case MapStateValue.START_AIMLESS_NAVI:
- MapState.getInstance().setAimless( true );
- break;
- case MapStateValue.STOP_AIMLESS_NAVI:
- MapState.getInstance().setAimless( false );
- break;
- case MapStateValue.EXIT_APP:
- break;
- case MapStateValue.DESTINATION:
- MogoNaviListenerHandler.getInstance().onArriveDestination();
- break;
- }
- }
-}
diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/GuideInfoExtraKey.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/GuideInfoExtraKey.java
deleted file mode 100644
index f71d166564..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/GuideInfoExtraKey.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-//引导信息对应的KEY值机器描述
-public class GuideInfoExtraKey {
- /**
- * 导航类型,对应的值为int类型
- * 0:GPS导航
- * 1:模拟导航
- * 2:巡航
- */
- public static final String TYPE = "TYPE";
-
- /**
- * 当前道路名称,对应的值为String类型
- */
- public static final String CUR_ROAD_NAME = "CUR_ROAD_NAME";
-
- /**
- * 下一道路名,对应的值为String类型
- */
- public static final String NEXT_ROAD_NAME = "NEXT_ROAD_NAME";
-
- /**
- * 电子眼限速度,对应的值为int类型,无限速则为0,单位:公里/小时
- */
- public static final String CAMERA_SPEED = "CAMERA_SPEED";
-
- /**
- * 导航转向图标,对应的值为int类型
- */
- public static final String ICON = "ICON";
-
- /**
- * 导航最新的转向图标,对应的值为int类型
- */
- public static final String NEW_ICON = "NEW_ICON";
-
- /**
- * 路径剩余距离,对应的值为int类型,单位:米
- */
- public static final String ROUTE_REMAIN_DIS = "ROUTE_REMAIN_DIS";
- /**
- * 路径剩余时间,对应的值为int类型,单位:秒
- */
- public static final String ROUTE_REMAIN_TIME = "ROUTE_REMAIN_TIME";
-
- /**
- * 当前导航段剩余距离,对应的值为int类型,单位:米
- */
- public static final String SEG_REMAIN_DIS = "SEG_REMAIN_DIS";
-
- /**
- * 当前导航段剩余时间,对应的值为int类型,单位:秒
- */
- public static final String SEG_REMAIN_TIME = "SEG_REMAIN_TIME";
-
- /**
- * 路径总距离,对应的值为int类型,单位:米
- */
- public static final String ROUTE_ALL_DIS = "ROUTE_ALL_DIS";
-
- /**
- * 路径总时间,对应的值为int类型,单位:秒
- */
- public static final String ROUTE_ALL_TIME = "ROUTE_ALL_TIME";
-
- /**
- * 当前车速,对应的值为int类型,单位:公里/小时
- */
- public static final String CUR_SPEED = "CUR_SPEED";
-
- /**
- * 当前道路类型,对应的值为int类型
- * 0:高速公路
- * 1:国道
- * 2:省道
- * 3:县道
- * 4:乡公路
- * 5:县乡村内部道路
- * 6:主要大街、城市快速道 * 7:主要道路
- * 8:次要道路
- * 9:普通道路
- * 10:非导航道路
- */
- public static final String ROAD_TYPE = "ROAD_TYPE";
-
- /**
- * 路径剩余时间,对应的值为String类型,单位:天/小时/分钟 比如:1天2小时, 21小时30分
- * 钟(只用于长安)
- */
- public static final String ROUTE_REMAIN_TIME_STRING = "ROUTE_REMAIN_TIME_S TRING";
-
- /**
- * 下下个路名名称,对应的值为String类型
- */
- public static final String NEXT_NEXT_ROAD_NAME = "NEXT_NEXT_ROAD_NAME";
- /**
- * 下下个路口转向图标,对应的值为int类型
- */
-
- public static final String NEXT_NEXT_TURN_ICON = "NEXT_NEXT_TURN_ICON";
-
- /**
- * 距离下下个路口剩余距离,对应的值为int类型,单位:米
- */
- public static final String NEXT_SEG_REMAIN_DIS = "NEXT_SEG_REMAIN_DIS";
-
- /**
- * 距离下下个路口剩余时间,对应的值为int类型,单位:秒
- */
- public static final String NEXT_SEG_REMAIN_TIME = "NEXT_SEG_REMAIN_TIME";
-
- /**
- * 距离最近的电子眼距离,对应的值为int类型,单位:米
- */
- public static final String CAMERA_DIST = "CAMERA_DIST";
-
- /**
- * 电子眼类型,对应的值为int类型
- * 0 测速摄像头
- * 1为监控摄像头
- * 2为闯红灯拍照
- * 3为违章拍照
- * 4为公交专用道摄像头
- * 5为应急车道摄像头
- * 6为非机动车道拍照
- */
- public static final String CAMERA_TYPE = "CAMERA_TYPE";
-}
\ No newline at end of file
diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapState.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapState.java
deleted file mode 100644
index 4b4f2ee126..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapState.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-public
-/**
- * @author congtaowang
- * @since 2020/6/3
- *
- * 高德地图状态
- */
-class MapState {
-
- private static volatile MapState sInstance;
-
- private MapState() {
- }
-
- public static MapState getInstance() {
- if ( sInstance == null ) {
- synchronized ( MapState.class ) {
- if ( sInstance == null ) {
- sInstance = new MapState();
- }
- }
- }
- return sInstance;
- }
-
- public synchronized void release() {
- sInstance = null;
- }
-
- private boolean isNaving = false;
- private boolean isAimless = false;
-
- public boolean isNaving() {
- return isNaving;
- }
-
- public void setNaving( boolean naving ) {
- isNaving = naving;
- }
-
- public boolean isAimless() {
- return isAimless;
- }
-
- public void setAimless( boolean aimless ) {
- isAimless = aimless;
- }
-}
diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapStateValue.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapStateValue.java
deleted file mode 100644
index f2913ff1bb..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MapStateValue.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-/**
- * @author congtaowang
- * @since 2020/6/3
- *
- * 高德公版地图状态值
- */
-public interface MapStateValue {
-
- int START_NAVI = 8;
-
- int STOP_NAVI = 9;
-
- int START_EMULATOR_NAVI = 10;
-
- int STOP_EMULATOR_NAVI = 12;
-
- int START_AIMLESS_NAVI = 24;
-
- int STOP_AIMLESS_NAVI = 25;
-
- int EXIT_APP = 45;
-
- int DESTINATION = 39;
-
- int APP_START = 0;
-}
diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MogoMapApi.java b/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MogoMapApi.java
deleted file mode 100644
index 1fe1f6f714..0000000000
--- a/libraries/map-usbcamera/src/main/java/com/mogo/map/impl/automap/navi/MogoMapApi.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.mogo.map.impl.automap.navi;
-
-import com.alibaba.android.arouter.launcher.ARouter;
-import com.mogo.map.IMogoMapApiBuilder;
-
-/**
- * @author congtaowang
- * @since 2020/12/10
- *
- * 描述
- */
-public class MogoMapApi {
-
- private static IMogoMapApiBuilder sApiBuilder;
-
- public static IMogoMapApiBuilder getApiBuilder() {
- if (sApiBuilder == null) {
- synchronized (AutoNaviClient.class) {
- if (sApiBuilder == null) {
- sApiBuilder = ARouter.getInstance().navigation(IMogoMapApiBuilder.class);
- }
- }
- }
- return sApiBuilder;
- }
-}
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..76fb1d5da2
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java
@@ -0,0 +1,371 @@
+package com.mogo.usbcamera;
+
+import android.app.Activity;
+import android.graphics.SurfaceTexture;
+import android.hardware.usb.UsbDevice;
+import android.os.Environment;
+
+import org.easydarwin.sw.TxtOverlay;
+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 java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/** UVCCamera Helper class
+ *
+ * Created by jiangdongguo on 2017/9/30.
+ */
+
+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);
+ }
+
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ // 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) {
+ }
+ });
+
+ 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/usb/CameraDialog.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java
new file mode 100644
index 0000000000..795e394782
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java
@@ -0,0 +1,245 @@
+/*
+ * 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 NullPointerException | ClassCastException e) {
+ e.printStackTrace();
+ }
+ }
+ 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 List list) {
+ 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/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..6fee8a76bb
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java
@@ -0,0 +1,1420 @@
+/*
+ * 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.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+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.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+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 = android.os.Build.MODEL;
+ String devSystemVersion = android.os.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..d619c566ff
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java
@@ -0,0 +1,1146 @@
+package com.serenegiant.usb.common;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+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.Locale;
+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, AbstractUVCCameraHandler.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 extends AbstractUVCCameraHandler> mHandlerClass;
+ private final WeakReference mWeakParent;
+ private final WeakReference mWeakCameraView;
+ private final int mEncoderType;
+ private final Set mCallbacks = new CopyOnWriteArraySet();
+ private int mWidth, mHeight, mPreviewMode;
+ private float mBandwidthFactor;
+ private boolean mIsPreviewing;
+ private boolean mIsRecording;
+
+ // 播放声音
+// 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 extends AbstractUVCCameraHandler> clazz,
+ final Activity parent, final CameraViewInterface cameraView,
+ final int encoderType, final int width, final int height, final int format,
+ final float bandwidthFactor) {
+
+ super("CameraThread");
+ mHandlerClass = clazz;
+ mEncoderType = encoderType;
+ mWidth = width;
+ mHeight = height;
+ mPreviewMode = format;
+ mBandwidthFactor = bandwidthFactor;
+ mWeakParent = new WeakReference<>(parent);
+ mWeakCameraView = new WeakReference<>(cameraView);
+// loadShutterSound(parent);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ Log.i(TAG, "CameraThread#finalize");
+ super.finalize();
+ }
+
+ public AbstractUVCCameraHandler getHandler() {
+ if (DEBUG) Log.v(TAG_THREAD, "getHandler:");
+ synchronized (mSync) {
+ if (mHandler == null)
+ try {
+ mSync.wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ return mHandler;
+ }
+
+ public int getWidth() {
+ synchronized (mSync) {
+ return mWidth;
+ }
+ }
+
+ public int getHeight() {
+ synchronized (mSync) {
+ return mHeight;
+ }
+ }
+
+ public boolean isCameraOpened() {
+ synchronized (mSync) {
+ return mUVCCamera != null;
+ }
+ }
+
+ public boolean isPreviewing() {
+ synchronized (mSync) {
+ return mUVCCamera != null && mIsPreviewing;
+ }
+ }
+
+ public boolean isRecording() {
+ synchronized (mSync) {
+ return (mUVCCamera != null) && (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 extends AbstractUVCCameraHandler> constructor = mHandlerClass.getDeclaredConstructor(CameraThread.class);
+ handler = constructor.newInstance(this);
+ } catch (final NoSuchMethodException e) {
+ Log.w(TAG, e);
+ } catch (final IllegalAccessException e) {
+ Log.w(TAG, e);
+ } catch (final InstantiationException e) {
+ Log.w(TAG, e);
+ } catch (final InvocationTargetException e) {
+ Log.w(TAG, e);
+ }
+ if (handler != null) {
+ synchronized (mSync) {
+ mHandler = handler;
+ mSync.notifyAll();
+ }
+ Looper.loop();
+// if (mSoundPool != null) {
+// mSoundPool.release();
+// mSoundPool = null;
+// }
+ if (mHandler != null) {
+ mHandler.mReleased = true;
+ }
+ }
+ mCallbacks.clear();
+ synchronized (mSync) {
+ mHandler = null;
+ mSync.notifyAll();
+ }
+ }
+
+ private void callOnOpen() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onOpen();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnClose() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onClose();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStartPreview() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStartPreview();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStopPreview() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStopPreview();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStartRecording() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStartRecording();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnStopRecording() {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onStopRecording();
+ } catch (final Exception e) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+
+ private void callOnError(final Exception e) {
+ for (final CameraCallback callback : mCallbacks) {
+ try {
+ callback.onError(e);
+ } catch (final Exception e1) {
+ mCallbacks.remove(callback);
+ Log.w(TAG, e);
+ }
+ }
+ }
+ }
+}
diff --git a/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..d2b99c1c1e
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java
@@ -0,0 +1,569 @@
+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.jiangdg.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..3036010509
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java
@@ -0,0 +1,385 @@
+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.File;
+import java.io.FileNotFoundException;
+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;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**将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..076758db30
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java
@@ -0,0 +1,114 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+import com.serenegiant.widget.IAspectRatioView;
+
+/**
+ * change the view size with keeping the specified aspect ratio.
+ * if you set this view with in a FrameLayout and set property "android:layout_gravity="center",
+ * you can show this view in the center of screen and keep the aspect ratio of content
+ * XXX it is better that can set the aspect ratio as xml property
+ */
+public class AspectRatioTextureView extends TextureView // API >= 14
+ implements IAspectRatioView {
+
+ private static final boolean DEBUG = true; // TODO set false on release
+ private static final String TAG = "AbstractCameraView";
+
+ private double mRequestedAspect = -1.0;
+ private CameraViewInterface.Callback mCallback;
+
+ public AspectRatioTextureView(final Context context) {
+ this(context, null, 0);
+ }
+
+ public AspectRatioTextureView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // 设置屏幕宽高比
+ @Override
+ public void setAspectRatio(final double aspectRatio) {
+ if (aspectRatio < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (mRequestedAspect != aspectRatio) {
+ mRequestedAspect = aspectRatio;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setAspectRatio(final int width, final int height) {
+ setAspectRatio(width / (double)height);
+ }
+
+ @Override
+ public double getAspectRatio() {
+ return mRequestedAspect;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ if (mRequestedAspect > 0) {
+ int initialWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int initialHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int horizPadding = getPaddingLeft() + getPaddingRight();
+ final int vertPadding = getPaddingTop() + getPaddingBottom();
+ initialWidth -= horizPadding;
+ initialHeight -= vertPadding;
+
+ final double viewAspectRatio = (double)initialWidth / initialHeight;
+ final double aspectDiff = mRequestedAspect / viewAspectRatio - 1;
+
+ if (Math.abs(aspectDiff) > 0.01) {
+ if (aspectDiff > 0) {
+ // width priority decision
+ initialHeight = (int) (initialWidth / mRequestedAspect);
+ } else {
+ // height priority decision
+ initialWidth = (int) (initialHeight * mRequestedAspect);
+ }
+ initialWidth += horizPadding;
+ initialHeight += vertPadding;
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+}
diff --git a/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..af903d6e92
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java
@@ -0,0 +1,47 @@
+/*
+ * UVCCamera
+ * library and sample to access to UVC web camera on non-rooted Android device
+ *
+ * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * All files in the folder are under this Apache License, Version 2.0.
+ * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
+ * may have a different license, see the respective files.
+ */
+
+package com.serenegiant.usb.widget;
+
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+
+import com.serenegiant.usb.encoder.IVideoEncoder;
+import com.serenegiant.widget.IAspectRatioView;
+
+public interface CameraViewInterface extends IAspectRatioView {
+ public interface Callback {
+ public void onSurfaceCreated(CameraViewInterface view, Surface surface);
+ public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height);
+ public void onSurfaceDestroy(CameraViewInterface view, Surface surface);
+ }
+ public void onPause();
+ public void onResume();
+ public void setCallback(Callback callback);
+ public SurfaceTexture getSurfaceTexture();
+ public Surface getSurface();
+ public boolean hasSurface();
+ public void setVideoEncoder(final IVideoEncoder encoder);
+ public Bitmap captureStillImage(int width,int height);
+}
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..b1880b96d1
--- /dev/null
+++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java
@@ -0,0 +1,598 @@
+/*
+ * 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.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/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..2c9b394b63
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..6e7e70e2e0
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..370d63d45a
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..03830b267c
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..a07bbbc70f
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..d1e7b07021
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..f0df5b052b
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..564d932313
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..7cb48799f9
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..ce788243ba
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..6b771d31ae
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..deba66615e
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..33057578d1
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..36b3f76a58
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..05431ef82d
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..0b37d34a94
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/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/modules.txt b/modules.txt
index 098f27631e..2d2002aa3d 100644
--- a/modules.txt
+++ b/modules.txt
@@ -14,6 +14,7 @@
:foudations:mogo-commons
:tts:tts-di
:tts:tts-noop
+:libraries:map-usbcamera
:libraries:map-autonavi
:libraries:map-custom
:libraries:mogo-map
diff --git a/settings.gradle b/settings.gradle
index f11d232630..642b586559 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -52,6 +52,7 @@ include ':foudations:mogo-utils'
include ':foudations:mogo-commons'
// 基础库
+include ':libraries:map-usbcamera'
include ':libraries:map-custom'
include ':libraries:mogo-map-api'
include ':libraries:map-autonavi'