├── .gitignore ├── LICENSE ├── README.md ├── aars └── common-4.1.1.aar ├── app-release.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jiangdg │ │ └── usbcamera │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── .classpath │ ├── .project │ ├── .settings │ │ ├── org.eclipse.core.resources.prefs │ │ ├── org.eclipse.core.runtime.prefs │ │ └── org.eclipse.jdt.core.prefs │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jiangdg │ │ │ └── usbcamera │ │ │ ├── application │ │ │ └── MyApplication.java │ │ │ ├── utils │ │ │ └── CrashHandler.java │ │ │ └── view │ │ │ ├── SplashActivity.java │ │ │ └── USBCameraActivity.java │ ├── project.properties │ └── res │ │ ├── layout │ │ ├── activity_splash.xml │ │ ├── activity_usbcamera.xml │ │ └── layout_dialog_list.xml │ │ ├── menu │ │ └── main_toobar.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jiangdg │ └── usbcamera │ └── ExampleUnitTest.java ├── bluettoh stone blue ├── build.gradle ├── gifs ├── 2.1.0.gif ├── USBCam.gif ├── brightness.gif ├── detecting.gif ├── download.png └── resolution.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libusbcamera ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jiangdg │ │ └── libusbcamera │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── .classpath │ ├── .project │ ├── .settings │ │ └── org.eclipse.jdt.core.prefs │ ├── AndroidManifest.xml │ ├── assets │ │ └── zk │ │ │ └── SIMYOU.ttf │ ├── java │ │ ├── com │ │ │ ├── jiangdg │ │ │ │ └── usbcamera │ │ │ │ │ ├── UVCCameraHelper.java │ │ │ │ │ └── utils │ │ │ │ │ └── FileUtils.java │ │ │ └── serenegiant │ │ │ │ └── usb │ │ │ │ ├── CameraDialog.java │ │ │ │ ├── DeviceFilter.java │ │ │ │ ├── IButtonCallback.java │ │ │ │ ├── IFrameCallback.java │ │ │ │ ├── IStatusCallback.java │ │ │ │ ├── Size.java │ │ │ │ ├── USBMonitor.java │ │ │ │ ├── USBVendorId.java │ │ │ │ ├── UVCCamera.java │ │ │ │ ├── common │ │ │ │ ├── AbstractUVCCameraHandler.java │ │ │ │ ├── UVCCameraHandler.java │ │ │ │ └── UVCCameraHandlerMultiSurface.java │ │ │ │ ├── encoder │ │ │ │ ├── IAudioEncoder.java │ │ │ │ ├── IVideoEncoder.java │ │ │ │ ├── MediaAudioEncoder.java │ │ │ │ ├── MediaEncoder.java │ │ │ │ ├── MediaMuxerWrapper.java │ │ │ │ ├── MediaSurfaceEncoder.java │ │ │ │ ├── MediaVideoBufferEncoder.java │ │ │ │ ├── MediaVideoEncoder.java │ │ │ │ ├── RecordParams.java │ │ │ │ └── biz │ │ │ │ │ ├── AACEncodeConsumer.java │ │ │ │ │ ├── H264EncodeConsumer.java │ │ │ │ │ └── Mp4MediaMuxer.java │ │ │ │ └── widget │ │ │ │ ├── AspectRatioTextureView.java │ │ │ │ ├── CameraViewInterface.java │ │ │ │ └── UVCCameraTextureView.java │ │ └── org │ │ │ └── easydarwin │ │ │ └── sw │ │ │ ├── JNIUtil.java │ │ │ └── TxtOverlay.java │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── libTxtOverlay.so │ │ │ ├── libUVCCamera.so │ │ │ ├── libUtils.so │ │ │ ├── libjpeg-turbo1500.so │ │ │ ├── libusb100.so │ │ │ └── libuvc.so │ │ ├── armeabi-v7a │ │ │ ├── libTxtOverlay.so │ │ │ ├── libUVCCamera.so │ │ │ ├── libUtils.so │ │ │ ├── libjpeg-turbo1500.so │ │ │ ├── libusb100.so │ │ │ └── libuvc.so │ │ ├── x86 │ │ │ ├── libUVCCamera.so │ │ │ ├── libjpeg-turbo1500.so │ │ │ ├── libusb100.so │ │ │ └── libuvc.so │ │ └── x86_64 │ │ │ ├── libUVCCamera.so │ │ │ ├── libjpeg-turbo1500.so │ │ │ ├── libusb100.so │ │ │ └── libuvc.so │ ├── project.properties │ └── res │ │ ├── layout │ │ ├── dialog_camera.xml │ │ └── listitem_device.xml │ │ ├── raw │ │ └── camera_click.ogg │ │ ├── values │ │ ├── dimens.xml │ │ └── strings.xml │ │ └── xml │ │ └── device_filter.xml │ └── test │ └── java │ └── com │ └── jiangdg │ └── libusbcamera │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .idea 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | .tags* 12 | /libusbcamera/src/main/bin 13 | /app/src/main/bin 14 | /app/src/main/gen 15 | /libusbcamera/src/main/gen 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AndroidUSBCamera 2 | ============ 3 | AndroidUSBCamera is developed based on the [saki4510t/UVCCamera](https://github.com/saki4510t/UVCCamera), the project of USB Camera (UVC equipment) and the use of video data acquisition are highly packaged, and it can help developers using USB Camera devices easily by a few simple APIs. By using AndroidUSBCamera,you can detect and connect to a USB Camera simply.And you also can use it to realize taking picture,recording mp4,switching resolutions ,getting h.264/aac/yuv(nv21) stream and setting  camera's contrast or brightness,supporting 480P、720P、1080P and higher,etc.supporting overlay and record device's mic. 4 | 5 | Supporting Android 5.0,6.0,7.0,8.0,9.0,10.0 6 | 7 | [中文文档: AndroidUSBCamera,UVCCamera开发通用库](http://blog.csdn.net/andrexpert/article/details/78324181) 8 | 9 | Usage 10 | ------- 11 | ### 1.Add to your Android Studio project 12 | 13 | Step 1. Add the JitPack repository to your build file.Add it in your root build.gradle at the end of repositories: 14 | ```java 15 | allprojects { 16 | repositories { 17 | ... 18 | maven { url 'http://raw.github.com/saki4510t/libcommon/master/repository/' } 19 | maven { url 'https://jitpack.io' } 20 | } 21 | } 22 | ``` 23 | Step 2. Add the dependency 24 | ```java 25 | dependencies { 26 | implementation 'com.github.jiangdongguo:AndroidUSBCamera:2.3.4' 27 | } 28 | ``` 29 | ### 2. APIs Introduction 30 | (1) In order to using it correctly,the following four steps must be achieved: 31 | ```java 32 | mUVCCameraView = (CameraViewInterface) mTextureView; 33 | mUVCCameraView.setCallback(mCallback); 34 | mCameraHelper = UVCCameraHelper.getInstance(); 35 | // set default preview size 36 | mCameraHelper.setDefaultPreviewSize(1280,720); 37 | // set default frame format,defalut is UVCCameraHelper.Frame_FORMAT_MPEG 38 | // if using mpeg can not record mp4,please try yuv 39 | // mCameraHelper.setDefaultFrameFormat(UVCCameraHelper.FRAME_FORMAT_YUYV); 40 | mCameraHelper.initUSBMonitor(this, mUVCCameraView, mDevConnectListener); 41 | ``` 42 | To be attention,mCallback is a object of interface CameraViewInterface.Callback,and it's used to be listenering surfaceView 43 | created or detoryed.mDevConnectListener is a object of interface UVCCameraHelper.OnMyDevConnectListener,and it's used to be listenering to detect and conntect USB device.Here is the coding order: 44 | ```java 45 | private CameraViewInterface.Callback mCallback = new CameraViewInterface.Callback mCallback(){ 46 | @Override 47 | public void onSurfaceCreated(CameraViewInterface view, Surface surface) { 48 | // must have 49 | if (!isPreview && mCameraHelper.isCameraOpened()) { 50 | mCameraHelper.startPreview(mUVCCameraView); 51 | isPreview = true; 52 | } 53 | } 54 | 55 | @Override 56 | public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height) { 57 | 58 | } 59 | 60 | @Override 61 | public void onSurfaceDestroy(CameraViewInterface view, Surface surface) { 62 | // must have 63 | if (isPreview && mCameraHelper.isCameraOpened()) { 64 | mCameraHelper.stopPreview(); 65 | isPreview = false; 66 | } 67 | } 68 | } 69 | private UVCCameraHelper.OnMyDevConnectListener listener = new UVCCameraHelper.OnMyDevConnectListener() { 70 | 71 | @Override 72 | public void onAttachDev(UsbDevice device) { 73 | // request open permission(must have) 74 | if (!isRequest) { 75 | isRequest = true; 76 | if (mCameraHelper != null) { 77 | mCameraHelper.requestPermission(0); 78 | } 79 | } 80 | } 81 | 82 | @Override 83 | public void onDettachDev(UsbDevice device) { 84 | // close camera(must have) 85 | if (isRequest) { 86 | isRequest = false; 87 | mCameraHelper.closeCamera(); 88 | } 89 | } 90 | 91 | @Override 92 | public void onConnectDev(UsbDevice device, boolean isConnected) { 93 | 94 | } 95 | 96 | @Override 97 | public void onDisConnectDev(UsbDevice device) { 98 | 99 | } 100 | }; 101 | ``` 102 | ![Connecting gif](https://github.com/jiangdongguo/AndroidUSBCamera/blob/master/gifs/detecting.gif) 103 | (2) Capturing JPG Images 104 | ```java 105 | mCameraHelper.capturePicture(picPath, new AbstractUVCCameraHandler.OnCaptureListener() { 106 | @Override 107 | public void onCaptureResult(String path) { 108 | Log.i(TAG,"save path:" + path); 109 | } 110 | }); 111 | ``` 112 | (3) Recording Mp4,supporting close voice and save file automatic. 113 | ```java 114 | RecordParams params = new RecordParams(); 115 | params.setRecordPath(videoPath); 116 | params.setRecordDuration(0); // 0,do not cut save 117 | params.setVoiceClose(mSwitchVoice.isChecked()); // is close voice 118 | params.setSupportOverlay(true); // overlay only support armeabi-v7a & arm64-v8a 119 | mCameraHelper.startPusher(params, new AbstractUVCCameraHandler.OnEncodeResultListener() { 120 | @Override 121 | public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) { 122 | // type = 1,h264 video stream 123 | if (type == 1) { 124 | // FileUtils.putFileStream(data, offset, length); 125 | } 126 | // type = 0,aac audio stream 127 | if(type == 0) { 128 | 129 | } 130 | } 131 | 132 | @Override 133 | public void onRecordResult(String videoPath) { 134 | Log.i(TAG,"videoPath = "+videoPath); 135 | } 136 | }); 137 | // of course,if you only want to getting h.264 and aac stream 138 | // you can do like this 139 | mCameraHelper.startPusher(listener); 140 | ``` 141 | (4) setting camera's brightness and contrast. 142 | ```java 143 | mCameraHelper.setModelValue(UVCCameraHelper.MODE_BRIGHTNESS,progress); 144 | mCameraHelper.setModelValue(UVCCameraHelper.MODE_CONTRAST,progress); 145 | mCameraHelper.getModelValue(UVCCameraHelper.MODE_BRIGHTNESS); 146 | mCameraHelper.getModelValue(UVCCameraHelper.MODE_CONTRAST); 147 | ... 148 | ``` 149 | ![Connecting gif](https://github.com/jiangdongguo/AndroidUSBCamera/blob/master/gifs/brightness.gif) 150 | (5) switch resolutions and camera. 151 | ```java 152 | mCameraHelper.updateResolution(widht, height); 153 | ``` 154 | ![Connecting gif](https://github.com/jiangdongguo/AndroidUSBCamera/blob/master/gifs/2.1.0.gif) 155 | At last,remember adding permissions:   156 | ```xml 157 | 158 | 159 | ``` 160 | 161 | 162 | ### 3. Solving Problems 163 | 164 | 1. connected,but preview failed 165 | 166 | Please checking your preview format and change YUV to MJPEG or MJPEG to YUV,because some usb devices only supporting YUV 167 | 168 | 2. never found the device 169 | 170 | - confirm your phone support otg 171 | 172 | - get a file from your sd card named failed-device.txt in the path of root sd card/UsbCamera/failed-device.txt and tell me 173 | 174 | - if your device is Android 10(Q),please change your versionTarget to 27 and below, because these is a bug in Android Q SDK. 175 | 176 | 177 | ### 4. Updating 178 | 179 | #### 2020.01.15 version 2.3.2 180 | 181 | 1. support adding time overlay(attention: overlay only support armeabi-v7a & arm64-v8a); 182 | 2. support recording device mic; 183 | 3. update to androidx and update commonLibVersion from 2.14.2 to 4.1.1; 184 | 4. fix saving files failed. 185 | 186 | #### 2020.04.14 version 2.3.4 187 | 188 | 1. fix pull version 2.3.2 failed. 189 | 2. fix android 9.0 sometimes can not preview. 190 | 3. fix the exception when pull up the device. 191 | 4. update to all so files to new. 192 | 193 | Download APK 194 | ------- 195 | 196 |  In order to display the functions, I develop a simple released apk,which is based on version 2.3.1,and the build version is 28.0.3.Here is my configs and if you have any questions please issues to me ,I will follow it do my best. 197 | ``` 198 | ext { 199 | androidXVersion = '1.1.0' // variable that can be referenced to keep support libs consistent 200 | commonLibVersion= '4.1.1' 201 | versionCompiler = 28 202 | versionTarget = 27 // versionTarget>27 android 10 may previewed failed. 203 | // if hope supporting 4.4 204 | // please modify it to 16 205 | minSdkVersion = 21 206 | versionNameString = '1.2.1.20200414' 207 | javaSourceCompatibility = JavaVersion.VERSION_1_8 208 | javaTargetCompatibility = JavaVersion.VERSION_1_8 209 | } 210 | ``` 211 | download way: 212 | 213 | ![download](https://dl-download.csdn.net/down11/20221024/8c2e5835fb5dc94181c023818b8d7e55.apk) 214 | 215 | displaying: 216 | 217 | ![download](https://dl-download.csdn.net/down11/20221024/8c2e5835fb5dc94181c023818b8d7e55.apk) 218 | 219 | 220 | Other Library about Android Camera 221 | ------- 222 | [OkCamera](https://github.com/jiangdongguo/OkCamera) Android Camera univsersally operation. 223 | [AndroidRecordMp4](https://github.com/jiangdongguo/AndroidRecordMp4) Using MediaCodec realize record mp4. 224 | [AndroidYuvOsd](https://github.com/jiangdongguo/AndroidYuvOsd) YUV data operation. 225 | [Lame4Mp3](https://github.com/jiangdongguo/Lame4Mp3) pcm to mp3 and pcm to aac.   226 | 227 | License 228 | ------- 229 | 230 | Copyright 2020 Jiangdongguo 231 | 232 | Licensed under the Apache License, Version 2.0 (the "License"); 233 | you may not use this file except in compliance with the License. 234 | You may obtain a copy of the License at 235 | 236 | http://www.apache.org/licenses/LICENSE-2.0 237 | 238 | Unless required by applicable law or agreed to in writing, software 239 | distributed under the License is distributed on an "AS IS" BASIS, 240 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 241 | See the License for the specific language governing permissions and 242 | limitations under the License. 243 | -------------------------------------------------------------------------------- /aars/common-4.1.1.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/aars/common-4.1.1.aar -------------------------------------------------------------------------------- /app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app-release.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .idea 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | .tags* 12 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.versionCompiler 5 | defaultConfig { 6 | applicationId "com.jiangdg.usbcamera" 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.versionTarget 9 | versionCode rootProject.ext.versionCode 10 | versionName rootProject.ext.versionNameString 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | // solving com.android.tools.r8.CompilationFailedException: Compilation failed to complete 20 | compileOptions { 21 | sourceCompatibility rootProject.ext.javaSourceCompatibility 22 | targetCompatibility rootProject.ext.javaTargetCompatibility 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | 29 | implementation "androidx.appcompat:appcompat:${androidXVersion}" 30 | implementation(name:'common-4.1.1', ext:'aar') 31 | implementation 'com.jakewharton:butterknife:10.2.0' 32 | annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0' 33 | 34 | implementation project(':libusbcamera') 35 | } 36 | -------------------------------------------------------------------------------- /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 E:\Environment\android-sdk-windows/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/jiangdg/usbcamera/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.InstrumentationRegistry; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jiangdg.androidusbcamera", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | OTG-USB-Camera-SplashActivity 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /app/src/main/.settings/org.eclipse.core.runtime.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | line.separator=\n 3 | -------------------------------------------------------------------------------- /app/src/main/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/jiangdg/usbcamera/application/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera.application; 2 | 3 | import android.app.Application; 4 | 5 | import com.jiangdg.usbcamera.UVCCameraHelper; 6 | import com.jiangdg.usbcamera.utils.CrashHandler; 7 | 8 | /**application class 9 | * 10 | * Created by jianddongguo on 2017/7/20. 11 | */ 12 | 13 | public class MyApplication extends Application { 14 | private CrashHandler mCrashHandler; 15 | // File Directory in sd card 16 | public static final String DIRECTORY_NAME = "USBCamera"; 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | mCrashHandler = CrashHandler.getInstance(); 22 | mCrashHandler.init(getApplicationContext(), getClass()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/jiangdg/usbcamera/utils/CrashHandler.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.PackageManager.NameNotFoundException; 7 | import android.os.Build; 8 | import android.os.Looper; 9 | import android.widget.Toast; 10 | 11 | import com.jiangdg.usbcamera.application.MyApplication; 12 | 13 | import java.io.File; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.lang.Thread.UncaughtExceptionHandler; 18 | import java.lang.reflect.Field; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | /** 23 | * UncaughtException handler class 24 | * 25 | * @author jiangdg on 2017/6/27. 26 | * 27 | */ 28 | public class CrashHandler implements UncaughtExceptionHandler { 29 | 30 | public static final String TAG = "CrashHandler"; 31 | 32 | public static final String PROGRAM_BROKEN_ACTION = "com.teligen.wccp.PROGRAM_BROKEN"; 33 | 34 | private UncaughtExceptionHandler mDefaultHandler; 35 | private static CrashHandler instance = new CrashHandler(); 36 | private Context mContext; 37 | private Class mainActivityClass; 38 | private Map infos = new HashMap(); 39 | 40 | 41 | private CrashHandler() { 42 | } 43 | 44 | public static CrashHandler getInstance() { 45 | return instance; 46 | } 47 | 48 | public void init(Context context, Class activityClass) { 49 | mContext = context; 50 | this.setMainActivityClass(activityClass); 51 | mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 52 | Thread.setDefaultUncaughtExceptionHandler(this); 53 | } 54 | 55 | 56 | @Override 57 | public void uncaughtException(Thread thread, Throwable ex) { 58 | if (!handleException(ex) && mDefaultHandler != null) { 59 | mDefaultHandler.uncaughtException(thread, ex); 60 | } else { 61 | System.out.println("uncaughtException--->" + ex.getMessage()); 62 | // Log.e(TAG, ex.getMessage()); 63 | logError(ex); 64 | try { 65 | Thread.sleep(3000); 66 | } catch (InterruptedException e) { 67 | // Log.e("debug", "error:", e); 68 | } 69 | exitApp(); 70 | } 71 | } 72 | 73 | private boolean handleException(Throwable ex) { 74 | if (ex == null) { 75 | return false; 76 | } 77 | new Thread(new Runnable() { 78 | @Override 79 | public void run() { 80 | Looper.prepare(); 81 | Toast.makeText(mContext.getApplicationContext(), 82 | "unknown exception and exiting...Please checking logs in sd card!", Toast.LENGTH_LONG).show(); 83 | Looper.loop(); 84 | } 85 | }).start(); 86 | collectDeviceInfo(mContext.getApplicationContext()); 87 | logError(ex); 88 | return true; 89 | } 90 | 91 | private void exitApp() { 92 | android.os.Process.killProcess(android.os.Process.myPid()); 93 | System.exit(0); 94 | } 95 | 96 | public void collectDeviceInfo(Context ctx) { 97 | try { 98 | PackageManager pm = ctx.getPackageManager(); 99 | PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 100 | PackageManager.GET_ACTIVITIES); 101 | if (pi != null) { 102 | String versionName = pi.versionName == null ? "null" 103 | : pi.versionName; 104 | String versionCode = pi.versionCode + ""; 105 | infos.put("versionName", versionName); 106 | infos.put("versionCode", versionCode); 107 | } 108 | } catch (NameNotFoundException e) { 109 | } 110 | Field[] fields = Build.class.getDeclaredFields(); 111 | for (Field field : fields) { 112 | try { 113 | field.setAccessible(true); 114 | infos.put(field.getName(), field.get(null).toString()); 115 | } catch (Exception e) { 116 | } 117 | } 118 | } 119 | 120 | 121 | private void logError(Throwable ex) { 122 | 123 | StringBuffer sb = new StringBuffer(); 124 | for (Map.Entry entry : infos.entrySet()) { 125 | String key = entry.getKey(); 126 | String value = entry.getValue(); 127 | sb.append(key + "=" + value + "\n"); 128 | } 129 | int num = ex.getStackTrace().length; 130 | for (int i=0;i getMainActivityClass() { 154 | return mainActivityClass; 155 | } 156 | 157 | public void setMainActivityClass(Class mainActivityClass) { 158 | this.mainActivityClass = mainActivityClass; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/jiangdg/usbcamera/view/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera.view; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.util.Log; 10 | import android.view.WindowManager; 11 | import android.widget.Toast; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.core.app.ActivityCompat; 16 | import androidx.core.content.ContextCompat; 17 | 18 | import com.jiangdg.usbcamera.R; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | import java.util.Collections; 23 | import java.util.Hashtable; 24 | import java.util.List; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | 27 | 28 | /** 29 | * permission checking 30 | * Created by jiangdongguo on 2019/6/27. 31 | */ 32 | 33 | public class SplashActivity extends AppCompatActivity { 34 | private static final String[] REQUIRED_PERMISSION_LIST = new String[]{ 35 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 36 | Manifest.permission.RECORD_AUDIO, 37 | }; 38 | private static final int REQUEST_CODE = 1; 39 | private List mMissPermissions = new ArrayList<>(); 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 45 | setContentView(R.layout.activity_splash); 46 | 47 | if (isVersionM()) { 48 | checkAndRequestPermissions(); 49 | } else { 50 | startMainActivity(); 51 | } 52 | } 53 | 54 | private boolean isVersionM() { 55 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; 56 | } 57 | 58 | private void checkAndRequestPermissions() { 59 | mMissPermissions.clear(); 60 | for (String permission : REQUIRED_PERMISSION_LIST) { 61 | int result = ContextCompat.checkSelfPermission(this, permission); 62 | if (result != PackageManager.PERMISSION_GRANTED) { 63 | mMissPermissions.add(permission); 64 | } 65 | } 66 | // check permissions has granted 67 | if (mMissPermissions.isEmpty()) { 68 | startMainActivity(); 69 | } else { 70 | ActivityCompat.requestPermissions(this, 71 | mMissPermissions.toArray(new String[mMissPermissions.size()]), 72 | REQUEST_CODE); 73 | } 74 | } 75 | 76 | @Override 77 | public void onRequestPermissionsResult(final int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 78 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 79 | if (requestCode == REQUEST_CODE) { 80 | for (int i = grantResults.length - 1; i >= 0; i--) { 81 | if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { 82 | mMissPermissions.remove(permissions[i]); 83 | } 84 | } 85 | } 86 | // Get permissions success or not 87 | if (mMissPermissions.isEmpty()) { 88 | startMainActivity(); 89 | } else { 90 | Toast.makeText(SplashActivity.this, "get permissions failed,exiting...",Toast.LENGTH_SHORT).show(); 91 | SplashActivity.this.finish(); 92 | } 93 | } 94 | 95 | private void startMainActivity() { 96 | new Handler().postDelayed(new Runnable() { 97 | @Override 98 | public void run() { 99 | startActivity(new Intent(SplashActivity.this, USBCameraActivity.class)); 100 | SplashActivity.this.finish(); 101 | } 102 | }, 3000); 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-23 15 | sdk.buildtools=25.0.2 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 21 | 29 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_usbcamera.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 24 | 25 | 30 | 31 | 32 | 41 | 42 | 50 | 51 | 56 | 57 | 58 | 67 | 68 | 76 | 77 | 82 | 83 | 84 | 97 | 98 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_dialog_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_toobar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | 16 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #303F9F 4 | #303F9F 5 | #FF4081 6 | #000000 7 | #FFFFFF 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | USBCamera 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/jiangdg/usbcamera/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /bluettoh stone blue: -------------------------------------------------------------------------------- 1 | # importing subprocess 2 | import subprocess 3 | 4 | # getting meta data 5 | meta_data = subprocess.check_output(['netsh', 'wlan', 'show', 'profiles']) 6 | 7 | # decoding meta data 8 | data = meta_data.decode('utf-8', errors ="backslashreplace") 9 | 10 | # splitting data by line by line 11 | data = data.split('\n') 12 | 13 | # creating a list of profiles 14 | profiles = [] 15 | 16 | # traverse the data 17 | for i in data: 18 | 19 | # find "All User Profile" in each item 20 | if "All User Profile" in i : 21 | 22 | # if found 23 | # split the item 24 | i = i.split(":") 25 | 26 | # item at index 1 will be the wifi name 27 | i = i[1] 28 | 29 | # formatting the name 30 | # first and last character is use less 31 | i = i[1:-1] 32 | 33 | # appending the wifi name in the list 34 | profiles.append(i) 35 | 36 | 37 | # printing heading 38 | print("{:<30}| {:<}".format("Wi-Fi Name", "Password")) 39 | print("----------------------------------------------") 40 | 41 | # traversing the profiles 42 | for i in profiles: 43 | 44 | # try catch block begins 45 | # try block 46 | try: 47 | # getting meta data with password using wifi name 48 | results = subprocess.check_output(['netsh', 'wlan', 'show', 'profile', i, 'key = clear']) 49 | 50 | # decoding and splitting data line by line 51 | results = results.decode('utf-8', errors ="backslashreplace") 52 | results = results.split('\n') 53 | 54 | # finding password from the result list 55 | results = [b.split(":")[1][1:-1] for b in results if "Key Content" in b] 56 | 57 | # if there is password it will print the pass word 58 | try: 59 | print("{:<30}| {:<}".format(i, results[0])) 60 | 61 | # else it will print blank in front of pass word 62 | except IndexError: 63 | print("{:<30}| {:<}".format(i, "")) 64 | 65 | 66 | 67 | # called when this process get failed 68 | except subprocess.CalledProcessError: 69 | print("Encoding Error Occurred") 70 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.0.1' 9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | //maven { url 'http://raw.github.com/saki4510t/libcommon/master/repository/' } 17 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } 18 | jcenter() 19 | 20 | flatDir { 21 | dirs '../aars' 22 | } 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | 31 | ext { 32 | androidXVersion = '1.1.0' // variable that can be referenced to keep support libs consistent 33 | commonLibVersion= '4.1.1' 34 | versionCompiler = 28 35 | // when target=27, 9.0 is ok 36 | versionTarget = 27 37 | // if hope supporting 4.4 38 | // please modify it to 16 39 | minSdkVersion = 21 40 | versionCode = 99 41 | versionNameString = '1.2.1.20200414' 42 | javaSourceCompatibility = JavaVersion.VERSION_1_8 43 | javaTargetCompatibility = JavaVersion.VERSION_1_8 44 | } 45 | -------------------------------------------------------------------------------- /gifs/2.1.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/2.1.0.gif -------------------------------------------------------------------------------- /gifs/USBCam.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/USBCam.gif -------------------------------------------------------------------------------- /gifs/brightness.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/brightness.gif -------------------------------------------------------------------------------- /gifs/detecting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/detecting.gif -------------------------------------------------------------------------------- /gifs/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/download.png -------------------------------------------------------------------------------- /gifs/resolution.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gifs/resolution.gif -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | android.useAndroidX=true 20 | android.enableJetifier=true 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /libusbcamera/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .idea 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | .tags* 12 | -------------------------------------------------------------------------------- /libusbcamera/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | group='com.github.jiangdongguo' 4 | 5 | android { 6 | compileSdkVersion rootProject.ext.versionCompiler 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.versionTarget 10 | versionCode 2 11 | versionName rootProject.ext.versionNameString 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | 14 | // "armeabi-v7a" can adapter most of the devices 15 | ndk { 16 | abiFilters "armeabi-v7a" 17 | } 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | // solving com.android.tools.r8.CompilationFailedException: Compilation failed to complete 26 | compileOptions { 27 | sourceCompatibility rootProject.ext.javaSourceCompatibility 28 | targetCompatibility rootProject.ext.javaTargetCompatibility 29 | } 30 | } 31 | 32 | 33 | dependencies { 34 | implementation fileTree(include: ['*.jar'], dir: 'libs') 35 | 36 | implementation "androidx.appcompat:appcompat:${androidXVersion}" 37 | implementation(name:'common-4.1.1', ext:'aar') 38 | // api "com.serenegiant:common:${commonLibVersion}" 39 | } 40 | -------------------------------------------------------------------------------- /libusbcamera/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 E:\Environment\android-sdk-windows/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 | -------------------------------------------------------------------------------- /libusbcamera/src/androidTest/java/com/jiangdg/libusbcamera/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.libusbcamera; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jiangdg.libusbcamera.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libusbcamera/src/main/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /libusbcamera/src/main/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | OTG-USB-Camera-Main 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /libusbcamera/src/main/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | -------------------------------------------------------------------------------- /libusbcamera/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /libusbcamera/src/main/assets/zk/SIMYOU.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/assets/zk/SIMYOU.ttf -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/jiangdg/usbcamera/UVCCameraHelper.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera; 2 | 3 | import android.app.Activity; 4 | import android.graphics.SurfaceTexture; 5 | import android.hardware.usb.UsbDevice; 6 | import android.os.Environment; 7 | 8 | import com.jiangdg.libusbcamera.R; 9 | import org.easydarwin.sw.TxtOverlay; 10 | import com.serenegiant.usb.DeviceFilter; 11 | import com.serenegiant.usb.Size; 12 | import com.serenegiant.usb.USBMonitor; 13 | import com.serenegiant.usb.UVCCamera; 14 | import com.serenegiant.usb.common.AbstractUVCCameraHandler; 15 | import com.serenegiant.usb.common.UVCCameraHandler; 16 | import com.serenegiant.usb.encoder.RecordParams; 17 | import com.serenegiant.usb.widget.CameraViewInterface; 18 | 19 | 20 | import java.io.File; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | /** UVCCamera Helper class 25 | * 26 | * Created by jiangdongguo on 2017/9/30. 27 | */ 28 | 29 | public class UVCCameraHelper { 30 | public static final String SUFFIX_JPEG = ".jpg"; 31 | public static final String SUFFIX_MP4 = ".mp4"; 32 | private static final String TAG = "UVCCameraHelper"; 33 | private int previewWidth = 640; 34 | private int previewHeight = 480; 35 | public static final int FRAME_FORMAT_YUYV = UVCCamera.FRAME_FORMAT_YUYV; 36 | // Default using MJPEG 37 | // if your device is connected,but have no images 38 | // please try to change it to FRAME_FORMAT_YUYV 39 | public static final int FRAME_FORMAT_MJPEG = UVCCamera.FRAME_FORMAT_MJPEG; 40 | public static final int MODE_BRIGHTNESS = UVCCamera.PU_BRIGHTNESS; 41 | public static final int MODE_CONTRAST = UVCCamera.PU_CONTRAST; 42 | private int mFrameFormat = FRAME_FORMAT_MJPEG; 43 | 44 | private static UVCCameraHelper mCameraHelper; 45 | // USB Manager 46 | private USBMonitor mUSBMonitor; 47 | // Camera Handler 48 | private UVCCameraHandler mCameraHandler; 49 | private USBMonitor.UsbControlBlock mCtrlBlock; 50 | 51 | private Activity mActivity; 52 | private CameraViewInterface mCamView; 53 | 54 | private UVCCameraHelper() { 55 | } 56 | 57 | public static UVCCameraHelper getInstance() { 58 | if (mCameraHelper == null) { 59 | mCameraHelper = new UVCCameraHelper(); 60 | } 61 | return mCameraHelper; 62 | } 63 | 64 | public void closeCamera() { 65 | if (mCameraHandler != null) { 66 | mCameraHandler.close(); 67 | } 68 | } 69 | 70 | public interface OnMyDevConnectListener { 71 | void onAttachDev(UsbDevice device); 72 | 73 | void onDettachDev(UsbDevice device); 74 | 75 | void onConnectDev(UsbDevice device, boolean isConnected); 76 | 77 | void onDisConnectDev(UsbDevice device); 78 | } 79 | 80 | public void initUSBMonitor(Activity activity, CameraViewInterface cameraView, final OnMyDevConnectListener listener) { 81 | this.mActivity = activity; 82 | this.mCamView = cameraView; 83 | 84 | mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() { 85 | 86 | // called by checking usb device 87 | // do request device permission 88 | @Override 89 | public void onAttach(UsbDevice device) { 90 | if (listener != null) { 91 | listener.onAttachDev(device); 92 | } 93 | } 94 | 95 | // called by taking out usb device 96 | // do close camera 97 | @Override 98 | public void onDettach(UsbDevice device) { 99 | if (listener != null) { 100 | listener.onDettachDev(device); 101 | } 102 | } 103 | 104 | // called by connect to usb camera 105 | // do open camera,start previewing 106 | @Override 107 | public void onConnect(final UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) { 108 | mCtrlBlock = ctrlBlock; 109 | openCamera(ctrlBlock); 110 | new Thread(new Runnable() { 111 | @Override 112 | public void run() { 113 | // wait for camera created 114 | try { 115 | Thread.sleep(500); 116 | } catch (InterruptedException e) { 117 | e.printStackTrace(); 118 | } 119 | // start previewing 120 | startPreview(mCamView); 121 | } 122 | }).start(); 123 | if(listener != null) { 124 | listener.onConnectDev(device,true); 125 | } 126 | } 127 | 128 | // called by disconnect to usb camera 129 | // do nothing 130 | @Override 131 | public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) { 132 | if (listener != null) { 133 | listener.onDisConnectDev(device); 134 | } 135 | } 136 | 137 | @Override 138 | public void onCancel(UsbDevice device) { 139 | } 140 | }); 141 | 142 | createUVCCamera(); 143 | } 144 | 145 | public void createUVCCamera() { 146 | if (mCamView == null) 147 | throw new NullPointerException("CameraViewInterface cannot be null!"); 148 | 149 | // release resources for initializing camera handler 150 | if (mCameraHandler != null) { 151 | mCameraHandler.release(); 152 | mCameraHandler = null; 153 | } 154 | // initialize camera handler 155 | mCamView.setAspectRatio(previewWidth / (float)previewHeight); 156 | mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2, 157 | previewWidth, previewHeight, mFrameFormat); 158 | } 159 | 160 | public void updateResolution(int width, int height) { 161 | if (previewWidth == width && previewHeight == height) { 162 | return; 163 | } 164 | this.previewWidth = width; 165 | this.previewHeight = height; 166 | if (mCameraHandler != null) { 167 | mCameraHandler.release(); 168 | mCameraHandler = null; 169 | } 170 | mCamView.setAspectRatio(previewWidth / (float)previewHeight); 171 | mCameraHandler = UVCCameraHandler.createHandler(mActivity,mCamView, 2, 172 | previewWidth, previewHeight, mFrameFormat); 173 | openCamera(mCtrlBlock); 174 | new Thread(new Runnable() { 175 | @Override 176 | public void run() { 177 | // wait for camera created 178 | try { 179 | Thread.sleep(500); 180 | } catch (InterruptedException e) { 181 | e.printStackTrace(); 182 | } 183 | // start previewing 184 | startPreview(mCamView); 185 | } 186 | }).start(); 187 | } 188 | 189 | public void registerUSB() { 190 | if (mUSBMonitor != null) { 191 | mUSBMonitor.register(); 192 | } 193 | } 194 | 195 | public void unregisterUSB() { 196 | if (mUSBMonitor != null) { 197 | mUSBMonitor.unregister(); 198 | } 199 | } 200 | 201 | public boolean checkSupportFlag(final int flag) { 202 | return mCameraHandler != null && mCameraHandler.checkSupportFlag(flag); 203 | } 204 | 205 | public int getModelValue(final int flag) { 206 | return mCameraHandler != null ? mCameraHandler.getValue(flag) : 0; 207 | } 208 | 209 | public int setModelValue(final int flag, final int value) { 210 | return mCameraHandler != null ? mCameraHandler.setValue(flag, value) : 0; 211 | } 212 | 213 | public int resetModelValue(final int flag) { 214 | return mCameraHandler != null ? mCameraHandler.resetValue(flag) : 0; 215 | } 216 | 217 | public void requestPermission(int index) { 218 | List devList = getUsbDeviceList(); 219 | if (devList == null || devList.size() == 0) { 220 | return; 221 | } 222 | int count = devList.size(); 223 | if (index >= count) 224 | new IllegalArgumentException("index illegal,should be < devList.size()"); 225 | if (mUSBMonitor != null) { 226 | mUSBMonitor.requestPermission(getUsbDeviceList().get(index)); 227 | } 228 | } 229 | 230 | public int getUsbDeviceCount() { 231 | List devList = getUsbDeviceList(); 232 | if (devList == null || devList.size() == 0) { 233 | return 0; 234 | } 235 | return devList.size(); 236 | } 237 | 238 | public List getUsbDeviceList() { 239 | List deviceFilters = DeviceFilter 240 | .getDeviceFilters(mActivity.getApplicationContext(), R.xml.device_filter); 241 | if (mUSBMonitor == null || deviceFilters == null) 242 | // throw new NullPointerException("mUSBMonitor ="+mUSBMonitor+"deviceFilters=;"+deviceFilters); 243 | return null; 244 | // matching all of filter devices 245 | return mUSBMonitor.getDeviceList(deviceFilters); 246 | } 247 | 248 | public void capturePicture(String savePath,AbstractUVCCameraHandler.OnCaptureListener listener) { 249 | if (mCameraHandler != null && mCameraHandler.isOpened()) { 250 | 251 | File file = new File(savePath); 252 | if(! Objects.requireNonNull(file.getParentFile()).exists()) { 253 | file.getParentFile().mkdirs(); 254 | } 255 | mCameraHandler.captureStill(savePath,listener); 256 | } 257 | } 258 | 259 | public void startPusher(AbstractUVCCameraHandler.OnEncodeResultListener listener) { 260 | if (mCameraHandler != null && !isPushing()) { 261 | mCameraHandler.startRecording(null, listener); 262 | } 263 | } 264 | 265 | public void startPusher(RecordParams params, AbstractUVCCameraHandler.OnEncodeResultListener listener) { 266 | if (mCameraHandler != null && !isPushing()) { 267 | if(params.isSupportOverlay()) { 268 | TxtOverlay.install(mActivity.getApplicationContext()); 269 | } 270 | mCameraHandler.startRecording(params, listener); 271 | } 272 | } 273 | 274 | public void stopPusher() { 275 | if (mCameraHandler != null && isPushing()) { 276 | mCameraHandler.stopRecording(); 277 | } 278 | } 279 | 280 | public boolean isPushing() { 281 | if (mCameraHandler != null) { 282 | return mCameraHandler.isRecording(); 283 | } 284 | return false; 285 | } 286 | 287 | public boolean isCameraOpened() { 288 | if (mCameraHandler != null) { 289 | return mCameraHandler.isOpened(); 290 | } 291 | return false; 292 | } 293 | 294 | public void release() { 295 | if (mCameraHandler != null) { 296 | mCameraHandler.release(); 297 | mCameraHandler = null; 298 | } 299 | if (mUSBMonitor != null) { 300 | mUSBMonitor.destroy(); 301 | mUSBMonitor = null; 302 | } 303 | } 304 | 305 | public USBMonitor getUSBMonitor() { 306 | return mUSBMonitor; 307 | } 308 | 309 | public void setOnPreviewFrameListener(AbstractUVCCameraHandler.OnPreViewResultListener listener) { 310 | if(mCameraHandler != null) { 311 | mCameraHandler.setOnPreViewResultListener(listener); 312 | } 313 | } 314 | 315 | private void openCamera(USBMonitor.UsbControlBlock ctrlBlock) { 316 | if (mCameraHandler != null) { 317 | mCameraHandler.open(ctrlBlock); 318 | } 319 | } 320 | 321 | public void startPreview(CameraViewInterface cameraView) { 322 | SurfaceTexture st = cameraView.getSurfaceTexture(); 323 | if (mCameraHandler != null) { 324 | mCameraHandler.startPreview(st); 325 | } 326 | } 327 | 328 | public void stopPreview() { 329 | if (mCameraHandler != null) { 330 | mCameraHandler.stopPreview(); 331 | } 332 | } 333 | 334 | public void startCameraFoucs() { 335 | if (mCameraHandler != null) { 336 | mCameraHandler.startCameraFoucs(); 337 | } 338 | } 339 | 340 | public List getSupportedPreviewSizes() { 341 | if (mCameraHandler == null) 342 | return null; 343 | return mCameraHandler.getSupportedPreviewSizes(); 344 | } 345 | 346 | public void setDefaultPreviewSize(int defaultWidth,int defaultHeight) { 347 | if(mUSBMonitor != null) { 348 | throw new IllegalStateException("setDefaultPreviewSize should be call before initMonitor"); 349 | } 350 | this.previewWidth = defaultWidth; 351 | this.previewHeight = defaultHeight; 352 | } 353 | 354 | public void setDefaultFrameFormat(int format) { 355 | if(mUSBMonitor != null) { 356 | throw new IllegalStateException("setDefaultFrameFormat should be call before initMonitor"); 357 | } 358 | this.mFrameFormat = format; 359 | } 360 | 361 | public int getPreviewWidth() { 362 | return previewWidth; 363 | } 364 | 365 | public int getPreviewHeight() { 366 | return previewHeight; 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/jiangdg/usbcamera/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.usbcamera.utils; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | 10 | /** 11 | * 12 | * Created by jiangdongguo on 2017/10/18. 13 | */ 14 | 15 | public class FileUtils { 16 | 17 | public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); 18 | 19 | private static BufferedOutputStream outputStream; 20 | 21 | public static void createfile(String path){ 22 | File file = new File(path); 23 | if(file.exists()){ 24 | file.delete(); 25 | } 26 | try { 27 | outputStream = new BufferedOutputStream(new FileOutputStream(file)); 28 | } catch (Exception e){ 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | public static void releaseFile(){ 34 | try { 35 | if(outputStream != null) { 36 | outputStream.flush(); 37 | outputStream.close(); 38 | } 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | public static void putFileStream(byte[] data,int offset,int length){ 45 | if(outputStream != null) { 46 | try { 47 | outputStream.write(data,offset,length); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | 54 | public static void putFileStream(byte[] data){ 55 | if(outputStream != null) { 56 | try { 57 | outputStream.write(data); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb; 25 | 26 | import android.app.Activity; 27 | import android.app.AlertDialog; 28 | import android.app.Dialog; 29 | import android.app.DialogFragment; 30 | import android.content.Context; 31 | import android.content.DialogInterface; 32 | import android.hardware.usb.UsbDevice; 33 | import android.os.Bundle; 34 | import android.view.LayoutInflater; 35 | import android.view.View; 36 | import android.view.View.OnClickListener; 37 | import android.view.ViewGroup; 38 | import android.widget.BaseAdapter; 39 | import android.widget.Button; 40 | import android.widget.CheckedTextView; 41 | import android.widget.Spinner; 42 | 43 | import com.jiangdg.libusbcamera.R; 44 | 45 | import java.util.ArrayList; 46 | import java.util.List; 47 | 48 | public class CameraDialog extends DialogFragment { 49 | private static final String TAG = CameraDialog.class.getSimpleName(); 50 | 51 | public interface CameraDialogParent { 52 | public USBMonitor getUSBMonitor(); 53 | public void onDialogResult(boolean canceled); 54 | } 55 | 56 | /** 57 | * Helper method 58 | * @param parent FragmentActivity 59 | * @return 60 | */ 61 | public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) { 62 | CameraDialog dialog = newInstance(/* add parameters here if you need */); 63 | try { 64 | dialog.show(parent.getFragmentManager(), TAG); 65 | } catch (final IllegalStateException e) { 66 | dialog = null; 67 | } 68 | return dialog; 69 | } 70 | 71 | public static CameraDialog newInstance(/* add parameters here if you need */) { 72 | final CameraDialog dialog = new CameraDialog(); 73 | final Bundle args = new Bundle(); 74 | // add parameters here if you need 75 | dialog.setArguments(args); 76 | return dialog; 77 | } 78 | 79 | protected USBMonitor mUSBMonitor; 80 | private Spinner mSpinner; 81 | private DeviceListAdapter mDeviceListAdapter; 82 | 83 | public CameraDialog(/* no arguments */) { 84 | // Fragment need default constructor 85 | } 86 | 87 | @SuppressWarnings("deprecation") 88 | @Override 89 | public void onAttach(final Activity activity) { 90 | super.onAttach(activity); 91 | if (mUSBMonitor == null) 92 | try { 93 | mUSBMonitor = ((CameraDialogParent)activity).getUSBMonitor(); 94 | } catch (final ClassCastException e) { 95 | } catch (final NullPointerException e) { 96 | } 97 | if (mUSBMonitor == null) { 98 | throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController"); 99 | } 100 | } 101 | 102 | @Override 103 | public void onCreate(Bundle savedInstanceState) { 104 | super.onCreate(savedInstanceState); 105 | if (savedInstanceState == null) 106 | savedInstanceState = getArguments(); 107 | } 108 | 109 | @Override 110 | public void onSaveInstanceState(final Bundle saveInstanceState) { 111 | final Bundle args = getArguments(); 112 | if (args != null) 113 | saveInstanceState.putAll(args); 114 | super.onSaveInstanceState(saveInstanceState); 115 | } 116 | 117 | @Override 118 | public Dialog onCreateDialog(final Bundle savedInstanceState) { 119 | final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 120 | builder.setView(initView()); 121 | builder.setTitle(R.string.select); 122 | builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener); 123 | builder.setNegativeButton(android.R.string.cancel , mOnDialogClickListener); 124 | builder.setNeutralButton(R.string.refresh, null); 125 | final Dialog dialog = builder.create(); 126 | dialog.setCancelable(true); 127 | dialog.setCanceledOnTouchOutside(true); 128 | return dialog; 129 | } 130 | 131 | /** 132 | * create view that this fragment shows 133 | * @return 134 | */ 135 | private final View initView() { 136 | final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null); 137 | mSpinner = (Spinner)rootView.findViewById(R.id.spinner1); 138 | final View empty = rootView.findViewById(android.R.id.empty); 139 | mSpinner.setEmptyView(empty); 140 | return rootView; 141 | } 142 | 143 | 144 | @Override 145 | public void onResume() { 146 | super.onResume(); 147 | updateDevices(); 148 | final Button button = (Button)getDialog().findViewById(android.R.id.button3); 149 | if (button != null) { 150 | button.setOnClickListener(mOnClickListener); 151 | } 152 | } 153 | 154 | private final OnClickListener mOnClickListener = new OnClickListener() { 155 | @Override 156 | public void onClick(final View v) { 157 | switch (v.getId()) { 158 | case android.R.id.button3: 159 | updateDevices(); 160 | break; 161 | } 162 | } 163 | }; 164 | 165 | private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() { 166 | @Override 167 | public void onClick(final DialogInterface dialog, final int which) { 168 | switch (which) { 169 | case DialogInterface.BUTTON_POSITIVE: 170 | final Object item = mSpinner.getSelectedItem(); 171 | if (item instanceof UsbDevice) { 172 | mUSBMonitor.requestPermission((UsbDevice)item); 173 | ((CameraDialogParent)getActivity()).onDialogResult(false); 174 | } 175 | break; 176 | case DialogInterface.BUTTON_NEGATIVE: 177 | ((CameraDialogParent)getActivity()).onDialogResult(true); 178 | break; 179 | } 180 | } 181 | }; 182 | 183 | @Override 184 | public void onCancel(final DialogInterface dialog) { 185 | ((CameraDialogParent)getActivity()).onDialogResult(true); 186 | super.onCancel(dialog); 187 | } 188 | 189 | public void updateDevices() { 190 | // mUSBMonitor.dumpDevices(); 191 | final List filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter); 192 | mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0))); 193 | mSpinner.setAdapter(mDeviceListAdapter); 194 | } 195 | 196 | private static final class DeviceListAdapter extends BaseAdapter { 197 | 198 | private final LayoutInflater mInflater; 199 | private final List mList; 200 | 201 | public DeviceListAdapter(final Context context, final Listlist) { 202 | mInflater = LayoutInflater.from(context); 203 | mList = list != null ? list : new ArrayList(); 204 | } 205 | 206 | @Override 207 | public int getCount() { 208 | return mList.size(); 209 | } 210 | 211 | @Override 212 | public UsbDevice getItem(final int position) { 213 | if ((position >= 0) && (position < mList.size())) 214 | return mList.get(position); 215 | else 216 | return null; 217 | } 218 | 219 | @Override 220 | public long getItemId(final int position) { 221 | return position; 222 | } 223 | 224 | @Override 225 | public View getView(final int position, View convertView, final ViewGroup parent) { 226 | if (convertView == null) { 227 | convertView = mInflater.inflate(R.layout.listitem_device, parent, false); 228 | } 229 | if (convertView instanceof CheckedTextView) { 230 | final UsbDevice device = getItem(position); 231 | ((CheckedTextView)convertView).setText( 232 | String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName())); 233 | } 234 | return convertView; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/IButtonCallback.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb; 2 | 3 | public interface IButtonCallback { 4 | void onButton(int button, int state); 5 | } 6 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb; 25 | 26 | import java.nio.ByteBuffer; 27 | 28 | /** 29 | * Callback interface for UVCCamera class 30 | * If you need frame data as ByteBuffer, you can use this callback interface with UVCCamera#setFrameCallback 31 | */ 32 | public interface IFrameCallback { 33 | /** 34 | * This method is called from native library via JNI on the same thread as UVCCamera#startCapture. 35 | * You can use both UVCCamera#startCapture and #setFrameCallback 36 | * but it is better to use either for better performance. 37 | * You can also pass pixel format type to UVCCamera#setFrameCallback for this method. 38 | * Some frames may drops if this method takes a time. 39 | * When you use some color format like NV21, this library never execute color space conversion, 40 | * just execute pixel format conversion. If you want to get same result as on screen, please try to 41 | * consider to get images via texture(SurfaceTexture) and read pixel buffer from it using OpenGL|ES2/3 42 | * instead of using IFrameCallback(this way is much efficient in most case than using IFrameCallback). 43 | * @param frame this is direct ByteBuffer from JNI layer and you should handle it's byte order and limitation. 44 | */ 45 | public void onFrame(ByteBuffer frame); 46 | } 47 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/IStatusCallback.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public interface IStatusCallback { 6 | void onStatus(int statusClass, int event, int selector, int statusAttribute, ByteBuffer data); 7 | } 8 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/Size.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb; 25 | 26 | import android.os.Parcel; 27 | import android.os.Parcelable; 28 | 29 | import java.util.Locale; 30 | 31 | public class Size implements Parcelable { 32 | // 33 | /** 34 | * native側のuvc_raw_format_tの値, こっちは主にlibuvc用 35 | * 9999 is still image 36 | */ 37 | public int type; 38 | /** 39 | * native側のraw_frame_tの値, androusb用, 40 | * libuvcは対応していない 41 | */ 42 | public int frame_type; 43 | public int index; 44 | public int width; 45 | public int height; 46 | public int frameIntervalType; 47 | public int frameIntervalIndex; 48 | public int[] intervals; 49 | // ここ以下はframeIntervalTypeとintervalsから#updateFrameRateで計算する 50 | public float[] fps; 51 | private String frameRates; 52 | 53 | /** 54 | * コンストラクタ 55 | * @param _type native側のraw_format_tの値, ただし9999は静止画 56 | * @param _frame_type native側のraw_frame_tの値 57 | * @param _index 58 | * @param _width 59 | * @param _height 60 | */ 61 | public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height) { 62 | type = _type; 63 | frame_type = _frame_type; 64 | index = _index; 65 | width = _width; 66 | height = _height; 67 | frameIntervalType = -1; 68 | frameIntervalIndex = 0; 69 | intervals = null; 70 | updateFrameRate(); 71 | } 72 | 73 | /** 74 | * コンストラクタ 75 | * @param _type native側のraw_format_tの値, ただし9999は静止画 76 | * @param _frame_type native側のraw_frame_tの値 77 | * @param _index 78 | * @param _width 79 | * @param _height 80 | * @param _min_intervals 81 | * @param _max_intervals 82 | */ 83 | public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int _min_intervals, final int _max_intervals, final int _step) { 84 | type = _type; 85 | frame_type = _frame_type; 86 | index = _index; 87 | width = _width; 88 | height = _height; 89 | frameIntervalType = 0; 90 | frameIntervalIndex = 0; 91 | intervals = new int[3]; 92 | intervals[0] = _min_intervals; 93 | intervals[1] = _max_intervals; 94 | intervals[2] = _step; 95 | updateFrameRate(); 96 | } 97 | 98 | /** 99 | * コンストラクタ 100 | * @param _type native側のraw_format_tの値, ただし9999は静止画 101 | * @param _frame_type native側のraw_frame_tの値 102 | * @param _index 103 | * @param _width 104 | * @param _height 105 | * @param _intervals 106 | */ 107 | public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int[] _intervals) { 108 | type = _type; 109 | frame_type = _frame_type; 110 | index = _index; 111 | width = _width; 112 | height = _height; 113 | final int n = _intervals != null ? _intervals.length : -1; 114 | if (n > 0) { 115 | frameIntervalType = n; 116 | intervals = new int[n]; 117 | System.arraycopy(_intervals, 0, intervals, 0, n); 118 | } else { 119 | frameIntervalType = -1; 120 | intervals = null; 121 | } 122 | frameIntervalIndex = 0; 123 | updateFrameRate(); 124 | } 125 | 126 | /** 127 | * コピーコンストラクタ 128 | * @param other 129 | */ 130 | public Size(final Size other) { 131 | type = other.type; 132 | frame_type = other.frame_type; 133 | index = other.index; 134 | width = other.width; 135 | height = other.height; 136 | frameIntervalType = other.frameIntervalType; 137 | frameIntervalIndex = other.frameIntervalIndex; 138 | final int n = other.intervals != null ? other.intervals.length : -1; 139 | if (n > 0) { 140 | intervals = new int[n]; 141 | System.arraycopy(other.intervals, 0, intervals, 0, n); 142 | } else { 143 | intervals = null; 144 | } 145 | updateFrameRate(); 146 | } 147 | 148 | private Size(final Parcel source) { 149 | // 読み取り順はwriteToParcelでの書き込み順と同じでないとダメ 150 | type = source.readInt(); 151 | frame_type = source.readInt(); 152 | index = source.readInt(); 153 | width = source.readInt(); 154 | height = source.readInt(); 155 | frameIntervalType = source.readInt(); 156 | frameIntervalIndex = source.readInt(); 157 | if (frameIntervalType >= 0) { 158 | if (frameIntervalType > 0) { 159 | intervals = new int[frameIntervalType]; 160 | } else { 161 | intervals = new int[3]; 162 | } 163 | source.readIntArray(intervals); 164 | } else { 165 | intervals = null; 166 | } 167 | updateFrameRate(); 168 | } 169 | 170 | public Size set(final Size other) { 171 | if (other != null) { 172 | type = other.type; 173 | frame_type = other.frame_type; 174 | index = other.index; 175 | width = other.width; 176 | height = other.height; 177 | frameIntervalType = other.frameIntervalType; 178 | frameIntervalIndex = other.frameIntervalIndex; 179 | final int n = other.intervals != null ? other.intervals.length : -1; 180 | if (n > 0) { 181 | intervals = new int[n]; 182 | System.arraycopy(other.intervals, 0, intervals, 0, n); 183 | } else { 184 | intervals = null; 185 | } 186 | updateFrameRate(); 187 | } 188 | return this; 189 | } 190 | 191 | public float getCurrentFrameRate() throws IllegalStateException { 192 | final int n = fps != null ? fps.length : 0; 193 | if ((frameIntervalIndex >= 0) && (frameIntervalIndex < n)) { 194 | return fps[frameIntervalIndex]; 195 | } 196 | throw new IllegalStateException("unknown frame rate or not ready"); 197 | } 198 | 199 | public void setCurrentFrameRate(final float frameRate) { 200 | // 一番近いのを選ぶ 201 | int index = -1; 202 | final int n = fps != null ? fps.length : 0; 203 | for (int i = 0; i < n; i++) { 204 | if (fps[i] <= frameRate) { 205 | index = i; 206 | break; 207 | } 208 | } 209 | frameIntervalIndex = index; 210 | } 211 | 212 | @Override 213 | public int describeContents() { 214 | return 0; 215 | } 216 | 217 | @Override 218 | public void writeToParcel(final Parcel dest, final int flags) { 219 | dest.writeInt(type); 220 | dest.writeInt(frame_type); 221 | dest.writeInt(index); 222 | dest.writeInt(width); 223 | dest.writeInt(height); 224 | dest.writeInt(frameIntervalType); 225 | dest.writeInt(frameIntervalIndex); 226 | if (intervals != null) { 227 | dest.writeIntArray(intervals); 228 | } 229 | } 230 | 231 | public void updateFrameRate() { 232 | final int n = frameIntervalType; 233 | if (n > 0) { 234 | fps = new float[n]; 235 | for (int i = 0; i < n; i++) { 236 | final float _fps = fps[i] = 10000000.0f / intervals[i]; 237 | } 238 | } else if (n == 0) { 239 | try { 240 | final int min = Math.min(intervals[0], intervals[1]); 241 | final int max = Math.max(intervals[0], intervals[1]); 242 | final int step = intervals[2]; 243 | if (step > 0) { 244 | int m = 0; 245 | for (int i = min; i <= max; i+= step) { m++; } 246 | fps = new float[m]; 247 | m = 0; 248 | for (int i = min; i <= max; i+= step) { 249 | final float _fps = fps[m++] = 10000000.0f / i; 250 | } 251 | } else { 252 | final float max_fps = 10000000.0f / min; 253 | int m = 0; 254 | for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { m++; } 255 | fps = new float[m]; 256 | m = 0; 257 | for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { 258 | this.fps[m++] = fps; 259 | } 260 | } 261 | } catch (final Exception e) { 262 | // ignore, なんでかminとmaxが0になってるんちゃうかな 263 | fps = null; 264 | } 265 | } 266 | final int m = fps != null ? fps.length : 0; 267 | final StringBuilder sb = new StringBuilder(); 268 | sb.append("["); 269 | for (int i = 0; i < m; i++) { 270 | sb.append(String.format(Locale.US, "%4.1f", fps[i])); 271 | if (i < m-1) { 272 | sb.append(","); 273 | } 274 | } 275 | sb.append("]"); 276 | frameRates = sb.toString(); 277 | if (frameIntervalIndex > m) { 278 | frameIntervalIndex = 0; 279 | } 280 | } 281 | 282 | @Override 283 | public String toString() { 284 | float frame_rate = 0.0f; 285 | try { 286 | frame_rate = getCurrentFrameRate(); 287 | } catch (final Exception e) { 288 | } 289 | return String.format(Locale.US, "Size(%dx%d@%4.1f,type:%d,frame:%d,index:%d,%s)", width, height, frame_rate, type, frame_type, index, frameRates); 290 | } 291 | 292 | public static final Creator CREATOR = new Creator() { 293 | @Override 294 | public Size createFromParcel(final Parcel source) { 295 | return new Size(source); 296 | } 297 | @Override 298 | public Size[] newArray(final int size) { 299 | return new Size[size]; 300 | } 301 | }; 302 | } 303 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.common; 25 | 26 | import android.app.Activity; 27 | 28 | import com.serenegiant.usb.UVCCamera; 29 | import com.serenegiant.usb.widget.CameraViewInterface; 30 | 31 | public class UVCCameraHandler extends AbstractUVCCameraHandler { 32 | 33 | /** 34 | * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG, default bandwidth 35 | * @param parent 36 | * @param cameraView 37 | * @param width 38 | * @param height 39 | * @return 40 | */ 41 | public static final UVCCameraHandler createHandler( 42 | final Activity parent, final CameraViewInterface cameraView, 43 | final int width, final int height) { 44 | 45 | return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); 46 | } 47 | 48 | /** 49 | * create UVCCameraHandler, use MediaVideoEncoder, try MJPEG 50 | * @param parent 51 | * @param cameraView 52 | * @param width 53 | * @param height 54 | * @param bandwidthFactor 55 | * @return 56 | */ 57 | public static final UVCCameraHandler createHandler( 58 | final Activity parent, final CameraViewInterface cameraView, 59 | final int width, final int height, final float bandwidthFactor) { 60 | 61 | return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor); 62 | } 63 | 64 | /** 65 | * create UVCCameraHandler, try MJPEG, default bandwidth 66 | * @param parent 67 | * @param cameraView 68 | * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder 69 | * @param width 70 | * @param height 71 | * @return 72 | */ 73 | public static final UVCCameraHandler createHandler( 74 | final Activity parent, final CameraViewInterface cameraView, 75 | final int encoderType, final int width, final int height) { 76 | 77 | return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); 78 | } 79 | 80 | /** 81 | * create UVCCameraHandler, default bandwidth 82 | * @param parent 83 | * @param cameraView 84 | * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder 85 | * @param width 86 | * @param height 87 | * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) 88 | * @return 89 | */ 90 | public static final UVCCameraHandler createHandler( 91 | final Activity parent, final CameraViewInterface cameraView, 92 | final int encoderType, final int width, final int height, final int format) { 93 | 94 | return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH); 95 | } 96 | 97 | /** 98 | * create UVCCameraHandler 99 | * @param parent 100 | * @param cameraView 101 | * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder 102 | * @param width 103 | * @param height 104 | * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) 105 | * @param bandwidthFactor 106 | * @return 107 | */ 108 | public static final UVCCameraHandler createHandler( 109 | final Activity parent, final CameraViewInterface cameraView, 110 | final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) { 111 | 112 | final CameraThread thread = new CameraThread(UVCCameraHandler.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor); 113 | thread.start(); 114 | return (UVCCameraHandler)thread.getHandler(); 115 | } 116 | 117 | protected UVCCameraHandler(final CameraThread thread) { 118 | super(thread); 119 | } 120 | 121 | @Override 122 | public void startPreview(final Object surface) { 123 | super.startPreview(surface); 124 | } 125 | 126 | // @Override 127 | // public void captureStill() { 128 | // super.captureStill(); 129 | // } 130 | 131 | @Override 132 | public void captureStill(final String path,OnCaptureListener listener) { 133 | super.captureStill(path,listener); 134 | } 135 | 136 | @Override 137 | public void startCameraFoucs() { 138 | super.startCameraFoucs(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.common; 25 | 26 | import android.app.Activity; 27 | import android.view.Surface; 28 | 29 | import com.serenegiant.glutils.RendererHolder; 30 | import com.serenegiant.usb.UVCCamera; 31 | import com.serenegiant.usb.widget.CameraViewInterface; 32 | 33 | import java.io.FileNotFoundException; 34 | 35 | public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { 36 | /** 37 | * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG, default bandwidth 38 | * @param parent 39 | * @param cameraView 40 | * @param width 41 | * @param height 42 | * @return 43 | */ 44 | public static final UVCCameraHandlerMultiSurface createHandler( 45 | final Activity parent, final CameraViewInterface cameraView, 46 | final int width, final int height) { 47 | 48 | return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); 49 | } 50 | 51 | /** 52 | * create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG 53 | * @param parent 54 | * @param cameraView 55 | * @param width 56 | * @param height 57 | * @param bandwidthFactor 58 | * @return 59 | */ 60 | public static final UVCCameraHandlerMultiSurface createHandler( 61 | final Activity parent, final CameraViewInterface cameraView, 62 | final int width, final int height, final float bandwidthFactor) { 63 | 64 | return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor); 65 | } 66 | 67 | /** 68 | * create UVCCameraHandlerMultiSurface, try MJPEG, default bandwidth 69 | * @param parent 70 | * @param cameraView 71 | * @param encoderType 72 | * @param width 73 | * @param height 74 | * @return 75 | */ 76 | public static final UVCCameraHandlerMultiSurface createHandler( 77 | final Activity parent, final CameraViewInterface cameraView, 78 | final int encoderType, final int width, final int height) { 79 | 80 | return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH); 81 | } 82 | 83 | /** 84 | * create UVCCameraHandlerMultiSurface, default bandwidth 85 | * @param parent 86 | * @param cameraView 87 | * @param encoderType 88 | * @param width 89 | * @param height 90 | * @param format 91 | * @return 92 | */ 93 | public static final UVCCameraHandlerMultiSurface createHandler( 94 | final Activity parent, final CameraViewInterface cameraView, 95 | final int encoderType, final int width, final int height, final int format) { 96 | 97 | return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH); 98 | } 99 | 100 | /** 101 | * create UVCCameraHandlerMultiSurface 102 | * @param parent 103 | * @param cameraView 104 | * @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder 105 | * @param width 106 | * @param height 107 | * @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1) 108 | * @param bandwidthFactor 109 | * @return 110 | */ 111 | public static final UVCCameraHandlerMultiSurface createHandler( 112 | final Activity parent, final CameraViewInterface cameraView, 113 | final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) { 114 | 115 | final CameraThread thread = new CameraThread(UVCCameraHandlerMultiSurface.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor); 116 | thread.start(); 117 | return (UVCCameraHandlerMultiSurface)thread.getHandler(); 118 | } 119 | 120 | private RendererHolder mRendererHolder; 121 | protected UVCCameraHandlerMultiSurface(final CameraThread thread) { 122 | super(thread); 123 | mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null); 124 | } 125 | 126 | public synchronized void release() { 127 | if (mRendererHolder != null) { 128 | mRendererHolder.release(); 129 | mRendererHolder = null; 130 | } 131 | super.release(); 132 | } 133 | 134 | public synchronized void resize(final int width, final int height) { 135 | super.resize(width, height); 136 | if (mRendererHolder != null) { 137 | mRendererHolder.resize(width, height); 138 | } 139 | } 140 | 141 | public synchronized void startPreview() { 142 | checkReleased(); 143 | if (mRendererHolder != null) { 144 | super.startPreview(mRendererHolder.getSurface()); 145 | } else { 146 | throw new IllegalStateException(); 147 | } 148 | } 149 | 150 | public synchronized void addSurface(final int surfaceId, final Surface surface, final boolean isRecordable) { 151 | checkReleased(); 152 | mRendererHolder.addSurface(surfaceId, surface, isRecordable); 153 | } 154 | 155 | public synchronized void removeSurface(final int surfaceId) { 156 | if (mRendererHolder != null) { 157 | mRendererHolder.removeSurface(surfaceId); 158 | } 159 | } 160 | 161 | // @Override 162 | // public void captureStill() { 163 | // checkReleased(); 164 | // super.captureStill(); 165 | // } 166 | 167 | @Override 168 | public void captureStill(final String path,OnCaptureListener listener) { 169 | checkReleased(); 170 | post(new Runnable() { 171 | @Override 172 | public void run() { 173 | synchronized (UVCCameraHandlerMultiSurface.this) { 174 | if (mRendererHolder != null) { 175 | try { 176 | mRendererHolder.captureStill(path); 177 | updateMedia(path); 178 | } catch (FileNotFoundException e) { 179 | e.printStackTrace(); 180 | } 181 | } 182 | } 183 | } 184 | }); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | public interface IAudioEncoder { 27 | } 28 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | public interface IVideoEncoder { 27 | public boolean frameAvailableSoon(); 28 | } 29 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | import android.media.AudioFormat; 27 | import android.media.AudioRecord; 28 | import android.media.MediaCodec; 29 | import android.media.MediaCodecInfo; 30 | import android.media.MediaCodecList; 31 | import android.media.MediaFormat; 32 | import android.media.MediaRecorder; 33 | import android.util.Log; 34 | 35 | import java.io.IOException; 36 | import java.nio.ByteBuffer; 37 | import java.nio.ByteOrder; 38 | 39 | public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder { 40 | private static final boolean DEBUG = true; // TODO set false on release 41 | private static final String TAG = "MediaAudioEncoder"; 42 | 43 | private static final String MIME_TYPE = "audio/mp4a-latm"; 44 | public static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices. 45 | private static final int BIT_RATE = 64000; 46 | public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel 47 | public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec 48 | 49 | private AudioThread mAudioThread = null; 50 | 51 | public MediaAudioEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) { 52 | super(muxer, listener); 53 | } 54 | 55 | @Override 56 | protected void prepare() throws IOException { 57 | if (DEBUG) Log.v(TAG, "prepare:"); 58 | mTrackIndex = -1; 59 | mMuxerStarted = mIsEOS = false; 60 | // prepare MediaCodec for AAC encoding of audio data from inernal mic. 61 | final MediaCodecInfo audioCodecInfo = selectAudioCodec(MIME_TYPE); 62 | if (audioCodecInfo == null) { 63 | Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); 64 | return; 65 | } 66 | if (DEBUG) Log.i(TAG, "selected codec: " + audioCodecInfo.getName()); 67 | 68 | final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1); 69 | audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 70 | audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO); 71 | audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 72 | audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 73 | // audioFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, inputFile.length()); 74 | // audioFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs ); 75 | if (DEBUG) Log.i(TAG, "format: " + audioFormat); 76 | mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); 77 | mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 78 | mMediaCodec.start(); 79 | if (DEBUG) Log.i(TAG, "prepare finishing"); 80 | if (mListener != null) { 81 | try { 82 | mListener.onPrepared(this); 83 | } catch (final Exception e) { 84 | Log.e(TAG, "prepare:", e); 85 | } 86 | } 87 | } 88 | 89 | @Override 90 | protected void startRecording() { 91 | super.startRecording(); 92 | // create and execute audio capturing thread using internal mic 93 | if (mAudioThread == null) { 94 | mAudioThread = new AudioThread(); 95 | mAudioThread.start(); 96 | } 97 | } 98 | 99 | @Override 100 | protected void release() { 101 | mAudioThread = null; 102 | super.release(); 103 | } 104 | 105 | private static final int[] AUDIO_SOURCES = new int[] { 106 | MediaRecorder.AudioSource.DEFAULT, 107 | MediaRecorder.AudioSource.MIC, 108 | MediaRecorder.AudioSource.CAMCORDER, 109 | }; 110 | 111 | /** 112 | * Thread to capture audio data from internal mic as uncompressed 16bit PCM data 113 | * and write them to the MediaCodec encoder 114 | */ 115 | private class AudioThread extends Thread { 116 | @Override 117 | public void run() { 118 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); // THREAD_PRIORITY_URGENT_AUDIO 119 | int cnt = 0; 120 | final int min_buffer_size = AudioRecord.getMinBufferSize( 121 | SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); 122 | int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER; 123 | if (buffer_size < min_buffer_size) 124 | buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2; 125 | final ByteBuffer buf = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME).order(ByteOrder.nativeOrder()); 126 | AudioRecord audioRecord = null; 127 | for (final int src: AUDIO_SOURCES) { 128 | try { 129 | audioRecord = new AudioRecord(src, 130 | SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size); 131 | if (audioRecord != null) { 132 | if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { 133 | audioRecord.release(); 134 | audioRecord = null; 135 | } 136 | } 137 | } catch (final Exception e) { 138 | audioRecord = null; 139 | } 140 | if (audioRecord != null) { 141 | break; 142 | } 143 | } 144 | if (audioRecord != null) { 145 | try { 146 | if (mIsCapturing) { 147 | if (DEBUG) Log.v(TAG, "AudioThread:start audio recording"); 148 | int readBytes; 149 | audioRecord.startRecording(); 150 | try { 151 | for ( ; mIsCapturing && !mRequestStop && !mIsEOS ; ) { 152 | // read audio data from internal mic 153 | buf.clear(); 154 | try { 155 | readBytes = audioRecord.read(buf, SAMPLES_PER_FRAME); 156 | } catch (final Exception e) { 157 | break; 158 | } 159 | if (readBytes > 0) { 160 | // set audio data to encoder 161 | buf.position(readBytes); 162 | buf.flip(); 163 | encode(buf, readBytes, getPTSUs()); 164 | frameAvailableSoon(); 165 | cnt++; 166 | } 167 | } 168 | if (cnt > 0) { 169 | frameAvailableSoon(); 170 | } 171 | } finally { 172 | audioRecord.stop(); 173 | } 174 | } 175 | } catch (final Exception e) { 176 | Log.e(TAG, "AudioThread#run", e); 177 | } finally { 178 | audioRecord.release(); 179 | } 180 | } 181 | if (cnt == 0) { 182 | for (int i = 0; mIsCapturing && (i < 5); i++) { 183 | buf.position(SAMPLES_PER_FRAME); 184 | buf.flip(); 185 | try { 186 | encode(buf, SAMPLES_PER_FRAME, getPTSUs()); 187 | frameAvailableSoon(); 188 | } catch (final Exception e) { 189 | break; 190 | } 191 | synchronized(this) { 192 | try { 193 | wait(50); 194 | } catch (final InterruptedException e) { 195 | 196 | } 197 | } 198 | } 199 | } 200 | if (DEBUG) Log.v(TAG, "AudioThread:finished"); 201 | } 202 | } 203 | 204 | /** 205 | * select the first codec that match a specific MIME type 206 | * @param mimeType 207 | * @return 208 | */ 209 | private static final MediaCodecInfo selectAudioCodec(final String mimeType) { 210 | if (DEBUG) Log.v(TAG, "selectAudioCodec:"); 211 | 212 | MediaCodecInfo result = null; 213 | // get the list of available codecs 214 | final int numCodecs = MediaCodecList.getCodecCount(); 215 | LOOP: for (int i = 0; i < numCodecs; i++) { 216 | final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 217 | if (!codecInfo.isEncoder()) { // skipp decoder 218 | continue; 219 | } 220 | final String[] types = codecInfo.getSupportedTypes(); 221 | for (int j = 0; j < types.length; j++) { 222 | if (DEBUG) Log.i(TAG, "supportedType:" + codecInfo.getName() + ",MIME=" + types[j]); 223 | if (types[j].equalsIgnoreCase(mimeType)) { 224 | if (result == null) { 225 | result = codecInfo; 226 | break LOOP; 227 | } 228 | } 229 | } 230 | } 231 | return result; 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaMuxerWrapper.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb.encoder; 2 | 3 | import android.annotation.TargetApi; 4 | import android.media.MediaCodec; 5 | import android.media.MediaFormat; 6 | import android.media.MediaMuxer; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.text.TextUtils; 10 | import android.util.Log; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | import java.text.SimpleDateFormat; 16 | import java.util.GregorianCalendar; 17 | import java.util.Locale; 18 | 19 | public class MediaMuxerWrapper { 20 | private static final boolean DEBUG = true; // TODO set false on release 21 | private static final String TAG = "MediaMuxerWrapper"; 22 | 23 | private static final String DIR_NAME = "USBCameraTest"; 24 | private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); 25 | 26 | private String mOutputPath; 27 | private final MediaMuxer mMediaMuxer; // API >= 18 28 | private int mEncoderCount, mStatredCount; 29 | private boolean mIsStarted; 30 | private MediaEncoder mVideoEncoder, mAudioEncoder; 31 | 32 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 33 | public MediaMuxerWrapper(String path) throws IOException { 34 | try { 35 | // 保存到自定义路径还是手机默认Movies路径 36 | if (TextUtils.isEmpty(path)) 37 | mOutputPath = getCaptureFile(Environment.DIRECTORY_MOVIES, ".mp4").toString(); 38 | mOutputPath = path; 39 | 40 | } catch (final NullPointerException e) { 41 | throw new RuntimeException("This app has no permission of writing external storage"); 42 | } 43 | mMediaMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 44 | mEncoderCount = mStatredCount = 0; 45 | mIsStarted = false; 46 | } 47 | 48 | public String getOutputPath() { 49 | return mOutputPath; 50 | } 51 | 52 | public void prepare() throws IOException { 53 | if (mVideoEncoder != null) 54 | mVideoEncoder.prepare(); 55 | if (mAudioEncoder != null) 56 | mAudioEncoder.prepare(); 57 | } 58 | 59 | public void startRecording() { 60 | if (mVideoEncoder != null) 61 | mVideoEncoder.startRecording(); 62 | if (mAudioEncoder != null) 63 | mAudioEncoder.startRecording(); 64 | } 65 | 66 | public void stopRecording() { 67 | if (mVideoEncoder != null) 68 | mVideoEncoder.stopRecording(); 69 | mVideoEncoder = null; 70 | if (mAudioEncoder != null) 71 | mAudioEncoder.stopRecording(); 72 | mAudioEncoder = null; 73 | } 74 | 75 | public synchronized boolean isStarted() { 76 | return mIsStarted; 77 | } 78 | 79 | //********************************************************************** 80 | //********************************************************************** 81 | /** 82 | * assign encoder to this calss. this is called from encoder. 83 | * @param encoder instance of MediaVideoEncoder or MediaAudioEncoder 84 | */ 85 | /*package*/ void addEncoder(final MediaEncoder encoder) { 86 | if (encoder instanceof MediaVideoEncoder) { 87 | if (mVideoEncoder != null) 88 | throw new IllegalArgumentException("Video encoder already added."); 89 | mVideoEncoder = encoder; 90 | } else if (encoder instanceof MediaSurfaceEncoder) { 91 | if (mVideoEncoder != null) 92 | throw new IllegalArgumentException("Video encoder already added."); 93 | mVideoEncoder = encoder; 94 | } else if (encoder instanceof MediaVideoBufferEncoder) { 95 | if (mVideoEncoder != null) 96 | throw new IllegalArgumentException("Video encoder already added."); 97 | mVideoEncoder = encoder; 98 | } else if (encoder instanceof MediaAudioEncoder) { 99 | if (mAudioEncoder != null) 100 | throw new IllegalArgumentException("Video encoder already added."); 101 | mAudioEncoder = encoder; 102 | } else 103 | throw new IllegalArgumentException("unsupported encoder"); 104 | mEncoderCount = (mVideoEncoder != null ? 1 : 0) + (mAudioEncoder != null ? 1 : 0); 105 | } 106 | 107 | /** 108 | * request start recording from encoder 109 | * @return true when muxer is ready to write 110 | */ 111 | /*package*/ synchronized boolean start() { 112 | if (DEBUG) Log.v(TAG, "start:"); 113 | mStatredCount++; 114 | if ((mEncoderCount > 0) && (mStatredCount == mEncoderCount)) { 115 | mMediaMuxer.start(); 116 | mIsStarted = true; 117 | notifyAll(); 118 | if (DEBUG) Log.v(TAG, "MediaMuxer started:"); 119 | } 120 | return mIsStarted; 121 | } 122 | 123 | /** 124 | * request stop recording from encoder when encoder received EOS 125 | */ 126 | /*package*/ synchronized void stop() { 127 | if (DEBUG) Log.v(TAG, "stop:mStatredCount=" + mStatredCount); 128 | mStatredCount--; 129 | if ((mEncoderCount > 0) && (mStatredCount <= 0)) { 130 | try { 131 | mMediaMuxer.stop(); 132 | } catch (final Exception e) { 133 | Log.w(TAG, e); 134 | } 135 | mIsStarted = false; 136 | if (DEBUG) Log.v(TAG, "MediaMuxer stopped:"); 137 | } 138 | } 139 | 140 | /** 141 | * assign encoder to muxer 142 | * @param format 143 | * @return minus value indicate error 144 | */ 145 | /*package*/ synchronized int addTrack(final MediaFormat format) { 146 | if (mIsStarted) 147 | throw new IllegalStateException("muxer already started"); 148 | final int trackIx = mMediaMuxer.addTrack(format); 149 | if (DEBUG) Log.i(TAG, "addTrack:trackNum=" + mEncoderCount + ",trackIx=" + trackIx + ",format=" + format); 150 | return trackIx; 151 | } 152 | 153 | /** 154 | * write encoded data to muxer 155 | * @param trackIndex 156 | * @param byteBuf 157 | * @param bufferInfo 158 | */ 159 | /*package*/ synchronized void writeSampleData(final int trackIndex, final ByteBuffer byteBuf, final MediaCodec.BufferInfo bufferInfo) { 160 | if (mStatredCount > 0) 161 | mMediaMuxer.writeSampleData(trackIndex, byteBuf, bufferInfo); 162 | } 163 | 164 | //********************************************************************** 165 | //********************************************************************** 166 | /** 167 | * generate output file 168 | * @param type Environment.DIRECTORY_MOVIES / Environment.DIRECTORY_DCIM etc. 169 | * @param ext .mp4(.m4a for audio) or .png 170 | * @return return null when this app has no writing permission to external storage. 171 | */ 172 | public static final File getCaptureFile(final String type, final String ext) { 173 | final File dir = new File(Environment.getExternalStoragePublicDirectory(type), DIR_NAME); 174 | Log.d(TAG, "path=" + dir.toString()); 175 | dir.mkdirs(); 176 | if (dir.canWrite()) { 177 | return new File(dir, getDateTimeString() + ext); 178 | } 179 | return null; 180 | } 181 | 182 | /** 183 | * get current date and time as String 184 | * @return 185 | */ 186 | private static final String getDateTimeString() { 187 | final GregorianCalendar now = new GregorianCalendar(); 188 | return mDateTimeFormat.format(now.getTime()); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | import android.media.MediaCodec; 27 | import android.media.MediaCodecInfo; 28 | import android.media.MediaCodecList; 29 | import android.media.MediaFormat; 30 | import android.util.Log; 31 | import android.view.Surface; 32 | 33 | import java.io.IOException; 34 | 35 | public class MediaSurfaceEncoder extends MediaEncoder implements IVideoEncoder { 36 | private static final boolean DEBUG = true; // TODO set false on release 37 | private static final String TAG = "MediaSurfaceEncoder"; 38 | 39 | private static final String MIME_TYPE = "video/avc"; 40 | // parameters for recording 41 | private final int mWidth, mHeight; 42 | private static final int FRAME_RATE = 15; 43 | private static final float BPP = 0.50f; 44 | 45 | private Surface mSurface; 46 | 47 | public MediaSurfaceEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { 48 | super(muxer, listener); 49 | if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); 50 | mWidth = width; 51 | mHeight = height; 52 | } 53 | 54 | /** 55 | * Returns the encoder's input surface. 56 | */ 57 | public Surface getInputSurface() { 58 | return mSurface; 59 | } 60 | 61 | @Override 62 | protected void prepare() throws IOException { 63 | if (DEBUG) Log.i(TAG, "prepare: "); 64 | mTrackIndex = -1; 65 | mMuxerStarted = mIsEOS = false; 66 | 67 | final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); 68 | if (videoCodecInfo == null) { 69 | Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); 70 | return; 71 | } 72 | if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); 73 | 74 | final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); 75 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18 76 | format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); 77 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 78 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); 79 | if (DEBUG) Log.i(TAG, "format: " + format); 80 | 81 | mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); 82 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 83 | // get Surface for encoder input 84 | // this method only can call between #configure and #start 85 | mSurface = mMediaCodec.createInputSurface(); // API >= 18 86 | mMediaCodec.start(); 87 | if (DEBUG) Log.i(TAG, "prepare finishing"); 88 | if (mListener != null) { 89 | try { 90 | mListener.onPrepared(this); 91 | } catch (final Exception e) { 92 | Log.e(TAG, "prepare:", e); 93 | } 94 | } 95 | } 96 | 97 | @Override 98 | protected void release() { 99 | if (DEBUG) Log.i(TAG, "release:"); 100 | if (mSurface != null) { 101 | mSurface.release(); 102 | mSurface = null; 103 | } 104 | super.release(); 105 | } 106 | 107 | private int calcBitRate() { 108 | final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); 109 | Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); 110 | return bitrate; 111 | } 112 | 113 | /** 114 | * select the first codec that match a specific MIME type 115 | * @param mimeType 116 | * @return null if no codec matched 117 | */ 118 | protected static final MediaCodecInfo selectVideoCodec(final String mimeType) { 119 | if (DEBUG) Log.v(TAG, "selectVideoCodec:"); 120 | 121 | // get the list of available codecs 122 | final int numCodecs = MediaCodecList.getCodecCount(); 123 | for (int i = 0; i < numCodecs; i++) { 124 | final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 125 | 126 | if (!codecInfo.isEncoder()) { // skipp decoder 127 | continue; 128 | } 129 | // select first codec that match a specific MIME type and color format 130 | final String[] types = codecInfo.getSupportedTypes(); 131 | for (int j = 0; j < types.length; j++) { 132 | if (types[j].equalsIgnoreCase(mimeType)) { 133 | if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); 134 | final int format = selectColorFormat(codecInfo, mimeType); 135 | if (format > 0) { 136 | return codecInfo; 137 | } 138 | } 139 | } 140 | } 141 | return null; 142 | } 143 | 144 | /** 145 | * select color format available on specific codec and we can use. 146 | * @return 0 if no colorFormat is matched 147 | */ 148 | protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { 149 | if (DEBUG) Log.i(TAG, "selectColorFormat: "); 150 | int result = 0; 151 | final MediaCodecInfo.CodecCapabilities caps; 152 | try { 153 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 154 | caps = codecInfo.getCapabilitiesForType(mimeType); 155 | } finally { 156 | Thread.currentThread().setPriority(Thread.NORM_PRIORITY); 157 | } 158 | int colorFormat; 159 | for (int i = 0; i < caps.colorFormats.length; i++) { 160 | colorFormat = caps.colorFormats[i]; 161 | if (isRecognizedVideoFormat(colorFormat)) { 162 | if (result == 0) 163 | result = colorFormat; 164 | break; 165 | } 166 | } 167 | if (result == 0) 168 | Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); 169 | return result; 170 | } 171 | 172 | /** 173 | * color formats that we can use in this class 174 | */ 175 | protected static int[] recognizedFormats; 176 | static { 177 | recognizedFormats = new int[] { 178 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, 179 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, 180 | // MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, 181 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, 182 | }; 183 | } 184 | 185 | private static final boolean isRecognizedVideoFormat(final int colorFormat) { 186 | if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat); 187 | final int n = recognizedFormats != null ? recognizedFormats.length : 0; 188 | for (int i = 0; i < n; i++) { 189 | if (recognizedFormats[i] == colorFormat) { 190 | return true; 191 | } 192 | } 193 | return false; 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | import android.media.MediaCodec; 27 | import android.media.MediaCodecInfo; 28 | import android.media.MediaCodecList; 29 | import android.media.MediaFormat; 30 | import android.os.Build; 31 | import android.os.Bundle; 32 | import android.util.Log; 33 | 34 | import java.io.IOException; 35 | import java.nio.ByteBuffer; 36 | 37 | public class MediaVideoBufferEncoder extends MediaEncoder implements IVideoEncoder { 38 | private static final boolean DEBUG = true; // TODO set false on release 39 | private static final String TAG = "MediaVideoBufferEncoder"; 40 | 41 | private static final String MIME_TYPE = "video/avc"; 42 | private static final int FRAME_RATE = 15; 43 | private static final float BPP = 0.50f; 44 | 45 | private final int mWidth, mHeight; 46 | protected int mColorFormat; 47 | 48 | public MediaVideoBufferEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { 49 | super(muxer, listener); 50 | if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); 51 | mWidth = width; 52 | mHeight = height; 53 | } 54 | 55 | public void encode(final ByteBuffer buffer) { 56 | synchronized (mSync) { 57 | if (!mIsCapturing || mRequestStop) return; 58 | } 59 | // encode(buffer, buffer.capacity(), getPTSUs()); 60 | encode(buffer, buffer.capacity()); 61 | } 62 | 63 | @Override 64 | protected void prepare() throws IOException { 65 | if (DEBUG) Log.i(TAG, "prepare: "); 66 | mTrackIndex = -1; 67 | mMuxerStarted = mIsEOS = false; 68 | 69 | final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); 70 | if (videoCodecInfo == null) { 71 | Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); 72 | return; 73 | } 74 | if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); 75 | 76 | final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); 77 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat); 78 | format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); 79 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 80 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); 81 | if (DEBUG) Log.i(TAG, "format: " + format); 82 | 83 | mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); 84 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 85 | mMediaCodec.start(); 86 | 87 | Bundle params = new Bundle(); 88 | params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); 89 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 90 | mMediaCodec.setParameters(params); 91 | } 92 | if (DEBUG) Log.i(TAG, "prepare finishing"); 93 | if (mListener != null) { 94 | try { 95 | mListener.onPrepared(this); 96 | } catch (final Exception e) { 97 | Log.e(TAG, "prepare:", e); 98 | } 99 | } 100 | } 101 | 102 | private int calcBitRate() { 103 | final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); 104 | Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); 105 | return bitrate; 106 | } 107 | 108 | // 选择第一个与制定MIME类型匹配的编码器 109 | @SuppressWarnings("deprecation") 110 | protected final MediaCodecInfo selectVideoCodec(final String mimeType) { 111 | if (DEBUG) Log.v(TAG, "selectVideoCodec:"); 112 | 113 | // get the list of available codecs 114 | final int numCodecs = MediaCodecList.getCodecCount(); 115 | for (int i = 0; i < numCodecs; i++) { 116 | final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 117 | 118 | if (!codecInfo.isEncoder()) { // skipp decoder 119 | continue; 120 | } 121 | // select first codec that match a specific MIME type and color format 122 | final String[] types = codecInfo.getSupportedTypes(); 123 | for (int j = 0; j < types.length; j++) { 124 | if (types[j].equalsIgnoreCase(mimeType)) { 125 | if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); 126 | final int format = selectColorFormat(codecInfo, mimeType); 127 | if (format > 0) { 128 | mColorFormat = format; 129 | return codecInfo; 130 | } 131 | } 132 | } 133 | } 134 | return null; 135 | } 136 | 137 | // 选择编码器支持的格式 138 | protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { 139 | if (DEBUG) Log.i(TAG, "selectColorFormat: "); 140 | int result = 0; 141 | final MediaCodecInfo.CodecCapabilities caps; 142 | try { 143 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 144 | caps = codecInfo.getCapabilitiesForType(mimeType); 145 | } finally { 146 | Thread.currentThread().setPriority(Thread.NORM_PRIORITY); 147 | } 148 | int colorFormat; 149 | for (int i = 0; i < caps.colorFormats.length; i++) { 150 | colorFormat = caps.colorFormats[i]; 151 | if (isRecognizedViewoFormat(colorFormat)) { 152 | if (result == 0) 153 | result = colorFormat; 154 | break; 155 | } 156 | } 157 | if (result == 0) 158 | Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); 159 | return result; 160 | } 161 | 162 | // YUV颜色格式 163 | protected static int[] recognizedFormats; 164 | static { 165 | recognizedFormats = new int[] { 166 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, 167 | MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, 168 | MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, 169 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, 170 | }; 171 | } 172 | 173 | private static final boolean isRecognizedViewoFormat(final int colorFormat) { 174 | if (DEBUG) Log.i(TAG, "isRecognizedViewoFormat:colorFormat=" + colorFormat); 175 | final int n = recognizedFormats != null ? recognizedFormats.length : 0; 176 | for (int i = 0; i < n; i++) { 177 | if (recognizedFormats[i] == colorFormat) { 178 | return true; 179 | } 180 | } 181 | return false; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.encoder; 25 | 26 | import android.annotation.TargetApi; 27 | import android.media.MediaCodec; 28 | import android.media.MediaCodecInfo; 29 | import android.media.MediaCodecList; 30 | import android.media.MediaFormat; 31 | import android.os.Build; 32 | import android.util.Log; 33 | import android.view.Surface; 34 | 35 | import com.serenegiant.glutils.EGLBase; 36 | import com.serenegiant.glutils.RenderHandler; 37 | 38 | import java.io.IOException; 39 | 40 | /** 41 | * Encode texture images as H.264 video 42 | * using MediaCodec. 43 | * This class render texture images into recording surface 44 | * camera from MediaCodec encoder using Open GL|ES 45 | */ 46 | public class MediaVideoEncoder extends MediaEncoder implements IVideoEncoder { 47 | private static final boolean DEBUG = true; // TODO set false on release 48 | private static final String TAG = "MediaVideoEncoder"; 49 | 50 | private static final String MIME_TYPE = "video/avc"; 51 | // parameters for recording 52 | private final int mWidth, mHeight; 53 | private static final int FRAME_RATE = 15; 54 | private static final float BPP = 0.50f; 55 | 56 | private RenderHandler mRenderHandler; 57 | private Surface mSurface; 58 | 59 | public MediaVideoEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) { 60 | super(muxer, listener); 61 | if (DEBUG) Log.i(TAG, "MediaVideoEncoder: "); 62 | mRenderHandler = RenderHandler.createHandler(TAG); 63 | mWidth = width; 64 | mHeight = height; 65 | } 66 | 67 | public boolean frameAvailableSoon(final float[] tex_matrix) { 68 | boolean result; 69 | if (result = super.frameAvailableSoon()) 70 | mRenderHandler.draw(tex_matrix); 71 | return result; 72 | } 73 | 74 | /** 75 | * This method does not work correctly on this class, 76 | * use #frameAvailableSoon(final float[]) instead 77 | * @return 78 | */ 79 | @Override 80 | public boolean frameAvailableSoon() { 81 | boolean result; 82 | if (result = super.frameAvailableSoon()) 83 | mRenderHandler.draw(null); 84 | return result; 85 | } 86 | 87 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 88 | @Override 89 | protected void prepare() throws IOException { 90 | if (DEBUG) Log.i(TAG, "prepare: "); 91 | mTrackIndex = -1; 92 | mMuxerStarted = mIsEOS = false; 93 | 94 | final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE); 95 | if (videoCodecInfo == null) { 96 | Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE); 97 | return; 98 | } 99 | if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName()); 100 | 101 | final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight); 102 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18 103 | format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate()); 104 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 105 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); 106 | if (DEBUG) Log.i(TAG, "format: " + format); 107 | 108 | mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); 109 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 110 | // get Surface for encoder input 111 | // this method only can call between #configure and #start 112 | mSurface = mMediaCodec.createInputSurface(); // API >= 18 113 | mMediaCodec.start(); 114 | if (DEBUG) Log.i(TAG, "prepare finishing"); 115 | if (mListener != null) { 116 | try { 117 | mListener.onPrepared(this); 118 | } catch (final Exception e) { 119 | Log.e(TAG, "prepare:", e); 120 | } 121 | } 122 | } 123 | 124 | public void setEglContext(final EGLBase.IContext sharedContext, final int tex_id) { 125 | mRenderHandler.setEglContext(sharedContext, tex_id, mSurface, true); 126 | } 127 | 128 | @Override 129 | protected void release() { 130 | if (DEBUG) Log.i(TAG, "release:"); 131 | if (mSurface != null) { 132 | mSurface.release(); 133 | mSurface = null; 134 | } 135 | if (mRenderHandler != null) { 136 | mRenderHandler.release(); 137 | mRenderHandler = null; 138 | } 139 | super.release(); 140 | } 141 | 142 | private int calcBitRate() { 143 | final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight); 144 | Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f)); 145 | return bitrate; 146 | } 147 | 148 | /** 149 | * select the first codec that match a specific MIME type 150 | * @param mimeType 151 | * @return null if no codec matched 152 | */ 153 | protected static final MediaCodecInfo selectVideoCodec(final String mimeType) { 154 | if (DEBUG) Log.v(TAG, "selectVideoCodec:"); 155 | 156 | // get the list of available codecs 157 | final int numCodecs = MediaCodecList.getCodecCount(); 158 | for (int i = 0; i < numCodecs; i++) { 159 | final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 160 | 161 | if (!codecInfo.isEncoder()) { // skipp decoder 162 | continue; 163 | } 164 | // select first codec that match a specific MIME type and color format 165 | final String[] types = codecInfo.getSupportedTypes(); 166 | for (int j = 0; j < types.length; j++) { 167 | if (types[j].equalsIgnoreCase(mimeType)) { 168 | if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]); 169 | final int format = selectColorFormat(codecInfo, mimeType); 170 | if (format > 0) { 171 | return codecInfo; 172 | } 173 | } 174 | } 175 | } 176 | return null; 177 | } 178 | 179 | /** 180 | * select color format available on specific codec and we can use. 181 | * @return 0 if no colorFormat is matched 182 | */ 183 | protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) { 184 | if (DEBUG) Log.i(TAG, "selectColorFormat: "); 185 | int result = 0; 186 | final MediaCodecInfo.CodecCapabilities caps; 187 | try { 188 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 189 | caps = codecInfo.getCapabilitiesForType(mimeType); 190 | } finally { 191 | Thread.currentThread().setPriority(Thread.NORM_PRIORITY); 192 | } 193 | int colorFormat; 194 | for (int i = 0; i < caps.colorFormats.length; i++) { 195 | colorFormat = caps.colorFormats[i]; 196 | if (isRecognizedVideoFormat(colorFormat)) { 197 | if (result == 0) 198 | result = colorFormat; 199 | break; 200 | } 201 | } 202 | if (result == 0) 203 | Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); 204 | return result; 205 | } 206 | 207 | /** 208 | * color formats that we can use in this class 209 | */ 210 | protected static int[] recognizedFormats; 211 | static { 212 | recognizedFormats = new int[] { 213 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, 214 | // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, 215 | // MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, 216 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface, 217 | }; 218 | } 219 | 220 | private static final boolean isRecognizedVideoFormat(final int colorFormat) { 221 | if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat); 222 | final int n = recognizedFormats != null ? recognizedFormats.length : 0; 223 | for (int i = 0; i < n; i++) { 224 | if (recognizedFormats[i] == colorFormat) { 225 | return true; 226 | } 227 | } 228 | return false; 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb.encoder; 2 | 3 | /** 录制参数 4 | * 5 | * Created by jiangdongguo on 2017/10/19. 6 | */ 7 | 8 | public class RecordParams { 9 | private String recordPath; 10 | private int recordDuration; 11 | private boolean voiceClose; 12 | private boolean isAutoSave; 13 | private boolean isSupportOverlay; 14 | 15 | public RecordParams() { 16 | } 17 | 18 | public boolean isSupportOverlay() { 19 | return isSupportOverlay; 20 | } 21 | 22 | public void setSupportOverlay(boolean supportOverlay) { 23 | isSupportOverlay = supportOverlay; 24 | } 25 | 26 | public boolean isVoiceClose() { 27 | return voiceClose; 28 | } 29 | 30 | public void setVoiceClose(boolean voiceClose) { 31 | this.voiceClose = voiceClose; 32 | } 33 | 34 | public String getRecordPath() { 35 | return recordPath; 36 | } 37 | 38 | public void setRecordPath(String recordPath) { 39 | this.recordPath = recordPath; 40 | } 41 | 42 | public int getRecordDuration() { 43 | return recordDuration; 44 | } 45 | 46 | public void setRecordDuration(int recordDuration) { 47 | this.recordDuration = recordDuration; 48 | } 49 | 50 | public boolean isAutoSave() { 51 | return isAutoSave; 52 | } 53 | 54 | public void setAutoSave(boolean autoSave) { 55 | isAutoSave = autoSave; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb.encoder.biz; 2 | 3 | import android.annotation.TargetApi; 4 | import android.media.AudioFormat; 5 | import android.media.AudioRecord; 6 | import android.media.MediaCodec; 7 | import android.media.MediaCodecInfo; 8 | import android.media.MediaCodecList; 9 | import android.media.MediaFormat; 10 | import android.media.MediaRecorder; 11 | import android.os.Build; 12 | import android.os.Process; 13 | import android.util.Log; 14 | 15 | import java.io.File; 16 | import java.io.FileNotFoundException; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.lang.ref.WeakReference; 20 | import java.nio.ByteBuffer; 21 | import java.nio.ByteOrder; 22 | import java.nio.ShortBuffer; 23 | import java.text.SimpleDateFormat; 24 | import java.util.Date; 25 | 26 | /**将PCM编码为AAC 27 | * 28 | * Created by jianddongguo on 2017/7/21. 29 | */ 30 | 31 | public class AACEncodeConsumer extends Thread{ 32 | private static final boolean DEBUG = false; 33 | private static final String TAG = "TMPU"; 34 | private static final String MIME_TYPE = "audio/mp4a-latm"; 35 | private static final long TIMES_OUT = 1000; 36 | private static final int SAMPLE_RATE = 8000; // 采样率 37 | private static final int BIT_RATE = 16000; // 比特率 38 | private static final int BUFFER_SIZE = 1920; // 最小缓存 39 | private int outChannel = 1; 40 | private int bitRateForLame = 32; 41 | private int qaulityDegree = 7; 42 | private int bufferSizeInBytes; 43 | 44 | private AudioRecord mAudioRecord; // 音频采集 45 | private MediaCodec mAudioEncoder; // 音频编码 46 | private OnAACEncodeResultListener listener; 47 | private int mSamplingRateIndex = 0;//ADTS 48 | private boolean isEncoderStart = false; 49 | private boolean isRecMp3 = false; 50 | private boolean isExit = false; 51 | private long prevPresentationTimes = 0; 52 | private WeakReference mMuxerRef; 53 | private MediaFormat newFormat; 54 | 55 | private static final int[] AUDIO_SOURCES = new int[] { 56 | MediaRecorder.AudioSource.DEFAULT, 57 | MediaRecorder.AudioSource.MIC, 58 | MediaRecorder.AudioSource.CAMCORDER, 59 | }; 60 | /** 61 | * There are 13 supported frequencies by ADTS. 62 | **/ 63 | public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 64 | 88200, // 1 65 | 64000, // 2 66 | 48000, // 3 67 | 44100, // 4 68 | 32000, // 5 69 | 24000, // 6 70 | 22050, // 7 71 | 16000, // 8 72 | 12000, // 9 73 | 11025, // 10 74 | 8000, // 11 75 | 7350, // 12 76 | -1, // 13 77 | -1, // 14 78 | -1, // 15 79 | }; 80 | 81 | private FileOutputStream fops; 82 | 83 | // 编码流结果回调接口 84 | public interface OnAACEncodeResultListener{ 85 | void onEncodeResult(byte[] data, int offset, 86 | int length, long timestamp); 87 | } 88 | 89 | public AACEncodeConsumer(){ 90 | for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { 91 | if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) { 92 | mSamplingRateIndex = i; 93 | break; 94 | } 95 | } 96 | } 97 | 98 | public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){ 99 | this.listener = listener; 100 | } 101 | 102 | public void exit(){ 103 | isExit = true; 104 | } 105 | 106 | public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){ 107 | this.mMuxerRef = new WeakReference<>(mMuxer); 108 | Mp4MediaMuxer muxer = mMuxerRef.get(); 109 | if (muxer != null && newFormat != null) { 110 | muxer.addTrack(newFormat, false); 111 | } 112 | } 113 | 114 | @Override 115 | public void run() { 116 | // 开启音频采集、编码 117 | if(! isEncoderStart){ 118 | initAudioRecord(); 119 | initMediaCodec(); 120 | } 121 | // 初始化音频文件参数 122 | byte[] mp3Buffer = new byte[1024]; 123 | 124 | // 这里有问题,当本地录制结束后,没有写入 125 | while(! isExit){ 126 | byte[] audioBuffer = new byte[2048]; 127 | // 采集音频 128 | int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE); 129 | 130 | if(DEBUG) 131 | Log.i(TAG,"采集音频readBytes = "+readBytes); 132 | // 编码音频 133 | if(readBytes > 0){ 134 | encodeBytes(audioBuffer,readBytes); 135 | } 136 | } 137 | // 停止音频采集、编码 138 | stopMediaCodec(); 139 | stopAudioRecord(); 140 | } 141 | 142 | 143 | @TargetApi(21) 144 | private void encodeBytes(byte[] audioBuf, int readBytes) { 145 | ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers(); 146 | ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers(); 147 | //返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区 148 | int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT); 149 | if(inputBufferIndex >= 0){ 150 | // 绑定一个被空的、可写的输入缓存区inputBuffer到客户端 151 | ByteBuffer inputBuffer = null; 152 | if(!isLollipop()){ 153 | inputBuffer = inputBuffers[inputBufferIndex]; 154 | }else{ 155 | inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex); 156 | } 157 | // 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理 158 | if(audioBuf==null || readBytes<=0){ 159 | mAudioEncoder.queueInputBuffer(inputBufferIndex,0,0,getPTSUs(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); 160 | }else{ 161 | inputBuffer.clear(); 162 | inputBuffer.put(audioBuf); 163 | mAudioEncoder.queueInputBuffer(inputBufferIndex,0,readBytes,getPTSUs(),0); 164 | } 165 | } 166 | 167 | // 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区 168 | // mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间 169 | MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); 170 | int outputBufferIndex = -1; 171 | do{ 172 | outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo,TIMES_OUT); 173 | if(outputBufferIndex == MediaCodec. INFO_TRY_AGAIN_LATER){ 174 | if(DEBUG) 175 | Log.i(TAG,"获得编码器输出缓存区超时"); 176 | }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ 177 | // 如果API小于21,APP需要重新绑定编码器的输入缓存区; 178 | // 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED 179 | if(!isLollipop()){ 180 | outputBuffers = mAudioEncoder.getOutputBuffers(); 181 | } 182 | }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ 183 | // 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次 184 | // 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步) 185 | if(DEBUG) 186 | Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器"); 187 | synchronized (AACEncodeConsumer.this) { 188 | newFormat = mAudioEncoder.getOutputFormat(); 189 | if(mMuxerRef != null){ 190 | Mp4MediaMuxer muxer = mMuxerRef.get(); 191 | if (muxer != null) { 192 | muxer.addTrack(newFormat, false); 193 | } 194 | } 195 | } 196 | }else{ 197 | // 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了 198 | if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ 199 | if(DEBUG) 200 | Log.i(TAG,"编码数据被消费,BufferInfo的size属性置0"); 201 | mBufferInfo.size = 0; 202 | } 203 | // 数据流结束标志,结束本次循环 204 | if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){ 205 | if(DEBUG) 206 | Log.i(TAG,"数据流结束,退出循环"); 207 | break; 208 | } 209 | // 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据 210 | ByteBuffer mBuffer = ByteBuffer.allocate(10240); 211 | ByteBuffer outputBuffer = null; 212 | if(!isLollipop()){ 213 | outputBuffer = outputBuffers[outputBufferIndex]; 214 | }else{ 215 | outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); 216 | } 217 | if(mBufferInfo.size != 0){ 218 | // 获取输出缓存区失败,抛出异常 219 | if(outputBuffer == null){ 220 | throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null"); 221 | } 222 | // 添加视频流到混合器 223 | if(mMuxerRef != null){ 224 | Mp4MediaMuxer muxer = mMuxerRef.get(); 225 | if (muxer != null) { 226 | muxer.pumpStream(outputBuffer, mBufferInfo, false); 227 | } 228 | } 229 | // AAC流添加ADTS头,缓存到mBuffer 230 | mBuffer.clear(); 231 | outputBuffer.get(mBuffer.array(), 7, mBufferInfo.size); 232 | outputBuffer.clear(); 233 | mBuffer.position(7 + mBufferInfo.size); 234 | addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); 235 | mBuffer.flip(); 236 | // 将AAC回调给MainModelImpl进行push 237 | if(listener != null){ 238 | Log.i(TAG,"----->得到aac数据流<-----"); 239 | listener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); 240 | } 241 | } 242 | // 处理结束,释放输出缓存区资源 243 | mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false); 244 | } 245 | }while (outputBufferIndex >= 0); 246 | } 247 | 248 | private void initAudioRecord(){ 249 | if(DEBUG) 250 | Log.d(TAG,"AACEncodeConsumer-->开始采集音频"); 251 | // 设置进程优先级 252 | Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); 253 | int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, 254 | AudioFormat.ENCODING_PCM_16BIT); 255 | for (final int src: AUDIO_SOURCES) { 256 | try { 257 | mAudioRecord = new AudioRecord(src, 258 | SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); 259 | if (mAudioRecord != null) { 260 | if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { 261 | mAudioRecord.release(); 262 | mAudioRecord = null; 263 | } 264 | } 265 | } catch (final Exception e) { 266 | mAudioRecord = null; 267 | } 268 | if (mAudioRecord != null) { 269 | break; 270 | } 271 | } 272 | mAudioRecord.startRecording(); 273 | } 274 | 275 | private void initMediaCodec(){ 276 | if(DEBUG) 277 | Log.d(TAG,"AACEncodeConsumer-->开始编码音频"); 278 | MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE); 279 | if(mCodecInfo == null){ 280 | Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型"); 281 | return; 282 | } 283 | try{ 284 | mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName()); 285 | }catch(IOException e){ 286 | Log.e(TAG,"创建编码器失败"+e.getMessage()); 287 | e.printStackTrace(); 288 | } 289 | MediaFormat format = new MediaFormat(); 290 | format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm"); 291 | format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 292 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 293 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE); 294 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); 295 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, BUFFER_SIZE); 296 | mAudioEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 297 | mAudioEncoder.start(); 298 | isEncoderStart = true; 299 | } 300 | 301 | private void stopAudioRecord() { 302 | if(DEBUG) 303 | Log.d(TAG,"AACEncodeConsumer-->停止采集音频"); 304 | if(mAudioRecord != null){ 305 | mAudioRecord.stop(); 306 | mAudioRecord.release(); 307 | mAudioRecord = null; 308 | } 309 | } 310 | 311 | private void stopMediaCodec() { 312 | if(DEBUG) 313 | Log.d(TAG,"AACEncodeConsumer-->停止编码音频"); 314 | if(mAudioEncoder != null){ 315 | mAudioEncoder.stop(); 316 | mAudioEncoder.release(); 317 | mAudioEncoder = null; 318 | } 319 | isEncoderStart = false; 320 | } 321 | 322 | // API>=21 323 | private boolean isLollipop(){ 324 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 325 | } 326 | 327 | // API<=19 328 | private boolean isKITKAT(){ 329 | return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; 330 | } 331 | 332 | private long getPTSUs(){ 333 | long result = System.nanoTime()/1000; 334 | if(result < prevPresentationTimes){ 335 | result = (prevPresentationTimes - result ) + result; 336 | } 337 | return result; 338 | } 339 | 340 | /** 341 | * 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器 342 | * 判断是否有支持指定mime类型的编码器 343 | * */ 344 | private MediaCodecInfo selectSupportCodec(String mimeType){ 345 | int numCodecs = MediaCodecList.getCodecCount(); 346 | for (int i = 0; i < numCodecs; i++) { 347 | MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 348 | // 判断是否为编码器,否则直接进入下一次循环 349 | if (!codecInfo.isEncoder()) { 350 | continue; 351 | } 352 | // 如果是编码器,判断是否支持Mime类型 353 | String[] types = codecInfo.getSupportedTypes(); 354 | for (int j = 0; j < types.length; j++) { 355 | if (types[j].equalsIgnoreCase(mimeType)) { 356 | return codecInfo; 357 | } 358 | } 359 | } 360 | return null; 361 | } 362 | 363 | private void addADTStoPacket(byte[] packet, int packetLen) { 364 | packet[0] = (byte) 0xFF; 365 | packet[1] = (byte) 0xF1; 366 | packet[2] = (byte) (((2 - 1) << 6) + (mSamplingRateIndex << 2) + (1 >> 2)); 367 | packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11)); 368 | packet[4] = (byte) ((packetLen & 0x7FF) >> 3); 369 | packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F); 370 | packet[6] = (byte) 0xFC; 371 | } 372 | 373 | 374 | private short[] transferByte2Short(byte[] data,int readBytes){ 375 | // byte[] 转 short[],数组长度缩减一半 376 | int shortLen = readBytes / 2; 377 | // 将byte[]数组装如ByteBuffer缓冲区 378 | ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, readBytes); 379 | // 将ByteBuffer转成小端并获取shortBuffer 380 | ShortBuffer shortBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); 381 | short[] shortData = new short[shortLen]; 382 | shortBuffer.get(shortData, 0, shortLen); 383 | return shortData; 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java: -------------------------------------------------------------------------------- 1 | package com.serenegiant.usb.encoder.biz; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.media.MediaMuxer; 6 | import android.os.Build; 7 | import android.util.Log; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | 13 | /**Mp4封装混合器 14 | * 15 | * Created by jianddongguo on 2017/7/28. 16 | */ 17 | 18 | public class Mp4MediaMuxer { 19 | private static final boolean VERBOSE = false; 20 | private static final String TAG = Mp4MediaMuxer.class.getSimpleName(); 21 | private String mFilePath; 22 | private MediaMuxer mMuxer; 23 | private long durationMillis; 24 | private int index = 0; 25 | private int mVideoTrackIndex = -1; 26 | private int mAudioTrackIndex = -1; 27 | private long mBeginMillis; 28 | private MediaFormat mVideoFormat; 29 | private MediaFormat mAudioFormat; 30 | private boolean isVoiceClose; 31 | 32 | // 文件路径;文件时长 33 | public Mp4MediaMuxer(String path, long durationMillis,boolean isVoiceClose) { 34 | String mFilePath; 35 | this.isVoiceClose = isVoiceClose; 36 | this.durationMillis = durationMillis; 37 | if(durationMillis != 0) { 38 | mFilePath = path + "-" + index++ + ".mp4"; 39 | }else{ 40 | mFilePath = path+".mp4"; 41 | } 42 | Object mux = null; 43 | try { 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 45 | mux = new MediaMuxer(mFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 46 | } 47 | } catch (IOException e) { 48 | e.printStackTrace(); 49 | } finally { 50 | mMuxer = (MediaMuxer) mux; 51 | } 52 | } 53 | 54 | public synchronized void addTrack(MediaFormat format, boolean isVideo) { 55 | // now that we have the Magic Goodies, start the muxer 56 | if ((!isVoiceClose && mAudioTrackIndex != -1) && mVideoTrackIndex != -1) 57 | throw new RuntimeException("already add all tracks"); 58 | 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 60 | int track = mMuxer.addTrack(format); 61 | if (VERBOSE) 62 | Log.i(TAG, String.format("addTrack %s result %d", isVideo ? "video" : "audio", track)); 63 | if (isVideo) { 64 | mVideoFormat = format; 65 | mVideoTrackIndex = track; 66 | // 当音频轨道添加 67 | // 或者开启静音就start 68 | if (isVoiceClose || mAudioTrackIndex != -1) { 69 | if (VERBOSE) 70 | Log.i(TAG, "both audio and video added,and muxer is started"); 71 | mMuxer.start(); 72 | mBeginMillis = System.currentTimeMillis(); 73 | } 74 | } else { 75 | mAudioFormat = format; 76 | mAudioTrackIndex = track; 77 | if (mVideoTrackIndex != -1) { 78 | mMuxer.start(); 79 | mBeginMillis = System.currentTimeMillis(); 80 | } 81 | } 82 | } 83 | } 84 | 85 | public synchronized void pumpStream(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo, boolean isVideo) { 86 | if ((!isVoiceClose && mAudioTrackIndex == -1) || mVideoTrackIndex == -1) { 87 | // Log.i(TAG, String.format("pumpStream [%s] but muxer is not start.ignore..", isVideo ? "video" : "audio")); 88 | return; 89 | } 90 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 91 | // The codec config data was pulled out and fed to the muxer when we got 92 | // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. 93 | } else if (bufferInfo.size != 0) { 94 | if (isVideo && mVideoTrackIndex == -1) { 95 | throw new RuntimeException("muxer hasn't started"); 96 | } 97 | 98 | // adjust the ByteBuffer values to match BufferInfo (not needed?) 99 | outputBuffer.position(bufferInfo.offset); 100 | outputBuffer.limit(bufferInfo.offset + bufferInfo.size); 101 | 102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 103 | mMuxer.writeSampleData(isVideo ? mVideoTrackIndex : mAudioTrackIndex, outputBuffer, bufferInfo); 104 | } 105 | // if (VERBOSE) 106 | // Log.d(TAG, String.format("sent %s [" + bufferInfo.size + "] with timestamp:[%d] to muxer", isVideo ? "video" : "audio", bufferInfo.presentationTimeUs / 1000)); 107 | } 108 | 109 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 110 | // if (VERBOSE) 111 | // Log.i(TAG, "BUFFER_FLAG_END_OF_STREAM received"); 112 | } 113 | 114 | if (durationMillis!=0 && System.currentTimeMillis() - mBeginMillis >= durationMillis) { 115 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 116 | // if (VERBOSE) 117 | // Log.i(TAG, String.format("record file reach expiration.create new file:" + index)); 118 | mMuxer.stop(); 119 | mMuxer.release(); 120 | mMuxer = null; 121 | mVideoTrackIndex = mAudioTrackIndex = -1; 122 | try { 123 | mMuxer = new MediaMuxer(mFilePath + "-" + ++index + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 124 | addTrack(mVideoFormat, true); 125 | addTrack(mAudioFormat, false); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | } 131 | } 132 | 133 | public synchronized void release() { 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 135 | if (mMuxer != null) { 136 | //(!isVoiceClose&&mAudioTrackIndex != -1) 137 | if (mVideoTrackIndex != -1) { 138 | if (VERBOSE) 139 | Log.i(TAG, String.format("muxer is started. now it will be stoped.")); 140 | try { 141 | mMuxer.stop(); 142 | mMuxer.release(); 143 | } catch (IllegalStateException ex) { 144 | ex.printStackTrace(); 145 | } 146 | 147 | if (System.currentTimeMillis() - mBeginMillis <= 1500){ 148 | new File(mFilePath + "-" + index + ".mp4").delete(); 149 | } 150 | mAudioTrackIndex = mVideoTrackIndex = -1; 151 | }else{ 152 | if (VERBOSE) 153 | Log.i(TAG, String.format("muxer is failed to be stoped.")); 154 | } 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.widget; 25 | 26 | import android.content.Context; 27 | import android.util.AttributeSet; 28 | import android.view.TextureView; 29 | 30 | import com.serenegiant.widget.IAspectRatioView; 31 | 32 | /** 33 | * change the view size with keeping the specified aspect ratio. 34 | * if you set this view with in a FrameLayout and set property "android:layout_gravity="center", 35 | * you can show this view in the center of screen and keep the aspect ratio of content 36 | * XXX it is better that can set the aspect ratio as xml property 37 | */ 38 | public class AspectRatioTextureView extends TextureView // API >= 14 39 | implements IAspectRatioView { 40 | 41 | private static final boolean DEBUG = true; // TODO set false on release 42 | private static final String TAG = "AbstractCameraView"; 43 | 44 | private double mRequestedAspect = -1.0; 45 | private CameraViewInterface.Callback mCallback; 46 | 47 | public AspectRatioTextureView(final Context context) { 48 | this(context, null, 0); 49 | } 50 | 51 | public AspectRatioTextureView(final Context context, final AttributeSet attrs) { 52 | this(context, attrs, 0); 53 | } 54 | 55 | public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) { 56 | super(context, attrs, defStyle); 57 | } 58 | 59 | // 设置屏幕宽高比 60 | @Override 61 | public void setAspectRatio(final double aspectRatio) { 62 | if (aspectRatio < 0) { 63 | throw new IllegalArgumentException(); 64 | } 65 | if (mRequestedAspect != aspectRatio) { 66 | mRequestedAspect = aspectRatio; 67 | requestLayout(); 68 | } 69 | } 70 | 71 | @Override 72 | public void setAspectRatio(final int width, final int height) { 73 | setAspectRatio(width / (double)height); 74 | } 75 | 76 | @Override 77 | public double getAspectRatio() { 78 | return mRequestedAspect; 79 | } 80 | 81 | @Override 82 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 83 | 84 | if (mRequestedAspect > 0) { 85 | int initialWidth = MeasureSpec.getSize(widthMeasureSpec); 86 | int initialHeight = MeasureSpec.getSize(heightMeasureSpec); 87 | 88 | final int horizPadding = getPaddingLeft() + getPaddingRight(); 89 | final int vertPadding = getPaddingTop() + getPaddingBottom(); 90 | initialWidth -= horizPadding; 91 | initialHeight -= vertPadding; 92 | 93 | final double viewAspectRatio = (double)initialWidth / initialHeight; 94 | final double aspectDiff = mRequestedAspect / viewAspectRatio - 1; 95 | 96 | if (Math.abs(aspectDiff) > 0.01) { 97 | if (aspectDiff > 0) { 98 | // width priority decision 99 | initialHeight = (int) (initialWidth / mRequestedAspect); 100 | } else { 101 | // height priority decision 102 | initialWidth = (int) (initialHeight * mRequestedAspect); 103 | } 104 | initialWidth += horizPadding; 105 | initialHeight += vertPadding; 106 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY); 107 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY); 108 | } 109 | } 110 | 111 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * UVCCamera 3 | * library and sample to access to UVC web camera on non-rooted Android device 4 | * 5 | * Copyright (c) 2014-2017 saki t_saki@serenegiant.com 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * All files in the folder are under this Apache License, Version 2.0. 20 | * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder 21 | * may have a different license, see the respective files. 22 | */ 23 | 24 | package com.serenegiant.usb.widget; 25 | 26 | import android.graphics.Bitmap; 27 | import android.graphics.SurfaceTexture; 28 | import android.view.Surface; 29 | 30 | import com.serenegiant.usb.encoder.IVideoEncoder; 31 | import com.serenegiant.widget.IAspectRatioView; 32 | 33 | public interface CameraViewInterface extends IAspectRatioView { 34 | public interface Callback { 35 | public void onSurfaceCreated(CameraViewInterface view, Surface surface); 36 | public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height); 37 | public void onSurfaceDestroy(CameraViewInterface view, Surface surface); 38 | } 39 | public void onPause(); 40 | public void onResume(); 41 | public void setCallback(Callback callback); 42 | public SurfaceTexture getSurfaceTexture(); 43 | public Surface getSurface(); 44 | public boolean hasSurface(); 45 | public void setVideoEncoder(final IVideoEncoder encoder); 46 | public Bitmap captureStillImage(int width,int height); 47 | } 48 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/org/easydarwin/sw/JNIUtil.java: -------------------------------------------------------------------------------- 1 | package org.easydarwin.sw; 2 | 3 | /** 4 | */ 5 | public class JNIUtil { 6 | 7 | static { 8 | System.loadLibrary("Utils"); 9 | } 10 | 11 | /** 12 | * 都是Y:U:V = 4:1:1但 U与 V顺序相反。变换可逆 13 | * 14 | * @param buffer 15 | * @param width 16 | * @param height 17 | */ 18 | public static void yV12ToYUV420P(byte[] buffer, int width, int height) { 19 | callMethod("YV12ToYUV420P", null, buffer, width, height); 20 | } 21 | 22 | /** 23 | * 都是Y:U+V = 4:2,但是这两者U、V方向相反。变换可逆 24 | * 25 | * @param buffer 26 | * @param width 27 | * @param height 28 | */ 29 | public static void nV21To420SP(byte[] buffer, int width, int height) { 30 | callMethod("NV21To420SP", null, buffer, width, height); 31 | } 32 | 33 | /** 34 | * 旋转1个字节为单位的矩阵 35 | * 36 | * @param data 要旋转的矩阵 37 | * @param offset 偏移量 38 | * @param width 宽度 39 | * @param height 高度 40 | * @param degree 旋转度数 41 | */ 42 | public static void rotateMatrix(byte[] data, int offset, int width, int height, int degree) { 43 | callMethod("RotateByteMatrix", null, data, offset, width, height, degree); 44 | } 45 | 46 | /** 47 | * 旋转2个字节为单位的矩阵 48 | * 49 | * @param data 要旋转的矩阵 50 | * @param offset 偏移量 51 | * @param width 宽度 52 | * @param height 高度 53 | * @param degree 旋转度数 54 | */ 55 | public static void rotateShortMatrix(byte[] data, int offset, int width, int height, int degree) { 56 | callMethod("RotateShortMatrix", null, data, offset, width, height, degree); 57 | } 58 | 59 | private static native void callMethod(String methodName, Object[] returnValue, Object... params); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /libusbcamera/src/main/java/org/easydarwin/sw/TxtOverlay.java: -------------------------------------------------------------------------------- 1 | package org.easydarwin.sw; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | /** 12 | * Created by jiangdg on 2020/1/11. 13 | */ 14 | 15 | public class TxtOverlay { 16 | 17 | static { 18 | System.loadLibrary("TxtOverlay"); 19 | } 20 | 21 | private static TxtOverlay instance; 22 | private final Context context; 23 | private long ctx; 24 | 25 | private TxtOverlay(Context context){ 26 | this.context = context; 27 | } 28 | 29 | public static TxtOverlay getInstance() { 30 | if(instance == null) { 31 | throw new IllegalArgumentException("please call install in your application!"); 32 | } 33 | return instance; 34 | } 35 | 36 | public static void install(Context context) { 37 | if(instance == null) { 38 | instance = new TxtOverlay(context.getApplicationContext()); 39 | 40 | File youyuan = context.getFileStreamPath("SIMYOU.ttf"); 41 | if (!youyuan.exists()){ 42 | AssetManager am = context.getAssets(); 43 | try { 44 | InputStream is = am.open("zk/SIMYOU.ttf"); 45 | FileOutputStream os = context.openFileOutput("SIMYOU.ttf", Context.MODE_PRIVATE); 46 | byte[] buffer = new byte[1024]; 47 | int len = 0; 48 | while ((len = is.read(buffer)) != -1) { 49 | os.write(buffer, 0, len); 50 | } 51 | os.close(); 52 | is.close(); 53 | 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | } 59 | } 60 | 61 | public void init(int width, int height) { 62 | File youyuan = context.getFileStreamPath("SIMYOU.ttf"); 63 | if (!youyuan.exists()){ 64 | throw new IllegalArgumentException("the font file must be exists,please call install before!"); 65 | } 66 | ctx = txtOverlayInit(width, height,youyuan.getAbsolutePath()); 67 | } 68 | 69 | public void overlay(byte[] data, 70 | String txt) { 71 | // txt = "drawtext=fontfile="+context.getFileStreamPath("SIMYOU.ttf")+": text='EasyPusher 2017':x=(w-text_w)/2:y=H-60 :fontcolor=white :box=1:boxcolor=0x00000000@0.3"; 72 | // txt = "movie=/sdcard/qrcode.png [logo];[in][logo] " 73 | // + "overlay=" + 0 + ":" + 0 74 | // + " [out]"; 75 | // if (ctx == 0) throw new RuntimeException("init should be called at first!"); 76 | if (ctx == 0) return; 77 | txtOverlay(ctx, data, txt); 78 | } 79 | 80 | public void release() { 81 | if (ctx == 0) return; 82 | txtOverlayRelease(ctx); 83 | ctx = 0; 84 | } 85 | 86 | 87 | private static native long txtOverlayInit(int width, int height, String fonts); 88 | 89 | private static native void txtOverlay(long ctx, byte[] data, String txt); 90 | 91 | private static native void txtOverlayRelease(long ctx); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libTxtOverlay.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libTxtOverlay.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libUVCCamera.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libUtils.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libUtils.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libjpeg-turbo1500.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libusb100.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libusb100.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/arm64-v8a/libuvc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/arm64-v8a/libuvc.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libTxtOverlay.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libTxtOverlay.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libUVCCamera.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libUtils.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libUtils.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libjpeg-turbo1500.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libusb100.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/armeabi-v7a/libuvc.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86/libUVCCamera.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86/libUVCCamera.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86/libjpeg-turbo1500.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86/libusb100.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86/libusb100.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86/libuvc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86/libuvc.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86_64/libUVCCamera.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86_64/libUVCCamera.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86_64/libjpeg-turbo1500.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86_64/libusb100.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86_64/libusb100.so -------------------------------------------------------------------------------- /libusbcamera/src/main/jniLibs/x86_64/libuvc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/jniLibs/x86_64/libuvc.so -------------------------------------------------------------------------------- /libusbcamera/src/main/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /libusbcamera/src/main/res/layout/dialog_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /libusbcamera/src/main/res/layout/listitem_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /libusbcamera/src/main/res/raw/camera_click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantum6/Android-USB-OTG-Camera/9b4383d3202b0c72a757a471decaec87fb742fa0/libusbcamera/src/main/res/raw/camera_click.ogg -------------------------------------------------------------------------------- /libusbcamera/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 16dp 26 | 16dp 27 | 48dp 28 | 18sp 29 | 32dp 30 | 31 | -------------------------------------------------------------------------------- /libusbcamera/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | libusbcamera 3 | 请选择USB摄像头 4 | 刷新 5 | Camera 6 | 未搜索到可用USB Camera设备 7 | 8 | -------------------------------------------------------------------------------- /libusbcamera/src/main/res/xml/device_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /libusbcamera/src/test/java/com/jiangdg/libusbcamera/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jiangdg.libusbcamera; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':libusbcamera' 2 | --------------------------------------------------------------------------------