formats;
32 | private BroadcastReceiver deviceDisconnectedReceiver;
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 |
39 | if (savedInstanceState != null) {
40 | openDevice = savedInstanceState.getParcelable(KEY_OPEN_DEVICE);
41 | }
42 |
43 | deviceDisconnectedReceiver = new BroadcastReceiver() {
44 | @Override
45 | public void onReceive(Context context, Intent intent) {
46 | if (webcam == null) {
47 | return;
48 | }
49 |
50 | final UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
51 | if (usbDevice.equals(webcam.getDevice())) {
52 | Timber.d("Active Webcam detached. Terminating connection.");
53 | stopStreaming();
54 | }
55 | }
56 | };
57 | }
58 |
59 | @Override
60 | protected void onNewIntent(Intent intent) {
61 | super.onNewIntent(intent);
62 |
63 | if (intent.hasExtra(UsbManager.EXTRA_DEVICE)) {
64 | openDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
65 | handleDevice();
66 | }
67 |
68 | // Replace intent returned by getIntent()
69 | setIntent(intent);
70 | }
71 |
72 | @Override
73 | protected void onResume() {
74 | super.onResume();
75 |
76 | registerReceiver(deviceDisconnectedReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
77 |
78 | EventBus.getDefault().register(this);
79 | }
80 |
81 | @Override
82 | protected void onStart() {
83 | super.onStart();
84 | }
85 |
86 | @Subscribe(threadMode = ThreadMode.MAIN)
87 | public void onNewVideoFormatSelected(VideoFormatSelected selected) {
88 | for (VideoFormat format : formats) {
89 | if (format.getFormatIndex() == selected.index) {
90 | try {
91 | webcam.beginStreaming(this, format);
92 | } catch (StreamCreationException e) {
93 | e.printStackTrace();
94 | }
95 | }
96 | }
97 | }
98 |
99 | private void handleDevice() {
100 | // Get the connected webcam if one is newly attached or already connected
101 |
102 | if (openDevice != null) {
103 | try {
104 | webcam = WebcamManager.getOrCreateWebcam(this, openDevice);
105 | formats = webcam.getAvailableFormats();
106 | showFormatPicker();
107 | } catch (UnknownDeviceException | DevicePermissionDenied e) {
108 | e.printStackTrace();
109 | Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
110 | }
111 | }
112 | }
113 |
114 | private void showFormatPicker() {
115 | FormatPickerDialog dialog = new FormatPickerDialog();
116 | final Bundle args = new Bundle();
117 | final String[] names = new String[formats.size()];
118 | final int[] indices = new int[formats.size()];
119 | for (int i = 0; i < formats.size(); ++i) {
120 | names[i] = formats.get(i).getClass().getSimpleName();
121 | indices[i] = formats.get(i).getFormatIndex();
122 | }
123 | args.putStringArray(FormatPickerDialog.ARGUMENT_VALUES, names);
124 | args.putIntArray(FormatPickerDialog.ARGUMENT_INDICES, indices);
125 | dialog.setArguments(args);
126 | dialog.show(getSupportFragmentManager(), FormatPickerDialog.TAG);
127 | }
128 |
129 | @Override
130 | protected void onPause() {
131 | super.onPause();
132 |
133 | unregisterReceiver(deviceDisconnectedReceiver);
134 |
135 | EventBus.getDefault().unregister(this);
136 | }
137 |
138 | /**
139 | * Shutdown the active webcam device if one exists.
140 | */
141 | private void stopStreaming() {
142 | if (webcam != null) {
143 | webcam.terminateStreaming(this);
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jwoolston/usb/webcam/app/UsbApplication.kt:
--------------------------------------------------------------------------------
1 | package com.jwoolston.usb.webcam.app
2 |
3 | import android.app.Application
4 | import timber.log.Timber
5 |
6 | class UsbApplication : Application() {
7 |
8 | override fun onCreate() {
9 | super.onCreate()
10 | if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jwoolston/usb/webcam/app/VideoFormatSelected.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.usb.webcam.app;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | */
6 | public class VideoFormatSelected {
7 |
8 | public final int index;
9 |
10 | public VideoFormatSelected(int index) {
11 | this.index = index;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_exposure_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_format_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Android Webcam
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.2.50'
3 | ext.support_lib_version = '27.1.1'
4 | ext.dokka_version = '0.9.16'
5 | ext.timber_version='4.7.0'
6 |
7 | repositories {
8 | mavenCentral()
9 | jcenter()
10 | maven { url "https://plugins.gradle.org/m2/" }
11 | google()
12 | }
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:3.1.3'
15 | classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$dokka_version"
16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
17 | }
18 | }
19 |
20 | repositories {
21 | google()
22 | }
23 |
24 | allprojects {
25 | String tagName = System.getenv('CIRCLE_TAG')
26 | boolean isTag = tagName != null && !tagName.isEmpty()
27 | String buildNumber = System.getenv('CIRCLE_BUILD_NUM') ?: "0"
28 |
29 | group 'com.jwoolston.android'
30 | version "$VERSION_NAME.${buildNumber}" + (isTag ? "" : "-SNAPSHOT")
31 |
32 | repositories {
33 | mavenLocal()
34 | mavenCentral()
35 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
36 | jcenter()
37 | google()
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | TARGET_VERSION=27
20 | COMPILE_VERSION=27
21 | BUILD_TOOLS=27.0.3
22 | VERSION_NAME=1.0
23 | VERSION_CODE=1
24 |
25 | POM_NAME=AndroidWebcam
26 | POM_PACKAGING=aar
27 | POM_DESCRIPTION=Android library for accessing USB UVC class devices.
28 | POM_URL=https://github.com/jwoolston/Android-Webcam
29 | POM_SCM_URL=https://github.com/jwoolston/Android-Webcam.git
30 | POM_SCM_CONNECTION=scm:git@github.com:jwoolston/Android-Webcam.git
31 | POM_SCM_DEV_CONNECTION=scm:git@github.com:jwoolston/Android-Webcam.git
32 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
33 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
34 | POM_LICENCE_DIST=repo
35 | POM_DEVELOPER_ID=jwoolston
36 | POM_DEVELOPER_NAME=Jared Woolston
37 | POM_DEVELOPER_EMAIL=Jared.Woolston@gmail.com
38 | POM_DEVELOPER_ORGANIZATION=jwoolston
39 | POM_DEVELOPER_ORGANIZATION_URL=https://github.com/jwoolston
40 |
41 | # Trailing new line required for CI
42 |
43 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 | apply plugin: 'org.jetbrains.dokka-android'
5 |
6 | android {
7 | compileSdkVersion Integer.parseInt(COMPILE_VERSION)
8 | buildToolsVersion BUILD_TOOLS
9 |
10 | lintOptions {
11 | disable 'InvalidPackage'
12 | abortOnError false
13 | }
14 |
15 | defaultConfig {
16 | versionName version
17 |
18 | minSdkVersion 16
19 | targetSdkVersion Integer.parseInt(TARGET_VERSION)
20 | resourcePrefix 'uvc_'
21 |
22 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
29 | }
30 | }
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_7
34 | targetCompatibility JavaVersion.VERSION_1_7
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(dir: 'libs', include: ['*.jar'])
40 |
41 | implementation "com.android.support:support-annotations:$support_lib_version"
42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
43 | implementation "com.jakewharton.timber:timber:$timber_version"
44 |
45 | //api 'com.jwoolston.android:libusb:1.0.0-SNAPSHOT'
46 | api 'com.jwoolston.android:libusb:1.0.73-SNAPSHOT'
47 |
48 | testImplementation 'junit:junit:4.12'
49 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
51 | }
52 |
53 | dokka {
54 | noStdlibLink = false
55 | includeNonPublic = false
56 | skipEmptyPackages = true
57 | outputFormat = 'html'
58 | outputDirectory = "$buildDir/javadoc"
59 | }
60 |
61 | afterEvaluate { project ->
62 | task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaAndroidTask) {
63 | outputFormat = 'javadoc'
64 | outputDirectory = "$buildDir/javadoc"
65 | inputs.dir 'src/main/java'
66 | }
67 |
68 | task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
69 | classifier = 'javadoc'
70 | from "$buildDir/javadoc"
71 | }
72 |
73 | task sourcesJar(type: Jar) {
74 | classifier = 'sources'
75 | from android.sourceSets.main.java.srcDirs
76 | }
77 |
78 | artifacts {
79 | archives sourcesJar
80 | archives javadocJar
81 | }
82 | }
83 |
84 | apply from: "${rootDir}/publish.gradle"
85 |
86 |
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2015 Jared Woolston
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | POM_ARTIFACT_ID=uvc
17 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/StreamCreationException.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | */
6 | public final class StreamCreationException extends Exception {
7 |
8 | public StreamCreationException() {
9 | super("Failed to create the requested stream.");
10 | }
11 |
12 | public StreamCreationException(String message) {
13 | super(message);
14 | }
15 |
16 | public StreamCreationException(Throwable throwable) {
17 | super("Failed to create the requested stream.", throwable);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/StreamManager.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import com.jwoolston.android.libusb.LibusbError;
6 | import com.jwoolston.android.libusb.UsbDeviceConnection;
7 | import com.jwoolston.android.libusb.async.IsochronousAsyncTransfer;
8 | import com.jwoolston.android.libusb.async.IsochronousTransferCallback;
9 | import com.jwoolston.android.uvc.interfaces.VideoControlInterface;
10 | import com.jwoolston.android.uvc.interfaces.VideoStreamingInterface;
11 | import com.jwoolston.android.uvc.interfaces.endpoints.Endpoint;
12 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat;
13 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFrame;
14 | import com.jwoolston.android.uvc.requests.control.RequestErrorCode;
15 | import com.jwoolston.android.uvc.requests.streaming.FramingInfo;
16 | import com.jwoolston.android.uvc.requests.streaming.ProbeControl;
17 | import com.jwoolston.android.uvc.util.Hexdump;
18 | import java.io.IOException;
19 | import java.nio.ByteBuffer;
20 | import timber.log.Timber;
21 |
22 | /**
23 | * Probe and Commit Operational Model
24 | *
25 | * Unsupported fields shall be set to zero by the device. Fields left for streaming parameters negotiation shall be set
26 | * to zero by the host. For example, after a SET_CUR request initializing the FormatIndex and FrameIndex, the device
27 | * will return the new negotiated field values for the supported fields when retrieving the Probe control GET_CUR
28 | * attribute. In order to avoid negotiation loops, the device shall always return streaming parameters with decreasing
29 | * data rate requirements. Unsupported streaming parameters shall be reset by the streaming interface to supported
30 | * values according to the negotiation loop avoidance rules. This convention allows the host to cycle through supported
31 | * values of a field.
32 | *
33 | * During Probe and Commit, the following fields, if supported, shall be negotiated in order of decreasing priority:
34 | *
35 | * - bFormatIndex
36 | * - bFrameIndex
37 | * - dwMaxPayloadTransferSize
38 | * - bUsage
39 | * - bmLayoutPerStream
40 | * - Fields set to zero by the host with their associated bmHint bit set to 1
41 | * - All the remaining fields set to zero by the host
42 | *
43 | * For simplicity when streaming temporally encoded video, the required bandwidth for each streaming interface shall be
44 | * estimated using the maximum bit rate for the selected profile/resolution and the number of simulcast streams. The USB
45 | * bandwidth reserved shall be the calculated by the host as the advertised dwMaxBitRate from the selected Frame
46 | * Descriptor multiplied times the number of simulcast streams as defined in the bmLayoutPerStream field. The interface
47 | * descriptor for the video function should have multiple alternate settings that support the required bandwidths
48 | * calculated in the manner above.
49 | *
50 | * @author Jared Woolston (Jared.Woolston@gmail.com)
51 | * @see UVC 1.5 Class
52 | * Specification §4.3.1.1.1
53 | */
54 | public class StreamManager implements IsochronousTransferCallback {
55 |
56 | private final UsbDeviceConnection connection;
57 | private final VideoControlInterface controlInterface;
58 | private final VideoStreamingInterface streamingInterface;
59 |
60 | public StreamManager(@NonNull UsbDeviceConnection connection, @NonNull VideoControlInterface controlInterface,
61 | @NonNull VideoStreamingInterface streamingInterface) {
62 | this.connection = connection;
63 | this.controlInterface = controlInterface;
64 | this.streamingInterface = streamingInterface;
65 | }
66 |
67 | public void establishStreaming(@Nullable VideoFormat format, @Nullable VideoFrame frame) throws
68 | StreamCreationException {
69 | final ProbeControl request = ProbeControl.setCurrentProbe(streamingInterface);
70 | final VideoFormat requestedFormat = format != null ? format : streamingInterface.getAvailableFormats().get(0);
71 | final VideoFrame requestedFrame = frame != null ? frame : requestedFormat.getDefaultFrame();
72 |
73 | Timber.v("Using video format: %s", format);
74 | Timber.v("Using video frame: %s", frame);
75 | request.setFormatIndex(requestedFormat.getFormatIndex());
76 | request.setFrameIndex(requestedFrame.getFrameIndex());
77 | request.setFrameInterval(requestedFrame.getDefaultFrameInterval());
78 | FramingInfo info = new FramingInfo();
79 | info.setFrameIdRequired(true);
80 | info.setEndOfFrameAllowed(true);
81 | request.setFramingInfo(info);
82 |
83 | int retval = connection.controlTransfer(request.getRequestType(), request.getRequest(), request.getValue(),
84 | request.getIndex(), request.getData(), request.getLength(), 500);
85 |
86 | if (retval < 0) {
87 | throw new StreamCreationException("Probe set request failed: " + LibusbError.fromNative(retval));
88 | }
89 |
90 | final ProbeControl current = ProbeControl.getCurrentProbe(streamingInterface);
91 | retval = connection.controlTransfer(current.getRequestType(), current.getRequest(), current.getValue(),
92 | current.getIndex(), current.getData(), current.getLength(), 500);
93 | if (retval < 0) {
94 | throw new StreamCreationException("Probe get request failed: " + LibusbError.fromNative(retval));
95 | }
96 |
97 | int maxPayload = current.getMaxPayloadTransferSize();
98 | int maxFrameSize = current.getMaxVideoFrameSize();
99 |
100 | final ProbeControl commit = current.getCommit();
101 |
102 | retval = connection.controlTransfer(commit.getRequestType(), commit.getRequest(),
103 | commit.getValue(), commit.getIndex(), commit.getData(), commit.getLength(),
104 | 500);
105 | if (retval < 0) {
106 | throw new StreamCreationException("Commit request failed: " + LibusbError.fromNative(retval));
107 | }
108 |
109 | final RequestErrorCode requestErrorCode = RequestErrorCode.getCurrentErrorCode(controlInterface);
110 | retval = connection.controlTransfer(requestErrorCode.getRequestType(), requestErrorCode.getRequest(),
111 | requestErrorCode.getValue(), requestErrorCode.getIndex(),
112 | requestErrorCode.getData(), requestErrorCode.getLength(), 500);
113 | if (retval < 0 || requestErrorCode.getData()[0] != 0) {
114 | throw new StreamCreationException("Error state failed: " + (retval < 0 ? LibusbError.fromNative(retval)
115 | : "Current error code: 0x" + Hexdump.toHexString(requestErrorCode.getData()[0])));
116 | }
117 |
118 | Timber.d("Current error code: 0x%s", Hexdump.toHexString(requestErrorCode.getData()[0]));
119 |
120 | initiateStream(maxPayload, maxFrameSize);
121 | }
122 |
123 | public void initiateStream(int maxPayload, int maxFrameSize) {
124 | streamingInterface.selectAlternateSetting(connection, 6);
125 | ByteBuffer buffer = ByteBuffer.allocateDirect(maxPayload);
126 | Endpoint endpoint = streamingInterface.getCurrentEndpoints()[0];
127 | try {
128 | IsochronousAsyncTransfer transfer = new IsochronousAsyncTransfer(this, endpoint.getEndpoint(),
129 | connection, 20);
130 | transfer.submit(buffer, 500);
131 | } catch (Exception e) {
132 | e.printStackTrace();
133 | }
134 | }
135 |
136 | @Override
137 | public void onIsochronousTransferComplete(@Nullable ByteBuffer data, int result) throws IOException {
138 | if (result < 0) {
139 | throw new IOException("Failure in isochronous callback:" + LibusbError.fromNative(result));
140 | } else {
141 | final byte[] raw = new byte[data.limit()];
142 | data.rewind();
143 | data.get(raw);
144 | Timber.d(" \n%s", Hexdump.dumpHexString(raw));
145 | Endpoint endpoint = streamingInterface.getCurrentEndpoints()[0];
146 | data.rewind();
147 | IsochronousAsyncTransfer transfer = new IsochronousAsyncTransfer(this, endpoint.getEndpoint(),
148 | connection, 20);
149 | transfer.submit(data, 500);
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/UnknownDeviceException.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | */
6 | public final class UnknownDeviceException extends Exception {
7 |
8 | public UnknownDeviceException() {
9 | super("Connected device is not a generic webcam.");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/Webcam.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | import android.content.Context;
4 | import android.hardware.usb.UsbDevice;
5 | import android.net.Uri;
6 | import android.support.annotation.NonNull;
7 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat;
8 | import java.util.List;
9 |
10 | /**
11 | * @author Jared Woolston (Jared.Woolston@gmail.com)
12 | */
13 | public interface Webcam {
14 |
15 | /**
16 | * The {@link UsbDevice} interface to this camera.
17 | *
18 | * @return camera {@link UsbDevice}
19 | */
20 | @NonNull
21 | UsbDevice getDevice();
22 |
23 | /**
24 | * Determine if an active connection to the camera exists.
25 | *
26 | * @return true if connected to the camera
27 | */
28 | boolean isConnected();
29 |
30 | /**
31 | * Begin streaming from the device and retrieve the {@link Uri} for the data stream for this {@link Webcam}.
32 | *
33 | * @param context {@link Context} The application context.
34 | * @param format The {@link VideoFormat} to stream in.
35 | *
36 | * @return {@link Uri} The data source {@link Uri}.
37 | *
38 | * @throws StreamCreationException Thrown if there is a problem establishing the stream buffer.
39 | */
40 | @NonNull
41 | Uri beginStreaming(@NonNull Context context, @NonNull VideoFormat format) throws StreamCreationException;
42 |
43 | /**
44 | * Terminates streaming from the device.
45 | *
46 | * @param context {@link Context} The application context.
47 | */
48 | void terminateStreaming(@NonNull Context context);
49 |
50 | /**
51 | * Retrieves the list of available {@link VideoFormat}s.
52 | *
53 | * @return The available {@link VideoFormat}s on the device.
54 | */
55 | List getAvailableFormats();
56 | }
57 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/WebcamConnection.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.support.annotation.NonNull;
6 | import com.jwoolston.android.libusb.DevicePermissionDenied;
7 | import com.jwoolston.android.libusb.UsbDeviceConnection;
8 | import com.jwoolston.android.libusb.UsbManager;
9 | import com.jwoolston.android.uvc.interfaces.Descriptor;
10 | import com.jwoolston.android.uvc.interfaces.InterfaceAssociationDescriptor;
11 | import com.jwoolston.android.uvc.interfaces.VideoControlInterface;
12 | import com.jwoolston.android.uvc.interfaces.VideoStreamingInterface;
13 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat;
14 | import java.util.List;
15 | import timber.log.Timber;
16 |
17 | /**
18 | * Helper class for abstracting communication to a camera. This implementation directly handles configuration, state,
19 | * and data transfer. The USB layer is constructed at instantiation and if possible, communication begins immediately.
20 | *
21 | * @author Jared Woolston (Jared.Woolston@gmail.com)
22 | */
23 | class WebcamConnection {
24 |
25 | private static final int INTERFACE_CONTROL = 0;
26 |
27 | final UsbDeviceConnection usbDeviceConnection;
28 | final UsbManager usbManager;
29 |
30 |
31 | private List iads;
32 |
33 | private InterfaceAssociationDescriptor activeIAD;
34 |
35 | private VideoControlInterface controlInterface;
36 | private VideoStreamingInterface streamingInterface;
37 | private StreamManager streamManager;
38 |
39 | WebcamConnection(@NonNull Context context, @NonNull android.hardware.usb.UsbDevice usbDevice)
40 | throws UnknownDeviceException, DevicePermissionDenied {
41 | this.usbManager = new UsbManager(context);
42 |
43 | // A Webcam must have at least a control interface and a video interface
44 | if (usbDevice.getInterfaceCount() < 2) {
45 | throw new UnknownDeviceException();
46 | }
47 |
48 | // Claim the control interface
49 | Timber.d("Initializing native layer.");
50 | usbDeviceConnection = usbManager.registerDevice(usbDevice);
51 | parseAssiociationDescriptors();
52 | }
53 |
54 | private void parseAssiociationDescriptors() {
55 | Timber.d("Parsing raw association descriptors.");
56 | final byte[] raw = usbDeviceConnection.getRawDescriptors();
57 | iads = Descriptor.parseDescriptors(usbDeviceConnection, raw);
58 | Timber.i("Determined IADs: %s", iads);
59 | selectIAD(0);
60 | }
61 |
62 | void selectIAD(int index) {
63 | activeIAD = iads.get(index);
64 | controlInterface = (VideoControlInterface) activeIAD.getInterface(0);
65 | streamingInterface = (VideoStreamingInterface) activeIAD.getInterface(1);
66 | }
67 |
68 | boolean isConnected() {
69 | // FIXME
70 | return true;
71 | }
72 |
73 | /**
74 | * Begins streaming from the device.
75 | *
76 | * @param context {@link Context} The application context.
77 | * @param format The {@link VideoFormat} to stream in.
78 | *
79 | * @return {@link Uri} pointing to the buffered stream.
80 | *
81 | * @throws StreamCreationException Thrown if there is a problem establishing the stream buffer.
82 | */
83 | Uri beginConnectionStreaming(@NonNull Context context, @NonNull VideoFormat format) throws StreamCreationException {
84 | Timber.d("Establishing streaming parameters.");
85 | streamManager = new StreamManager(usbDeviceConnection, controlInterface, streamingInterface);
86 | streamManager.establishStreaming(format, format.getDefaultFrame());
87 | return null;
88 | }
89 |
90 | /**
91 | * Terminates streaming from the device.
92 | *
93 | * @param context {@link Context} The application context.
94 | */
95 | void terminateConnection(Context context) {
96 |
97 | }
98 |
99 | /**
100 | * Retrieves the list of available {@link VideoFormat}s.
101 | *
102 | * @return The available {@link VideoFormat}s on the device.
103 | */
104 | public List getAvailableFormats() {
105 | return streamingInterface.getAvailableFormats();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/WebcamImpl.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | import android.content.Context;
4 | import android.hardware.usb.UsbDevice;
5 | import android.net.Uri;
6 | import android.support.annotation.NonNull;
7 | import com.jwoolston.android.libusb.DevicePermissionDenied;
8 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat;
9 | import java.util.List;
10 |
11 | /**
12 | * @author Jared Woolston (Jared.Woolston@gmail.com)
13 | */
14 | class WebcamImpl implements Webcam {
15 |
16 | private final Context context;
17 | private final UsbDevice device;
18 | private final WebcamConnection webcamConnection;
19 |
20 | WebcamImpl(Context context, UsbDevice device) throws UnknownDeviceException, DevicePermissionDenied {
21 | this.context = context;
22 | this.device = device;
23 |
24 | webcamConnection = new WebcamConnection(context.getApplicationContext(), device);
25 | }
26 |
27 | @NonNull
28 | @Override
29 | public UsbDevice getDevice() {
30 | return device;
31 | }
32 |
33 | @Override
34 | public boolean isConnected() {
35 | return webcamConnection.isConnected();
36 | }
37 |
38 | /**
39 | * Retrieves the list of available {@link VideoFormat}s.
40 | *
41 | * @return The available {@link VideoFormat}s on the device.
42 | */
43 | public List getAvailableFormats() {
44 | return webcamConnection.getAvailableFormats();
45 | }
46 |
47 | @NonNull
48 | @Override
49 | public Uri beginStreaming(@NonNull Context context, @NonNull VideoFormat format) throws StreamCreationException {
50 | return webcamConnection.beginConnectionStreaming(context, format);
51 | }
52 |
53 | public void terminateStreaming(@NonNull Context context) {
54 | webcamConnection.terminateConnection(context);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/WebcamManager.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc;
2 |
3 | import android.content.Context;
4 | import android.hardware.usb.UsbDevice;
5 | import android.support.annotation.NonNull;
6 | import com.jwoolston.android.libusb.DevicePermissionDenied;
7 | import java.util.Collections;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * @author Jared Woolston (Jared.Woolston@gmail.com)
13 | */
14 | public class WebcamManager {
15 |
16 | private static final String BUFFER_CACHE_DIR = "/buffer_data";
17 |
18 | private static final Map CONNECTIONS =
19 | Collections.synchronizedMap(new HashMap());
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param context The application {@link Context}.
25 | */
26 | private WebcamManager(@NonNull Context context) {
27 | // We are going to need this a lot, so store a copy
28 | //TODO
29 | }
30 |
31 | /**
32 | * Get the {@link Webcam} for the {@link android.hardware.usb.UsbDevice}
33 | * or create a new instance.
34 | *
35 | * @param context application context
36 | * @param device link for {@link Webcam} instance
37 | *
38 | * @return an existing or new {@link Webcam} instance
39 | */
40 | public static
41 | @NonNull
42 | Webcam getOrCreateWebcam(@NonNull Context context, @NonNull UsbDevice device) throws UnknownDeviceException,
43 | DevicePermissionDenied {
44 | Webcam webcam = CONNECTIONS.get(device);
45 | if (webcam == null) {
46 | webcam = new WebcamImpl(context.getApplicationContext(), device);
47 | CONNECTIONS.put(device, webcam);
48 | }
49 |
50 | return webcam;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/Descriptor.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import com.jwoolston.android.libusb.UsbDeviceConnection;
4 | import com.jwoolston.android.uvc.interfaces.endpoints.Endpoint;
5 | import com.jwoolston.android.uvc.util.Hexdump;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import timber.log.Timber;
9 |
10 | /**
11 | * @author Jared Woolston (Jared.Woolston@gmail.com)
12 | */
13 | public class Descriptor {
14 |
15 | static final byte VIDEO_CLASS_CODE = ((byte) 0x0E);
16 | static final byte AUDIO_CLASS_CODE = ((byte) 0x01);
17 |
18 | private static final int INDEX_DESCRIPTOR_TYPE = 1;
19 |
20 | public static List parseDescriptors(UsbDeviceConnection connection,
21 | byte[] rawDescriptor) {
22 | int length;
23 | byte[] desc;
24 | Type type;
25 | int i = 0;
26 | State state = null;
27 | InterfaceAssociationDescriptor iad = null;
28 | ArrayList iads = new ArrayList<>();
29 | UvcInterface uvcInterface = null;
30 | Endpoint aEndpoint = null;
31 | int endpointIndex = 1;
32 | while (i < rawDescriptor.length) {
33 | length = rawDescriptor[i];
34 | desc = new byte[length];
35 | System.arraycopy(rawDescriptor, i, desc, 0, length);
36 | type = Type.getType(desc);
37 | switch (type) {
38 | case INTERFACE_ASSOCIATION:
39 | if (state == State.STANDARD_ENDPOINT) {
40 | i = rawDescriptor.length;
41 | break;
42 | }
43 | if (state != null) {
44 | throw new IllegalStateException("Tried parsing an IAD at an invalid time: " + state);
45 | }
46 | state = State.IAD;
47 | iad = InterfaceAssociationDescriptor.parseIAD(desc);
48 | iads.add(iad);
49 | break;
50 | case INTERFACE:
51 | if (state != State.IAD && state != State.CLASS_INTERFACE && state != State.STANDARD_ENDPOINT
52 | && state != State.CLASS_ENDPOINT) {
53 | throw new IllegalStateException(
54 | "Tried parsing a STANDARD INTERFACE at an invalid time: " + state);
55 | }
56 | state = State.STANDARD_INTERFACE;
57 | endpointIndex = 1;
58 | uvcInterface = UvcInterface.parseDescriptor(connection, desc);
59 | if (iad != null && uvcInterface != null) {
60 | final UvcInterface existing = iad.getInterface(uvcInterface.getInterfaceNumber());
61 | if (existing != null) {
62 | uvcInterface = existing;
63 | existing.parseAlternateFunction(connection, desc);
64 | } else {
65 | // We need to save the old one
66 | iad.addInterface(uvcInterface);
67 | }
68 | }
69 | break;
70 | case CS_INTERFACE:
71 | if (uvcInterface == null) {
72 | throw new IllegalStateException(
73 | "Tried parsing a class interface when no standard interface has been parsed.");
74 | }
75 | if (state != State.STANDARD_INTERFACE && state != State.CLASS_INTERFACE) {
76 | throw new IllegalStateException("Tried parsing a CLASS INTERFACE at an invalid time: " + state);
77 | }
78 | state = State.CLASS_INTERFACE;
79 | uvcInterface.parseClassDescriptor(desc);
80 | break;
81 | case ENDPOINT:
82 | if (uvcInterface == null) {
83 | throw new IllegalStateException(
84 | "Tried parsing a standard endpoint when no standard interface has been parsed.");
85 | }
86 | if (state != State.STANDARD_INTERFACE && state != State.CLASS_INTERFACE) {
87 | throw new IllegalStateException(
88 | "Tried parsing a STANDARD ENDPOINT at an invalid time: " + state);
89 | }
90 | state = State.STANDARD_ENDPOINT;
91 | aEndpoint = Endpoint.parseDescriptor(uvcInterface.getUsbInterface(), desc);
92 | uvcInterface.addEndpoint(endpointIndex, aEndpoint);
93 | ++endpointIndex;
94 | break;
95 | case CS_ENDPOINT:
96 | if (aEndpoint == null) {
97 | throw new IllegalStateException(
98 | "Tried parsing a class endpoint when no standard endpoint has been parsed.");
99 | }
100 | if (state != State.STANDARD_ENDPOINT) {
101 | throw new IllegalStateException(
102 | "Tried parsing a STANDARD ENDPOINT at an invalid time: " + state);
103 | }
104 | state = State.CLASS_ENDPOINT;
105 | aEndpoint.parseClassDescriptor(desc);
106 | break;
107 | case DEVICE:
108 | case DEVICE_QUALIFIER:
109 | case CONFIGURATION:
110 | break;
111 | default:
112 | Timber.d("Descriptor: %s", Hexdump.dumpHexString(desc));
113 | }
114 | i += length;
115 | }
116 | return iads;
117 | }
118 |
119 | private static enum State {
120 | IAD, STANDARD_INTERFACE, CLASS_INTERFACE, STANDARD_ENDPOINT, CLASS_ENDPOINT
121 | }
122 |
123 | public static enum VideoSubclass {
124 | SC_UNDEFINED(0x00),
125 | SC_VIDEOCONTROL(0x01),
126 | SC_VIDEOSTREAMING(0x02),
127 | SC_VIDEO_INTERFACE_COLLECTION(0x03);
128 |
129 | public final byte subclass;
130 |
131 | private VideoSubclass(int subclass) {
132 | this.subclass = (byte) (subclass & 0xFF);
133 | }
134 |
135 | public static VideoSubclass getVideoSubclass(byte subclass) {
136 | for (VideoSubclass s : VideoSubclass.values()) {
137 | if (s.subclass == subclass) {
138 | return s;
139 | }
140 | }
141 | return null;
142 | }
143 | }
144 |
145 | public static enum AudioSubclass {
146 | SC_UNDEFINED(0x00),
147 | SC_AUDIOCONTROL(0x01),
148 | SC_AUDIOSTREAMING(0x02),
149 | SC_AUDIO_INTERFACE_COLLECTION(0x03);
150 |
151 | public final byte subclass;
152 |
153 | private AudioSubclass(int subclass) {
154 | this.subclass = (byte) (subclass & 0xFF);
155 | }
156 |
157 | public static AudioSubclass getAudioSubclass(byte subclass) {
158 | for (AudioSubclass a : AudioSubclass.values()) {
159 | if (a.subclass == subclass) {
160 | return a;
161 | }
162 | }
163 | return null;
164 | }
165 | }
166 |
167 | public static enum Type {
168 | DEVICE(0x01),
169 | CONFIGURATION(0x02),
170 | STRING(0x03),
171 | INTERFACE(0x04),
172 | ENDPOINT(0x05),
173 | DEVICE_QUALIFIER(0x06),
174 | INTERFACE_ASSOCIATION(0x0B),
175 | CS_UNDEFINED(0x20),
176 | CS_DEVICE(0x21),
177 | CS_CONFIGURATION(0x22),
178 | CS_STRING(0x23),
179 | CS_INTERFACE(0x24),
180 | CS_ENDPOINT(0x25);
181 |
182 | public final byte type;
183 |
184 | private Type(int type) {
185 | this.type = (byte) (type & 0xFF);
186 | }
187 |
188 | public static Type getType(byte[] raw) {
189 | for (Type t : Type.values()) {
190 | if (t.type == raw[INDEX_DESCRIPTOR_TYPE]) {
191 | return t;
192 | }
193 | }
194 | throw new IllegalArgumentException("Unknown descriptor? " + Hexdump.dumpHexString(raw));
195 | }
196 | }
197 |
198 | public static enum Protocol {
199 | PC_PROTOCOL_UNDEFINED(0x00),
200 | PC_PROTOCOL_15(0x01);
201 |
202 | public final byte protocol;
203 |
204 | private Protocol(int protocol) {
205 | this.protocol = (byte) (protocol & 0xFF);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/InterfaceAssociationDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import com.jwoolston.android.uvc.interfaces.Descriptor.Protocol;
4 |
5 | import timber.log.Timber;
6 |
7 | /**
8 | * @author Jared Woolston (Jared.Woolston@gmail.com)
9 | */
10 | public abstract class InterfaceAssociationDescriptor {
11 |
12 | private static final int LENGTH_DESCRIPTOR = 8;
13 |
14 | protected static final int bFirstInterface = 2;
15 | protected static final int bInterfaceCount = 3;
16 | protected static final int bFunctionClass = 4;
17 | protected static final int bFunctionSubClass = 5;
18 | protected static final int bFunctionProtocol = 6;
19 | protected static final int iFunction = 7;
20 |
21 | private final int firstInterface;
22 |
23 | private final int interfaceCount;
24 |
25 | private final int indexFunction;
26 |
27 | protected static InterfaceAssociationDescriptor parseIAD(byte[] descriptor) throws IllegalArgumentException {
28 | Timber.d("Parsing Interface Association Descriptor.");
29 | if (descriptor.length < LENGTH_DESCRIPTOR) {
30 | throw new IllegalArgumentException("The provided descriptor is not long enough. Have " + descriptor.length + " need " + LENGTH_DESCRIPTOR);
31 | }
32 | if (descriptor[bFunctionClass] == Descriptor.VIDEO_CLASS_CODE) {
33 | if (descriptor[bFunctionProtocol] != Protocol.PC_PROTOCOL_UNDEFINED.protocol) {
34 | throw new IllegalArgumentException("The provided descriptor has an invalid protocol: " + descriptor[bFunctionProtocol]);
35 | }
36 | return new VideoIAD(descriptor);
37 | } else if (descriptor[bFunctionClass] == Descriptor.AUDIO_CLASS_CODE) {
38 | // TODO: Parse audio IAD
39 | return null;
40 | } else {
41 | throw new IllegalArgumentException("The provided descriptor has an invalid function class: " + descriptor[bFunctionClass]);
42 | }
43 | }
44 |
45 | protected InterfaceAssociationDescriptor(byte[] descriptor) throws IllegalArgumentException {
46 | firstInterface = descriptor[bFirstInterface];
47 | interfaceCount = descriptor[bInterfaceCount];
48 | indexFunction = descriptor[iFunction];
49 | }
50 |
51 | public int getIndexFirstInterface() {
52 | return firstInterface;
53 | }
54 |
55 | public int getInterfaceCount() {
56 | return interfaceCount;
57 | }
58 |
59 | public int getIndexFunction() {
60 | return indexFunction;
61 | }
62 |
63 | public abstract void addInterface(UvcInterface uvcInterface);
64 |
65 | public abstract UvcInterface getInterface(int index);
66 |
67 | @Override
68 | public String toString() {
69 | return "InterfaceAssociationDescriptor{" +
70 | "FirstInterface=" + firstInterface +
71 | ", InterfaceCount=" + interfaceCount +
72 | ", IndexFunction=" + indexFunction +
73 | '}';
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/UvcInterface.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import static com.jwoolston.android.uvc.interfaces.Descriptor.VideoSubclass;
4 |
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 | import android.util.SparseArray;
8 | import com.jwoolston.android.libusb.LibusbError;
9 | import com.jwoolston.android.libusb.UsbDeviceConnection;
10 | import com.jwoolston.android.libusb.UsbInterface;
11 | import com.jwoolston.android.uvc.interfaces.Descriptor.Protocol;
12 | import com.jwoolston.android.uvc.interfaces.endpoints.Endpoint;
13 | import java.util.LinkedList;
14 | import java.util.List;
15 | import timber.log.Timber;
16 |
17 | /**
18 | * @author Jared Woolston (Jared.Woolston@gmail.com)
19 | */
20 | public abstract class UvcInterface {
21 |
22 | private static final int LENGTH_STANDARD_DESCRIPTOR = 9;
23 |
24 | protected static final int bLength = 0;
25 | protected static final int bDescriptorType = 1;
26 | protected static final int bInterfaceNumber = 2;
27 | protected static final int bAlternateSetting = 3;
28 | protected static final int bNumEndpoints = 4;
29 | protected static final int bInterfaceClass = 5;
30 | protected static final int bInterfaceSubClass = 6;
31 | protected static final int bInterfaceProtocol = 7;
32 | protected static final int iInterface = 8;
33 |
34 | protected final SparseArray usbInterfaces;
35 | protected final SparseArray endpoints;
36 |
37 | protected int currentSetting = 0;
38 |
39 | protected static UsbInterface getUsbInterface(UsbDeviceConnection connection, byte[] descriptor) {
40 | final int indexNumber = (0xFF & descriptor[bInterfaceNumber]);
41 | final int alternateSetting = (0xFF & descriptor[bAlternateSetting]);
42 | return connection.getDevice().getInterface(indexNumber + alternateSetting);
43 | }
44 |
45 | public static UvcInterface parseDescriptor(UsbDeviceConnection connection, byte[] descriptor) throws
46 | IllegalArgumentException {
47 | // Check the length
48 | if (descriptor.length < LENGTH_STANDARD_DESCRIPTOR) {
49 | throw new IllegalArgumentException("Descriptor is not long enough to be a standard interface descriptor.");
50 | }
51 | // Check the class
52 | if (descriptor[bInterfaceClass] == Descriptor.VIDEO_CLASS_CODE) {
53 | // For video class, only PC_PROTOCOL_15 is permitted
54 | if (descriptor[bInterfaceProtocol] != Protocol.PC_PROTOCOL_15.protocol) {
55 | switch (VideoSubclass.getVideoSubclass(descriptor[bInterfaceSubClass])) {
56 | // We could handle Interface Association Descriptors here, but they don't correspond to an
57 | // accessable interface, so we
58 | // treat them separately
59 | case SC_VIDEOCONTROL:
60 | Timber.i("Creating control interface");
61 | return VideoControlInterface.parseVideoControlInterface(connection, descriptor);
62 | case SC_VIDEOSTREAMING:
63 | Timber.i("Creating streaming interface.");
64 | return VideoStreamingInterface.parseVideoStreamingInterface(connection, descriptor);
65 | default:
66 | throw new IllegalArgumentException(
67 | "The provided descriptor has an invalid video interface subclass.");
68 | }
69 | } else {
70 | throw new IllegalArgumentException(
71 | "The provided descriptor has an invalid protocol: " + descriptor[bInterfaceProtocol]);
72 | }
73 | } else if (descriptor[bInterfaceClass] == Descriptor.AUDIO_CLASS_CODE) {
74 | // TODO: Something with the audio class
75 | return null;
76 | } else {
77 | throw new IllegalArgumentException(
78 | "The provided descriptor has an invalid interface class: " + descriptor[bInterfaceClass]);
79 | }
80 | }
81 |
82 | protected UvcInterface(UsbInterface usbInterface, byte[] descriptor) {
83 | usbInterfaces = new SparseArray<>();
84 | endpoints = new SparseArray<>();
85 | currentSetting = 0xFF & descriptor[bAlternateSetting];
86 | usbInterfaces.put(currentSetting, usbInterface);
87 | final int endpointCount = (0xFF & descriptor[bNumEndpoints]);
88 | endpoints.put(currentSetting, new Endpoint[endpointCount]);
89 | }
90 |
91 | public LibusbError selectAlternateSetting(@NonNull UsbDeviceConnection connection, int alternateSetting)
92 | throws UnsupportedOperationException {
93 | currentSetting = alternateSetting;
94 | final UsbInterface usbInterface = getUsbInterface();
95 | if (usbInterface == null) {
96 | throw new UnsupportedOperationException("There is not alternate setting: " + alternateSetting);
97 | }
98 | connection.claimInterface(usbInterface, true);
99 | return connection.setInterface(usbInterface);
100 | }
101 |
102 | public void addEndpoint(int index, @NonNull Endpoint endpoint) {
103 | endpoints.get(currentSetting)[index - 1] = endpoint;
104 | }
105 |
106 | public Endpoint getEndpoint(int index) {
107 | return endpoints.get(currentSetting)[index - 1];
108 | }
109 |
110 | public Endpoint[] getCurrentEndpoints() {
111 | return endpoints.get(currentSetting);
112 | }
113 |
114 | public int getInterfaceNumber() {
115 | return usbInterfaces.get(currentSetting).getId();
116 | }
117 |
118 | @Nullable
119 | public UsbInterface getUsbInterface() {
120 | return usbInterfaces.get(currentSetting);
121 | }
122 |
123 | public List getUsbInterfaceList() {
124 | final LinkedList interfaces = new LinkedList<>();
125 | for (int i = 0; i < usbInterfaces.size(); ++i) {
126 | interfaces.add(usbInterfaces.get(usbInterfaces.keyAt(i)));
127 | }
128 | return interfaces;
129 | }
130 |
131 | public abstract void parseClassDescriptor(byte[] descriptor);
132 |
133 | public abstract void parseAlternateFunction(@NonNull UsbDeviceConnection connection, byte[] descriptor);
134 |
135 | @Override
136 | public String toString() {
137 | return "AInterface{" +
138 | "usbInterface=" + usbInterfaces +
139 | '}';
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/VideoClassInterface.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import com.jwoolston.android.libusb.UsbInterface;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public abstract class VideoClassInterface extends UvcInterface {
9 |
10 | VideoClassInterface(UsbInterface usbInterface, byte[] descriptor) {
11 | super(usbInterface, descriptor);
12 | }
13 |
14 | public static enum VC_INF_SUBTYPE {
15 | VC_DESCRIPTOR_UNDEFINED(0x00),
16 | VC_HEADER(0x01),
17 | VC_INPUT_TERMINAL(0x02),
18 | VC_OUTPUT_TERMINAL(0x03),
19 | VC_SELECTOR_UNIT(0x04),
20 | VC_PROCESSING_UNIT(0x05),
21 | VC_EXTENSION_UNIT(0x06),
22 | VC_ENCODING_UNIT(0x07);
23 |
24 | public final byte subtype;
25 |
26 | private VC_INF_SUBTYPE(int subtype) {
27 | this.subtype = (byte) (subtype & 0xFF);
28 | }
29 |
30 | public static VC_INF_SUBTYPE getSubtype(byte subtype) {
31 | for (VC_INF_SUBTYPE s : VC_INF_SUBTYPE.values()) {
32 | if (s.subtype == subtype) {
33 | return s;
34 | }
35 | }
36 | return null;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/VideoControlInterface.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import android.support.annotation.NonNull;
4 | import com.jwoolston.android.libusb.UsbDeviceConnection;
5 | import com.jwoolston.android.libusb.UsbInterface;
6 | import com.jwoolston.android.uvc.interfaces.terminals.CameraTerminal;
7 | import com.jwoolston.android.uvc.interfaces.terminals.VideoInputTerminal;
8 | import com.jwoolston.android.uvc.interfaces.terminals.VideoOutputTerminal;
9 | import com.jwoolston.android.uvc.interfaces.terminals.VideoTerminal;
10 | import com.jwoolston.android.uvc.interfaces.units.AVideoExtensionUnit;
11 | import com.jwoolston.android.uvc.interfaces.units.VideoEncodingUnit;
12 | import com.jwoolston.android.uvc.interfaces.units.VideoProcessingUnit;
13 | import com.jwoolston.android.uvc.interfaces.units.VideoSelectorUnit;
14 | import com.jwoolston.android.uvc.interfaces.units.VideoUnit;
15 | import java.util.Arrays;
16 | import java.util.LinkedList;
17 | import java.util.List;
18 | import timber.log.Timber;
19 |
20 | /**
21 | * @author Jared Woolston (Jared.Woolston@gmail.com)
22 | */
23 | public class VideoControlInterface extends VideoClassInterface {
24 |
25 | private static final int VIDEO_CLASS_HEADER_LENGTH = 12;
26 | private static final int INTERRUPT_ENDPOINT = 0x3;
27 |
28 | private static final int bDescriptorSubType = 2;
29 | private static final int bcdUVC = 3;
30 | private static final int wTotalLength = 5;
31 | private static final int dwClockFrequency = 7;
32 | private static final int bInCollection = 11;
33 | private static final int baInterfaceNr_1 = 12;
34 |
35 | private int uvc;
36 | private int numberStreamingInterfaces;
37 | private int[] streamingInterfaces;
38 |
39 | private List inputTerminals = new LinkedList<>();
40 | private List outputTerminals = new LinkedList<>();
41 | private List units = new LinkedList<>();
42 |
43 | public static VideoControlInterface parseVideoControlInterface(UsbDeviceConnection connection, byte[] descriptor)
44 | throws IllegalArgumentException {
45 | Timber.d("Parsing Video Class Interface header.");
46 |
47 | final UsbInterface usbInterface = UvcInterface.getUsbInterface(connection, descriptor);
48 | return new VideoControlInterface(usbInterface, descriptor);
49 | }
50 |
51 | VideoControlInterface(UsbInterface usbInterface, byte[] descriptor) {
52 | super(usbInterface, descriptor);
53 | }
54 |
55 | @Override
56 | public void parseClassDescriptor(byte[] descriptor) {
57 | if (isClassInterfaceHeader(descriptor)) {
58 | parseClassInterfaceHeader(descriptor);
59 | Timber.d("%s", this);
60 | } else if (isTerminal(descriptor)) {
61 | parseTerminal(descriptor);
62 | } else if (isUnit(descriptor)) {
63 | parseUnit(descriptor);
64 | } else {
65 | throw new IllegalArgumentException("Unknown class specific interface type.");
66 | }
67 | }
68 |
69 | @Override
70 | public void parseAlternateFunction(@NonNull UsbDeviceConnection device, byte[] descriptor) {
71 | // Do nothing
72 | Timber.d("parseAlternateFunction() called for VideoControlInterface.");
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | StringBuilder builder = new StringBuilder("\tVideoControlInterface{" +
78 | "\n\t\t\tuvc=" + uvc +
79 | "\n\t\t\tnumberStreamingInterfaces=" + numberStreamingInterfaces +
80 | "\n\t\t\tstreamingInterfaces=" + Arrays.toString(streamingInterfaces) +
81 | "\n\t\t\tUSB Interface=" + getUsbInterface() +
82 | "\n\t\t\tEndpoints=" + Arrays.toString(getCurrentEndpoints()) +
83 | "\n\t\t\tinput terminals=" + inputTerminals +
84 | "\n\t\t\toutput terminals=" + outputTerminals);
85 | builder.append("\n\t\t\tVideo Units:");
86 | for (VideoUnit unit : units) {
87 | builder.append("\n\t\t\t\t").append(unit);
88 | }
89 | builder.append('}');
90 | return builder.toString();
91 | }
92 |
93 | public int getNumberStreamingInterfaces() {
94 | return numberStreamingInterfaces;
95 | }
96 |
97 | public int getUVCVersion() {
98 | return uvc;
99 | }
100 |
101 | public boolean isClassInterfaceHeader(byte[] descriptor) {
102 | return (descriptor.length >= VIDEO_CLASS_HEADER_LENGTH && (descriptor[bDescriptorSubType]
103 | == VC_INF_SUBTYPE.VC_HEADER.subtype));
104 | }
105 |
106 | public boolean isTerminal(byte[] descriptor) {
107 | return VideoTerminal.isVideoTerminal(descriptor);
108 | }
109 |
110 | public boolean isUnit(byte[] descriptor) {
111 | return VideoUnit.isVideoUnit(descriptor);
112 | }
113 |
114 | public void parseClassInterfaceHeader(byte[] descriptor) throws IllegalArgumentException {
115 | Timber.d("Parsing Video Class Interface header.");
116 | if (descriptor.length < VIDEO_CLASS_HEADER_LENGTH) {
117 | throw new IllegalArgumentException("The provided descriptor is not a valid Video Class Interface.");
118 | }
119 | uvc = ((0xFF & descriptor[bcdUVC]) << 8) | (0xFF & descriptor[bcdUVC + 1]);
120 | numberStreamingInterfaces = descriptor[bInCollection];
121 | streamingInterfaces = new int[numberStreamingInterfaces];
122 | for (int i = 0; i < numberStreamingInterfaces; ++i) {
123 | streamingInterfaces[i] = (0xFF & descriptor[baInterfaceNr_1 + i]);
124 | }
125 | }
126 |
127 | public void parseTerminal(byte[] descriptor) throws IllegalArgumentException {
128 | if (VideoInputTerminal.isInputTerminal(descriptor)) {
129 | if (CameraTerminal.isCameraTerminal(descriptor)) {
130 | // Parse as camera terminal
131 | final CameraTerminal cameraTerminal = new CameraTerminal(descriptor);
132 | inputTerminals.add(cameraTerminal);
133 | } else {
134 | // Parse as input terminal
135 | VideoInputTerminal inputTerminal = new VideoInputTerminal(descriptor);
136 | inputTerminals.add(inputTerminal);
137 | }
138 | } else if (VideoOutputTerminal.isOutputTerminal(descriptor)) {
139 | // Parse as output terminal
140 | VideoOutputTerminal outputTerminal = new VideoOutputTerminal(descriptor);
141 | outputTerminals.add(outputTerminal);
142 | } else {
143 | throw new IllegalArgumentException("The provided descriptor is not a valid Video Terminal.");
144 | }
145 | }
146 |
147 | public void parseUnit(byte[] descriptor) throws IllegalArgumentException {
148 | if (VideoSelectorUnit.isVideoSelectorUnit(descriptor)) {
149 | // Parse as video selector unit
150 | final VideoSelectorUnit selectorUnit = new VideoSelectorUnit(descriptor);
151 | units.add(selectorUnit);
152 | } else if (VideoProcessingUnit.isVideoProcessingUnit(descriptor)) {
153 | // Parse as video processing unit
154 | final VideoProcessingUnit processingUnit = new VideoProcessingUnit(descriptor);
155 | units.add(processingUnit);
156 | } else if (VideoEncodingUnit.isVideoEncodingUnit(descriptor)) {
157 | // Parse as video encoding unit
158 | final VideoEncodingUnit encodingUnit = new VideoEncodingUnit(descriptor);
159 | units.add(encodingUnit);
160 | } else if (AVideoExtensionUnit.isVideoExtensionUnit(descriptor)) {
161 | // Parse as a video extension unit
162 | Timber.d("Parsing video extension unit.");
163 | // TODO: Figure out how to handle extensions
164 | } else {
165 | throw new IllegalArgumentException("The provided descriptor is not a valid Video Unit");
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/VideoIAD.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import android.util.SparseArray;
4 |
5 | import com.jwoolston.android.uvc.interfaces.Descriptor.VideoSubclass;
6 |
7 | import timber.log.Timber;
8 |
9 | /**
10 | * @author Jared Woolston (Jared.Woolston@gmail.com)
11 | */
12 | public class VideoIAD extends InterfaceAssociationDescriptor {
13 |
14 | private SparseArray interfaces;
15 |
16 | VideoIAD(byte[] descriptor) throws IllegalArgumentException {
17 | super(descriptor);
18 | if (VideoSubclass.getVideoSubclass(descriptor[bFunctionSubClass])
19 | != VideoSubclass.SC_VIDEO_INTERFACE_COLLECTION) {
20 | throw new IllegalArgumentException(
21 | "The provided descriptor does not represent a Video Class Interface Association Descriptor.");
22 | }
23 | interfaces = new SparseArray<>();
24 | }
25 |
26 | @Override
27 | public void addInterface(UvcInterface uvcInterface) throws IllegalArgumentException {
28 | try {
29 | final VideoClassInterface videoClassInterface = (VideoClassInterface) uvcInterface;
30 | if (interfaces.get(videoClassInterface.getInterfaceNumber()) != null) {
31 | throw new IllegalArgumentException(
32 | "An interface with the same index as the provided interface already exists!");
33 | }
34 | interfaces.put(videoClassInterface.getInterfaceNumber(), videoClassInterface);
35 | } catch (ClassCastException e) {
36 | throw new IllegalArgumentException(
37 | "The provided interface is not an instance of VideoClassInterface or its subclasses.");
38 | }
39 | }
40 |
41 | @Override
42 | public VideoClassInterface getInterface(int index) {
43 | return interfaces.get(index);
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | StringBuilder builder = new StringBuilder("VideoIAD{" +
49 | "FirstInterface=" + getIndexFirstInterface() +
50 | ", InterfaceCount=" + getInterfaceCount() +
51 | ", IndexFunction=" + getIndexFunction() +
52 | ", \nInterfaces:\n");
53 | final int count = getInterfaceCount();
54 | for (int i = 0; i < count; ++i) {
55 | builder.append(getInterface(i)).append(',').append('\n');
56 | }
57 | builder.append('}');
58 | return builder.toString();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/VideoStreamingInterface.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.libusb.UsbDeviceConnection;
6 | import com.jwoolston.android.libusb.UsbInterface;
7 | import com.jwoolston.android.uvc.interfaces.endpoints.Endpoint;
8 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat;
9 | import com.jwoolston.android.uvc.interfaces.streaming.MJPEGVideoFormat;
10 | import com.jwoolston.android.uvc.interfaces.streaming.MJPEGVideoFrame;
11 | import com.jwoolston.android.uvc.interfaces.streaming.UncompressedVideoFormat;
12 | import com.jwoolston.android.uvc.interfaces.streaming.UncompressedVideoFrame;
13 | import com.jwoolston.android.uvc.interfaces.streaming.VideoColorMatchingDescriptor;
14 | import com.jwoolston.android.uvc.interfaces.streaming.VideoStreamInputHeader;
15 | import com.jwoolston.android.uvc.interfaces.streaming.VideoStreamOutputHeader;
16 | import com.jwoolston.android.uvc.util.Hexdump;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import timber.log.Timber;
20 |
21 | /**
22 | * @author Jared Woolston (Jared.Woolston@gmail.com)
23 | */
24 | public class VideoStreamingInterface extends VideoClassInterface {
25 |
26 | private static final int bLength = 0;
27 | private static final int bDescriptorType = 1;
28 | private static final int bDescriptorSubtype = 2;
29 |
30 | private VideoStreamInputHeader inputHeader;
31 | private VideoStreamOutputHeader outputHeader;
32 |
33 | private final List videoFormats;
34 |
35 | // Only used during descriptor parsing
36 | private VideoFormat lastFormat;
37 |
38 | private VideoColorMatchingDescriptor colorMatchingDescriptor;
39 |
40 | public static VideoStreamingInterface parseVideoStreamingInterface(UsbDeviceConnection connection,
41 | byte[] descriptor) throws
42 | IllegalArgumentException {
43 | final UsbInterface usbInterface = UvcInterface.getUsbInterface(connection, descriptor);
44 | return new VideoStreamingInterface(usbInterface, descriptor);
45 | }
46 |
47 | VideoStreamingInterface(UsbInterface usbInterface, byte[] descriptor) {
48 | super(usbInterface, descriptor);
49 | videoFormats = new ArrayList<>();
50 | }
51 |
52 | public List getAvailableFormats() {
53 | return videoFormats;
54 | }
55 |
56 | @Override
57 | public void parseClassDescriptor(byte[] descriptor) {
58 | final VS_INTERFACE_SUBTYPE subtype = VS_INTERFACE_SUBTYPE.fromByte(descriptor[bDescriptorSubtype]);
59 | switch (subtype) {
60 | case VS_INPUT_HEADER:
61 | inputHeader = new VideoStreamInputHeader(descriptor);
62 | break;
63 | case VS_OUTPUT_HEADER:
64 | outputHeader = new VideoStreamOutputHeader(descriptor);
65 | break;
66 | case VS_FORMAT_UNCOMPRESSED:
67 | final UncompressedVideoFormat uncompressedVideoFormat = new UncompressedVideoFormat(descriptor);
68 | videoFormats.add(uncompressedVideoFormat);
69 | lastFormat = uncompressedVideoFormat;
70 | break;
71 | case VS_FRAME_UNCOMPRESSED:
72 | final UncompressedVideoFrame uncompressedVideoFrame = new UncompressedVideoFrame(descriptor);
73 | try {
74 | ((UncompressedVideoFormat) lastFormat).addUncompressedVideoFrame(uncompressedVideoFrame);
75 | } catch (ClassCastException e) {
76 | throw new IllegalArgumentException(
77 | "The parsed uncompressed frame descriptor is not valid for the " +
78 | "previously parsed Format: " + lastFormat.getClass().getName());
79 | }
80 | break;
81 | case VS_FORMAT_MJPEG:
82 | final MJPEGVideoFormat mjpegVideoFormat = new MJPEGVideoFormat(descriptor);
83 | videoFormats.add(mjpegVideoFormat);
84 | lastFormat = mjpegVideoFormat;
85 | break;
86 | case VS_FRAME_MJPEG:
87 | final MJPEGVideoFrame mjpegVideoFrame = new MJPEGVideoFrame(descriptor);
88 | try {
89 | ((MJPEGVideoFormat) lastFormat).addMJPEGVideoFrame(mjpegVideoFrame);
90 | } catch (ClassCastException e) {
91 | throw new IllegalArgumentException(
92 | "The parsed MJPEG frame descriptor is not valid for the previously parsed Format: "
93 | + lastFormat.getClass().getName());
94 | }
95 | break;
96 | case VS_STILL_IMAGE_FRAME:
97 | Timber.d("VideoStream Still Image Frame Descriptor");
98 | Timber.d("%s", Hexdump.dumpHexString(descriptor));
99 | //TODO: Handle STILL IMAGE FRAME descriptor section 3.9.2.5 Pg. 81
100 | break;
101 | case VS_COLORFORMAT:
102 | colorMatchingDescriptor = new VideoColorMatchingDescriptor(descriptor);
103 | lastFormat.setColorMatchingDescriptor(colorMatchingDescriptor);
104 | Timber.d("%s", colorMatchingDescriptor);
105 | break;
106 | default:
107 | Timber.d("Unknown streaming interface descriptor: %s", Hexdump.dumpHexString(descriptor));
108 | }
109 | }
110 |
111 | @Override
112 | public void parseAlternateFunction(@NonNull UsbDeviceConnection connection, byte[] descriptor) {
113 | currentSetting = 0xFF & descriptor[bAlternateSetting];
114 | usbInterfaces.put(currentSetting, getUsbInterface(connection, descriptor));
115 | final int endpointCount = (0xFF & descriptor[bNumEndpoints]);
116 | endpoints.put(currentSetting, new Endpoint[endpointCount]);
117 | }
118 |
119 | @Override
120 | public String toString() {
121 | return "VideoStreamingInterface{" +
122 | "\n\tinputHeader=" + inputHeader +
123 | "\n\toutputHeader=" + outputHeader +
124 | "\n\tvideoFormats=" + videoFormats +
125 | "\n\tcolorMatchingDescriptor=" + colorMatchingDescriptor +
126 | "\n\tUsb Interface=" + getUsbInterfaceList() +
127 | "\n\tNumber Alternate Functions=" + usbInterfaces.size() +
128 | '}';
129 | }
130 |
131 | public static enum VS_INTERFACE_SUBTYPE {
132 | VS_UNDEFINED(0x00),
133 | VS_INPUT_HEADER(0x01),
134 | VS_OUTPUT_HEADER(0x02),
135 | VS_STILL_IMAGE_FRAME(0x03),
136 | VS_FORMAT_UNCOMPRESSED(0x04),
137 | VS_FRAME_UNCOMPRESSED(0x05),
138 | VS_FORMAT_MJPEG(0x06),
139 | VS_FRAME_MJPEG(0x07),
140 | RESERVED_0(0x08),
141 | RESERVED_1(0x09),
142 | VS_FORMAT_MPEG2TS(0x0A),
143 | RESERVED_2(0x0B),
144 | VS_FORMAT_DV(0x0C),
145 | VS_COLORFORMAT(0x0D),
146 | RESERVED_3(0x0E),
147 | RESERVED_4(0x0F),
148 | VS_FORMAT_FRAME_BASED(0x10),
149 | VS_FRAME_FRAME_BASED(0x11),
150 | VS_FORMAT_STREAM_BASED(0x12),
151 | VS_FORMAT_H264(0x13),
152 | VS_FRAME_H264(0x14),
153 | VS_FRAME_H264_SIMULCAST(0x15),
154 | VS_FORMAT_VP8(0x16),
155 | VS_FRAME_VP8(0x17),
156 | VS_FORMAT_VP8_SIMULCAST(0x18);
157 |
158 | public final byte code;
159 |
160 | VS_INTERFACE_SUBTYPE(int code) {
161 | this.code = (byte) (0xFF & code);
162 | }
163 |
164 | public static VS_INTERFACE_SUBTYPE fromByte(byte code) {
165 | for (VS_INTERFACE_SUBTYPE subtype : VS_INTERFACE_SUBTYPE.values()) {
166 | if (subtype.code == code) {
167 | return subtype;
168 | }
169 | }
170 | return null;
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/endpoints/BulkEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.endpoints;
2 |
3 | import com.jwoolston.android.libusb.UsbInterface;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public class BulkEndpoint extends Endpoint {
9 |
10 | protected BulkEndpoint(UsbInterface usbInterface, byte[] descriptor) throws IllegalArgumentException {
11 | super(usbInterface, descriptor);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/endpoints/Endpoint.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.endpoints;
2 |
3 | import com.jwoolston.android.libusb.UsbEndpoint;
4 | import com.jwoolston.android.libusb.UsbInterface;
5 | import timber.log.Timber;
6 |
7 | /**
8 | * @author Jared Woolston (Jared.Woolston@gmail.com)
9 | */
10 | public abstract class Endpoint {
11 |
12 | private static final int LENGTH_STANDARD_DESCRIPTOR = 7;
13 | private static final int LENGTH_CLASS_DESCRIPTOR = 5;
14 |
15 | private static final int bLength = 0;
16 | private static final int bDescriptorType = 1;
17 | private static final int bEndpointAddress = 2;
18 | private static final int bmAttributes = 3;
19 | private static final int wMaxPacketSize = 4;
20 | private static final int bInterval = 6; // Interval is 2^(value-1) ms
21 |
22 | private UsbEndpoint endpoint; // Often this will be null after initial parsing, the endpoint wont enumerate until
23 | // we activate the alternate setting.
24 |
25 | private final VideoEndpoint type;
26 | private final byte rawAttributes;
27 | private final int endpointAddress;
28 | private final int interval; // USB Frames
29 |
30 | private int maxPacketSize;
31 |
32 | public static Endpoint parseDescriptor(UsbInterface usbInterface, byte[] descriptor) throws IllegalArgumentException {
33 | if (descriptor.length < LENGTH_STANDARD_DESCRIPTOR) {
34 | throw new IllegalArgumentException("Descriptor is not long enough to be a standard endpoint descriptor.");
35 | }
36 | VideoEndpoint type = VideoEndpoint.fromAttributes(descriptor[bmAttributes]);
37 | switch (type) {
38 | case EP_ISOCHRONOUS:
39 | return new IsochronousEndpoint(usbInterface, descriptor);
40 | case EP_BULK:
41 | return new BulkEndpoint(usbInterface, descriptor);
42 | case EP_INTERRUPT:
43 | return new InterruptEndpoint(usbInterface, descriptor);
44 | default:
45 | throw new IllegalArgumentException("Descriptor is not for a recognized endpoint type.");
46 | }
47 | }
48 |
49 | protected Endpoint(UsbInterface usbInterface, byte[] descriptor) throws IllegalArgumentException {
50 | if (descriptor.length < LENGTH_STANDARD_DESCRIPTOR) {
51 | throw new IllegalArgumentException("The provided descriptor is not a valid standard endpoint descriptor.");
52 | }
53 |
54 | endpointAddress = 0xFF & descriptor[bEndpointAddress]; // Masking to deal with Java's signed bytes
55 | final int count = usbInterface.getEndpointCount();
56 | for (int i = 0; i < count; ++i) {
57 | final UsbEndpoint endpoint = usbInterface.getEndpoint(i);
58 | if (endpoint.getAddress() == endpointAddress) {
59 | this.endpoint = endpoint;
60 | break;
61 | }
62 | }
63 |
64 | rawAttributes = descriptor[bmAttributes];
65 | interval = descriptor[bInterval];
66 | maxPacketSize = ((0xFF & descriptor[wMaxPacketSize]) << 8) | (0xFF & descriptor[wMaxPacketSize + 1]);
67 | type = VideoEndpoint.fromAttributes(rawAttributes);
68 | }
69 |
70 | public void parseClassDescriptor(byte[] descriptor) throws IllegalArgumentException {
71 | Timber.d("Parsing Class Specific Endpoint Descriptor.");
72 | if (descriptor.length < LENGTH_CLASS_DESCRIPTOR) {
73 | throw new IllegalArgumentException("The provided descriptor is not a valid class endpoint descriptor.");
74 | }
75 | }
76 |
77 | public int getInterval() {
78 | return interval;
79 | }
80 |
81 | public UsbEndpoint getEndpoint() {
82 | return endpoint;
83 | }
84 |
85 | protected byte getRawAttributes() {
86 | return rawAttributes;
87 | }
88 |
89 | @Override
90 | public String toString() {
91 | return "Endpoint{" +
92 | "endpoint=" + endpoint +
93 | ", type=" + type +
94 | ", rawAttributes=" + rawAttributes +
95 | ", endpointAddress=" + endpointAddress +
96 | ", interval=" + interval +
97 | ", maxPacketSize=" + maxPacketSize +
98 | '}';
99 | }
100 |
101 | public static enum VideoEndpoint {
102 | EP_CONTROL(0x0),
103 | EP_ISOCHRONOUS(0x01),
104 | EP_BULK(0x02),
105 | EP_INTERRUPT(0x03);
106 |
107 | public final byte code;
108 |
109 | private VideoEndpoint(int code) {
110 | this.code = (byte) (code & 0xFF);
111 | }
112 |
113 | public static VideoEndpoint fromAttributes(byte attributes) {
114 | int typeCode = attributes & 0x3;
115 | switch (typeCode) {
116 | case 0x0:
117 | return EP_CONTROL;
118 | case 0x01:
119 | return EP_ISOCHRONOUS;
120 | case 0x02:
121 | return EP_BULK;
122 | case 0x03:
123 | return EP_INTERRUPT;
124 | }
125 | return null;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/endpoints/InterruptEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.endpoints;
2 |
3 | import com.jwoolston.android.libusb.UsbInterface;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public class InterruptEndpoint extends Endpoint {
9 |
10 | protected InterruptEndpoint(UsbInterface usbInterface, byte[] descriptor) throws IllegalArgumentException {
11 | super(usbInterface, descriptor);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/endpoints/IsochronousEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.endpoints;
2 |
3 | import com.jwoolston.android.libusb.UsbInterface;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public class IsochronousEndpoint extends Endpoint {
9 |
10 | private final SynchronizationType synchronizationType;
11 |
12 | protected IsochronousEndpoint(UsbInterface usbInterface, byte[] descriptor) throws IllegalArgumentException {
13 | super(usbInterface, descriptor);
14 | synchronizationType = SynchronizationType.fromAttributes(getRawAttributes());
15 | }
16 |
17 | public SynchronizationType getSynchronizationType() {
18 | return synchronizationType;
19 | }
20 |
21 | public static enum SynchronizationType {
22 | NONE,
23 | ASYNCRONOUS,
24 | ADAPTIVE,
25 | SYNCHRONOUS;
26 |
27 | public static SynchronizationType fromAttributes(byte attributes) {
28 | int code = attributes & 0xC;
29 | switch (code) {
30 | case 0x0:
31 | return NONE;
32 | case 0x4:
33 | return ASYNCRONOUS;
34 | case 0x8:
35 | return ADAPTIVE;
36 | case 0xC:
37 | return SYNCHRONOUS;
38 | }
39 | return null;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/AVideoStreamHeader.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | */
6 | abstract class AVideoStreamHeader {
7 |
8 | protected static final int bNumFormats = 3; //p
9 | protected static final int wTotalLength = 4;
10 | protected static final int bEndpointAddress = 6;
11 |
12 | private final int numberFormats;
13 | private final int endpointAddress;
14 |
15 | protected AVideoStreamHeader(byte[] descriptor) throws IllegalArgumentException {
16 | numberFormats = (0xFF & descriptor[bNumFormats]);
17 | endpointAddress = (0xFF & descriptor[bEndpointAddress]);
18 | }
19 |
20 | int getNumberFormats() {
21 | return numberFormats;
22 | }
23 |
24 | int getEndpointAddress() {
25 | return endpointAddress;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/MJPEGVideoFormat.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import android.support.annotation.NonNull;
4 | import com.jwoolston.android.uvc.util.Hexdump;
5 |
6 | import timber.log.Timber;
7 |
8 | /**
9 | * @author Jared Woolston (Jared.Woolston@gmail.com)
10 | */
11 | public class MJPEGVideoFormat extends VideoFormat {
12 |
13 | private static final int LENGTH = 11;
14 |
15 | private static final int bFormatIndex = 3;
16 | private static final int bNumFrameDescriptors = 4;
17 | private static final int bmFlags = 5;
18 | private static final int bDefaultFrameIndex = 6;
19 | private static final int bAspectRatioX = 7;
20 | private static final int bAspectRatioY = 8;
21 | private static final int bmInterlaceFlags = 9;
22 | private static final int bCopyProtect = 10;
23 |
24 | private final boolean fixedSampleSize;
25 |
26 | public MJPEGVideoFormat(byte[] descriptor) throws IllegalArgumentException {
27 | super(descriptor);
28 | if (descriptor.length < LENGTH) {
29 | throw new IllegalArgumentException("The provided descriptor is not long enough for an MJPEG Video Format.");
30 | }
31 | formatIndex = (0xFF & descriptor[bFormatIndex]);
32 | numberFrames = (0xFF & descriptor[bNumFrameDescriptors]);
33 | fixedSampleSize = descriptor[bmFlags] != 0;
34 | defaultFrameIndex = (0xFF & descriptor[bDefaultFrameIndex]);
35 | aspectRatioX = (0xFF & descriptor[bAspectRatioX]);
36 | aspectRatioY = (0xFF & descriptor[bAspectRatioY]);
37 | interlaceFlags = descriptor[bmInterlaceFlags];
38 | copyProtect = descriptor[bCopyProtect] != 0;
39 | }
40 |
41 | public void addMJPEGVideoFrame(@NonNull MJPEGVideoFrame frame) {
42 | Timber.d("Adding video frame: %s", frame);
43 | videoFrames.add(frame);
44 | }
45 |
46 | public boolean getFixedSampleSize() {
47 | return fixedSampleSize;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "MJPEGVideoFormat{" +
53 | "formatIndex=" + formatIndex +
54 | ", numberFrames=" + numberFrames +
55 | ", fixedSampleSize=" + fixedSampleSize +
56 | ", defaultFrameIndex=" + defaultFrameIndex +
57 | ", AspectRatio=" + aspectRatioX + ":" + aspectRatioY +
58 | ", interlaceFlags=0x" + Hexdump.toHexString(interlaceFlags) +
59 | ", copyProtect=" + copyProtect +
60 | '}';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/MJPEGVideoFrame.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public class MJPEGVideoFrame extends VideoFrame {
9 |
10 | public MJPEGVideoFrame(byte[] descriptor) {
11 | super(descriptor);
12 | }
13 |
14 | @Override
15 | public String toString() {
16 | return "MJPEGVideoFrame{" +
17 | "mStillImageSupported=" + getStillImageSupported() +
18 | ", mFixedFrameRateEnabled=" + getFixedFrameRateEnabled() +
19 | ", mWidth=" + getWidth() +
20 | ", mHeight=" + getHeight() +
21 | ", mMinBitRate=" + getMinBitRate() +
22 | ", mMaxBitRate=" + getMaxBitRate() +
23 | ", mDefaultFrameInterval=" + getDefaultFrameInterval() +
24 | ", mFrameIntervalType=" + getFrameIntervalType() +
25 | ", mMinFrameInterval=" + getMinFrameInterval() +
26 | ", mMaxFrameInterval=" + getMaxFrameInterval() +
27 | ", mFrameIntervalStep=" + getFrameIntervalStep() +
28 | ", mFrameIntervals=" + Arrays.toString(getFrameIntervals()) +
29 | '}';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/UncompressedVideoFormat.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.util.Hexdump;
6 |
7 | import timber.log.Timber;
8 |
9 | /**
10 | * The Uncompressed Video Format descriptor defines the characteristics of a specific video stream. It is used for
11 | * formats that carry uncompressed video information, including all YUV variants.
12 | * A Terminal corresponding to a USB IN or OUT endpoint, and the interface it belongs to, supports one or more format
13 | * definitions. To select a particular format, host software sends control requests to the corresponding interface.
14 | *
15 | * This specification defines uncompressed streams in YUV color spaces. Each frame is independently sent by the
16 | * device to the host.
17 | *
18 | * The vertical and horizontal dimensions of the image are constrained by the color component subsampling; image size
19 | * must be a multiple of macropixel block size. No padding is allowed. This Uncompressed video payload specification
20 | * supports any YUV format. The recommended YUV formats are one packed 4:2:2 YUV format (YUY2), one packed 4:2:0 YUV
21 | * format (M420), and two planar 4:2:0 YUV formats (NV12, I420).
22 | *
23 | * @author Jared Woolston (Jared.Woolston@gmail.com)
24 | * @see USB Video Payload
25 | * Uncompressed 1.5 Specification §2.2 Table 2-1
26 | */
27 | public class UncompressedVideoFormat extends VideoFormat {
28 |
29 | private static final int LENGTH = 27;
30 |
31 | //|-----------------------------------------------|
32 | //| Format | GUID |
33 | //|-----------------------------------------------|
34 | //| YUY2 | 32595559-0000-0010-8000-00AA00389B71 |
35 | //| NV12 | 3231564E-0000-0010-8000-00AA00389B71 |
36 | //| M420 | 3032344D-0000-0010-8000-00AA00389B71 |
37 | //| I420 | 30323449-0000-0010-8000-00AA00389B71 |
38 | //|-----------------------------------------------|
39 |
40 | private static final String YUY2_GUID = "32595559-0000-0010-8000-00AA00389B71";
41 | private static final String NV12_GUID = "3231564E-0000-0010-8000-00AA00389B71";
42 | private static final String M420_GUID = "3032344D-0000-0010-8000-00AA00389B71";
43 | private static final String I420_GUID = "30323449-0000-0010-8000-00AA00389B71";
44 |
45 | private static final int bFormatIndex = 3;
46 | private static final int bNumFrameDescriptors = 4;
47 | private static final int guidFormat = 5;
48 | private static final int bBitsPerPixel = 21;
49 | private static final int bDefaultFrameIndex = 22;
50 | private static final int bAspectRatioX = 23;
51 | private static final int bAspectRatioY = 24;
52 | private static final int bmInterlaceFlags = 25;
53 | private static final int bCopyProtect = 26;
54 |
55 | private final String guid;
56 | private final int bitsPerPixel;
57 |
58 | public void addUncompressedVideoFrame(@NonNull UncompressedVideoFrame frame) {
59 | Timber.d("Adding video frame: %s", frame);
60 | videoFrames.add(frame);
61 | }
62 |
63 | public UncompressedVideoFormat(@NonNull byte[] descriptor) throws IllegalArgumentException {
64 | super(descriptor);
65 | if (descriptor.length < LENGTH) {
66 | throw new IllegalArgumentException(
67 | "The provided discriptor is not long enough for an Uncompressed Video Format.");
68 | }
69 | formatIndex = (0xFF & descriptor[bFormatIndex]);
70 | numberFrames = (0xFF & descriptor[bNumFrameDescriptors]);
71 | byte[] GUIDBytes = new byte[16];
72 | System.arraycopy(descriptor, guidFormat, GUIDBytes, 0, GUIDBytes.length);
73 | bitsPerPixel = (0xFF & descriptor[bBitsPerPixel]);
74 | defaultFrameIndex = (0xFF & descriptor[bDefaultFrameIndex]);
75 | aspectRatioX = (0xFF & descriptor[bAspectRatioX]);
76 | aspectRatioY = (0xFF & descriptor[bAspectRatioY]);
77 | interlaceFlags = descriptor[bmInterlaceFlags];
78 | copyProtect = descriptor[bCopyProtect] != 0;
79 |
80 | // Parse the GUID bytes to String
81 | final StringBuilder builder = new StringBuilder();
82 | builder.append(Hexdump.toHexString(GUIDBytes[3])).append(Hexdump.toHexString(GUIDBytes[2]))
83 | .append(Hexdump.toHexString(GUIDBytes[1])).append(Hexdump.toHexString(GUIDBytes[0]));
84 | builder.append('-').append(Hexdump.toHexString(GUIDBytes[5])).append(Hexdump.toHexString(GUIDBytes[4]));
85 | builder.append('-').append(Hexdump.toHexString(GUIDBytes[7])).append(Hexdump.toHexString(GUIDBytes[6]));
86 | builder.append('-');
87 | for (int i = 8; i < 10; ++i) {
88 | builder.append(Hexdump.toHexString(GUIDBytes[i]));
89 | }
90 | builder.append('-');
91 | for (int i = 10; i < 16; ++i) {
92 | builder.append(Hexdump.toHexString(GUIDBytes[i]));
93 | }
94 | guid = builder.toString();
95 | }
96 |
97 | @Override
98 | public String toString() {
99 | return "UncompressedVideoFormat{" +
100 | "formatIndex=" + formatIndex +
101 | ", numberFrames=" + numberFrames +
102 | ", GUID=" + guid +
103 | ", bitsPerPixel=" + bitsPerPixel +
104 | ", defaultFrameIndex=" + defaultFrameIndex +
105 | ", AspectRatio=" + aspectRatioX + ":" + aspectRatioY +
106 | ", interlaceFlags=0x" + Hexdump.toHexString(interlaceFlags) +
107 | ", copyProtect=" + copyProtect +
108 | '}';
109 | }
110 |
111 | public String getGUID() {
112 | return guid;
113 | }
114 |
115 | public int getBitsPerPixel() {
116 | return bitsPerPixel;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/UncompressedVideoFrame.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * Uncompressed Video Frame descriptors (or Frame descriptors for short) are used to describe the decoded video and
7 | * still-image frame dimensions and other frame-specific characteristics supported by a particular stream. One or
8 | * more Frame descriptors follow the Uncompressed Video Format descriptor they correspond to. The Frame descriptor is
9 | * also used to determine the range of frame intervals supported for the frame size specified.
10 | * The Uncompressed Video Frame descriptor is used only for video formats for which the Uncompressed Video Format
11 | * descriptor applies (see section 3.1.1, "Uncompressed Video Format Descriptor").
12 | *
13 | * @author Jared Woolston (Jared.Woolston@gmail.com)
14 | * @see USB Video Payload
15 | * Uncompressed 1.5 Specification §2.2 Table 2-1
16 | */
17 | public class UncompressedVideoFrame extends VideoFrame {
18 |
19 | public UncompressedVideoFrame(byte[] descriptor) throws IllegalArgumentException {
20 | super(descriptor);
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "UncompressedVideoFrame{" +
26 | "Frame Index=" + getFrameIndex() +
27 | ", StillImageSupported=" + getStillImageSupported() +
28 | ", FixedFrameRateEnabled=" + getFixedFrameRateEnabled() +
29 | ", Width=" + getWidth() +
30 | ", Height=" + getHeight() +
31 | ", MinBitRate=" + getMinBitRate() +
32 | ", MaxBitRate=" + getMaxBitRate() +
33 | ", DefaultFrameInterval=" + getDefaultFrameInterval() +
34 | ", FrameIntervalType=" + getFrameIntervalType() +
35 | ", MinFrameInterval=" + getMinFrameInterval() +
36 | ", MaxFrameInterval=" + getMaxFrameInterval() +
37 | ", FrameIntervalStep=" + getFrameIntervalStep() +
38 | ", FrameIntervals=" + Arrays.toString(getFrameIntervals()) +
39 | '}';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/VideoColorMatchingDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | * @see UVC 1.5 Class Specification Table 3-19
6 | */
7 | public class VideoColorMatchingDescriptor {
8 |
9 | private static final int LENGTH = 6;
10 |
11 | private static final int bColorPrimaries = 3;
12 | private static final int bTransferCharacteristics = 4;
13 | private static final int bMatrixCoefficients = 5;
14 |
15 | private final ColorPrimaries colorPrimaries;
16 | private final TransferCharacteristics transferCharacteristics;
17 | private final MatrixCoefficients matrixCoefficients;
18 |
19 | public VideoColorMatchingDescriptor(byte[] descriptor) throws IllegalArgumentException {
20 | if (descriptor.length < LENGTH) {
21 | throw new IllegalArgumentException("Provided descriptor is not long enough to be a Video Color Matching " +
22 | "Descriptor.");
23 | }
24 | colorPrimaries = ColorPrimaries.fromDescriptor(descriptor[bColorPrimaries] & 0xFF);
25 | transferCharacteristics = TransferCharacteristics.fromDescriptor(descriptor[bTransferCharacteristics] & 0xFF);
26 | matrixCoefficients = MatrixCoefficients.fromDescriptor(descriptor[bMatrixCoefficients] & 0xFF);
27 | }
28 |
29 | public ColorPrimaries getColorPrimaries() {
30 | return colorPrimaries;
31 | }
32 |
33 | public TransferCharacteristics getTransferCharacteristics() {
34 | return transferCharacteristics;
35 | }
36 |
37 | public MatrixCoefficients getMatrixCoefficients() {
38 | return matrixCoefficients;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return "VideoColorMatchingDescriptor{" +
44 | "colorPrimaries=" + colorPrimaries +
45 | ", transferCharacteristics=" + transferCharacteristics +
46 | ", matrixCoefficients=" + matrixCoefficients +
47 | '}';
48 | }
49 |
50 | public static enum ColorPrimaries {
51 | UNSPECIFIED,
52 | BT_709,
53 | sRGB,
54 | BT_470_2M,
55 | BT_470_2BG,
56 | SMPTE_170M,
57 | SMPTE_240M;
58 |
59 | public static ColorPrimaries fromDescriptor(int id) {
60 | if (id >= 8) return sRGB;
61 | for (ColorPrimaries characteristics : ColorPrimaries.values()) {
62 | if (characteristics.ordinal() == id) {
63 | return characteristics;
64 | }
65 | }
66 | return sRGB;
67 | }
68 | }
69 |
70 | public static enum TransferCharacteristics {
71 | UNSPECIFIED, BT_709, BT_470_2M, BT_470_2BG, SMPTE_170M, SMPTE_240M, LINEAR, sRGB;
72 |
73 | public static TransferCharacteristics fromDescriptor(int id) {
74 | if (id >= 8) return BT_709;
75 | for (TransferCharacteristics characteristics : TransferCharacteristics.values()) {
76 | if (characteristics.ordinal() == id) {
77 | return characteristics;
78 | }
79 | }
80 | return BT_709;
81 | }
82 | }
83 |
84 | public static enum MatrixCoefficients {
85 | UNSPECIFIED,
86 | BT_709,
87 | FCC,
88 | BT_470_2BG,
89 | SMPTE_170M,
90 | SMPTE_240M;
91 |
92 | public static MatrixCoefficients fromDescriptor(int id) {
93 | if (id >= 8) return SMPTE_170M;
94 | for (MatrixCoefficients characteristics : MatrixCoefficients.values()) {
95 | if (characteristics.ordinal() == id) {
96 | return characteristics;
97 | }
98 | }
99 | return SMPTE_170M;
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/VideoFormat.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | /**
9 | * @author Jared Woolston (Jared.Woolston@gmail.com)
10 | */
11 | public class VideoFormat {
12 |
13 | protected int formatIndex;
14 | protected int numberFrames;
15 | protected int defaultFrameIndex;
16 | protected int aspectRatioX;
17 | protected int aspectRatioY;
18 | protected byte interlaceFlags;
19 | protected boolean copyProtect;
20 |
21 | private VideoColorMatchingDescriptor colorMatchingDescriptor;
22 |
23 | protected final Set videoFrames = new HashSet<>();
24 |
25 | VideoFormat(@NonNull byte[] descriptor) throws IllegalArgumentException {
26 |
27 | }
28 |
29 | public void setColorMatchingDescriptor(VideoColorMatchingDescriptor descriptor) {
30 | colorMatchingDescriptor = descriptor;
31 | }
32 |
33 | public VideoColorMatchingDescriptor getColorMatchingDescriptor() {
34 | return colorMatchingDescriptor;
35 | }
36 |
37 | public int getFormatIndex() {
38 | return formatIndex;
39 | }
40 |
41 | public int getNumberFrames() {
42 | return numberFrames;
43 | }
44 |
45 | public int getDefaultFrameIndex() {
46 | return defaultFrameIndex;
47 | }
48 |
49 | @NonNull
50 | public Set getVideoFrames() {
51 | return videoFrames;
52 | }
53 |
54 | public int getAspectRatioX() {
55 | return aspectRatioX;
56 | }
57 |
58 | public int getAspectRatioY() {
59 | return aspectRatioY;
60 | }
61 |
62 | public byte getInterlaceFlags() {
63 | return interlaceFlags;
64 | }
65 |
66 | public boolean isCopyProtect() {
67 | return copyProtect;
68 | }
69 |
70 | public VideoFrame getDefaultFrame() throws IllegalStateException {
71 | for (VideoFrame frame : videoFrames) {
72 | if (frame.getFrameIndex() == getDefaultFrameIndex()) {
73 | return frame;
74 | }
75 | }
76 | throw new IllegalStateException("No default frame was found!");
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/VideoFrame.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import com.jwoolston.android.uvc.util.ArrayTools;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public class VideoFrame {
9 |
10 | private static final int LENGTH_INTERVAL_TYPE_0 = 38;
11 | private static final int MIN_LENGTH_INTERVAL_TYPE_NOT_0 = 26; //26+4*n
12 |
13 | private static final int bFrameIndex = 3;
14 | private static final int bmCapabilites = 4;
15 | private static final int wWidth = 5;
16 | private static final int wHeight = 7;
17 | private static final int dwMinBitRate = 9;
18 | private static final int dwMaxBitRate = 13;
19 |
20 | private static final int dwMaxVideoFrameBufferSize = 17;
21 | private static final int dwDefaultFrameInterval = 21;
22 | private static final int bFrameIntervalType = 25; //n
23 |
24 | // Continuous frame intervals
25 | private static final int dwMinFrameInterval = 26;
26 | private static final int dwMaxFrameInterval = 30;
27 | private static final int dwFrameIntervalStep = 34;
28 |
29 | // Discrete frame intervals
30 | private static final int dwFrameInterval = 26;
31 |
32 | private final int frameIndex;
33 | private final boolean stillImageSupported;
34 | private final boolean fixedFrameRateEnabled;
35 | private final int width;
36 | private final int height;
37 | private final int minBitRate;
38 | private final int maxBitRate;
39 | private final int defaultFrameInterval;
40 | private final int frameIntervalType;
41 |
42 | // Continuous frame intervals
43 | private final int minFrameInterval; // Shortest frame interval supported in 100 ns units.
44 | private final int maxFrameInterval; // Longest frame interval supported in 100 ns units.
45 | private final int frameIntervalStep; // Frame interval step in 100 ns units.
46 |
47 | // Discrete frame intervals
48 | private final int[] frameIntervals;
49 |
50 | VideoFrame(byte[] descriptor) throws IllegalArgumentException {
51 | frameIntervalType = (0xFF & descriptor[bFrameIntervalType]);
52 | if (frameIntervalType == 0 && descriptor.length < LENGTH_INTERVAL_TYPE_0) {
53 | throw new IllegalArgumentException(
54 | "The provided descriptor is not long enough to be an Uncompressed Video Frame.");
55 | }
56 | if (frameIntervalType != 0 && descriptor.length < (MIN_LENGTH_INTERVAL_TYPE_NOT_0 + 4 * frameIntervalType)) {
57 | throw new IllegalArgumentException(
58 | "The provided descriptor is not long enough to be an Uncompressed Video Frame.");
59 | }
60 | frameIndex = (0xFF & descriptor[bFrameIndex]);
61 | stillImageSupported = (descriptor[bmCapabilites] & 0x01) != 0;
62 | fixedFrameRateEnabled = (descriptor[bmCapabilites] & 0x02) != 0;
63 |
64 | width = ArrayTools.extractShort(descriptor, wWidth);
65 | height = ArrayTools.extractShort(descriptor, wHeight);
66 |
67 | minBitRate = ArrayTools.extractInteger(descriptor , dwMinBitRate);
68 | maxBitRate = ArrayTools.extractInteger(descriptor, dwMaxBitRate);
69 | defaultFrameInterval = ((0xFF & descriptor[dwDefaultFrameInterval + 3]) << 24)
70 | | ((0xFF & descriptor[dwDefaultFrameInterval + 2]) << 16)
71 | | ((0xFF & descriptor[dwDefaultFrameInterval + 1]) << 8)
72 | | (0xFF & descriptor[dwDefaultFrameInterval]);
73 |
74 | if (frameIntervalType == 0) {
75 | minFrameInterval = ArrayTools.extractInteger(descriptor, dwMinFrameInterval);
76 | maxFrameInterval = ArrayTools.extractInteger(descriptor, dwMaxFrameInterval);
77 | frameIntervalStep = ArrayTools.extractInteger(descriptor, dwFrameIntervalStep);
78 | frameIntervals = null;
79 | } else {
80 | frameIntervals = new int[frameIntervalType];
81 | minFrameInterval = 0;
82 | maxFrameInterval = 0;
83 | frameIntervalStep = 0;
84 | for (int i = 0; i < frameIntervalType; ++i) {
85 | final int index = dwFrameInterval + 4 * i - 4;
86 | frameIntervals[i] = ArrayTools.extractInteger(descriptor, index);
87 | }
88 | }
89 | }
90 |
91 | protected int[] getFrameIntervals() {
92 | return frameIntervals;
93 | }
94 |
95 | public boolean getStillImageSupported() {
96 | return stillImageSupported;
97 | }
98 |
99 | public boolean getFixedFrameRateEnabled() {
100 | return fixedFrameRateEnabled;
101 | }
102 |
103 | public int getFrameIndex() {
104 | return frameIndex;
105 | }
106 |
107 | public boolean isStillImageSupported() {
108 | return stillImageSupported;
109 | }
110 |
111 | public boolean isFixedFrameRateEnabled() {
112 | return fixedFrameRateEnabled;
113 | }
114 |
115 | public int getWidth() {
116 | return width;
117 | }
118 |
119 | public int getHeight() {
120 | return height;
121 | }
122 |
123 | public int getMinBitRate() {
124 | return minBitRate;
125 | }
126 |
127 | public int getMaxBitRate() {
128 | return maxBitRate;
129 | }
130 |
131 | public int getDefaultFrameInterval() {
132 | return defaultFrameInterval;
133 | }
134 |
135 | public int getFrameIntervalType() {
136 | return frameIntervalType;
137 | }
138 |
139 | public int getMinFrameInterval() {
140 | return minFrameInterval;
141 | }
142 |
143 | public int getMaxFrameInterval() {
144 | return maxFrameInterval;
145 | }
146 |
147 | public int getFrameIntervalStep() {
148 | return frameIntervalStep;
149 | }
150 |
151 | public int getFrameInterval(int index) {
152 | return frameIntervals[index];
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/VideoStreamInputHeader.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import java.util.Arrays;
4 |
5 | import timber.log.Timber;
6 |
7 | /**
8 | * @author Jared Woolston (Jared.Woolston@gmail.com)
9 | * @see UVC 1.5 Class Specification Table 3-14
10 | */
11 | public class VideoStreamInputHeader extends AVideoStreamHeader {
12 |
13 | private static final int MIN_HEADER_LENGTH = 13;
14 |
15 | private static final int bmInfo = 7;
16 | private static final int bTerminalLink = 8;
17 | private static final int bStillCaptureMethod = 9;
18 | private static final int bTriggerSupport = 10;
19 | private static final int bTriggerUsage = 11;
20 | private static final int bControlSize = 12; //n
21 | private static final int bmaControls = 13; // Last index is 13 + p*n - n
22 |
23 | private final byte infoMask;
24 | private final int terminalLink;
25 | private final int stillCaptureMethod;
26 | private final boolean hardwareTriggerSupported;
27 | private final boolean triggerStillImageCapture;
28 | private final byte[] controlsMask;
29 |
30 | public VideoStreamInputHeader(byte[] descriptor) throws IllegalArgumentException {
31 | super(descriptor);
32 | Timber.d("Parsing VideoStreamInputHeader");
33 | if (descriptor.length < MIN_HEADER_LENGTH) throw new IllegalArgumentException("The provided descriptor is not long enough to be a valid VideoStreamInputHeader.");
34 | infoMask = descriptor[bmInfo];
35 | terminalLink = (0xFF & descriptor[bTerminalLink]);
36 | stillCaptureMethod = (0xFF & descriptor[bStillCaptureMethod]);
37 | hardwareTriggerSupported = ((0xFF & descriptor[bTriggerSupport]) == 1);
38 | triggerStillImageCapture = ((0xFF & descriptor[bTriggerUsage]) == 0); // True means still image should be captured (opposite of spec)
39 | final int sizeControls = (0xFF & descriptor[bControlSize]);
40 | controlsMask = new byte[sizeControls];
41 | System.arraycopy(descriptor, bmaControls, controlsMask, 0, controlsMask.length);
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return "VideoStreamInputHeader{" +
47 | "\n\tinfoMask=" + infoMask +
48 | "\n\tterminalLink=" + terminalLink +
49 | "\n\tstillCaptureMethod=" + stillCaptureMethod +
50 | "\n\thardwareTriggerSupported=" + hardwareTriggerSupported +
51 | "\n\ttriggerStillImageCapture=" + triggerStillImageCapture +
52 | "\n\tcontrolsMask=" + Arrays.toString(controlsMask) +
53 | '}';
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/streaming/VideoStreamOutputHeader.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.streaming;
2 |
3 | import timber.log.Timber;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | * @see UVC 1.5 Class Specification Table 3-15
8 | */
9 | public class VideoStreamOutputHeader extends AVideoStreamHeader {
10 |
11 | private static final int MIN_HEADER_LENGTH = 9;
12 |
13 | private static final int bTerminalLink = 7;
14 | private static final int bControlSize = 8; //n
15 | private static final int bmaControls = 9; // Last index is 9 + p*n - n
16 |
17 | private final int terminalLink;
18 | private final byte[] controlsMask;
19 |
20 | public VideoStreamOutputHeader(byte[] descriptor) throws IllegalArgumentException {
21 | super(descriptor);
22 | Timber.d("Parsing VideoStreamOutputHeader");
23 | if (descriptor.length < MIN_HEADER_LENGTH) throw new IllegalArgumentException("The provided descriptor is not long enough to be a valid VideoStreamOutputHeader.");
24 | terminalLink = (0xFF & descriptor[bTerminalLink]);
25 | final int sizeControls = (0xFF & descriptor[bControlSize]);
26 | controlsMask = new byte[sizeControls];
27 | System.arraycopy(descriptor, bmaControls, controlsMask, 0, controlsMask.length);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/terminals/CameraTerminal.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.terminals;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
6 |
7 | import java.util.Collections;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | /**
12 | * The Camera Terminal (CT) controls mechanical (or equivalent digital) features of the device component that
13 | * transmits the video stream. As such, it is only applicable to video capture devices with controllable lens or
14 | * sensor characteristics. A Camera Terminal is always represented as an Input Terminal with a single output pin. It
15 | * provides support for the following features.
16 | *
17 | *
-Scanning Mode (Progressive or Interlaced)
18 | *
-Auto-Exposure Mode
19 | *
-Auto-Exposure Priority
20 | *
-Exposure Time
21 | *
-Focus
22 | *
-Auto-Focus
23 | *
-Simple Focus
24 | *
-Iris
25 | *
-Zoom
26 | *
-Pan
27 | *
-Roll
28 | *
-Tilt
29 | *
-Digital Windowing
30 | *
-Region of Interest
31 | *
32 | * Support for any particular control is optional. The Focus control can optionally provide support for an auto
33 | * setting (with an on/off state). If the auto setting is supported and set to the on state, the device will provide
34 | * automatic focus adjustment, and read requests will reflect the automatically set value. Attempts to
35 | * programmatically set the Focus control when in auto mode shall result in protocol STALL with an error code of
36 | * bRequestErrorCode = “Wrong State”. When leaving Auto-Focus mode (entering manual focus mode), the control
37 | * shall remain at the value that was in effect just before the transition.
38 | *
39 | * @author Jared Woolston (Jared.Woolston@gmail.com)
40 | * @see UVC 1.5 Class
41 | * Specification §2.3.3
42 | */
43 | public class CameraTerminal extends VideoInputTerminal {
44 |
45 | private static final int LENGTH_DESCRIPTOR = 18;
46 |
47 | private static final int wObjectiveFocalLengthMin = 8;
48 | private static final int wObjectiveFocalLengthMax = 10;
49 | private static final int wOcularFocalLength = 12;
50 | private static final int bControlSize = 14;
51 | private static final int bmControls = 15;
52 |
53 | private final int objectiveFocalLengthMin;
54 | private final int objectiveFocalLengthMax;
55 | private final int objectiveFocalLength;
56 | private final int ocularFocalLength;
57 |
58 | private final Set controlSet;
59 |
60 | public static boolean isCameraTerminal(byte[] descriptor) {
61 | if (descriptor.length != LENGTH_DESCRIPTOR) {
62 | return false;
63 | }
64 | if (descriptor[bDescriptorSubtype] != VideoClassInterface.VC_INF_SUBTYPE.VC_INPUT_TERMINAL.subtype) {
65 | return false;
66 | }
67 | final TerminalType type = TerminalType.toTerminalType(descriptor[wTerminalType], descriptor[wTerminalType + 1]);
68 | return (type == TerminalType.ITT_CAMERA);
69 | }
70 |
71 | public CameraTerminal(byte[] descriptor) throws IllegalArgumentException {
72 | super(descriptor);
73 | if (!isCameraTerminal(descriptor)) {
74 | throw new IllegalArgumentException("The provided descriptor is not a valid Camera Terminal descriptor.");
75 | }
76 | objectiveFocalLengthMin = descriptor[wObjectiveFocalLengthMin] | (descriptor[wObjectiveFocalLengthMin + 1]
77 | << 8);
78 | objectiveFocalLengthMax = descriptor[wObjectiveFocalLengthMax] | (descriptor[wObjectiveFocalLengthMax + 1]
79 | << 8);
80 | objectiveFocalLength = descriptor[wOcularFocalLength] | (descriptor[wOcularFocalLength + 1] << 8);
81 | ocularFocalLength = descriptor[wOcularFocalLength] | (descriptor[wOcularFocalLength + 1] << 8);
82 | final int bitMap = descriptor[bmControls] | (descriptor[bmControls + 1] << 8) | (descriptor[bmControls + 2]
83 | << 8);
84 | final Set controlSet = new HashSet<>();
85 | for (int i = 0; i < 24; ++i) {
86 | if ((bitMap & (0x01 << i)) != 0) {
87 | // The specified flag is present
88 | final Control control = Control.controlFromIndex(i);
89 | if (control == null) {
90 | throw new IllegalArgumentException("Unknown camera control from index: " + i);
91 | }
92 | controlSet.add(control);
93 | }
94 | }
95 | // The contents of this set should never change
96 | this.controlSet = Collections.unmodifiableSet(controlSet);
97 | }
98 |
99 | public int getObjectiveFocalLengthMin() {
100 | return objectiveFocalLengthMin;
101 | }
102 |
103 | public int getObjectiveFocalLengthMax() {
104 | return objectiveFocalLengthMax;
105 | }
106 |
107 | public int getObjectiveFocalLength() {
108 | return objectiveFocalLength;
109 | }
110 |
111 | public int getOcularFocalLength() {
112 | return ocularFocalLength;
113 | }
114 |
115 | public boolean hasControl(@NonNull Control control) {
116 | return controlSet.contains(control);
117 | }
118 |
119 | @NonNull
120 | public Set getControlSet() {
121 | return controlSet;
122 | }
123 |
124 | @Override
125 | public String toString() {
126 | final String base = "CameraTerminal{" +
127 | "terminalType=" + getTerminalType() +
128 | ", terminalID=" + getTerminalID() +
129 | ", associatedTerminalID=" + getAssociatedTerminalID() +
130 | ", Min Objective Focal Length: " + getObjectiveFocalLengthMin() +
131 | ", Max Objective Focal Length: " + getObjectiveFocalLengthMax() +
132 | ", Objective Focal Length: " + getObjectiveFocalLength() +
133 | ", Ocular Focal Length: " + getOcularFocalLength() +
134 | ", Available Controls: ";
135 | final StringBuilder builder = new StringBuilder(base);
136 | for (Control control : controlSet) {
137 | builder.append(control).append(',');
138 | }
139 | builder.deleteCharAt(builder.length() - 1);
140 | builder.append('}');
141 | return builder.toString();
142 | }
143 |
144 | public static enum Control {
145 | SCANNING_MODE((byte) 0x01),
146 | AUTO_EXPOSURE_MODE((byte) 0x02),
147 | AUTO_EXPOSURE_PRIORITY((byte) 0x03),
148 | EXPOSURE_TIME_ABSOLUTE((byte) 0x04),
149 | EXPOSURE_TIME_RELATIVE((byte) 0x05),
150 | FOCUS_ABSOLUTE((byte) 0x06),
151 | FOCUS_RELATIVE((byte) 0x07),
152 | FOCUS_AUTO((byte) 0x08),
153 | IRIS_ABSOLUTE((byte) 0x09),
154 | IRIS_RELATIVE((byte) 0x0A),
155 | ZOOM_ABSOLUTE((byte) 0x0B),
156 | ZOOM_RELATIVE((byte) 0x0C),
157 | PAN_TILT_ABSOLUTE((byte) 0x0D),
158 | PAN_TILT_RELATIVE((byte) 0x0E),
159 | ROLL_ABSOLUTE((byte) 0x0F),
160 | ROLL_RELATIVE((byte) 0x10),
161 | PRIVACY((byte) 0x11),
162 | FOCUS_SIMPLE((byte) 0x12),
163 | WINDOW((byte) 0x13),
164 | REGION_OF_INTEREST((byte) 0x14);
165 |
166 | public final byte code;
167 |
168 | Control(byte code) {
169 | this.code = code;
170 | }
171 |
172 | public static Control controlFromIndex(int i) {
173 | final Control[] values = Control.values();
174 | if (i > values.length || i < 0) {
175 | return null;
176 | }
177 | for (Control control : values) {
178 | if (control.ordinal() == i) {
179 | return control;
180 | }
181 | }
182 | return null;
183 | }
184 |
185 | public short valueFromControl() {
186 | return ((short) (0xFFFF & (code << 8)));
187 | }
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/terminals/VideoInputTerminal.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.terminals;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 |
5 | /**
6 | * The Input Terminal (IT) is used as an interface between the video function’s "outside world" and other Units
7 | * inside the video function. It serves as a receptacle for data flowing into the video function. Its function is to
8 | * represent a source of incoming data after this data has been extracted from the data source. The data may include
9 | * audio and metadata associated with a video stream. These physical streams are grouped into a cluster of logical
10 | * streams, leaving the Input Terminal through a single Output Pin.
11 | * An Input Terminal can represent inputs to the video function other than USB OUT endpoints. A CCD sensor on a video
12 | * camera or a composite video input is an example of such a non-USB input. However, if the video stream is entering
13 | * the video function by means of a USB OUT endpoint, there is a one-to-one relationship between that endpoint and
14 | * its associated Input Terminal. The class-specific Output Header descriptor contains a field that holds a direct
15 | * reference to this Input Terminal (see section 3.9.2.2, “Output Header Descriptor”). The Host needs to use both the
16 | * endpoint descriptors and the Input Terminal descriptor to get a full understanding of the characteristics and
17 | * capabilities of the Input Terminal. Stream-related parameters are stored in the endpoint descriptors.
18 | * Control-related parameters are stored in the Terminal descriptor.
19 | *
20 | * @author Jared Woolston (Jared.Woolston@gmail.com)
21 | * @see UVC 1.5 Class
22 | * Specification §2.3.2
23 | */
24 | public class VideoInputTerminal extends VideoTerminal {
25 |
26 | protected static final int MIN_LENGTH = 8;
27 |
28 | protected static final int iTerminal = 7;
29 |
30 | public static boolean isInputTerminal(byte[] descriptor) {
31 | return (descriptor.length >= MIN_LENGTH
32 | && descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_INPUT_TERMINAL.subtype);
33 | }
34 |
35 | public VideoInputTerminal(byte[] descriptor) throws IllegalArgumentException {
36 | super(descriptor);
37 | if (!isInputTerminal(descriptor)) {
38 | throw new IllegalArgumentException("Provided descriptor is not a valid video input terminal.");
39 | }
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return "InputTerminal{" +
45 | "terminalType=" + getTerminalType() +
46 | ", terminalID=" + getTerminalID() +
47 | ", associatedTerminalID=" + getAssociatedTerminalID() +
48 | '}';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/terminals/VideoOutputTerminal.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.terminals;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 |
5 | /**
6 | * The Output Terminal (OT) is used as an interface between Units inside the video function and the "outside world".
7 | * It serves as an outlet for video information, flowing out of the video function. Its function is to represent a
8 | * sink of outgoing data. The video data stream enters the Output Terminal through a single Input Pin.
9 | * An Output Terminal can represent outputs from the video function other than USB IN endpoints. A Liquid Crystal
10 | * Display (LCD) screen built into a video device or a composite video out connector are examples of such an output.
11 | * However, if the video stream is leaving the video function by means of a USB IN endpoint, there is a one-to-one
12 | * relationship between that endpoint and its associated Output Terminal. The class-specific Input Header descriptor
13 | * contains a field that holds a direct reference to this Output Terminal (see section 3.9.2.1, “Input Header
14 | * Descriptor”). The Host needs to use both the endpoint descriptors and the Output Terminal descriptor to fully
15 | * understand the characteristics and capabilities of the Output Terminal. Stream-related parameters are stored in
16 | * the endpoint descriptors. Control-related parameters are stored in the Terminal descriptor.
17 | *
18 | * @author Jared Woolston (Jared.Woolston@gmail.com)
19 | * @see UVC 1.5 Class
20 | * Specification §2.3.2
21 | */
22 | public class VideoOutputTerminal extends VideoTerminal {
23 |
24 | protected static final int MIN_LENGTH = 9;
25 |
26 | protected static final int bSourceID = 7;
27 | protected static final int iTerminal = 8;
28 |
29 | private final int sourceID;
30 |
31 | public static boolean isOutputTerminal(byte[] descriptor) {
32 | return (descriptor.length >= MIN_LENGTH
33 | && descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_OUTPUT_TERMINAL.subtype);
34 | }
35 |
36 | public VideoOutputTerminal(byte[] descriptor) throws IllegalArgumentException {
37 | super(descriptor);
38 | if (!isOutputTerminal(descriptor)) {
39 | throw new IllegalArgumentException("Provided descriptor is not a valid video input terminal.");
40 | }
41 | sourceID = descriptor[bSourceID];
42 | }
43 |
44 | public int getSourceID() {
45 | return sourceID;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return "OutputTerminal{" +
51 | "terminalType=" + getTerminalType() +
52 | ", terminalID=" + getTerminalID() +
53 | ", associatedTerminalID=" + getAssociatedTerminalID() +
54 | ", sourceID=" + sourceID +
55 | '}';
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/terminals/VideoTerminal.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.terminals;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | public abstract class VideoTerminal {
9 |
10 | protected static final int bLength = 0;
11 | protected static final int bDescriptorType = 1;
12 | protected static final int bDescriptorSubtype = 2;
13 | protected static final int bTerminalID = 3;
14 | protected static final int wTerminalType = 4;
15 | protected static final int bAssocTerminal = 6;
16 |
17 | private final TerminalType terminalType;
18 | private final int terminalID;
19 | private final int associatedTerminalID;
20 |
21 | public static boolean isVideoTerminal(byte[] descriptor) {
22 | return (descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_INPUT_TERMINAL.subtype ||
23 | descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_OUTPUT_TERMINAL.subtype);
24 | }
25 |
26 | protected VideoTerminal(byte[] descriptor) {
27 | terminalType = TerminalType.toTerminalType(descriptor[wTerminalType], descriptor[wTerminalType + 1]);
28 | terminalID = descriptor[bTerminalID];
29 | associatedTerminalID = descriptor[bAssocTerminal];
30 | }
31 |
32 | public TerminalType getTerminalType() {
33 | return terminalType;
34 | }
35 |
36 | public int getTerminalID() {
37 | return terminalID;
38 | }
39 |
40 | public int getAssociatedTerminalID() {
41 | return associatedTerminalID;
42 | }
43 |
44 | /**
45 | * @see UVC Class specification
46 | * v1.5 Table B-1 through B-4.
47 | */
48 | public static enum TerminalType {
49 | /**
50 | * A terminal dealing with a signal carried over a vendor-specific interface. The vendor-specfic interface
51 | * descriptor must contain a field that references the terminal.
52 | */
53 | TT_VENDOR_SPECIFIC(0x0100),
54 |
55 | /**
56 | * A terminal dealing with a signal carried over an endpoint in a VideoStreaming interface. The
57 | * VideoStreaming interface descriptor points to the associated terminal through the bTerminalLink field.
58 | */
59 | TT_STREAMING(0x0101),
60 |
61 | /**
62 | * Vendor specific input terminal.
63 | */
64 | ITT_VENDOR_SPECIFIC(0x0200),
65 |
66 | /**
67 | * Camera sensor. To be used only in Camera Terminal descriptors.
68 | */
69 | ITT_CAMERA(0x0201),
70 |
71 | /**
72 | * Sequential media. To be used only in Media Transport Terminal descriptors.
73 | */
74 | ITT_MEDIA_TRANSPORT_INPUT(0x0202),
75 |
76 | /**
77 | * Vendor specific output terminal.
78 | */
79 | OTT_VENDOR_SPECIFIC(0x0300),
80 |
81 | /**
82 | * Generic display (LCD, CRT, etc.).
83 | */
84 | OTT_DISPLAY(0x0301),
85 |
86 | /**
87 | * Sequential media. To be used only in Media Transport Terminal descriptors.
88 | */
89 | OTT_MEDIA_TRANSPORT_OUTPUT(0x0302),
90 |
91 | /**
92 | * Vendor-Specific External Terminal.
93 | */
94 | EXTERNAL_VENDOR_SPECIFIC(0x0400),
95 |
96 | /**
97 | * Composite video connector.
98 | */
99 | COMPOSITE_CONNECTOR(0x0401),
100 |
101 | /**
102 | * S-video connector.
103 | */
104 | SVIDEO_CONNECTOR(0x0402),
105 |
106 | /**
107 | * Component video connector.
108 | */
109 | COMPONENT_CONNECTOR(0x0403);
110 |
111 | public int code;
112 |
113 | private TerminalType(int code) {
114 | this.code = (code & 0xFFFF);
115 | }
116 |
117 | public static TerminalType toTerminalType(byte low, byte high) {
118 | final int code = ((high << 8) | low);
119 | for (TerminalType terminal : TerminalType.values()) {
120 | if (terminal.code == code) {
121 | return terminal;
122 | }
123 | }
124 | return null;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/units/AVideoExtensionUnit.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.units;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 |
5 | /**
6 | * The Extension Unit (XU) is the method provided by this specification to add vendor-specific building blocks to the
7 | * specification. The Extension Unit can have one or more Input Pins and has a single Output Pin.
8 | * Although a generic host driver will not be able to determine what functionality is implemented in the Extension
9 | * Unit, it shall report the presence of these extensions to vendor-supplied client software, and provide a method
10 | * for sending control requests from the client software to the Unit, and receiving status from the unit.
11 | *
12 | * @author Jared Woolston (Jared.Woolston@gmail.com)
13 | * @see UVC 1.5 Class
14 | * Specification §2.3.7
15 | */
16 | public class AVideoExtensionUnit extends VideoUnit {
17 |
18 | private static final int MIN_LENGTH = 24;
19 |
20 | private static final int guidExtensionCode = 4;
21 | private static final int bNumControls = 20;
22 | private static final int bNrInPins = 21; // p
23 | private static final int baSourceID = 22;
24 | private static final int bControlSize = 22; // + p = n
25 | private static final int bmControls = 23; // + p
26 | private static final int iExtension = 23; // + descriptor[bNrInPins] + p + n
27 |
28 | private final byte[] mGUID = new byte[bNumControls - guidExtensionCode];
29 | private final int mNumControls;
30 | private final int mNumInputPins;
31 | private final int[] mSourceIDs;
32 | private final byte[] mRawControlMask;
33 | private final int mIndexExtension;
34 |
35 | protected final int mControlSize;
36 |
37 | public static boolean isVideoExtensionUnit(byte[] descriptor) {
38 | return (descriptor.length >= MIN_LENGTH
39 | & descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_EXTENSION_UNIT.subtype);
40 | }
41 |
42 | protected AVideoExtensionUnit(byte[] descriptor) throws IllegalArgumentException {
43 | super(descriptor);
44 |
45 | if (descriptor.length < MIN_LENGTH) {
46 | throw new IllegalArgumentException(
47 | "The provided descriptor is not long enough to be a video extension unit.");
48 | }
49 |
50 | System.arraycopy(descriptor, guidExtensionCode, mGUID, 0, mGUID.length);
51 | mNumControls = descriptor[bNumControls];
52 | mNumInputPins = descriptor[bNrInPins];
53 | mSourceIDs = new int[mNumInputPins];
54 | for (int i = 0; i < mNumInputPins; ++i) {
55 | mSourceIDs[i] = descriptor[baSourceID + i];
56 | }
57 | mControlSize = descriptor[bControlSize + mNumInputPins];
58 | mRawControlMask = new byte[mControlSize];
59 | for (int i = 0; i < mControlSize; ++i) {
60 | mRawControlMask[i] = descriptor[bmControls + mNumInputPins];
61 | }
62 | mIndexExtension = descriptor[iExtension + mNumInputPins + mNumControls];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/units/VideoEncodingUnit.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.units;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 | import java.util.Collections;
5 | import java.util.HashSet;
6 | import java.util.Set;
7 | import timber.log.Timber;
8 |
9 | /**
10 | * The Encoding Unit controls attributes of the encoder that encodes the video being streamed through it. It has a
11 | * single input and multiple output pins. It provides support for the following features which can be used before or
12 | * after streaming has started.
13 | *
14 | *
-Select Layer
15 | *
-Video Resolution
16 | *
-Profile and Toolset
17 | *
-Minimum Frame Interval
18 | *
-Slice Mode
19 | *
-Rate Control Mode
20 | *
-Average Bitrate Control
21 | *
-CPB Size Control
22 | *
-Peak Bit Rate
23 | *
-Quantization Parameter
24 | *
-Synchronization and Long Term Reference Frame
25 | *
-Long Term Reference Buffers
26 | *
-Long Term Picture
27 | *
-Valid Long Term Pictures
28 | *
-LevelIDC
29 | *
-SEI Message
30 | *
-QP Range
31 | *
-Priority ID
32 | *
-Start or Stop Layer
33 | *
-Error Resiliency
34 | *
35 | * Support for the Encoding Unit control is optional and only applicable to devices with onboard video encoders. The
36 | * Select Layer control also allows control of individual streams for devices that support simulcast transport of
37 | * more than one stream. Individual payloads may specialize the behavior of each of these controls to align with the
38 | * feature set defined by the associated encoder, e.g. H.264. This specialized behavior is defined in the associated
39 | * payload specification.
40 | *
41 | * @author Jared Woolston (Jared.Woolston@gmail.com)
42 | * @see UVC 1.5 Class
43 | * Specification §2.3.6
44 | */
45 | public class VideoEncodingUnit extends VideoUnit {
46 |
47 | private static final int LENGTH = 13;
48 |
49 | private static final int bSourceID = 4;
50 | private static final int iEncoding = 5;
51 | private static final int bmControls = 7;
52 | private static final int bmControlsRuntime = 10;
53 |
54 | private final int mSourceID;
55 | private final int mIndexEncoding;
56 |
57 | private final Set mControlSet;
58 | private final Set mRuntimeControlSet;
59 |
60 | public static boolean isVideoEncodingUnit(byte[] descriptor) {
61 | return (descriptor.length >= LENGTH
62 | & descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_ENCODING_UNIT.subtype);
63 | }
64 |
65 | public VideoEncodingUnit(byte[] descriptor) throws IllegalArgumentException {
66 | super(descriptor);
67 | Timber.d("Parsing video processing unit.");
68 | if (!isVideoEncodingUnit(descriptor)) {
69 | throw new IllegalArgumentException(
70 | "The provided descriptor is not a valid Video Encoding Unit descriptor.");
71 | }
72 | mSourceID = descriptor[bSourceID];
73 | mIndexEncoding = descriptor[iEncoding];
74 |
75 | final int controlBitMap = descriptor[bmControls] | (descriptor[bmControls + 1] << 8) | (
76 | descriptor[bmControls + 2] << 8);
77 | final Set controlSet = new HashSet<>();
78 | for (int i = 0; i < 24; ++i) {
79 | if ((controlBitMap & (0x01 << i)) != 0) {
80 | // The specified flag is present
81 | final CONTROL control = CONTROL.controlFromIndex(i);
82 | if (control == null) {
83 | throw new IllegalArgumentException("Unknown processing unit control from index: " + i);
84 | }
85 | controlSet.add(control);
86 | }
87 | }
88 | // The contents of this set should never change
89 | mControlSet = Collections.unmodifiableSet(controlSet);
90 |
91 | final int runtimeControlBitMap = descriptor[bmControlsRuntime] | (descriptor[bmControlsRuntime + 1] << 8) | (
92 | descriptor[bmControlsRuntime + 2] << 8);
93 | final Set runtimeControlSet = new HashSet<>();
94 | for (int i = 0; i < 24; ++i) {
95 | if ((runtimeControlBitMap & (0x01 << i)) != 0) {
96 | // The specified flag is present
97 | final CONTROL control = CONTROL.controlFromIndex(i);
98 | if (control == null) {
99 | throw new IllegalArgumentException("Unknown processing unit control from index: " + i);
100 | }
101 | runtimeControlSet.add(control);
102 | }
103 | }
104 | // The contents of this set should never change
105 | mRuntimeControlSet = Collections.unmodifiableSet(runtimeControlSet);
106 | }
107 |
108 | public int getSourceID() {
109 | return mSourceID;
110 | }
111 |
112 | public int getIndexEncoding() {
113 | return mIndexEncoding;
114 | }
115 |
116 | public boolean hasControl(CONTROL control) {
117 | return mControlSet.contains(control);
118 | }
119 |
120 | public boolean hasRuntimeControl(CONTROL control) {
121 | return mRuntimeControlSet.contains(control);
122 | }
123 |
124 | @Override
125 | public String toString() {
126 | final String base = "VideoProcessingUnit{" +
127 | ", Unit ID: " + getUnitID() +
128 | ", Source ID: " + getSourceID() +
129 | ", Index Encoding: " + getIndexEncoding() +
130 | ", Available Controls: ";
131 | StringBuilder builder = new StringBuilder(base);
132 | for (CONTROL control : mControlSet) {
133 | builder.append(control).append(',');
134 | }
135 | builder.deleteCharAt(builder.length() - 1);
136 | builder.append(", Runtime Controls: ");
137 | for (CONTROL control : mRuntimeControlSet) {
138 | builder.append(control).append(',');
139 | }
140 | builder.deleteCharAt(builder.length() - 1);
141 | builder.append('}');
142 | return builder.toString();
143 | }
144 |
145 | public static enum CONTROL {
146 | SELECT_LAYER,
147 | PROFILE_AND_TOOLSET,
148 | VIDEO_RESOLUTION,
149 | MINIMUM_FRAME_INTERVAL,
150 | SLICE_MODE,
151 | RATE_CONTROL_MODE,
152 | AVERAGE_BIT_RATE,
153 | CPB_SIZE,
154 | PEAK_BIT_RATE,
155 | QUANTIZATION_PARAMETER,
156 | SYNCH_AND_LONG_TERM_REF_FRAME,
157 | LONG_TERM_BUFFER,
158 | PICTURE_LONG_TERM_BUFFER,
159 | LTR_VALIDATION,
160 | LEVEL_IDC,
161 | SEI_MESSAGE,
162 | QP_RANGE,
163 | PRIORITY_ID,
164 | START_STOP_LAYER_VIEW,
165 | ERROR_RESILIENCY,
166 | RESERVED_0,
167 | RESERVED_1,
168 | RESERVED_2,
169 | RESERVED_3;
170 |
171 | public static CONTROL controlFromIndex(int i) {
172 | final CONTROL[] values = CONTROL.values();
173 | if (i > values.length || i < 0) {
174 | return null;
175 | }
176 | for (CONTROL control : values) {
177 | if (control.ordinal() == i) {
178 | return control;
179 | }
180 | }
181 | return null;
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/units/VideoProcessingUnit.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.units;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 | import java.util.Set;
5 | import timber.log.Timber;
6 |
7 | /**
8 | * The Processing Unit (PU) controls image attributes of the video being streamed through it. It has a single input
9 | * and output pin. It provides support for the following features:
10 | *
User Controls
11 | *
- Brightness
12 | *
- Hue
13 | *
- Saturation
14 | *
- Sharpness
15 | *
- Gamma
16 | *
- Digital Multiplier (Zoom)
17 | *
Auto Controls
18 | *
-White Balance Temperature
19 | *
-White Balance Component
20 | *
-Backlight Compensation
21 | *
-Contrast
22 | *
Other
23 | *
-Gain
24 | *
-Power Line Frequency
25 | *
-Analog Video Standard
26 | *
-Analog Video Lock Status
27 | *
28 | * Support for any particular control is optional. In particular, if the device supports the White Balance function,
29 | * it shall implement either the White Balance Temperature control or the White Balance Component control, but not
30 | * both. The User Controls indicate properties that are governed by user preference and not subject to any automatic
31 | * adjustment by the device. The Auto Controls will provide support for an auto setting (with an on/off state). If
32 | * the auto setting for a particular control is supported and set to the on state, the device will provide automatic
33 | * adjustment of the control, and read requests to the related control will reflect the automatically set value.
34 | * Attempts to programmatically set the Focus control when in auto mode shall result in protocol STALL with an error
35 | * code of bRequestErrorCode = “Wrong State”. When leaving an auto mode, the related control shall remain at the
36 | * value that was in effect just before the transition.
37 | *
38 | * @author Jared Woolston (Jared.Woolston@gmail.com)
39 | * @see UVC 1.5 Class
40 | * Specification §2.3.5
41 | */
42 | public class VideoProcessingUnit extends VideoUnit {
43 |
44 | private static final int LENGTH = 11; //TODO: Spec says 13?
45 |
46 | private static final int bSourceID = 4;
47 | private static final int wMaxMultiplier = 5;
48 | private static final int bmControls = 8;
49 | private static final int iProcessing = 11;
50 | private static final int bmVideoStandards = 12;
51 |
52 | private final int mSourceID;
53 |
54 | /**
55 | * Represented multiplier is x100. For example, 4.5 is represented as 450.
56 | */
57 | private final int mMaxMultiplier;
58 | private final int mIndexProcessing = 0;
59 |
60 | private final Set mControlSet = null;
61 | private final Set mStandardSet = null;
62 |
63 | public static boolean isVideoProcessingUnit(byte[] descriptor) {
64 | return (descriptor.length >= LENGTH
65 | & descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_PROCESSING_UNIT.subtype);
66 | }
67 |
68 | public VideoProcessingUnit(byte[] descriptor) throws IllegalArgumentException {
69 | super(descriptor);
70 | Timber.d("Parsing video processing unit.");
71 | if (!isVideoProcessingUnit(descriptor)) {
72 | throw new IllegalArgumentException(
73 | "The provided descriptor is not a valid Video Processing Unit descriptor.");
74 | }
75 | mSourceID = descriptor[bSourceID];
76 | mMaxMultiplier = descriptor[wMaxMultiplier] | (descriptor[wMaxMultiplier + 1] << 8);
77 |
78 | return; //FIXME: This is to deal with the discrepancy with the standard for testing
79 |
80 | /*mIndexProcessing = descriptor[iProcessing];
81 |
82 | final int controlBitMap = descriptor[bmControls] | (descriptor[bmControls + 1] << 8) | (descriptor[bmControls
83 | + 2] << 8);
84 | final Set controlSet = new HashSet<>();
85 | for (int i = 0; i < 24; ++i) {
86 | if ((controlBitMap & (0x01 << i)) != 0) {
87 | // The specified flag is present
88 | final Control control = Control.controlFromIndex(i);
89 | if (control == null) { throw new IllegalArgumentException("Unknown processing unit control from
90 | index: " + i); }
91 | controlSet.add(control);
92 | }
93 | }
94 | // The contents of this set should never change
95 | mControlSet = Collections.unmodifiableSet(controlSet);
96 |
97 | final int standardsBitMap = descriptor[bmVideoStandards];
98 | final Set standardsSet = new HashSet<>();
99 | for (int i = 0; i < 8; ++i) {
100 | if ((standardsBitMap & (0x01 << i)) != 0) {
101 | // The specified flag is present
102 | final STANDARD standard = STANDARD.standardFromIndex(i);
103 | if (standard == null) { throw new IllegalArgumentException("Unknown video standard from index: " + i); }
104 | standardsSet.add(standard);
105 | }
106 | }
107 | // The contents of this set should never change
108 | mStandardSet = Collections.unmodifiableSet(standardsSet);*/
109 | }
110 |
111 | public int getSourceID() {
112 | return mSourceID;
113 | }
114 |
115 | public int getMaxMultiplier() {
116 | return mMaxMultiplier;
117 | }
118 |
119 | public int getIndexProcessing() {
120 | return mIndexProcessing;
121 | }
122 |
123 | public boolean hasControl(CONTROL control) {
124 | return mControlSet.contains(control);
125 | }
126 |
127 | public boolean supportsStandard(STANDARD standard) {
128 | return mStandardSet.contains(standard);
129 | }
130 |
131 | @Override
132 | public String toString() {
133 | final String base = "VideoProcessingUnit{" +
134 | "Unit ID: " + getUnitID() +
135 | ", Source ID: " + getSourceID() +
136 | ", Max Multiplier: " + getMaxMultiplier() +
137 | ", Index Processing: " + getIndexProcessing() +
138 | ", Available Controls: ";
139 | StringBuilder builder = new StringBuilder(base);
140 | /*for (Control control : mControlSet) {
141 | builder.append(control).append(',');
142 | }
143 | builder.deleteCharAt(builder.length() - 1);
144 | builder.append(", Supported Standards: ");
145 | for (STANDARD standard : mStandardSet) {
146 | builder.append(standard).append(',');
147 | }
148 | builder.deleteCharAt(builder.length() - 1);*/
149 | builder.append('}');
150 | return builder.toString();
151 | }
152 |
153 | public static enum CONTROL {
154 | BRIGHTNESS,
155 | CONTRAST,
156 | HUE,
157 | SATURATION,
158 | SHARPNESS,
159 | GAMMA,
160 | WHITE_BALANCE_TEMPERATURE,
161 | WHITE_BALANCE_COMPONENT,
162 | BACKLIGHT_COMPENSATION,
163 | GAIN,
164 | POWER_LINE_FREQUENCY,
165 | HUE_AUTO,
166 | WHITE_BALANCE_TEMPERATURE_AUTO,
167 | WHITE_BALANCE_COMPONENT_AUTO,
168 | DIGITAL_MULTIPLIER,
169 | DIGITAL_MULTIPLIER_LIMIT,
170 | ANALOG_VIDEO_STANDARD,
171 | ANALOG_VIDEO_LOCK_STATUS,
172 | CONTRAST_AUTO,
173 | RESERVED_0,
174 | RESERVED_1,
175 | RESERVED_2,
176 | RESERVED_3,
177 | RESERVED_4;
178 |
179 | public static CONTROL controlFromIndex(int i) {
180 | final CONTROL[] values = CONTROL.values();
181 | if (i > values.length || i < 0) {
182 | return null;
183 | }
184 | for (CONTROL control : values) {
185 | if (control.ordinal() == i) {
186 | return control;
187 | }
188 | }
189 | return null;
190 | }
191 | }
192 |
193 | public static enum STANDARD {
194 | NONE,
195 | NTSC_525_60,
196 | PAL_625_50,
197 | SECAM_625_50,
198 | NTSC_625_50,
199 | PAL_525_60,
200 | RESERVED_0,
201 | RESERVED_1;
202 |
203 | public static STANDARD standardFromIndex(int i) {
204 | final STANDARD[] values = STANDARD.values();
205 | if (i > values.length || i < 0) {
206 | return null;
207 | }
208 | for (STANDARD control : values) {
209 | if (control.ordinal() == i) {
210 | return control;
211 | }
212 | }
213 | return null;
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/units/VideoSelectorUnit.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.units;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 | import java.util.Arrays;
5 |
6 | /**
7 | * The Selector Unit (SU) selects from n input data streams and routes them unaltered to the single output stream. It
8 | * represents a source selector, capable of selecting among a number of sources. It has an Input Pin for each source
9 | * stream and a single Output Pin
10 | *
11 | * @author Jared Woolston (Jared.Woolston@gmail.com)
12 | * @see UVC 1.5 Class
13 | * Specification §2.3.4
14 | */
15 | public class VideoSelectorUnit extends VideoUnit {
16 |
17 | private static final int MIN_LENGTH = 6;
18 |
19 | private static final int bNrInPins = 4;
20 | private static final int baSourceID = 5;
21 | private static final int iSelector = 5;
22 |
23 | private final int numInPins;
24 | private final int[] sourceIDs;
25 | private final int selectorValue;
26 |
27 | public static boolean isVideoSelectorUnit(byte[] descriptor) {
28 | return (descriptor.length >= MIN_LENGTH
29 | & descriptor[bDescriptorSubtype] == VideoClassInterface.VC_INF_SUBTYPE.VC_SELECTOR_UNIT.subtype);
30 | }
31 |
32 | public VideoSelectorUnit(byte[] descriptor) throws IllegalArgumentException {
33 | super(descriptor);
34 | if (descriptor.length < MIN_LENGTH) {
35 | throw new IllegalArgumentException(
36 | "The provided descriptor is not long enough to be a video selector unit.");
37 | }
38 | numInPins = descriptor[bNrInPins];
39 | sourceIDs = new int[numInPins];
40 | for (int i = 0; i < numInPins; ++i) {
41 | sourceIDs[i] = descriptor[baSourceID + i];
42 | }
43 | selectorValue = descriptor[iSelector + numInPins];
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return "VideoSelectorUnit{" +
49 | "Unit ID: " + getUnitID() +
50 | ", numInPins=" + numInPins +
51 | ", sourceIDs=" + Arrays.toString(sourceIDs) +
52 | ", selectorValue=" + selectorValue +
53 | '}';
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/interfaces/units/VideoUnit.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.interfaces.units;
2 |
3 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
4 | import com.jwoolston.android.uvc.interfaces.Descriptor.Type;
5 |
6 | /**
7 | * @author Jared Woolston (Jared.Woolston@gmail.com)
8 | */
9 | public class VideoUnit {
10 |
11 | protected static final int bLength = 0;
12 | protected static final int bDescriptorType = 1;
13 | protected static final int bDescriptorSubtype = 2;
14 | protected static final int bUnitID = 3;
15 |
16 | private final int mUnitID;
17 |
18 | public static boolean isVideoUnit(byte[] descriptor) {
19 | if (descriptor[bDescriptorType] != Type.CS_INTERFACE.type) return false;
20 | final byte subtype = descriptor[bDescriptorSubtype];
21 | return (subtype == VideoClassInterface.VC_INF_SUBTYPE.VC_SELECTOR_UNIT.subtype ||
22 | subtype == VideoClassInterface.VC_INF_SUBTYPE.VC_PROCESSING_UNIT.subtype ||
23 | subtype == VideoClassInterface.VC_INF_SUBTYPE.VC_ENCODING_UNIT.subtype ||
24 | subtype == VideoClassInterface.VC_INF_SUBTYPE.VC_EXTENSION_UNIT.subtype);
25 | }
26 |
27 | protected VideoUnit(byte[] descriptor) throws IllegalArgumentException {
28 | if (descriptor.length < 4) throw new IllegalArgumentException("The provided descriptor is not long enough for an arbitrary video unit.");
29 | mUnitID = descriptor[bUnitID];
30 | }
31 |
32 | public int getUnitID() {
33 | return mUnitID;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/Request.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests;
2 |
3 | /**
4 | * @author Jared Woolston (Jared.Woolston@gmail.com)
5 | * @see UVC 1.5 Class
6 | * Specification §A.8
7 | */
8 | public enum Request {
9 |
10 | RC_UNDEFINED((byte) 0x00),
11 | SET_CUR((byte) 0x01),
12 | SET_CUR_ALL((byte) 0x11),
13 | GET_CUR((byte) 0x81),
14 | GET_MIN((byte) 0x82),
15 | GET_MAX((byte) 0x83),
16 | GET_RES((byte) 0x84),
17 | GET_LEN((byte) 0x85),
18 | GET_INFO((byte) 0x86),
19 | GET_DEF((byte) 0x87),
20 | GET_CUR_ALL((byte) 0x91),
21 | GET_MIN_ALL((byte) 0x92),
22 | GET_MAX_ALL((byte) 0x93),
23 | GET_RES_ALL((byte) 0x94),
24 | GET_DEF_ALL((byte) 0x97);
25 |
26 | public final byte code;
27 |
28 | Request(byte code) {
29 | this.code = code;
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return (name() + "(0x" + Integer.toHexString(0xFF & code) + ')');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/VideoClassRequest.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.interfaces.VideoClassInterface;
6 | import com.jwoolston.android.uvc.interfaces.terminals.VideoTerminal;
7 |
8 | import java.util.Locale;
9 |
10 | /**
11 | * @author Jared Woolston (Jared.Woolston@gmail.com)
12 | * @see UVC 1.5 Class
13 | * Specification §4
14 | */
15 | public abstract class VideoClassRequest {
16 |
17 | protected static final byte SET_REQUEST_INF_ENTITY = 0x21; // Set Request type targeting entity or interface
18 | protected static final byte SET_REQUEST_ENDPOINT = 0x22; // Set Request type targeting endpoint
19 | protected static final byte GET_REQUEST_INF_ENTITY = (byte) 0xA1; // Get Request type targeting entity or interface
20 | protected static final byte GET_REQUEST_ENDPOINT = (byte) 0xA2; // Get Request type targeting endpoint
21 |
22 | private final byte requestType;
23 | private final Request request;
24 |
25 | private short wValue;
26 | private short wIndex;
27 | private byte[] data;
28 |
29 | protected static short getIndex(VideoTerminal terminal, VideoClassInterface classInterface) {
30 | return (short) (((0xFF & terminal.getTerminalID()) << 8) | (0xFF & classInterface.getUsbInterface().getId()));
31 | }
32 |
33 | protected VideoClassRequest(byte requestType, @NonNull Request request, short value, short index,
34 | @NonNull byte[] data) {
35 | this.requestType = requestType;
36 | this.request = request;
37 | wValue = value;
38 | wIndex = index;
39 | this.data = data;
40 | }
41 |
42 | public byte getRequestType() {
43 | return requestType;
44 | }
45 |
46 | public byte getRequest() {
47 | return request.code;
48 | }
49 |
50 | public short getValue() {
51 | return wValue;
52 | }
53 |
54 | public short getIndex() {
55 | return wIndex;
56 | }
57 |
58 | public byte[] getData() {
59 | return data;
60 | }
61 |
62 | public int getLength() {
63 | return data.length;
64 | }
65 |
66 | @Override public String toString() {
67 | final StringBuffer sb = new StringBuffer(getClass().getSimpleName());
68 | sb.append("{");
69 | sb.append("requestType=0x").append(Integer.toHexString(0xFF & requestType).toUpperCase(Locale.US));
70 | sb.append(", request=").append(request);
71 | sb.append(", wValue=0x").append(Integer.toHexString(wValue).toUpperCase(Locale.US));
72 | sb.append(", wIndex=").append(wIndex);
73 | sb.append(", data=");
74 | if (data == null) {
75 | sb.append("null");
76 | } else {
77 | sb.append('[');
78 | for (int i = 0; i < data.length; ++i) {
79 | sb.append(i == 0 ? "" : ", ").append(data[i]);
80 | }
81 | sb.append(']');
82 | }
83 | sb.append('}');
84 | return sb.toString();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/PowerModeControl.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control;
2 |
3 | import static com.jwoolston.android.uvc.requests.control.VCInterfaceControlRequest.ControlSelector.VC_VIDEO_POWER_MODE_CONTROL;
4 |
5 | import android.support.annotation.NonNull;
6 | import com.jwoolston.android.uvc.interfaces.VideoControlInterface;
7 | import com.jwoolston.android.uvc.requests.Request;
8 |
9 | /**
10 | * This control sets the device power mode. Power modes are defined in the following table.
11 | *
12 | * Table 4-5 Power Mode Control
13 | *
14 | *
15 | * Device Power Mode |
16 | * Description |
17 | *
18 | *
19 | * Full Power Mode |
20 | * Device operates at full functionality in this mode. For example, the device can stream video data via USB, and
21 | * can
22 | * execute all requests that are supported by the device. This mode is mandatory, even if the device doesn’t support
23 | * VIDEO POWER MODE CONTROL. |
24 | *
25 | *
26 | * Vendor-Dependent
27 | * Power Mode |
28 | * Device operates in low power mode. In this mode, the device continues to operate, although not at full
29 | * functionality.
30 | * For example, as the result of setting the device to this power mode, the device will stop the Zoom function. To
31 | * avoid confusing the user, the device should issue an interrupt (GET_INFO) to notify the user that the Zoom
32 | * function is disabled.
33 | * In this mode, the device can stream video data, the functionality of USB is not affected, and the device can
34 | * execute all requests that it supports.
35 | * This mode is optional. |
36 | *
37 | *
38 | *
39 | * The power mode that is supported by the device must be passed to the host, as well as the power source, since if
40 | * the device is working with battery power, the host can change the device power mode to “vendor-dependent power
41 | * mode” to reduce power consumption.
42 | * Information regarding power modes and power sources is communicated through the following bit fields. D7..D5
43 | * indicates which power source is currently used in the device. The D4 indicates that the device supports
44 | * “vendor-dependent power mode”. Bits D7..D4 are set by the device and are read-only. The host can change the device
45 | * power mode by setting a combination of D3..D0.
46 | * The host can update the power mode during video streaming.
47 | * The D3..D0 value of 0000B indicates that the device is in, or should transition to, full power mode. The D3..D0
48 | * value of 0001B indicates that the device is in, or should transition to, vendor-dependent power mode.
49 | * The host must specify D3..D0 only when the power mode is required to switch, and the other fields must be set to 0.
50 | *
51 | * @author Jared Woolston (Jared.Woolston@gmail.com)
52 | * @see UVC 1.5 Class
53 | * Specification §4.2.1.1
54 | */
55 | public class PowerModeControl extends VCInterfaceControlRequest {
56 |
57 | private static final byte SET_MASK = 0x0F;
58 | private static final byte GET_MASK = (byte) 0xF0;
59 | private static final byte FULL_POWER_MODE = 0x00;
60 | private static final byte VENDOR_SPECIFIC_MODE = 0x01;
61 |
62 | @NonNull
63 | public static PowerModeControl setFullPowerMode(@NonNull VideoControlInterface controlInterface) {
64 | return new PowerModeControl(Request.SET_CUR, (short) (0xFF & controlInterface.getInterfaceNumber()),
65 | new byte[] { FULL_POWER_MODE });
66 | }
67 |
68 | @NonNull
69 | public static PowerModeControl setVendorPowerMode(@NonNull VideoControlInterface controlInterface) {
70 | return new PowerModeControl(Request.SET_CUR, (short) (0xFF & controlInterface.getInterfaceNumber()),
71 | new byte[] { VENDOR_SPECIFIC_MODE });
72 | }
73 |
74 | @NonNull
75 | public static PowerModeControl getCurrentPowerMode(@NonNull VideoControlInterface controlInterface) {
76 | return new PowerModeControl(Request.GET_CUR, (short) (0xFF & controlInterface.getInterfaceNumber()),
77 | new byte[1]);
78 | }
79 |
80 | @NonNull
81 | public static PowerModeControl getInfoPowerMode(@NonNull VideoControlInterface controlInterface) {
82 | return new PowerModeControl(Request.GET_INFO, (short) (0xFF & controlInterface.getInterfaceNumber()),
83 | new byte[1]);
84 | }
85 |
86 | private PowerModeControl(@NonNull Request request, short index, @NonNull byte[] data) {
87 | super(request, VC_VIDEO_POWER_MODE_CONTROL, index, data);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/RequestErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control;
2 |
3 | import static com.jwoolston.android.uvc.requests.control.VCInterfaceControlRequest.ControlSelector.VC_REQUEST_ERROR_CODE_CONTROL;
4 |
5 | import android.support.annotation.NonNull;
6 | import com.jwoolston.android.uvc.interfaces.VideoControlInterface;
7 | import com.jwoolston.android.uvc.requests.Request;
8 |
9 | /**
10 | * This read-only control indicates the status of each host-initiated request to a Terminal, Unit, interface or
11 | * endpoint of the video function. If the device is unable to fulfill the request, it will indicate a stall on the
12 | * control pipe and update this control with the appropriate code to indicate the cause. This control will be reset
13 | * to 0 (No error) upon the successful completion of any control request (including requests to this control). The
14 | * table below specifies the bRequestErrorCode error codes that the device must return from a
15 | * VC_REQUEST_ERROR_CODE_CONTROL request. Asynchronous control requests are a special case, where the initial request
16 | * will update this control, but the final result is delivered via the Status Interrupt Endpoint (see sections
17 | * 2.4.2.2, "Status Interrupt Endpoint" and 2.4.4, "Control Transfer and Request Processing").
18 | *
19 | * 0x00: No error
20 | * 0x01: Not ready
21 | * 0x02: Wrong state
22 | * 0x03: Power
23 | * 0x04: Out of range
24 | * 0x05: Invalid unit
25 | * 0x06: Invalid control
26 | * 0x07: Invalid Request
27 | * 0x08: Invalid value within range
28 | * 0x09-0xFE: Reserved for future use
29 | * 0xFF: Unknown
30 | *
31 | * -No error: The request succeeded.
32 | * -Not ready: The device has not completed a previous operation. The device will recover from this state as soon as
33 | * the previous operation has completed.
34 | * -Wrong State: The device is in a state that disallows the specific request. The device will remain in this state
35 | * until a specific action from the host or the user is completed.
36 | * -Power: The actual Power Mode of the device is not sufficient to complete the Request.
37 | * -Out of Range: Result of a SET_CUR Request when attempting to set a value outside of the MIN and MAX range, or a
38 | * value that does not satisfy the constraint on resolution (see section 4.2.2, “Unit and Terminal Control Requests”).
39 | * -Invalid Unit: The Unit ID addressed in this Request is not assigned.
40 | * -Invalid Control: The Control addressed by this Request is not supported.
41 | * -Invalid Request: This Request is not supported by the Control.
42 | * -Invalid value with range: Results of a SET_CUR Request when attempting to set a value that is inside the MIN and
43 | * MAX range but is not supported.
44 | *
45 | * @author Jared Woolston (Jared.Woolston@gmail.com)
46 | * @see UVC 1.5 Class
47 | * Specification §4.2.1.2
48 | */
49 | public class RequestErrorCode extends VCInterfaceControlRequest {
50 |
51 | @NonNull
52 | public static RequestErrorCode getCurrentErrorCode(@NonNull VideoControlInterface controlInterface) {
53 | return new RequestErrorCode(Request.GET_CUR, (short) (0xFF & controlInterface.getInterfaceNumber()),
54 | new byte[1]);
55 | }
56 |
57 | @NonNull
58 | public static RequestErrorCode getInfoErrorCode(@NonNull VideoControlInterface controlInterface) {
59 | return new RequestErrorCode(Request.GET_INFO, (short) (0xFF & controlInterface.getInterfaceNumber()),
60 | new byte[1]);
61 | }
62 |
63 | private RequestErrorCode(@NonNull Request request, short index, @NonNull byte[] data) {
64 | super(request, VC_REQUEST_ERROR_CODE_CONTROL, index, data);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/UnitTerminalControlRequest.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.requests.Request;
6 | import com.jwoolston.android.uvc.requests.VideoClassRequest;
7 |
8 | /**
9 | * These used to set or read an attribute of a Control inside of a Unit or Terminal of the video function.
10 | *
11 | * The bRequest field indicates which attribute the request is manipulating. The MIN, MAX and RES attributes are not
12 | * supported for the Set request. The wValue field specifies the Control Selector (CS) in the high byte, and zero in the
13 | * low byte. The Control Selector indicates which type of Control this request is manipulating. When processing all
14 | * Controls as part of a batch request (GET_###_ALL), wValue is not needed and must be set to 0. If the request
15 | * specifies an unknown or unsupported CS to that Unit or Terminal, the control pipe must indicate a protocol STALL.
16 | * The value of wLength must be calculated as follows. Use wIndex to determine the Unit or Terminal of interest. For
17 | * that Unit or Terminal, establish which Controls are supported using the bmControls field of the associated Unit or
18 | * Terminal Descriptor. wLength is the sum of the length of all supported Controls for the target Unit or Terminal. The
19 | * Data must be ordered according to the order of the Controls listed in the bmControls field of the target Unit or
20 | * Terminal descriptor. If the Unit or Terminal supports batch requests, then each Control in the Unit or Terminal must
21 | * contribute to the Data field, even if it does not support the associated single operation request.
22 | *
23 | * If a Control supports GET_MIN, GET_MAX and GET_RES requests, the values of MAX, MIN and RES shall be constrained such
24 | * that (MAX-MIN)/RES is an integral number. Furthermore, the CUR value (returned by GET_CUR, or set via SET_CUR) shall
25 | * be constrained such that (CUR-MIN)/RES is an integral number. The device shall indicate protocol STALL and update the
26 | * Request Error Code Control with 0x04 “Out of Range” if an invalid CUR value is provided in a SET_CUR operation (see
27 | * section 2.4.4, “Control Transfer and Request Processing”).
28 | *
29 | * There are special Terminal types (such as the Camera Terminal and Media Transport Terminal) that have type-specific
30 | * Terminal Controls defined. The controls for the Media Transport Terminal are defined in a companion specification
31 | * (see the USB Device Class Definition for Video Media Transport Terminal specification). The controls for the Camera
32 | * Terminal are defined in the following sections.
33 | *
34 | * As this specification evolves, new controls in the Camera Terminal, Processing Unit, and Encoding Unit are added to
35 | * the list of associated Control Selectors at the end (Tables A-12 through A-14). However, in the sections below, the
36 | * description of the functionality is placed next to controls with associated functionality.
37 | *
38 | * @author Jared Woolston (Jared.Woolston@gmail.com)
39 | * @see UVC 1.5 Class
40 | * Specification §4.2.2
41 | */
42 | //TODO: all of the ***_ALL requests.
43 | public abstract class UnitTerminalControlRequest extends VideoClassRequest {
44 |
45 | protected UnitTerminalControlRequest(@NonNull Request request, short value, short index,
46 | @NonNull byte[] data) {
47 | super((request == Request.SET_CUR || request == Request.SET_CUR_ALL)
48 | ? SET_REQUEST_INF_ENTITY : GET_REQUEST_INF_ENTITY, request, value, index, data);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/VCInterfaceControlRequest.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.requests.Request;
6 | import com.jwoolston.android.uvc.requests.VideoClassRequest;
7 |
8 | /**
9 | * These requests are used to set or read an attribute of an interface Control inside the VideoControl interface of
10 | * the video function.
11 | *
12 | * The bRequest field indicates which attribute the request is manipulating. The MIN, MAX, and RES attributes are not
13 | * supported for the Set request. The wValue field specifies the Control Selector (CS) in the high byte, and the low
14 | * byte must be set to zero. The Control Selector indicates which type of Control this request is manipulating. If
15 | * the request specifies an unknown S to that endpoint, the control pipe must indicate a stall.
16 | *
17 | * @author Jared Woolston (Jared.Woolston@gmail.com)
18 | * @see UVC 1.5 Class
19 | * Specification §4.2.1
20 | */
21 | public abstract class VCInterfaceControlRequest extends VideoClassRequest {
22 |
23 | private static short valueFromControlSelector(@NonNull ControlSelector selector) {
24 | return ((short) (0xFFFF & (selector.code << 8)));
25 | }
26 |
27 | protected VCInterfaceControlRequest(@NonNull Request request, ControlSelector selector, short index,
28 | @NonNull byte[] data) {
29 | super(request == Request.SET_CUR ? SET_REQUEST_INF_ENTITY : GET_REQUEST_INF_ENTITY,
30 | request, valueFromControlSelector(selector), index, data);
31 | }
32 |
33 | public static enum ControlSelector {
34 | VC_CONTROL_UNDEFINED((byte) 0x00),
35 | VC_VIDEO_POWER_MODE_CONTROL((byte) 0x01),
36 | VC_REQUEST_ERROR_CODE_CONTROL((byte) 0x02),
37 | RESERVED((byte) 0x03);
38 |
39 | final byte code;
40 |
41 | ControlSelector(byte code) {
42 | this.code = code;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/camera/CameraTerminalControlRequest.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control.camera;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.interfaces.terminals.CameraTerminal;
6 | import com.jwoolston.android.uvc.requests.Request;
7 | import com.jwoolston.android.uvc.requests.control.UnitTerminalControlRequest;
8 |
9 | /**
10 | * @author Jared Woolston (Jared.Woolston@gmail.com)
11 | */
12 | class CameraTerminalControlRequest extends UnitTerminalControlRequest {
13 |
14 | private static short valueFromControlSelector(@NonNull CameraTerminal.Control selector) {
15 | return ((short) (0xFFFF & (selector.code << 8)));
16 | }
17 |
18 | protected CameraTerminalControlRequest(@NonNull Request request, short value, short index, @NonNull byte[] data) {
19 | super(request, value, index, data);
20 | }
21 |
22 | protected CameraTerminalControlRequest(@NonNull Request request, short index, @NonNull byte[] data) {
23 | this(request, (short) 0, index, data);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/control/camera/ScaningModeControl.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.control.camera;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.jwoolston.android.uvc.interfaces.VideoControlInterface;
6 | import com.jwoolston.android.uvc.interfaces.terminals.CameraTerminal;
7 | import com.jwoolston.android.uvc.requests.Request;
8 | import com.jwoolston.android.uvc.requests.VideoClassRequest;
9 |
10 | import static com.jwoolston.android.uvc.interfaces.terminals.CameraTerminal.Control.SCANNING_MODE;
11 |
12 | /**
13 | * The Scanning Mode Control setting is used to control the scanning mode of the camera sensor. A value of 0 indicates
14 | * that the interlace mode is enabled, and a value of 1 indicates that the progressive or the non-interlace mode is
15 | * enabled.
16 | *
17 | * @author Jared Woolston (Jared.Woolston@gmail.com)
18 | * @see UVC 1.5 Class
19 | * Specification §4.2.2.1.1
20 | */
21 | public class ScaningModeControl extends CameraTerminalControlRequest {
22 |
23 | private static final int Index_bScanningMode = 0; // 1 byte
24 |
25 | private static final byte BYTE_TRUE = 0x01;
26 | private static final byte BYTE_FALSE = 0x00;
27 |
28 | private final byte[] data;
29 |
30 | public static ScaningModeControl getCurrentScanningMode(@NonNull CameraTerminal terminal,
31 | @NonNull VideoControlInterface controlInterface) {
32 | return new ScaningModeControl(Request.GET_CUR, SCANNING_MODE.valueFromControl(),
33 | VideoClassRequest.getIndex(terminal, controlInterface), new byte[1]);
34 | }
35 |
36 | public static ScaningModeControl getScanningModeInfo(@NonNull CameraTerminal terminal,
37 | @NonNull VideoControlInterface controlInterface) {
38 | return new ScaningModeControl(Request.GET_INFO, SCANNING_MODE.valueFromControl(),
39 | VideoClassRequest.getIndex(terminal, controlInterface), new byte[1]);
40 | }
41 |
42 | public static ScaningModeControl setCurrentScanningMode(@NonNull CameraTerminal terminal,
43 | @NonNull VideoControlInterface controlInterface,
44 | boolean progressive) {
45 | return new ScaningModeControl(Request.GET_CUR, SCANNING_MODE.valueFromControl(),
46 | VideoClassRequest.getIndex(terminal, controlInterface),
47 | new byte[] { progressive ? BYTE_TRUE : BYTE_FALSE });
48 | }
49 |
50 | private ScaningModeControl(@NonNull Request request, short value, short index, @NonNull byte[] data) {
51 | super(request, value, index, data);
52 | this.data = data;
53 | }
54 |
55 | public boolean isProgressive() {
56 | return data[0] != BYTE_FALSE;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/streaming/FramingInfo.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.streaming;
2 |
3 | import java.util.BitSet;
4 |
5 | /**
6 | * Bitfield control supporting the following values:
7 | * - D0: If set to 1, the Frame ID (FID) field is required in the Payload Header (see description of D0 in section
8 | * 2.4.3.3, “Video and Still Image Payload Headers”). The sender is required to toggle the Frame ID at least every
9 | * dwMaxVideoFrameSize bytes.
10 | * - D1: If set to 1, indicates that the End of Frame (EOF) field may be present in the Payload Header (see
11 | * description of D1 in section 2.4.3.3, “Video and Still Image Payload Headers”). It is an error to specify this bit
12 | * without also specifying D0.
13 | * - D2: If set to 1, indicates that the End of Slice (EOS) field may be present in the Payload Header. It is an
14 | * error to specify this bit without also specifying D0.
15 | * - D7..3: Reserved (0)
16 | *
17 | * This control indicates to the function whether payload transfers will contain out-of-band framing information in
18 | * the Video Payload Header (see section 2.4.3.3, “Video and Still Image Payload Headers”).
19 | * For known frame-based formats (e.g., MJPEG, Uncompressed, DV), this field is ignored.
20 | * For known stream-based formats, this field allows the sender to indicate that it will identify segment boundaries
21 | * in the stream, enabling low-latency buffer handling by the receiver without the overhead of parsing the stream
22 | * itself.
23 | * When used in conjunction with an IN endpoint, this control is set by the device, and is read-only from the host.
24 | * When used in conjunction with an OUT endpoint, this parameter is set by the host, and is read-only from the device.
25 | *
26 | */
27 | public final class FramingInfo {
28 |
29 | private final int Index_frameIdRequired = 0;
30 | private final int Index_endOfFrameAllowed = 1;
31 | private final int Index_endOfSliceAllowed = 2;
32 |
33 | private final BitSet bitSet = new BitSet(8);
34 |
35 | public FramingInfo() {
36 |
37 | }
38 |
39 | public FramingInfo(byte raw) {
40 | for (int i = 0; i < 3; ++i) {
41 | bitSet.set(i, (raw & (0x01 << i)) == 1);
42 | }
43 | }
44 |
45 | byte getRaw() {
46 | byte value = 0;
47 | for (int i = 0; i < 3; ++i) {
48 | value |= ((bitSet.get(i) ? 0x1 : 0x0) << i);
49 | }
50 | return value;
51 | }
52 |
53 | public boolean getFrameIdRequired() {
54 | return bitSet.get(Index_frameIdRequired);
55 | }
56 |
57 | public boolean getEndOfFrameAllowed() {
58 | return bitSet.get(Index_endOfFrameAllowed);
59 | }
60 |
61 | public boolean getEndOfSliceAllowed() {
62 | return bitSet.get(Index_endOfSliceAllowed);
63 | }
64 |
65 |
66 | public void setFrameIdRequired(boolean state) {
67 | bitSet.set(Index_frameIdRequired, state);
68 | }
69 |
70 | public void setEndOfFrameAllowed(boolean state) {
71 | if (state) {
72 | bitSet.set(Index_frameIdRequired, true);
73 | }
74 | bitSet.set(Index_endOfFrameAllowed, state);
75 | }
76 |
77 | public void setEndOfSliceAllowed(boolean state) {
78 | if (state) {
79 | bitSet.set(Index_frameIdRequired, true);
80 | }
81 | bitSet.set(Index_endOfSliceAllowed, state);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/streaming/Hint.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.streaming;
2 |
3 | import java.util.BitSet;
4 |
5 | /**
6 | * Bitfield control indicating to the function what fields shall be kept fixed (indicative only):
7 | * - D0: dwFrameInterval
8 | * - D1: wKeyFrameRate
9 | * - D2: wPFrameRate
10 | * - D3: wCompQuality
11 | * - D4: wCompWindowSize
12 | * - D15..5: Reserved (0)
13 | *
14 | * The hint bitmap indicates to the video streaming interface which fields shall be kept constant during stream
15 | * parameter negotiation. For example, if the selection wants to favor frame rate over quality, the
16 | * dwFrameInterval bit will be set (1).
17 | *
18 | * This field is set by the host, and is read-only for the video streaming interface.
19 | *
20 | */
21 | public final class Hint {
22 |
23 | private final int Index_dwFrameInterval = 0;
24 | private final int Index_wKeyFrameRate = 1;
25 | private final int Index_wPFrameRate = 2;
26 | private final int Index_wCompQuality = 3;
27 | private final int Index_wCompWindowSize = 4;
28 |
29 | private final BitSet bitSet = new BitSet(16);
30 |
31 | public Hint() {
32 |
33 | }
34 |
35 | public Hint(short raw) {
36 | for (int i = 0; i < 5; ++i) {
37 | bitSet.set(i, (raw & (0x01 << i)) == 1);
38 | }
39 | }
40 |
41 | short getRaw() {
42 | short value = 0;
43 | for (int i = 0; i < 5; ++i) {
44 | value |= ((bitSet.get(i) ? 0x1 : 0x0) << i);
45 | }
46 | return value;
47 | }
48 |
49 | public boolean getFrameInterval() {
50 | return bitSet.get(Index_dwFrameInterval);
51 | }
52 |
53 | public boolean getKeyFrameRate() {
54 | return bitSet.get(Index_wKeyFrameRate);
55 | }
56 |
57 | public boolean getPFrameRate() {
58 | return bitSet.get(Index_wPFrameRate);
59 | }
60 |
61 | public boolean getCompQuality() {
62 | return bitSet.get(Index_wCompQuality);
63 | }
64 |
65 | public boolean getCompWindowSize() {
66 | return bitSet.get(Index_wCompWindowSize);
67 | }
68 |
69 | public void setFrameInterval(boolean state) {
70 | bitSet.set(Index_dwFrameInterval, state);
71 | }
72 |
73 | public void setKeyFrameRate(boolean state) {
74 | bitSet.set(Index_wKeyFrameRate, state);
75 | }
76 |
77 | public void setPFrameRate(boolean state) {
78 | bitSet.set(Index_wPFrameRate, state);
79 | }
80 |
81 | public void setCompQuality(boolean state) {
82 | bitSet.set(Index_wCompQuality, state);
83 | }
84 |
85 | public void setCompWindowSize(boolean state) {
86 | bitSet.set(Index_wCompWindowSize, state);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/streaming/StillProbeControl.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.streaming;
2 |
3 | import static com.jwoolston.android.uvc.requests.streaming.VSInterfaceControlRequest.ControlSelector.VS_COMMIT_CONTROL;
4 |
5 | import android.support.annotation.IntRange;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Size;
8 | import com.jwoolston.android.uvc.interfaces.VideoStreamingInterface;
9 | import com.jwoolston.android.uvc.requests.Request;
10 | import java.nio.ByteBuffer;
11 |
12 | /**
13 | * @author Jared Woolston (Jared.Woolston@gmail.com)
14 | * @see UVC 1.5 Class
15 | * Specification §4.3.1.2
16 | */
17 | public class StillProbeControl extends VSInterfaceControlRequest {
18 |
19 | private static final int LENGTH_COMMIT_DATA = 11;
20 |
21 | private static final int Index_bFormatIndex = 0; // 1 byte
22 | private static final int Index_bFrameIndex = 1; // 1 byte
23 | private static final int Index_bCompressionIndex = 2; // 1 bytes
24 | private static final int Index_dwMaxVideoFrameSize = 3; // 4 bytes
25 | private static final int Index_dwMaxPayloadTransferSize = 7; // 4 bytes
26 |
27 | private final ByteBuffer wrapper;
28 |
29 | @NonNull
30 | public static StillProbeControl getCurrentCommit(@NonNull VideoStreamingInterface streamingInterface) {
31 | return new StillProbeControl(Request.GET_CUR, (short) (0xFF & streamingInterface.getInterfaceNumber()),
32 | new byte[LENGTH_COMMIT_DATA]);
33 | }
34 |
35 | @NonNull
36 | public static StillProbeControl getMinCommit(@NonNull VideoStreamingInterface streamingInterface) {
37 | return new StillProbeControl(Request.GET_MIN, (short) (0xFF & streamingInterface.getInterfaceNumber()),
38 | new byte[LENGTH_COMMIT_DATA]);
39 | }
40 |
41 | @NonNull
42 | public static StillProbeControl getMaxCommit(@NonNull VideoStreamingInterface streamingInterface) {
43 | return new StillProbeControl(Request.GET_MAX, (short) (0xFF & streamingInterface.getInterfaceNumber()),
44 | new byte[LENGTH_COMMIT_DATA]);
45 | }
46 |
47 | @NonNull
48 | public static StillProbeControl getDefaultCommit(@NonNull VideoStreamingInterface streamingInterface) {
49 | return new StillProbeControl(Request.GET_DEF, (short) (0xFF & streamingInterface.getInterfaceNumber()),
50 | new byte[LENGTH_COMMIT_DATA]);
51 | }
52 |
53 | @NonNull
54 | public static StillProbeControl getLengthCommit(@NonNull VideoStreamingInterface streamingInterface) {
55 | return new StillProbeControl(Request.GET_LEN, (short) (0xFF & streamingInterface.getInterfaceNumber()),
56 | new byte[LENGTH_COMMIT_DATA]);
57 | }
58 |
59 | @NonNull
60 | public static StillProbeControl getInfoCommit(@NonNull VideoStreamingInterface streamingInterface) {
61 | return new StillProbeControl(Request.GET_INFO, (short) (0xFF & streamingInterface.getInterfaceNumber()),
62 | new byte[LENGTH_COMMIT_DATA]);
63 | }
64 |
65 | @NonNull
66 | public static StillProbeControl setCurrentCommit(@NonNull VideoStreamingInterface streamingInterface) {
67 | return new StillProbeControl(Request.SET_CUR, (short) (0xFF & streamingInterface.getInterfaceNumber()),
68 | new byte[LENGTH_COMMIT_DATA]);
69 | }
70 |
71 | private StillProbeControl(@NonNull Request request, short index, @NonNull @Size(value = LENGTH_COMMIT_DATA) byte[] data) {
72 | super(request, VS_COMMIT_CONTROL, index, data);
73 | wrapper = ByteBuffer.wrap(data);
74 | }
75 |
76 | public void setFormatIndex(@IntRange(from = 0, to = 7) int index) {
77 | wrapper.put(Index_bFormatIndex, (byte) (0xFF & index));
78 | }
79 |
80 | public void setFrameIndex(@IntRange(from = 0, to = 7) int index) {
81 | wrapper.put(Index_bFrameIndex, (byte) (0xFF & index));
82 | }
83 |
84 | public void setMaxVideoFrameSize(@IntRange(from = 0) long size) {
85 | wrapper.putInt(Index_dwMaxVideoFrameSize, (int) size);
86 | }
87 |
88 | public void setMaxPayloadTransferSize(@IntRange(from =0) long size) {
89 | wrapper.putInt(Index_dwMaxPayloadTransferSize, (int) size);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/streaming/Usage.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.streaming;
2 |
3 | /**
4 | * This bitmap enables features reported by the bmUsages field of the Video Frame Descriptor.
5 | * For temporally encoded video formats, this field must be supported, even if the device only supports a single
6 | * value for bUsage.
7 | *
8 | * @author Jared Woolston (Jared.Woolston@gmail.com)
9 | */
10 | public enum Usage {
11 | REAL_TIME,
12 | BROADCAST,
13 | FILE_STORAGE,
14 | MULTIVIEW,
15 | RESERVED;
16 |
17 | public int getValue() {
18 | switch (ordinal()) {
19 | case 0:
20 | return 1;
21 | case 1:
22 | return 9;
23 | case 2:
24 | return 17;
25 | case 3:
26 | return 25;
27 | case 4:
28 | default:
29 | return 32;
30 | }
31 | }
32 |
33 | public static final Usage fromValue(int value) {
34 | if (value < 32) {
35 | if (value < 25) {
36 | if (value < 17) {
37 | if (value < 9) {
38 | return REAL_TIME;
39 | } else {
40 | return BROADCAST;
41 | }
42 | } else {
43 | return FILE_STORAGE;
44 | }
45 | } else {
46 | return MULTIVIEW;
47 | }
48 | } else {
49 | return RESERVED;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/requests/streaming/VSInterfaceControlRequest.java:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.requests.streaming;
2 |
3 | import android.support.annotation.NonNull;
4 | import com.jwoolston.android.uvc.requests.Request;
5 | import com.jwoolston.android.uvc.requests.VideoClassRequest;
6 |
7 | /**
8 | * These requests are used to set or read an attribute of an interface Control inside the VideoStreaming interface of
9 | * the video function.
10 | *
11 | * The bRequest field indicates which attribute the request is manipulating. The MIN, MAX, and RES attributes are not
12 | * supported for the Set request. The wValue field specifies the Control Selector (CS) in the high byte, and the low
13 | * byte must be set to zero. The Control Selector indicates which type of Control this request is manipulating. If
14 | * the request specifies an unknown S to that endpoint, the control pipe must indicate a stall.
15 | *
16 | * @author Jared Woolston (Jared.Woolston@gmail.com)
17 | * @see UVC 1.5 Class
18 | * Specification §4.2.1
19 | */
20 | public abstract class VSInterfaceControlRequest extends VideoClassRequest {
21 |
22 | private static short valueFromControlSelector(@NonNull ControlSelector selector) {
23 | return ((short) (0xFFFF & (selector.code << 8)));
24 | }
25 |
26 | protected VSInterfaceControlRequest(@NonNull Request request, ControlSelector selector, short index,
27 | @NonNull byte[] data) {
28 | super(request == Request.SET_CUR ? SET_REQUEST_INF_ENTITY : GET_REQUEST_INF_ENTITY,
29 | request, valueFromControlSelector(selector), index, data);
30 | }
31 |
32 | public static enum ControlSelector {
33 | VS_CONTROL_UNDEFINED((byte) 0x00),
34 | VS_PROBE_CONTROL((byte) 0x01),
35 | VS_COMMIT_CONTROL((byte) 0x02),
36 | VS_STILL_PROBE_CONTROL((byte) 0x03),
37 | VS_STILL_COMMIT_CONTROL((byte) 0x04),
38 | VS_STILL_IMAGE_TRIGGER_CONTROL((byte) 0x05),
39 | VS_STREAM_ERROR_CODE_CONTROL((byte) 0x06),
40 | VS_GENERATE_KEY_FRAME_CONTROL((byte) 0x07),
41 | VS_UPDATE_FRAME_SEGMENT_CONTROL((byte) 0x08),
42 | VS_SYNC_DELAY_CONTROL((byte) 0x09);
43 |
44 | final byte code;
45 |
46 | ControlSelector(byte code) {
47 | this.code = code;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/util/ArrayTools.kt:
--------------------------------------------------------------------------------
1 | package com.jwoolston.android.uvc.util
2 |
3 | import android.support.annotation.IntRange
4 |
5 | /**
6 | * @author Jared Woolston (Jared.Woolston@gmail.com)
7 | */
8 | class ArrayTools private constructor() {
9 |
10 | companion object {
11 |
12 | @JvmStatic
13 | fun extractShort(array: ByteArray, @IntRange(from = 0) offset: Int): Short {
14 | return (((0xFF and array[offset + 1].toInt()) shl 8) or (0xFF and array[offset].toInt())).toShort()
15 | }
16 |
17 | @JvmStatic
18 | fun extractInteger(array: ByteArray, @IntRange(from = 0) offset: Int): Int {
19 | return (((0xFF and array[offset + 3].toInt()) shl 24) or ((0xFF and array[offset + 2].toInt()) shl 16)
20 | or ((0xFF and array[offset + 1].toInt()) shl 8) or (0xFF and array[offset].toInt()))
21 | }
22 |
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jwoolston/android/uvc/util/Hexdump.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2006 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jwoolston.android.uvc.util;
18 |
19 | /**
20 | * Clone of Android's HexDump class, for use in debugging. Cosmetic changes
21 | * only.
22 | *
23 | * @author Jared Woolston (Jared.Woolston@gmail.com)
24 | */
25 | public class Hexdump {
26 | private final static char[] HEX_DIGITS = {
27 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
28 | };
29 |
30 | public static String dumpHexString(byte[] array) {
31 | return dumpHexString(array, 0, array.length);
32 | }
33 |
34 | public static String dumpHexString(byte[] array, int offset, int length) {
35 | StringBuilder result = new StringBuilder();
36 |
37 | byte[] line = new byte[16];
38 | int lineIndex = 0;
39 |
40 | result.append("\n0x");
41 | result.append(toHexString(offset));
42 |
43 | for (int i = offset; i < offset + length; i++) {
44 | if (lineIndex == 16) {
45 | result.append(" ");
46 |
47 | for (int j = 0; j < 16; j++) {
48 | if (line[j] > ' ' && line[j] < '~') {
49 | result.append(new String(line, j, 1));
50 | } else {
51 | result.append(".");
52 | }
53 | }
54 |
55 | result.append("\n0x");
56 | result.append(toHexString(i));
57 | lineIndex = 0;
58 | }
59 |
60 | byte b = array[i];
61 | result.append(" ");
62 | result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
63 | result.append(HEX_DIGITS[b & 0x0F]);
64 |
65 | line[lineIndex++] = b;
66 | }
67 |
68 | if (lineIndex != 16) {
69 | int count = (16 - lineIndex) * 3;
70 | count++;
71 | for (int i = 0; i < count; i++) {
72 | result.append(" ");
73 | }
74 |
75 | for (int i = 0; i < lineIndex; i++) {
76 | if (line[i] > ' ' && line[i] < '~') {
77 | result.append(new String(line, i, 1));
78 | } else {
79 | result.append(".");
80 | }
81 | }
82 | }
83 |
84 | return result.toString();
85 | }
86 |
87 | public static String toHexString(byte b) {
88 | return toHexString(toByteArray(b));
89 | }
90 |
91 | public static String toHexString(byte[] array) {
92 | return toHexString(array, 0, array.length);
93 | }
94 |
95 | public static String toHexString(byte[] array, int offset, int length) {
96 | char[] buf = new char[length * 2];
97 |
98 | int bufIndex = 0;
99 | for (int i = offset; i < offset + length; i++) {
100 | byte b = array[i];
101 | buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
102 | buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
103 | }
104 |
105 | return new String(buf);
106 | }
107 |
108 | public static String toHexString(int i) {
109 | return toHexString(toByteArray(i));
110 | }
111 |
112 | public static byte[] toByteArray(byte b) {
113 | byte[] array = new byte[1];
114 | array[0] = b;
115 | return array;
116 | }
117 |
118 | public static byte[] toByteArray(int i) {
119 | byte[] array = new byte[4];
120 |
121 | array[3] = (byte) (i & 0xFF);
122 | array[2] = (byte) ((i >> 8) & 0xFF);
123 | array[1] = (byte) ((i >> 16) & 0xFF);
124 | array[0] = (byte) ((i >> 24) & 0xFF);
125 |
126 | return array;
127 | }
128 |
129 | private static int toByte(char c) {
130 | if (c >= '0' && c <= '9')
131 | return (c - '0');
132 | if (c >= 'A' && c <= 'F')
133 | return (c - 'A' + 10);
134 | if (c >= 'a' && c <= 'f')
135 | return (c - 'a' + 10);
136 |
137 | throw new RuntimeException("Invalid hex char '" + c + "'");
138 | }
139 |
140 | public static byte[] hexStringToByteArray(String hexString) {
141 | int length = hexString.length();
142 | byte[] buffer = new byte[length / 2];
143 |
144 | for (int i = 0; i < length; i += 2) {
145 | buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString
146 | .charAt(i + 1)));
147 | }
148 |
149 | return buffer;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/library/src/main/res/xml/webcam_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/private.gpg.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jwoolston/Android-Webcam/262361d884b4b8e098b13212ce428e3a99565005/private.gpg.enc
--------------------------------------------------------------------------------
/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 | apply plugin: 'signing'
3 |
4 | static String getTagName() { return System.getenv('CIRCLE_TAG') ?: "" }
5 |
6 | boolean isSnapshot() { return version.contains("SNAPSHOT") }
7 |
8 | static boolean isTag() { return !getTagName().isEmpty() }
9 |
10 | static boolean isCircle() { return System.getenv('CIRCLECI') ? true : false }
11 |
12 | static String buildNumber() { return System.getenv('CIRCLE_BUILD_NUM') ?: "0" }
13 |
14 | static String getRepositoryUsername() { return System.getenv('SONATYPE_USERNAME') ?: "" }
15 |
16 | static String getRepositoryPassword() { return System.getenv('SONATYPE_PASSWORD') ?: "" }
17 |
18 | static String getBranchName() { return System.getenv('CIRCLE_BRANCH') }
19 |
20 | boolean isRelease() { return isTag() && !isSnapshot() }
21 |
22 | afterEvaluate { project ->
23 |
24 | println "Tag ${getTagName()}"
25 | println "Branch ${getBranchName()}"
26 | println "Is Release ${isRelease()}"
27 | println "Is Circle ${isCircle()}"
28 | println "Has Username ${!getRepositoryUsername().empty}"
29 | println "Has Password ${!getRepositoryPassword().empty}"
30 | println "Version ${version}"
31 |
32 | uploadArchives {
33 | repositories {
34 | mavenDeployer {
35 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
36 |
37 | pom.artifactId = POM_ARTIFACT_ID
38 |
39 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
40 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
41 | }
42 |
43 | snapshotRepository(url: isCircle() ? "https://oss.sonatype.org/content/repositories/snapshots" : mavenLocal().url) {
44 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
45 | }
46 |
47 | pom.project {
48 | name POM_NAME
49 | packaging POM_PACKAGING
50 | description POM_DESCRIPTION
51 | url POM_URL
52 |
53 | scm {
54 | url POM_SCM_URL
55 | connection POM_SCM_CONNECTION
56 | developerConnection POM_SCM_DEV_CONNECTION
57 | }
58 |
59 | licenses {
60 | license {
61 | name POM_LICENCE_NAME
62 | url POM_LICENCE_URL
63 | distribution POM_LICENCE_DIST
64 | }
65 | }
66 |
67 | developers {
68 | developer {
69 | id POM_DEVELOPER_ID
70 | name POM_DEVELOPER_NAME
71 | email POM_DEVELOPER_EMAIL
72 | organization POM_DEVELOPER_ORGANIZATION
73 | organizationUrl POM_DEVELOPER_ORGANIZATION_URL
74 | }
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | signing {
82 | required false
83 | sign configurations.archives
84 | }
85 |
86 | /*task androidJavadocs(type: Javadoc) {
87 | source = android.sourceSets.main.java.srcDirs
88 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
89 | }
90 |
91 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
92 | classifier = 'javadoc'
93 | from androidJavadocs.destinationDir
94 | }
95 |
96 | task androidSourcesJar(type: Jar) {
97 | classifier = 'sources'
98 | from android.sourceSets.main.java.sourceFiles
99 | }*/
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------