├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jwoolston │ │ └── usb │ │ └── webcam │ │ └── app │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jwoolston │ │ └── usb │ │ └── webcam │ │ └── app │ │ ├── FormatPickerDialog.java │ │ ├── MainActivity.java │ │ ├── UsbApplication.kt │ │ └── VideoFormatSelected.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── drawable │ └── ic_exposure_black_24dp.xml │ ├── layout │ ├── activity_main.xml │ └── dialog_format_picker.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jwoolston │ │ └── android │ │ └── uvc │ │ ├── StreamCreationException.java │ │ ├── StreamManager.java │ │ ├── UnknownDeviceException.java │ │ ├── Webcam.java │ │ ├── WebcamConnection.java │ │ ├── WebcamImpl.java │ │ ├── WebcamManager.java │ │ ├── interfaces │ │ ├── Descriptor.java │ │ ├── InterfaceAssociationDescriptor.java │ │ ├── UvcInterface.java │ │ ├── VideoClassInterface.java │ │ ├── VideoControlInterface.java │ │ ├── VideoIAD.java │ │ ├── VideoStreamingInterface.java │ │ ├── endpoints │ │ │ ├── BulkEndpoint.java │ │ │ ├── Endpoint.java │ │ │ ├── InterruptEndpoint.java │ │ │ └── IsochronousEndpoint.java │ │ ├── streaming │ │ │ ├── AVideoStreamHeader.java │ │ │ ├── MJPEGVideoFormat.java │ │ │ ├── MJPEGVideoFrame.java │ │ │ ├── UncompressedVideoFormat.java │ │ │ ├── UncompressedVideoFrame.java │ │ │ ├── VideoColorMatchingDescriptor.java │ │ │ ├── VideoFormat.java │ │ │ ├── VideoFrame.java │ │ │ ├── VideoStreamInputHeader.java │ │ │ └── VideoStreamOutputHeader.java │ │ ├── terminals │ │ │ ├── CameraTerminal.java │ │ │ ├── VideoInputTerminal.java │ │ │ ├── VideoOutputTerminal.java │ │ │ └── VideoTerminal.java │ │ └── units │ │ │ ├── AVideoExtensionUnit.java │ │ │ ├── VideoEncodingUnit.java │ │ │ ├── VideoProcessingUnit.java │ │ │ ├── VideoSelectorUnit.java │ │ │ └── VideoUnit.java │ │ ├── requests │ │ ├── Request.java │ │ ├── VideoClassRequest.java │ │ ├── control │ │ │ ├── PowerModeControl.java │ │ │ ├── RequestErrorCode.java │ │ │ ├── UnitTerminalControlRequest.java │ │ │ ├── VCInterfaceControlRequest.java │ │ │ └── camera │ │ │ │ ├── CameraTerminalControlRequest.java │ │ │ │ └── ScaningModeControl.java │ │ └── streaming │ │ │ ├── FramingInfo.java │ │ │ ├── Hint.java │ │ │ ├── ProbeControl.java │ │ │ ├── StillProbeControl.java │ │ │ ├── Usage.java │ │ │ └── VSInterfaceControlRequest.java │ │ └── util │ │ ├── ArrayTools.kt │ │ └── Hexdump.java │ └── res │ └── xml │ └── webcam_filter.xml ├── private.gpg.enc ├── publish.gradle └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | working_directory: ~/repo 3 | docker: 4 | - image: toxicbakery/alpine-glibc-android:release-1.1.1 5 | environment: 6 | # Customize the JVM maximum heap limit 7 | JVM_OPTS: -Xmx3200m 8 | TERM: dumb 9 | 10 | version: 2 11 | jobs: 12 | build: 13 | <<: *defaults 14 | steps: 15 | - checkout 16 | - run: 17 | name: Execute Gradle 18 | command: | 19 | echo "org.gradle.daemon=false" >> gradle.properties 20 | if [ "master" = "$CIRCLE_BRANCH" ] || [ ! -z "$CIRCLE_TAG" ]; then 21 | echo "signing.keyId=${SIGNING_KEY}" >> "gradle.properties" 22 | echo "signing.password=${SIGNING_PASSWORD}" >> "gradle.properties" 23 | echo "signing.secretKeyRingFile=../private.gpg" >> "gradle.properties" 24 | openssl aes-256-cbc -d -in private.gpg.enc -out private.gpg -k $ENC_FILE_KEY 25 | ./gradlew build uploadArchives 26 | else 27 | ./gradlew build 28 | fi 29 | 30 | workflows: 31 | version: 2 32 | build: 33 | jobs: 34 | - build: 35 | context: Sonatype 36 | filters: 37 | tags: 38 | only: /.*/ 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | /*/build/ 3 | build 4 | 5 | # Crashlytics configuations 6 | com_crashlytics_export_strings.xml 7 | 8 | # Local configuration file (sdk path, etc) 9 | local.properties 10 | local.gradle 11 | 12 | # Gradle generated files 13 | .gradle/ 14 | 15 | # Signing files 16 | .signing/ 17 | private.gpg 18 | 19 | # User-specific configurations 20 | .idea/libraries 21 | .idea/workspace.xml 22 | .idea/tasks.xml 23 | .idea/datasources.xml 24 | .idea/dataSources.ids 25 | 26 | # OS-specific files 27 | .DS_Store 28 | .DS_Store? 29 | ._* 30 | .Spotlight-V100 31 | .Trashes 32 | ehthumbs.db 33 | Thumbs.db 34 | .idea/ 35 | library/.externalNativeBuild/ 36 | *.iml 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android-Webcam [![CircleCI](https://circleci.com/gh/jwoolston/Android-Webcam/tree/master.svg?style=svg)](https://circleci.com/gh/jwoolston/Android-Webcam/tree/master) 2 | ============== 3 | 4 | ## Overview 5 | This is an implementation of a USB webcam driver for Android which provides a pure Java interface. The goal of this project is to avoid the shortcomings of other available libraries and deliver cross device support for a spectrum of webcam devices with minimal "tuning" requirements. There is native C code under the hood but this is fully abstracted away by the library and binaries for all architectures are provided. 6 | 7 | ## Main Feature Set (Planned) 8 | 1. Dependency free Mavenized Android library with Java only interface 9 | 2. Support for most USB Host capable Android devices. We require the use of `UsbDeviceConnect.getRawDescriptors()` which was added in API 13 (Honeycomb 3.2) so this is the minimum API level. 10 | 3. Support for USB webcam implementations that adhere to the UVC 1.5 classification 11 | 4. Support for full range of device resolutions (240p and greater) 12 | 5. Simple integration to 3rd party applications with multiple implementation options dependant on use case requirements. 13 | 14 | ## NDK Usage 15 | Due to an unfortunate omission in Android's Java USB Host API, support for isochronous endpoints is non-existant. To get around this shortcoming, this library includes a straight copy of **libusb**, along with some custom JNI and Java wrappers inspired by SpecLad's repostiory [libusb-android](https://github.com/SpecLad/libusb-android). All platform ABIs are built and deployed with the Maven artifact to help with our commitment to cross device support. Use of this artifact is through a simple, straightforward Java interface - no handling of native code or JNI required. 16 | 17 | ## Building 18 | Due to the NDK requirement, building this project will require you to have the Android NDK on your machine. Setup of the NDK is left to the user. See [Google's NDK website](https://developer.android.com/tools/sdk/ndk/index.html) for more information on this. 19 | 20 | After setting up the NDK, you will need to reference it in your version of `local.properties` by declaring `ndk.dir`. 21 | 22 | Following this, the project should build successfully. 23 | 24 | ### Supporting Documentation 25 | 1. [UVC Class Article on Wikipedia](http://en.wikipedia.org/wiki/USB_video_device_class) 26 | 2. [UVC Class Specification](http://www.usb.org/developers/docs/devclass_docs/USB_Video_Class_1_5.zip) 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 27 6 | 7 | defaultConfig { 8 | applicationId "com.jwoolston.android.uvc.demo" 9 | minSdkVersion 16 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled true 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | debug { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 23 | } 24 | } 25 | 26 | lintOptions { 27 | abortOnError false 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_7 32 | targetCompatibility JavaVersion.VERSION_1_7 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: 'libs', include: ['*.jar']) 38 | implementation project(':library') 39 | 40 | implementation 'org.greenrobot:eventbus:3.1.1' 41 | implementation 'com.android.support:appcompat-v7:27.1.1' 42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 43 | implementation "com.jakewharton.timber:timber:$timber_version" 44 | 45 | testImplementation 'junit:junit:4.12' 46 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 47 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 48 | implementation 'com.android.support:design:27.1.1' 49 | } 50 | repositories { 51 | mavenCentral() 52 | } 53 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jwoolston/usb/webcam/app/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.jwoolston.usb.webcam.app; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/jwoolston/usb/webcam/app/FormatPickerDialog.java: -------------------------------------------------------------------------------- 1 | package com.jwoolston.usb.webcam.app; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.content.DialogInterface.OnClickListener; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v4.app.DialogFragment; 9 | import android.support.v7.app.AlertDialog; 10 | import android.widget.NumberPicker; 11 | import android.widget.NumberPicker.OnValueChangeListener; 12 | import java.util.Arrays; 13 | import org.greenrobot.eventbus.EventBus; 14 | import timber.log.Timber; 15 | 16 | /** 17 | * @author Jared Woolston (Jared.Woolston@gmail.com) 18 | */ 19 | public class FormatPickerDialog extends DialogFragment { 20 | 21 | public static final String ARGUMENT_VALUES = FormatPickerDialog.class.getCanonicalName() + ".ARGUMENT_VALUES"; 22 | public static final String ARGUMENT_INDICES = FormatPickerDialog.class.getCanonicalName() + ".ARGUMENT_INDICES"; 23 | 24 | public static final String TAG = "FormatPickerDialog"; 25 | 26 | private NumberPicker numberPicker; 27 | private String[] values; 28 | private int[] indices; 29 | private int currentIndex = 0; 30 | 31 | public FormatPickerDialog() {}; 32 | 33 | @Override 34 | public void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | 37 | final Bundle args = getArguments(); 38 | if (args == null) { 39 | throw new IllegalStateException("An arguments bundle must be provided."); 40 | } 41 | values = args.getStringArray(ARGUMENT_VALUES); 42 | indices = args.getIntArray(ARGUMENT_INDICES); 43 | Timber.d("Displayed values: %s", Arrays.toString(values)); 44 | } 45 | 46 | @Override 47 | public void onStart() { 48 | super.onStart(); 49 | setCancelable(false); 50 | 51 | numberPicker = (NumberPicker) getDialog().findViewById(R.id.number_picker); 52 | numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); 53 | numberPicker.setDisplayedValues(values); 54 | numberPicker.setMinValue(getMinimumValue()); 55 | numberPicker.setMaxValue(getMaximumValue()); 56 | numberPicker.setValue(getInitialValue()); 57 | numberPicker.setWrapSelectorWheel(false); 58 | numberPicker.setOnValueChangedListener(new OnValueChangeListener() { 59 | @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 60 | currentIndex = newVal; 61 | } 62 | }); 63 | } 64 | 65 | @NonNull 66 | @Override 67 | public Dialog onCreateDialog(Bundle savedInstanceState) { 68 | return new AlertDialog.Builder(getContext()).setTitle("Available Formats") 69 | .setMessage("Please pick from the available video formats.") 70 | .setView(R.layout.dialog_format_picker) 71 | .setPositiveButton(android.R.string.ok, new OnClickListener() { 72 | @Override public void onClick(DialogInterface dialog, int which) { 73 | EventBus.getDefault().post(new VideoFormatSelected(indices[currentIndex])); 74 | } 75 | }) 76 | .create(); 77 | } 78 | 79 | 80 | protected int getInitialValue() { 81 | return currentIndex; 82 | } 83 | 84 | protected int getMinimumValue() { 85 | return 0; 86 | } 87 | 88 | protected int getMaximumValue() { 89 | return values.length - 1; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/jwoolston/usb/webcam/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jwoolston.usb.webcam.app; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.hardware.usb.UsbDevice; 8 | import android.hardware.usb.UsbManager; 9 | import android.os.Bundle; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.widget.Toast; 12 | import com.jwoolston.android.libusb.DevicePermissionDenied; 13 | import com.jwoolston.android.uvc.StreamCreationException; 14 | import com.jwoolston.android.uvc.UnknownDeviceException; 15 | import com.jwoolston.android.uvc.Webcam; 16 | import com.jwoolston.android.uvc.WebcamManager; 17 | import com.jwoolston.android.uvc.interfaces.streaming.VideoFormat; 18 | import java.util.List; 19 | import org.greenrobot.eventbus.EventBus; 20 | import org.greenrobot.eventbus.Subscribe; 21 | import org.greenrobot.eventbus.ThreadMode; 22 | import timber.log.Timber; 23 | 24 | public class MainActivity extends AppCompatActivity { 25 | 26 | private static final String KEY_OPEN_DEVICE = MainActivity.class.getCanonicalName() + ".KEY_OPEN_DEVICE"; 27 | 28 | private UsbDevice openDevice; 29 | 30 | private Webcam webcam; 31 | private List 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 | * 16 | * 17 | * 18 | * 19 | * 20 | * 24 | * 25 | * 26 | * 28 | * 36 | * 37 | *
Device Power ModeDescription
Full Power ModeDevice 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.
Vendor-Dependent 27 | * Power ModeDevice 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.
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 | --------------------------------------------------------------------------------