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 mHandlerClass; + private final WeakReference mWeakParent; + private final WeakReference mWeakCameraView; + private final int mEncoderType; + private final Set mCallbacks = new CopyOnWriteArraySet(); + private int mWidth, mHeight, mPreviewMode; + private float mBandwidthFactor; + private boolean mIsPreviewing; + private boolean mIsRecording; + + // 播放声音 +// private SoundPool mSoundPool; +// private int mSoundId; + private AbstractUVCCameraHandler mHandler; + // 处理与Camera相关的逻辑,比如获取byte数据流等 + private UVCCamera mUVCCamera; + + // private MediaMuxerWrapper mMuxer; + private MediaVideoBufferEncoder mVideoEncoder; + private Mp4MediaMuxer mMuxer; + private boolean isPushing; + private String videoPath; + private boolean isSupportOverlay; +// private boolean isAudioThreadStart; + + /** + * 构造方法 + *

+ * clazz 继承于AbstractUVCCameraHandler + * parent Activity子类 + * cameraView 用于捕获静止图像 + * encoderType 0表示使用MediaSurfaceEncoder;1表示使用MediaVideoEncoder, 2表示使用MediaVideoBufferEncoder + * width 分辨率的宽 + * height 分辨率的高 + * format 颜色格式,0为FRAME_FORMAT_YUYV;1为FRAME_FORMAT_MJPEG + * bandwidthFactor + */ + CameraThread(final Class clazz, + final Activity parent, final CameraViewInterface cameraView, + final int encoderType, final int width, final int height, final int format, + final float bandwidthFactor) { + + super("CameraThread"); + mHandlerClass = clazz; + mEncoderType = encoderType; + mWidth = width; + mHeight = height; + mPreviewMode = format; + mBandwidthFactor = bandwidthFactor; + mWeakParent = new WeakReference<>(parent); + mWeakCameraView = new WeakReference<>(cameraView); +// loadShutterSound(parent); + } + + @Override + protected void finalize() throws Throwable { + Log.i(TAG, "CameraThread#finalize"); + super.finalize(); + } + + public AbstractUVCCameraHandler getHandler() { + if (DEBUG) Log.v(TAG_THREAD, "getHandler:"); + synchronized (mSync) { + if (mHandler == null) + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + return mHandler; + } + + public int getWidth() { + synchronized (mSync) { + return mWidth; + } + } + + public int getHeight() { + synchronized (mSync) { + return mHeight; + } + } + + public boolean isCameraOpened() { + synchronized (mSync) { + return mUVCCamera != null; + } + } + + public boolean isPreviewing() { + synchronized (mSync) { + return mUVCCamera != null && mIsPreviewing; + } + } + + public boolean isRecording() { + synchronized (mSync) { + return (mUVCCamera != null) && (mH264Consumer != null); + } + } + +// public boolean isAudioRecording(){ +// synchronized (mSync){ +// return isAudioThreadStart; +// } +// } + + public boolean isEqual(final UsbDevice device) { + return (mUVCCamera != null) && (mUVCCamera.getDevice() != null) && mUVCCamera.getDevice().equals(device); + } + + public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) { + if (DEBUG) Log.v(TAG_THREAD, "handleOpen:"); + handleClose(); + try { + final UVCCamera camera = new UVCCamera(); + camera.open(ctrlBlock); + synchronized (mSync) { + mUVCCamera = camera; + } + callOnOpen(); + } catch (final Exception e) { + callOnError(e); + } + if (DEBUG) + Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null)); + } + + public void handleClose() { + if (DEBUG) Log.v(TAG_THREAD, "handleClose:"); + handleStopPusher(); + final UVCCamera camera; + synchronized (mSync) { + camera = mUVCCamera; + mUVCCamera = null; + } + if (camera != null) { + camera.stopPreview(); + camera.destroy(); + callOnClose(); + } + } + + public void handleStartPreview(final Object surface) { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || mIsPreviewing) return; + try { + mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor); + // 获取USB Camera预览数据,使用NV21颜色会失真 + // 无论使用YUV还是MPEG,setFrameCallback的设置效果一致 +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); + mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP); + } catch (final IllegalArgumentException e) { + try { + // fallback to YUV mode + mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, UVCCamera.DEFAULT_PREVIEW_MODE, mBandwidthFactor); + } catch (final IllegalArgumentException e1) { + callOnError(e1); + return; + } + } + if (surface instanceof SurfaceHolder) { + mUVCCamera.setPreviewDisplay((SurfaceHolder) surface); + } + if (surface instanceof Surface) { + mUVCCamera.setPreviewDisplay((Surface) surface); + } else { + mUVCCamera.setPreviewTexture((SurfaceTexture) surface); + } + mUVCCamera.startPreview(); + mUVCCamera.updateCameraParams(); + synchronized (mSync) { + mIsPreviewing = true; + } + callOnStartPreview(); + } + + public void handleStopPreview() { + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:"); + if (mIsPreviewing) { + if (mUVCCamera != null) { + mUVCCamera.stopPreview(); + mUVCCamera.setFrameCallback(null, 0); + } + synchronized (mSync) { + mIsPreviewing = false; + mSync.notifyAll(); + } + callOnStopPreview(); + } + if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished"); + } + + // 捕获静态图片 + public void handleCaptureStill(final String path) { + if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:"); + final Activity parent = mWeakParent.get(); + if (parent == null) return; +// mSoundPool.play(mSoundId, 0.2f, 0.2f, 0, 0, 1.0f); // play shutter sound + try { + final Bitmap bitmap = mWeakCameraView.get().captureStillImage(mWidth, mHeight); + // get buffered output stream for saving a captured still image as a file on external storage. + // the file name is came from current time. + // You should use extension name as same as CompressFormat when calling Bitmap#compress. + final File outputFile = TextUtils.isEmpty(path) + ? MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".jpg") + : new File(path); + final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile)); + try { + try { + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath())); + } catch (final IOException e) { + } + } finally { + os.close(); + } + if (mCaptureListener != null) { + mCaptureListener.onCaptureResult(path); + } + } catch (final Exception e) { + callOnError(e); + } + } + + // 开始录制视频 +// public void handleStartRecording2(String path) { +// if (DEBUG) Log.v(TAG_THREAD, "handleStartRecording:"); +// try { +// if ((mUVCCamera == null) || (mMuxer != null)) return; +//// final MediaMuxerWrapper muxer = new MediaMuxerWrapper(".mp4"); // if you record audio only, ".m4a" is also OK. +// final MediaMuxerWrapper muxer = new MediaMuxerWrapper(path); +// MediaVideoBufferEncoder videoEncoder = null; +// switch (mEncoderType) { +// case 1: // for video capturing using MediaVideoEncoder +// // 开启视频编码线程 +// new MediaVideoEncoder(muxer,getWidth(), getHeight(), mMediaEncoderListener); +// break; +// case 2: // for video capturing using MediaVideoBufferEncoder +// videoEncoder = new MediaVideoBufferEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); +// break; +// // case 0: // for video capturing using MediaSurfaceEncoder +// default: +// new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); +// break; +// } +// // 开启音频编码线程 +// if (true) { +// // for audio capturing +//// new MediaAudioEncoder(muxer, mMediaEncoderListener); +// } +// muxer.prepare(); +// muxer.startRecording(); +// if (videoEncoder != null) { +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); +// } +// synchronized (mSync) { +// mMuxer = muxer; +// mVideoEncoder = videoEncoder; +// } +// callOnStartRecording(); +// } catch (final IOException e) { +// callOnError(e); +// Log.e(TAG, "startCapture:", e); +// } +// } + + private AACEncodeConsumer mAacConsumer; + private H264EncodeConsumer mH264Consumer; + + public void handleStartPusher(RecordParams params) { + if ((mUVCCamera == null) || (mH264Consumer != null)) + return; +// // 获取USB Camera预览数据 +// mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); + + // 初始化混合器 + if (params != null) { + isSupportOverlay = params.isSupportOverlay(); + if(isSupportOverlay) { + // init overlay engine + TxtOverlay.getInstance().init(mWidth, mHeight); + } + videoPath = params.getRecordPath(); + File file = new File(videoPath); + if(! Objects.requireNonNull(file.getParentFile()).exists()) { + file.getParentFile().mkdirs(); + } + mMuxer = new Mp4MediaMuxer(params.getRecordPath(), + params.getRecordDuration() * 60 * 1000, params.isVoiceClose()); + } + // 启动视频编码线程 + startVideoRecord(); + // 启动音频编码线程 + if (params != null && !params.isVoiceClose()) { + startAudioRecord(); + } + callOnStartRecording(); + } + + + public void handleStopPusher() { + // 停止混合器 + if (mMuxer != null) { + mMuxer.release(); + mMuxer = null; + Log.i(TAG, TAG + "---->停止本地录制"); + } + // 停止音视频编码线程 + stopAudioRecord(); + stopVideoRecord(); + if(isSupportOverlay) + TxtOverlay.getInstance().release(); +// // 停止捕获视频数据 +// if (mUVCCamera != null) { +// mUVCCamera.stopCapture(); +// } + mWeakCameraView.get().setVideoEncoder(null); + // you should not wait here + callOnStopRecording(); + // 返回路径 + if (mListener != null) { + mListener.onRecordResult(videoPath + ".mp4"); + } + } + + private void startVideoRecord() { + mH264Consumer = new H264EncodeConsumer(getWidth(), getHeight()); + mH264Consumer.setOnH264EncodeResultListener(new H264EncodeConsumer.OnH264EncodeResultListener() { + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, 1); + } + } + }); + mH264Consumer.start(); + // 添加混合器 + if (mMuxer != null) { + if (mH264Consumer != null) { + mH264Consumer.setTmpuMuxer(mMuxer); + } + } + } + + private void stopVideoRecord() { + if (mH264Consumer != null) { + mH264Consumer.exit(); + mH264Consumer.setTmpuMuxer(null); + try { + Thread t2 = mH264Consumer; + mH264Consumer = null; + if (t2 != null) { + t2.interrupt(); + t2.join(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void startAudioRecord() { + mAacConsumer = new AACEncodeConsumer(); + mAacConsumer.setOnAACEncodeResultListener(new AACEncodeConsumer.OnAACEncodeResultListener() { + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, 0); + } + } + }); + mAacConsumer.start(); + // 添加混合器 + if (mMuxer != null) { + if (mAacConsumer != null) { + mAacConsumer.setTmpuMuxer(mMuxer); + } + } + } + + private void stopAudioRecord() { + if (mAacConsumer != null) { + mAacConsumer.exit(); + mAacConsumer.setTmpuMuxer(null); + try { + Thread t1 = mAacConsumer; + mAacConsumer = null; + if (t1 != null) { + t1.interrupt(); + t1.join(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +// isAudioThreadStart = false; + } + + private String picPath = null; + + public void handleStillPicture(String picPath) { + this.picPath = picPath; + } + + private final IFrameCallback mIFrameCallback = new IFrameCallback() { + @Override + public void onFrame(final ByteBuffer frame) { +// final MediaVideoBufferEncoder videoEncoder; +// synchronized (mSync) { +// videoEncoder = mVideoEncoder; +// } +// if (videoEncoder != null) { +// videoEncoder.frameAvailableSoon(); +// videoEncoder.encode(frame); +// } + int len = frame.capacity(); + final byte[] yuv = new byte[len]; + frame.get(yuv); + // nv21 yuv data callback + if (mPreviewListener != null) { + mPreviewListener.onPreviewResult(yuv); + } + // picture + if (isCaptureStill && !TextUtils.isEmpty(picPath)) { + isCaptureStill = false; + new Thread(new Runnable() { + @Override + public void run() { + saveYuv2Jpeg(picPath, yuv); + } + }).start(); + } + // video + if (mH264Consumer != null) { + // overlay + if(isSupportOverlay) { + TxtOverlay.getInstance().overlay(yuv, new SimpleDateFormat("yyyy-MM-dd EEEE HH:mm:ss").format(new Date())); + } + + mH264Consumer.setRawYuv(yuv, mWidth, mHeight); + } + } + }; + + private void saveYuv2Jpeg(String path, byte[] data) { + YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, mWidth, mHeight, null); + ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length); + boolean result = yuvImage.compressToJpeg(new Rect(0, 0, mWidth, mHeight), 100, bos); + if (result) { + + byte[] buffer = bos.toByteArray(); + File file = new File(path); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + // fixing bm is null bug instead of using BitmapFactory.decodeByteArray + fos.write(buffer); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + if (mCaptureListener != null) { + mCaptureListener.onCaptureResult(path); + } + } + try { + bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public void handleUpdateMedia(final String path) { + if (DEBUG) Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); + final Activity parent = mWeakParent.get(); + final boolean released = (mHandler == null) || mHandler.mReleased; + if (parent != null && parent.getApplicationContext() != null) { + try { + if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile"); + MediaScannerConnection.scanFile(parent.getApplicationContext(), new String[]{path}, null, null); + } catch (final Exception e) { + Log.e(TAG, "handleUpdateMedia:", e); + } + if (released || parent.isDestroyed()) + handleRelease(); + } else { + Log.w(TAG, "MainActivity already destroyed"); + // give up to add this movie to MediaStore now. + // Seeing this movie on Gallery app etc. will take a lot of time. + handleRelease(); + } + } + + public void handleRelease() { + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); + handleClose(); + mCallbacks.clear(); + if (!mIsRecording) { + mHandler.mReleased = true; + Looper.myLooper().quit(); + } + if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished"); + } + + // 自动对焦 + public void handleCameraFoucs() { + if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); + if ((mUVCCamera == null) || !mIsPreviewing) + return; + mUVCCamera.setAutoFocus(true); + } + + // 获取支持的分辨率 + public List getSupportedSizes() { + if ((mUVCCamera == null) || !mIsPreviewing) + return null; + return mUVCCamera.getSupportedSizeList(); + } + + private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() { + @Override + public void onPrepared(final MediaEncoder encoder) { + if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder); + mIsRecording = true; + if (encoder instanceof MediaVideoEncoder) + try { + mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder); + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + if (encoder instanceof MediaSurfaceEncoder) + try { + mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder); + mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface()); + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @Override + public void onStopped(final MediaEncoder encoder) { + if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); + if ((encoder instanceof MediaVideoEncoder) + || (encoder instanceof MediaSurfaceEncoder)) + try { + mIsRecording = false; + final Activity parent = mWeakParent.get(); + mWeakCameraView.get().setVideoEncoder(null); + synchronized (mSync) { + if (mUVCCamera != null) { + mUVCCamera.stopCapture(); + } + } + final String path = encoder.getOutputPath(); + if (!TextUtils.isEmpty(path)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_MEDIA_UPDATE, path), 1000); + } else { + final boolean released = (mHandler == null) || mHandler.mReleased; + if (released || parent == null || parent.isDestroyed()) { + handleRelease(); + } + } + } catch (final Exception e) { + Log.e(TAG, "onPrepared:", e); + } + } + + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) { + if (mListener != null) { + mListener.onEncodeResult(data, offset, length, timestamp, type); + } + } + + }; + +// private void loadShutterSound(final Context context) { +// // get system stream type using reflection +// int streamType; +// try { +// final Class audioSystemClass = Class.forName("android.media.AudioSystem"); +// final Field sseField = audioSystemClass.getDeclaredField("STREAM_SYSTEM_ENFORCED"); +// streamType = sseField.getInt(null); +// } catch (final Exception e) { +// streamType = AudioManager.STREAM_SYSTEM; // set appropriate according to your app policy +// } +// if (mSoundPool != null) { +// try { +// mSoundPool.release(); +// } catch (final Exception e) { +// } +// mSoundPool = null; +// } +// // load shutter sound from resource +// mSoundPool = new SoundPool(2, streamType, 0); +// mSoundId = mSoundPool.load(context, R.raw.camera_click, 1); +// } + + /** + * prepare and load shutter sound for still image capturing + */ + @SuppressWarnings("deprecation") +// private void loadShutterSound(final Context context) { +// // get system stream type using reflection +// int streamType; +// try { +// final Class audioSystemClass = Class.forName("android.media.AudioSystem"); +// final Field sseField = audioSystemClass.getDeclaredField("STREAM_SYSTEM_ENFORCED"); +// streamType = sseField.getInt(null); +// } catch (final Exception e) { +// streamType = AudioManager.STREAM_SYSTEM; // set appropriate according to your app policy +// } +// if (mSoundPool != null) { +// try { +// mSoundPool.release(); +// } catch (final Exception e) { +// } +// mSoundPool = null; +// } +// // load shutter sound from resource +// mSoundPool = new SoundPool(2, streamType, 0); +// mSoundId = mSoundPool.load(context, R.raw.camera_click, 1); +// } + + @Override + public void run() { + Looper.prepare(); + AbstractUVCCameraHandler handler = null; + try { + final Constructor constructor = mHandlerClass.getDeclaredConstructor(CameraThread.class); + handler = constructor.newInstance(this); + } catch (final NoSuchMethodException e) { + Log.w(TAG, e); + } catch (final IllegalAccessException e) { + Log.w(TAG, e); + } catch (final InstantiationException e) { + Log.w(TAG, e); + } catch (final InvocationTargetException e) { + Log.w(TAG, e); + } + if (handler != null) { + synchronized (mSync) { + mHandler = handler; + mSync.notifyAll(); + } + Looper.loop(); +// if (mSoundPool != null) { +// mSoundPool.release(); +// mSoundPool = null; +// } + if (mHandler != null) { + mHandler.mReleased = true; + } + } + mCallbacks.clear(); + synchronized (mSync) { + mHandler = null; + mSync.notifyAll(); + } + } + + private void callOnOpen() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onOpen(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnClose() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onClose(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStartPreview() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStartPreview(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStopPreview() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStopPreview(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStartRecording() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStartRecording(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnStopRecording() { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onStopRecording(); + } catch (final Exception e) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + + private void callOnError(final Exception e) { + for (final CameraCallback callback : mCallbacks) { + try { + callback.onError(e); + } catch (final Exception e1) { + mCallbacks.remove(callback); + Log.w(TAG, e); + } + } + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/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'