├── settings.gradle ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_activity_video_record_full_screen.xml │ │ │ │ ├── ic_activity_video_record_aspect.xml │ │ │ │ ├── ic_activity_video_record_camera_switch.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ │ └── activity_record_video.xml │ │ │ ├── layout-land │ │ │ │ └── activity_record_video.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── sharry │ │ │ └── sample │ │ │ └── camera │ │ │ ├── MainActivity.kt │ │ │ └── AspectRatioFragment.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── sharry │ │ │ └── sample │ │ │ └── camera │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── sharry │ │ └── sample │ │ └── camera │ │ └── ExampleInstrumentedTest.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── lib-scamera ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── raw │ │ │ ├── camera_fragment_shader.glsl │ │ │ └── camera_vertex_shader.glsl │ │ └── values │ │ │ ├── public.xml │ │ │ ├── styles.xml │ │ │ └── attrs.xml │ │ ├── previewer │ │ └── com │ │ │ └── sharry │ │ │ └── lib │ │ │ └── camera │ │ │ ├── ITextureRenderer.java │ │ │ ├── IPreviewerRenderer.java │ │ │ ├── IPreviewer.java │ │ │ ├── Previewer.java │ │ │ ├── PreviewerFramebufferRenderer.java │ │ │ ├── PreviewRenderer.java │ │ │ └── GLTextureView.java │ │ ├── device │ │ └── com │ │ │ └── sharry │ │ │ └── lib │ │ │ └── camera │ │ │ ├── ICameraDevice.java │ │ │ ├── AbsCameraDevice.java │ │ │ ├── CameraXDevice.java │ │ │ └── Camera1Device.java │ │ ├── common │ │ └── com │ │ │ └── sharry │ │ │ └── lib │ │ │ └── camera │ │ │ ├── Constants.java │ │ │ ├── CameraContext.java │ │ │ ├── Size.java │ │ │ ├── SizeMap.java │ │ │ └── AspectRatio.java │ │ ├── utils │ │ └── com │ │ │ └── sharry │ │ │ └── lib │ │ │ └── camera │ │ │ ├── LibyuvUtil.java │ │ │ ├── GLUtil.java │ │ │ └── EglCore.java │ │ ├── orientation │ │ └── com │ │ │ └── sharry │ │ │ └── lib │ │ │ └── camera │ │ │ └── ScreenOrientationDetector.java │ │ └── api │ │ └── com │ │ └── sharry │ │ └── lib │ │ └── camera │ │ └── SCameraView.java ├── .gitignore └── build.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='CameraSample' 2 | include ':app' 3 | include ':lib-scamera' 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CameraSample 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SharryChoo/CameraSample/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 18 16:45:24 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CameraSample 2 | 相机预览的 Sample 3 | 4 | # 相机引擎 5 | - Camera1 6 | - CameraX 7 | 8 | # 渲染视图 9 | TextureView + OpenGL ES 2.0 10 | 11 | # 实现功能 12 | - 根据比例选取最合适的预览分辨率 13 | - 根据比例计算 TextureView 的宽高 14 | - 支持渲染数据的 CenterCrop 的全屏展示 15 | - 支持横竖屏切换渲染效果的统一 16 | - Renderer 中添加了缓冲帧 FBO 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /lib-scamera/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib-scamera/src/main/res/raw/camera_fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_OES_EGL_image_external : require 2 | // 设置精度,中等精度 3 | precision mediump float; 4 | // 由顶点着色器输出, 经过栅格化转换之后的纹理坐标 5 | varying vec2 vTextureCoordinate; 6 | // 2D 纹理, uniform 用于 application 向 gl 传值 (扩展纹理) 7 | uniform samplerExternalOES uTexture; 8 | void main(){ 9 | // 取相应坐标点的范围转成 texture2D 10 | gl_FragColor = texture2D(uTexture, vTextureCoordinate); 11 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/sharry/sample/camera/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.sharry.sample.camera 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_activity_video_record_full_screen.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_activity_video_record_aspect.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /lib-scamera/src/main/res/raw/camera_vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 aVertexCoordinate; // 传入参数: 顶点坐标, Java 传入 2 | attribute vec4 aTextureCoordinate; // 传入参数: 纹理坐标, Java 传入 3 | uniform mat4 uVertexMatrix; // 全局参数: 4x4 顶点的裁剪矩阵, Java 传入 4 | uniform mat4 uTextureMatrix; // 全局参数: 4x4 矩阵纹理变化矩阵, Java 传入 5 | varying vec2 vTextureCoordinate; // 传出参数: 计算纹理坐标传递给 片元着色器 6 | void main() { 7 | // 计算纹理坐标, 传出给片元着色器 8 | vTextureCoordinate = (uTextureMatrix * aTextureCoordinate).xy; 9 | // 计算顶点坐标, 输出给内建输出变量 10 | gl_Position = uVertexMatrix * aVertexCoordinate; 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_activity_video_record_camera_switch.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea 38 | 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | # external files 44 | .externalNativeBuild/ -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea 38 | 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | # external files 44 | .externalNativeBuild/ -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/ITextureRenderer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.opengl.EGLContext; 4 | 5 | import androidx.annotation.WorkerThread; 6 | 7 | /** 8 | * OpenGL ES 基础的 Texture Renderer 9 | * 10 | * @author Sharry Contact me. 11 | * @version 1.0 12 | * @since 2019-08-08 14:10 13 | */ 14 | public interface ITextureRenderer { 15 | 16 | @WorkerThread 17 | void onEglContextCreated(EGLContext eglContext); 18 | 19 | @WorkerThread 20 | void onSurfaceSizeChanged(int width, int height); 21 | 22 | @WorkerThread 23 | void drawTexture(int textureId, float[] textureMatrix); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lib-scamera/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea 38 | 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | # external files 44 | .externalNativeBuild/ -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sharry/sample/camera/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sharry.sample.camera 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.sharry.sample.camera", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/IPreviewerRenderer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.opengl.EGLContext; 4 | 5 | import androidx.annotation.UiThread; 6 | 7 | /** 8 | * 相机预览器的 Renderer 9 | *

10 | * 对 ITextureRenderer 的增强, 拓展 matrix 功能 11 | * 12 | * @author Sharry Contact me. 13 | * @version 1.0 14 | * @since 2019-07-27 15 | */ 16 | public interface IPreviewerRenderer extends ITextureRenderer { 17 | 18 | @UiThread 19 | EGLContext getEGLContext(); 20 | 21 | @UiThread 22 | int getPreviewerTextureId(); 23 | 24 | @UiThread 25 | void resetMatrix(); 26 | 27 | @UiThread 28 | void rotate(int degrees); 29 | 30 | @UiThread 31 | void centerCrop(boolean isLandscape, Size surfaceSize, Size textureSize); 32 | 33 | @UiThread 34 | void transformMatrix(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /lib-scamera/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib-scamera/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileSdkVersion 5 | defaultConfig { 6 | minSdkVersion rootProject.minSdkVersion 7 | targetSdkVersion rootProject.targetSdkVersion 8 | } 9 | sourceSets { 10 | main { 11 | java.srcDirs += 'src/main/api' 12 | java.srcDirs += 'src/main/device' 13 | java.srcDirs += 'src/main/previewer' 14 | java.srcDirs += 'src/main/orientation' 15 | java.srcDirs += 'src/main/common' 16 | java.srcDirs += 'src/main/utils' 17 | jniLibs.srcDirs = ['src/main/jniLibs'] 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | // 需要去 root project 解决依赖冲突 25 | def camerax_version = "1.0.0-alpha03" 26 | implementation "androidx.camera:camera-core:${camerax_version}" 27 | implementation "androidx.camera:camera-camera2:${camerax_version}" 28 | } 29 | -------------------------------------------------------------------------------- /lib-scamera/src/main/device/com/sharry/lib/camera/ICameraDevice.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.SurfaceTexture; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | /** 9 | * The interface desc camera device. 10 | * 11 | * @author Sharry Contact me. 12 | * @version 1.0 13 | * @since 2019-04-24 14 | */ 15 | interface ICameraDevice { 16 | 17 | void open(); 18 | 19 | void close(); 20 | 21 | Bitmap takePicture(); 22 | 23 | boolean isCameraOpened(); 24 | 25 | void notifyFacingChanged(); 26 | 27 | void notifyAspectRatioChanged(); 28 | 29 | void notifyAutoFocusChanged(); 30 | 31 | void notifyFlashModeChanged(); 32 | 33 | void notifyScreenOrientationChanged(); 34 | 35 | void notifyDesiredSizeChanged(); 36 | 37 | interface OnCameraReadyListener { 38 | 39 | void onCameraReady(@NonNull SurfaceTexture cameraTexture, @NonNull Size textureSize, int displayRotation); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion rootProject.compileSdkVersion 9 | defaultConfig { 10 | minSdkVersion rootProject.minSdkVersion 11 | targetSdkVersion rootProject.targetSdkVersion 12 | multiDexEnabled true 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation fileTree(dir: 'libs', include: ['*.jar']) 18 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 19 | // androidx 20 | api "androidx.constraintlayout:constraintlayout:$constraintLayout" 21 | api "androidx.appcompat:appcompat:$supportLibraryVersion" 22 | api "androidx.recyclerview:recyclerview:$supportLibraryVersion" 23 | api "com.google.android.material:material:$supportLibraryVersion" 24 | // my dependencies 25 | api "com.github.SharryChoo:SToolbar:$sToolbarVersion" 26 | api "com.github.SharryChoo:SLibrary:$sLibraryVersion" 27 | // module dependencies 28 | implementation project(':lib-scamera') 29 | } 30 | -------------------------------------------------------------------------------- /lib-scamera/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_record_video.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 24 | 25 | -------------------------------------------------------------------------------- /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 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | 26 | # Kotlin code style for this project: "official" or "obsolete": 27 | kotlin.code.style=official 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_record_video.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 26 | 27 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/IPreviewer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.SurfaceTexture; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | /** 10 | * 相机预览器的抽象描述 11 | * 12 | * @author Sharry Contact me. 13 | * @version 1.0 14 | * @since 2019-08-08 10:49 15 | */ 16 | public interface IPreviewer { 17 | 18 | /** 19 | * 设置要预览的数据源 20 | */ 21 | void setDataSource(@NonNull SurfaceTexture bufferTexture); 22 | 23 | /** 24 | * 设置 previewer 的渲染器 25 | */ 26 | void setRender(@NonNull IPreviewerRenderer renderer); 27 | 28 | /** 29 | * 设置监听器 30 | */ 31 | void setWatcher(Watcher watcher); 32 | 33 | /** 34 | * 获取用于预览的 view 35 | */ 36 | View getView(); 37 | 38 | /** 39 | * 设置 previewer 的渲染器 40 | */ 41 | IPreviewerRenderer getRenderer(); 42 | 43 | /** 44 | * 获取渲染器的尺寸 45 | */ 46 | Size getSize(); 47 | 48 | Bitmap getBitmap(); 49 | 50 | interface Watcher { 51 | 52 | void onSizeChanged(int previewerWidth, int previewerHeight); 53 | 54 | void onRenderChanged(IPreviewerRenderer renderer); 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /lib-scamera/src/main/common/com/sharry/lib/camera/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sharry.lib.camera; 18 | 19 | 20 | import android.hardware.Camera; 21 | 22 | interface Constants { 23 | 24 | /** 25 | * Constants of facing 26 | */ 27 | int FACING_BACK = Camera.CameraInfo.CAMERA_FACING_BACK; 28 | int FACING_FRONT = Camera.CameraInfo.CAMERA_FACING_FRONT; 29 | 30 | /** 31 | * Constants of flash 32 | */ 33 | int FLASH_OFF = 0; 34 | int FLASH_ON = 1; 35 | int FLASH_TORCH = 2; 36 | int FLASH_AUTO = 3; 37 | int FLASH_RED_EYE = 4; 38 | 39 | /** 40 | * Constants of orientation 41 | */ 42 | int LANDSCAPE_90 = 90; 43 | int LANDSCAPE_270 = 270; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /lib-scamera/src/main/utils/com/sharry/lib/camera/LibyuvUtil.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * 处理 YUV 的工具类 7 | * 8 | * @author Sharry Contact me. 9 | * @version 1.0 10 | * @since 2019-07-23 11 | */ 12 | public class LibyuvUtil { 13 | 14 | static { 15 | System.loadLibrary("scamera"); 16 | } 17 | 18 | /** 19 | * 将 NV21 转 I420 20 | */ 21 | public static native void convertNV21ToI420(byte[] src, byte[] dst, int width, int height); 22 | 23 | /** 24 | * 压缩 I420 数据 25 | *

26 | * 执行顺序为:缩放->旋转->镜像 27 | * 28 | * @param src 原始数据 29 | * @param srcWidth 原始宽度 30 | * @param srcHeight 原始高度 31 | * @param dst 输出数据 32 | * @param dstWidth 输出宽度 33 | * @param dstHeight 输出高度 34 | * @param degree 旋转(90, 180, 270) 35 | * @param isMirror 镜像(镜像在旋转之后) 36 | */ 37 | public static native void compressI420(byte[] src, int srcWidth, int srcHeight, 38 | byte[] dst, int dstWidth, int dstHeight, 39 | int degree, boolean isMirror); 40 | 41 | /** 42 | * 将 I420 数据注入到 Bitmap 中 43 | */ 44 | public static native void convertI420ToBitmap(byte[] src, Bitmap dst, int width, int height); 45 | 46 | /** 47 | * 将 I420 转 NV12 48 | */ 49 | public static native void convertI420ToNV12(byte[] src, byte[] dst, int width, int height); 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /lib-scamera/src/main/common/com/sharry/lib/camera/CameraContext.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.content.Context; 4 | import android.content.ContextWrapper; 5 | 6 | /** 7 | * @author Sharry Contact me. 8 | * @version 1.0 9 | * @since 2019-08-07 10 | */ 11 | class CameraContext extends ContextWrapper { 12 | 13 | private AspectRatio aspectRatio = AspectRatio.DEFAULT; 14 | private int facing; 15 | private boolean autoFocus; 16 | private int flashMode; 17 | private int screenOrientationDegrees; 18 | boolean adjustViewBounds; 19 | Size desiredSize; 20 | 21 | CameraContext(Context base) { 22 | super(base); 23 | } 24 | 25 | AspectRatio getAspectRatio() { 26 | return aspectRatio; 27 | } 28 | 29 | void setAspectRatio(AspectRatio aspectRatio) { 30 | this.aspectRatio = aspectRatio; 31 | } 32 | 33 | int getFacing() { 34 | return facing; 35 | } 36 | 37 | void setFacing(int facing) { 38 | this.facing = facing; 39 | } 40 | 41 | boolean isAutoFocus() { 42 | return autoFocus; 43 | } 44 | 45 | void setAutoFocus(boolean autoFocus) { 46 | this.autoFocus = autoFocus; 47 | } 48 | 49 | int getFlashMode() { 50 | return flashMode; 51 | } 52 | 53 | void setFlashMode(int flashMode) { 54 | this.flashMode = flashMode; 55 | } 56 | 57 | int getScreenOrientationDegrees() { 58 | return screenOrientationDegrees; 59 | } 60 | 61 | void setScreenOrientationDegrees(int screenOrientationDegrees) { 62 | this.screenOrientationDegrees = screenOrientationDegrees; 63 | } 64 | 65 | boolean isAdjustViewBounds() { 66 | return adjustViewBounds; 67 | } 68 | 69 | public void setAdjustViewBounds(boolean adjustViewBounds) { 70 | this.adjustViewBounds = adjustViewBounds; 71 | } 72 | 73 | public boolean getAdjustViewBounds() { 74 | return adjustViewBounds; 75 | } 76 | 77 | public void setDesiredSize(Size desiredSize) { 78 | this.desiredSize = desiredSize; 79 | } 80 | 81 | public Size getDesiredSize() { 82 | return desiredSize; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib-scamera/src/main/device/com/sharry/lib/camera/AbsCameraDevice.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | /** 4 | * @author Sharry Contact me. 5 | * @version 1.0 6 | * @since 2019-08-05 7 | */ 8 | abstract class AbsCameraDevice implements ICameraDevice { 9 | 10 | final CameraContext context; 11 | OnCameraReadyListener listener; 12 | AspectRatio aspectRatio = AspectRatio.DEFAULT; 13 | int facing; 14 | boolean autoFocus; 15 | int flashMode; 16 | int screenOrientationDegrees; 17 | int previewWidth, previewHeight; 18 | 19 | AbsCameraDevice(CameraContext context, OnCameraReadyListener listener) { 20 | this.context = context; 21 | this.listener = listener; 22 | } 23 | 24 | @Override 25 | public void notifyFacingChanged() { 26 | if (this.facing == context.getFacing()) { 27 | return; 28 | } 29 | this.facing = context.getFacing(); 30 | if (isCameraOpened()) { 31 | open(); 32 | } 33 | } 34 | 35 | @Override 36 | public void notifyAspectRatioChanged() { 37 | // Handle this later when camera is opened 38 | if (!isCameraOpened()) { 39 | aspectRatio = context.getAspectRatio(); 40 | } 41 | // if camera opened 42 | if (!aspectRatio.equals(context.getAspectRatio())) { 43 | aspectRatio = context.getAspectRatio(); 44 | open(); 45 | } 46 | } 47 | 48 | @Override 49 | public void notifyScreenOrientationChanged() { 50 | if (this.screenOrientationDegrees == context.getScreenOrientationDegrees()) { 51 | return; 52 | } 53 | this.screenOrientationDegrees = context.getScreenOrientationDegrees(); 54 | if (isCameraOpened()) { 55 | open(); 56 | } 57 | } 58 | 59 | @Override 60 | public void notifyDesiredSizeChanged() { 61 | if (previewWidth == context.getDesiredSize().getWidth() 62 | && previewHeight == context.getDesiredSize().getHeight()) { 63 | return; 64 | } 65 | previewWidth = context.getDesiredSize().getWidth(); 66 | previewHeight = context.getDesiredSize().getHeight(); 67 | if (isCameraOpened()) { 68 | open(); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /lib-scamera/src/main/common/com/sharry/lib/camera/Size.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sharry.lib.camera; 18 | 19 | import androidx.annotation.NonNull; 20 | 21 | /** 22 | * Immutable class for describing width and height dimensions in pixels. 23 | */ 24 | public class Size implements Comparable { 25 | 26 | private final int mWidth; 27 | private final int mHeight; 28 | 29 | /** 30 | * Create a new immutable Size instance. 31 | * 32 | * @param width The width of the size, in pixels 33 | * @param height The height of the size, in pixels 34 | */ 35 | public Size(int width, int height) { 36 | mWidth = width; 37 | mHeight = height; 38 | } 39 | 40 | public int getWidth() { 41 | return mWidth; 42 | } 43 | 44 | public int getHeight() { 45 | return mHeight; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (o == null) { 51 | return false; 52 | } 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o instanceof Size) { 57 | Size size = (Size) o; 58 | return mWidth == size.mWidth && mHeight == size.mHeight; 59 | } 60 | return false; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return mWidth + "x" + mHeight; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 71 | return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); 72 | } 73 | 74 | @Override 75 | public int compareTo(@NonNull Size another) { 76 | return mWidth * mHeight - another.mWidth * another.mHeight; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lib-scamera/src/main/common/com/sharry/lib/camera/SizeMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sharry.lib.camera; 18 | 19 | import android.util.ArrayMap; 20 | 21 | import java.util.Set; 22 | import java.util.SortedSet; 23 | import java.util.TreeSet; 24 | 25 | /** 26 | * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s. 27 | */ 28 | class SizeMap { 29 | 30 | private final ArrayMap> mRatios = new ArrayMap<>(); 31 | 32 | /** 33 | * Add a new {@link Size} to this collection. 34 | * 35 | * @param size The size to add. 36 | * @return {@code true} if it is added, {@code false} if it already exists and is not added. 37 | */ 38 | public boolean add(Size size) { 39 | for (AspectRatio ratio : mRatios.keySet()) { 40 | if (ratio.matches(size)) { 41 | final SortedSet sizes = mRatios.get(ratio); 42 | if (sizes.contains(size)) { 43 | return false; 44 | } else { 45 | sizes.add(size); 46 | return true; 47 | } 48 | } 49 | } 50 | // None of the existing ratio matches the provided size; add a new key 51 | SortedSet sizes = new TreeSet<>(); 52 | sizes.add(size); 53 | mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); 54 | return true; 55 | } 56 | 57 | /** 58 | * Removes the specified aspect ratio and all sizes associated with it. 59 | * 60 | * @param ratio The aspect ratio to be removed. 61 | */ 62 | public void remove(AspectRatio ratio) { 63 | mRatios.remove(ratio); 64 | } 65 | 66 | Set ratios() { 67 | return mRatios.keySet(); 68 | } 69 | 70 | SortedSet sizes(AspectRatio ratio) { 71 | return mRatios.get(ratio); 72 | } 73 | 74 | void clear() { 75 | mRatios.clear(); 76 | } 77 | 78 | boolean isEmpty() { 79 | return mRatios.isEmpty(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/Previewer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.SurfaceTexture; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | /** 15 | * Camera 预览器 16 | *

17 | * 使用 TextureView 渲染硬件相机输出的 SurfaceTexture 18 | * 19 | * @author Sharry Contact me. 20 | * @version 1.0 21 | * @since 2019-04-24 22 | */ 23 | @SuppressLint("ViewConstructor") 24 | public final class Previewer extends GLTextureView implements IPreviewer { 25 | 26 | private IPreviewerRenderer mRenderer; 27 | private Watcher mWatcher; 28 | 29 | Previewer(Context context, FrameLayout parent) { 30 | super(context); 31 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 32 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 33 | params.gravity = Gravity.CENTER; 34 | parent.addView(this, params); 35 | // set default renderer 36 | setRender(new PreviewRenderer(context)); 37 | } 38 | 39 | @Override 40 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 41 | super.onLayout(changed, left, top, right, bottom); 42 | if (mWatcher != null) { 43 | mWatcher.onSizeChanged(getWidth(), getHeight()); 44 | } 45 | } 46 | 47 | @Override 48 | public void setDataSource(@NonNull SurfaceTexture dataSource) { 49 | super.setBufferTexture(dataSource); 50 | } 51 | 52 | @Override 53 | public void setRender(@NonNull IPreviewerRenderer renderer) { 54 | this.mRenderer = renderer; 55 | setRenderer(mRenderer); 56 | if (mWatcher != null) { 57 | mWatcher.onRenderChanged(mRenderer); 58 | } 59 | } 60 | 61 | @Override 62 | public void setWatcher(Watcher watcher) { 63 | this.mWatcher = watcher; 64 | // call at once. 65 | if (mWatcher != null) { 66 | mWatcher.onSizeChanged(getWidth(), getHeight()); 67 | mWatcher.onRenderChanged(mRenderer); 68 | } 69 | } 70 | 71 | @Override 72 | public View getView() { 73 | return this; 74 | } 75 | 76 | @Override 77 | public IPreviewerRenderer getRenderer() { 78 | return mRenderer; 79 | } 80 | 81 | @Override 82 | public Size getSize() { 83 | return new Size(getWidth(), getHeight()); 84 | } 85 | 86 | @Override 87 | public Bitmap getBitmap() { 88 | return super.getBitmap(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib-scamera/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 45 | 46 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /lib-scamera/src/main/orientation/com/sharry/lib/camera/ScreenOrientationDetector.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.content.Context; 4 | import android.util.SparseIntArray; 5 | import android.view.Display; 6 | import android.view.OrientationEventListener; 7 | import android.view.Surface; 8 | 9 | /** 10 | * 屏幕方向探测器 11 | * 12 | * @author Sharry Contact me. 13 | * @version 1.0 14 | * @since 2019-08-05 15 | */ 16 | class ScreenOrientationDetector { 17 | 18 | /** 19 | * Mapping from Surface.Rotation_n to degrees. 20 | */ 21 | private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); 22 | 23 | static { 24 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); 25 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); 26 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); 27 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); 28 | } 29 | 30 | private final OrientationEventListener mOrientationEventListener; 31 | private OnDisplayChangedListener mListener; 32 | /** 33 | * This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). 34 | */ 35 | private int mLastRotation = 0; 36 | private Display mDisplay; 37 | 38 | ScreenOrientationDetector(Context context, final OnDisplayChangedListener listener) { 39 | this.mListener = listener; 40 | this.mOrientationEventListener = new OrientationEventListener(context) { 41 | @Override 42 | public void onOrientationChanged(int orientation) { 43 | if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || 44 | mDisplay == null) { 45 | return; 46 | } 47 | final int rotation = mDisplay.getRotation(); 48 | if (mLastRotation != rotation) { 49 | mLastRotation = rotation; 50 | mListener.onDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(mLastRotation)); 51 | } 52 | } 53 | }; 54 | } 55 | 56 | void enable(Display display) { 57 | mDisplay = display; 58 | mOrientationEventListener.enable(); 59 | // callback at once 60 | mLastRotation = mDisplay.getRotation(); 61 | mListener.onDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(mLastRotation)); 62 | } 63 | 64 | void disable() { 65 | mOrientationEventListener.disable(); 66 | mDisplay = null; 67 | } 68 | 69 | boolean isLandscape() { 70 | int screenOrientationDegrees = DISPLAY_ORIENTATIONS.get(mLastRotation); 71 | return (screenOrientationDegrees == Constants.LANDSCAPE_90 72 | || screenOrientationDegrees == Constants.LANDSCAPE_270); 73 | } 74 | 75 | interface OnDisplayChangedListener { 76 | 77 | /** 78 | * Called when display orientation is changed. 79 | * 80 | * @param displayOrientation One of 0, 90, 180, and 270. 81 | */ 82 | void onDisplayOrientationChanged(int displayOrientation); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /lib-scamera/src/main/device/com/sharry/lib/camera/CameraXDevice.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.annotation.TargetApi; 4 | import android.graphics.Bitmap; 5 | import android.util.Rational; 6 | 7 | import androidx.annotation.Nullable; 8 | import androidx.camera.core.CameraX; 9 | import androidx.camera.core.Preview; 10 | import androidx.camera.core.PreviewConfig; 11 | import androidx.lifecycle.LifecycleOwner; 12 | 13 | /** 14 | * @author Sharry Contact me. 15 | * @version 1.0 16 | * @since 2019-07-26 13:57 17 | */ 18 | @TargetApi(21) 19 | class CameraXDevice extends AbsCameraDevice implements Preview.OnPreviewOutputUpdateListener { 20 | 21 | private Preview mPreview; 22 | private LifecycleOwner mLifecycleOwner; 23 | 24 | CameraXDevice(CameraContext context, OnCameraReadyListener listener) { 25 | super(context, listener); 26 | this.mLifecycleOwner = (LifecycleOwner) context.getBaseContext(); 27 | } 28 | 29 | @Override 30 | public boolean isCameraOpened() { 31 | return mPreview != null && CameraX.isBound(mPreview); 32 | } 33 | 34 | @Override 35 | public void open() { 36 | close(); 37 | setupPreview(); 38 | CameraX.bindToLifecycle(mLifecycleOwner, mPreview); 39 | } 40 | 41 | @Override 42 | public void close() { 43 | CameraX.unbindAll(); 44 | } 45 | 46 | @Nullable 47 | @Override 48 | public Bitmap takePicture() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public void notifyAutoFocusChanged() { 54 | if (this.autoFocus == context.isAutoFocus()) { 55 | return; 56 | } 57 | this.autoFocus = context.isAutoFocus(); 58 | if (isCameraOpened()) { 59 | // TODO: 探究 CameraX 的自动对焦技术 60 | } 61 | } 62 | 63 | @Override 64 | public void notifyFlashModeChanged() { 65 | if (flashMode == context.getFlashMode()) { 66 | return; 67 | } 68 | flashMode = context.getFlashMode(); 69 | if (isCameraOpened()) { 70 | // TODO: 探究 CameraX 的闪光灯控制 71 | } 72 | } 73 | 74 | @Override 75 | public void onUpdated(final Preview.PreviewOutput output) { 76 | listener.onCameraReady( 77 | output.getSurfaceTexture(), 78 | new Size(output.getTextureSize().getWidth(), output.getTextureSize().getHeight()), 79 | screenOrientationDegrees 80 | ); 81 | } 82 | 83 | private void setupPreview() { 84 | PreviewConfig config = new PreviewConfig.Builder() 85 | // CameraX 的宽高比和 Camera1 相反, 为 3:4 9:16...... 86 | .setTargetAspectRatio(new Rational(aspectRatio.getY(), aspectRatio.getX())) 87 | // 分辨率 88 | .setTargetResolution(new android.util.Size(previewWidth, previewHeight)) 89 | // 前置与否 90 | .setLensFacing(facing == Constants.FACING_FRONT ? CameraX.LensFacing.FRONT 91 | : CameraX.LensFacing.BACK) 92 | .build(); 93 | mPreview = new Preview(config); 94 | mPreview.setOnPreviewOutputUpdateListener(this); 95 | } 96 | 97 | 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sharry/sample/camera/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sharry.sample.camera 2 | 3 | import android.Manifest 4 | import android.os.Bundle 5 | import android.util.TypedValue 6 | import androidx.appcompat.app.AppCompatActivity 7 | import com.sharry.lib.camera.AspectRatio 8 | import com.sharry.lib.camera.SCameraView 9 | import com.sharry.libcore.permission.PermissionsManager 10 | import com.sharry.libtoolbar.ImageViewOptions 11 | import kotlinx.android.synthetic.main.activity_record_video.* 12 | 13 | private val ASPECT_RATIOS = arrayOf( 14 | AspectRatio.of(1, 1), // 1:1 15 | AspectRatio.of(4, 3), // 4:3 16 | AspectRatio.of(16, 9), // 16:9 17 | AspectRatio.of(18, 9) // 18:9 18 | ) 19 | 20 | class MainActivity : AppCompatActivity(), AspectRatioFragment.Listener { 21 | 22 | private var isGranted = false 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_record_video) 27 | initTitle() 28 | initViews() 29 | PermissionsManager.getManager(this) 30 | .request(Manifest.permission.CAMERA) 31 | .execute { granted -> 32 | isGranted = granted 33 | if (!granted) { 34 | finish() 35 | } else { 36 | cameraView.startPreview() 37 | } 38 | } 39 | } 40 | 41 | override fun onResume() { 42 | super.onResume() 43 | if (isGranted) { 44 | cameraView.startPreview() 45 | } 46 | } 47 | 48 | override fun onPause() { 49 | if (isGranted) { 50 | cameraView.stopPreview() 51 | } 52 | super.onPause() 53 | } 54 | 55 | private fun initTitle() { 56 | val paddingSize = TypedValue.applyDimension( 57 | TypedValue.COMPLEX_UNIT_DIP, 20f, 58 | resources.displayMetrics 59 | ).toInt() 60 | toolbar.addLeftMenuImage( 61 | ImageViewOptions.Builder() 62 | .setDrawableResId(R.drawable.ic_activity_video_record_full_screen) 63 | .setPaddingLeft(paddingSize) 64 | .setListener { 65 | cameraView.adjustViewBounds = !cameraView.adjustViewBounds 66 | } 67 | .build() 68 | ) 69 | toolbar.addLeftMenuImage( 70 | ImageViewOptions.Builder() 71 | .setDrawableResId(R.drawable.ic_activity_video_record_aspect) 72 | .setPaddingLeft(paddingSize) 73 | .setListener { 74 | AspectRatioFragment.newInstance(ASPECT_RATIOS, cameraView.aspectRatio) 75 | .show(supportFragmentManager, AspectRatioFragment::class.java.simpleName) 76 | } 77 | .build() 78 | ) 79 | toolbar.addLeftMenuImage( 80 | ImageViewOptions.Builder() 81 | .setDrawableResId(R.drawable.ic_activity_video_record_camera_switch) 82 | .setPaddingLeft(paddingSize) 83 | .setListener { 84 | val curFacing = cameraView.facing 85 | cameraView.facing = if (curFacing == SCameraView.FACING_FRONT) { 86 | SCameraView.FACING_BACK 87 | } else { 88 | SCameraView.FACING_FRONT 89 | } 90 | } 91 | .build() 92 | ) 93 | } 94 | 95 | private fun initViews() { 96 | cameraView.autoFocus = true 97 | } 98 | 99 | override fun onAspectRatioSelected(ratio: AspectRatio) { 100 | cameraView.aspectRatio = ratio 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /lib-scamera/src/main/utils/com/sharry/lib/camera/GLUtil.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.content.Context; 4 | import android.opengl.GLES20; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.nio.FloatBuffer; 12 | 13 | class GlUtil { 14 | 15 | /** 16 | * 获取 glsl 资源 17 | */ 18 | static String getGLResource(Context context, int rawId) { 19 | InputStream inputStream = context.getResources().openRawResource(rawId); 20 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 21 | StringBuilder builder = new StringBuilder(); 22 | String line; 23 | try { 24 | while ((line = reader.readLine()) != null) { 25 | builder.append(line).append("\n"); 26 | } 27 | reader.close(); 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | return builder.toString(); 32 | } 33 | 34 | /** 35 | * 创建顶点 buffer 36 | */ 37 | static FloatBuffer createFloatBuffer(float[] coords) { 38 | FloatBuffer buffer = ByteBuffer.allocateDirect(coords.length * 4) 39 | .order(ByteOrder.nativeOrder()) 40 | .asFloatBuffer(); 41 | buffer.put(coords, 0, coords.length) 42 | .position(0); 43 | return buffer; 44 | } 45 | 46 | /** 47 | * 创建一个 OpenGL 程序 48 | * 49 | * @param vertexSource 顶点着色器源码 50 | * @param fragmentSource 片元着色器源码 51 | */ 52 | static int createProgram(String vertexSource, String fragmentSource) { 53 | // 分别加载创建着色器 54 | int vertexShaderId = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 55 | int fragmentShaderId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 56 | if (vertexShaderId != 0 && fragmentShaderId != 0) { 57 | // 创建 OpenGL 程序 ID 58 | int programId = GLES20.glCreateProgram(); 59 | if (programId == 0) { 60 | return 0; 61 | } 62 | // 链接上 顶点着色器 63 | GLES20.glAttachShader(programId, vertexShaderId); 64 | // 链接上 片段着色器 65 | GLES20.glAttachShader(programId, fragmentShaderId); 66 | // 链接 OpenGL 程序 67 | GLES20.glLinkProgram(programId); 68 | // 验证链接结果是否失败 69 | int[] status = new int[1]; 70 | GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, status, 0); 71 | if (status[0] != GLES20.GL_TRUE) { 72 | // 失败后删除这个 OpenGL 程序 73 | GLES20.glDeleteProgram(programId); 74 | return 0; 75 | } 76 | return programId; 77 | } 78 | return 0; 79 | } 80 | 81 | /** 82 | * 编译着色器 83 | * 84 | * @param shaderType 着色器的类型 85 | * @param source 资源源代码 86 | */ 87 | private static int loadShader(int shaderType, String source) { 88 | // 创建着色器 ID 89 | int shaderId = GLES20.glCreateShader(shaderType); 90 | if (shaderId != 0) { 91 | // 1. 将着色器 ID 和着色器程序内容关联 92 | GLES20.glShaderSource(shaderId, source); 93 | // 2. 编译着色器 94 | GLES20.glCompileShader(shaderId); 95 | // 3. 验证编译结果 96 | int[] status = new int[1]; 97 | GLES20.glGetShaderiv(shaderId, GLES20.GL_COMPILE_STATUS, status, 0); 98 | if (status[0] != GLES20.GL_TRUE) { 99 | // 编译失败删除这个着色器 id 100 | GLES20.glDeleteShader(shaderId); 101 | return 0; 102 | } 103 | } 104 | return shaderId; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/sharry/sample/camera/AspectRatioFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sharry.sample.camera; 18 | 19 | import android.app.Dialog; 20 | import android.content.Context; 21 | import android.content.DialogInterface; 22 | import android.os.Bundle; 23 | import android.view.LayoutInflater; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.widget.BaseAdapter; 27 | import android.widget.TextView; 28 | 29 | import androidx.annotation.NonNull; 30 | import androidx.appcompat.app.AlertDialog; 31 | import androidx.fragment.app.DialogFragment; 32 | 33 | import com.sharry.lib.camera.AspectRatio; 34 | 35 | 36 | /** 37 | * A simple dialog that allows user to pick an aspect ratio. 38 | */ 39 | public class AspectRatioFragment extends DialogFragment { 40 | 41 | private static final String ARG_ASPECT_RATIOS = "aspect_ratios"; 42 | private static final String ARG_CURRENT_ASPECT_RATIO = "current_aspect_ratio"; 43 | 44 | private Listener mListener; 45 | 46 | public static AspectRatioFragment newInstance(AspectRatio[] ratios, 47 | AspectRatio currentRatio) { 48 | final AspectRatioFragment fragment = new AspectRatioFragment(); 49 | final Bundle args = new Bundle(); 50 | args.putParcelableArray(ARG_ASPECT_RATIOS, ratios); 51 | args.putParcelable(ARG_CURRENT_ASPECT_RATIO, currentRatio); 52 | fragment.setArguments(args); 53 | return fragment; 54 | } 55 | 56 | @Override 57 | public void onAttach(Context context) { 58 | super.onAttach(context); 59 | mListener = (Listener) context; 60 | } 61 | 62 | @Override 63 | public void onDetach() { 64 | mListener = null; 65 | super.onDetach(); 66 | } 67 | 68 | @NonNull 69 | @Override 70 | public Dialog onCreateDialog(Bundle savedInstanceState) { 71 | final Bundle args = getArguments(); 72 | final AspectRatio[] ratios = (AspectRatio[]) args.getParcelableArray(ARG_ASPECT_RATIOS); 73 | if (ratios == null) { 74 | throw new RuntimeException("No ratios"); 75 | } 76 | final AspectRatio current = args.getParcelable(ARG_CURRENT_ASPECT_RATIO); 77 | final AspectRatioAdapter adapter = new AspectRatioAdapter(ratios, current); 78 | return new AlertDialog.Builder(getContext()) 79 | .setAdapter(adapter, new DialogInterface.OnClickListener() { 80 | @Override 81 | public void onClick(DialogInterface dialog, int position) { 82 | mListener.onAspectRatioSelected(ratios[position]); 83 | } 84 | }) 85 | .create(); 86 | } 87 | 88 | private static class AspectRatioAdapter extends BaseAdapter { 89 | 90 | private final AspectRatio[] mRatios; 91 | private final AspectRatio mCurrentRatio; 92 | 93 | AspectRatioAdapter(AspectRatio[] ratios, AspectRatio current) { 94 | mRatios = ratios; 95 | mCurrentRatio = current; 96 | } 97 | 98 | @Override 99 | public int getCount() { 100 | return mRatios.length; 101 | } 102 | 103 | @Override 104 | public AspectRatio getItem(int position) { 105 | return mRatios[position]; 106 | } 107 | 108 | @Override 109 | public long getItemId(int position) { 110 | return position; 111 | } 112 | 113 | @Override 114 | public View getView(int position, View view, ViewGroup parent) { 115 | AspectRatioAdapter.ViewHolder holder; 116 | if (view == null) { 117 | view = LayoutInflater.from(parent.getContext()) 118 | .inflate(android.R.layout.simple_list_item_1, parent, false); 119 | holder = new AspectRatioAdapter.ViewHolder(); 120 | holder.text = view.findViewById(android.R.id.text1); 121 | view.setTag(holder); 122 | } else { 123 | holder = (AspectRatioAdapter.ViewHolder) view.getTag(); 124 | } 125 | AspectRatio ratio = getItem(position); 126 | StringBuilder sb = new StringBuilder(ratio.toString()); 127 | if (ratio.equals(mCurrentRatio)) { 128 | sb.append(" *"); 129 | } 130 | holder.text.setText(sb); 131 | return view; 132 | } 133 | 134 | private static class ViewHolder { 135 | TextView text; 136 | } 137 | 138 | } 139 | 140 | public interface Listener { 141 | void onAspectRatioSelected(@NonNull AspectRatio ratio); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /lib-scamera/src/main/common/com/sharry/lib/camera/AspectRatio.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sharry.lib.camera; 18 | 19 | import android.os.Parcel; 20 | import android.os.Parcelable; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.collection.SparseArrayCompat; 24 | 25 | /** 26 | * Immutable class for describing proportional relationship between width and height. 27 | */ 28 | public class AspectRatio implements Comparable, Parcelable { 29 | 30 | public static final AspectRatio DEFAULT = new AspectRatio(4, 3); 31 | private final static SparseArrayCompat> sCache 32 | = new SparseArrayCompat<>(16); 33 | 34 | private final int mX; 35 | private final int mY; 36 | 37 | /** 38 | * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. 39 | * The values {@code x} and {@code} will be reduced by their greatest common divider. 40 | * 41 | * @param x The width 42 | * @param y The height 43 | * @return An instance of {@link AspectRatio} 44 | */ 45 | public static AspectRatio of(int x, int y) { 46 | int gcd = gcd(x, y); 47 | x /= gcd; 48 | y /= gcd; 49 | SparseArrayCompat arrayX = sCache.get(x); 50 | if (arrayX == null) { 51 | AspectRatio ratio = new AspectRatio(x, y); 52 | arrayX = new SparseArrayCompat<>(); 53 | arrayX.put(y, ratio); 54 | sCache.put(x, arrayX); 55 | return ratio; 56 | } else { 57 | AspectRatio ratio = arrayX.get(y); 58 | if (ratio == null) { 59 | ratio = new AspectRatio(x, y); 60 | arrayX.put(y, ratio); 61 | } 62 | return ratio; 63 | } 64 | } 65 | 66 | /** 67 | * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". 68 | * 69 | * @param s The string representation of the aspect ratio 70 | * @return The aspect ratio 71 | * @throws IllegalArgumentException when the format is incorrect. 72 | */ 73 | public static AspectRatio parse(String s) { 74 | int position = s.indexOf(':'); 75 | if (position == -1) { 76 | throw new IllegalArgumentException("Malformed aspect ratio: " + s); 77 | } 78 | try { 79 | int x = Integer.parseInt(s.substring(0, position)); 80 | int y = Integer.parseInt(s.substring(position + 1)); 81 | return AspectRatio.of(x, y); 82 | } catch (NumberFormatException e) { 83 | throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); 84 | } 85 | } 86 | 87 | private AspectRatio(int x, int y) { 88 | mX = x; 89 | mY = y; 90 | } 91 | 92 | public int getX() { 93 | return mX; 94 | } 95 | 96 | public int getY() { 97 | return mY; 98 | } 99 | 100 | public boolean matches(Size size) { 101 | int gcd = gcd(size.getWidth(), size.getHeight()); 102 | int x = size.getWidth() / gcd; 103 | int y = size.getHeight() / gcd; 104 | return mX == x && mY == y; 105 | } 106 | 107 | @Override 108 | public boolean equals(Object o) { 109 | if (o == null) { 110 | return false; 111 | } 112 | if (this == o) { 113 | return true; 114 | } 115 | if (o instanceof AspectRatio) { 116 | AspectRatio ratio = (AspectRatio) o; 117 | return mX == ratio.mX && mY == ratio.mY; 118 | } 119 | return false; 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | return mX + ":" + mY; 125 | } 126 | 127 | public float toFloat() { 128 | return (float) mX / mY; 129 | } 130 | 131 | @Override 132 | public int hashCode() { 133 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 134 | return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); 135 | } 136 | 137 | @Override 138 | public int compareTo(@NonNull AspectRatio another) { 139 | if (equals(another)) { 140 | return 0; 141 | } else if (toFloat() - another.toFloat() > 0) { 142 | return 1; 143 | } 144 | return -1; 145 | } 146 | 147 | /** 148 | * @return The inverse of this {@link AspectRatio}. 149 | */ 150 | public AspectRatio inverse() { 151 | //noinspection SuspiciousNameCombination 152 | return AspectRatio.of(mY, mX); 153 | } 154 | 155 | /** 156 | * 计算 a 与 b 的最大公约数 157 | *

158 | * 辗转相除法:两个整数的最大公约数等于其中较小的那个数和两个数相除余数的最大公约数。 159 | */ 160 | private static int gcd(int a, int b) { 161 | while (b != 0) { 162 | int c = b; 163 | b = a % b; 164 | a = c; 165 | } 166 | return a; 167 | } 168 | 169 | @Override 170 | public int describeContents() { 171 | return 0; 172 | } 173 | 174 | @Override 175 | public void writeToParcel(Parcel dest, int flags) { 176 | dest.writeInt(mX); 177 | dest.writeInt(mY); 178 | } 179 | 180 | public static final Creator CREATOR 181 | = new Creator() { 182 | 183 | @Override 184 | public AspectRatio createFromParcel(Parcel source) { 185 | int x = source.readInt(); 186 | int y = source.readInt(); 187 | return AspectRatio.of(x, y); 188 | } 189 | 190 | @Override 191 | public AspectRatio[] newArray(int size) { 192 | return new AspectRatio[size]; 193 | } 194 | }; 195 | 196 | } 197 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/PreviewerFramebufferRenderer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.opengl.GLES20; 4 | 5 | import java.nio.FloatBuffer; 6 | 7 | /** 8 | * 离屏渲染:把所有的纹理先绘制到 fbo 上面,然后再从 fbo 绘制到窗口上 9 | *

10 | * 在绘制到屏幕上之前, 对数据进行一次拦截 11 | * 12 | * @author Sharry Contact me. 13 | * @version 1.0 14 | * @since 2019-08-01 16:04 15 | */ 16 | class PreviewerFramebufferRenderer { 17 | 18 | private static final String VERTEX_SHADER_STR = "attribute vec4 aVertexPosition;\n" + 19 | " attribute vec2 aTexturePosition;\n" + 20 | " varying vec2 vPosition;\n" + 21 | " void main() {\n" + 22 | " vPosition = aTexturePosition;\n" + 23 | " gl_Position = aVertexPosition;\n" + 24 | " }"; 25 | 26 | 27 | private static final String FRAGMENT_SHADER_STR = "precision mediump float;\n" + 28 | "varying vec2 vPosition;\n" + 29 | "uniform sampler2D uTexture;\n" + 30 | "void main() {\n" + 31 | " gl_FragColor=texture2D(uTexture, vPosition);\n" + 32 | "}"; 33 | 34 | private final float[] mVertexCoordinate = new float[]{ 35 | -1f, 1f, // 左上 36 | -1f, -1f, // 左下 37 | 1f, 1f, // 右上 38 | 1f, -1f // 右下 39 | }; 40 | private final float[] mTextureCoordinate = new float[]{ 41 | 0f, 1f, // 左上 42 | 0f, 0f, // 左下 43 | 1f, 1f, // 右上 44 | 1f, 0f // 右下 45 | }; 46 | private final FloatBuffer mVertexBuffer = GlUtil.createFloatBuffer(mVertexCoordinate); 47 | private final FloatBuffer mTextureBuffer = GlUtil.createFloatBuffer(mTextureCoordinate); 48 | 49 | private int mProgramId; 50 | private int aVertexPosition; 51 | private int aTexturePosition; 52 | private int mVboId; 53 | private int mFramebufferId; 54 | private int mTextureId; 55 | private int uTexture; 56 | 57 | PreviewerFramebufferRenderer() { 58 | } 59 | 60 | void onEglContextCreated() { 61 | // 上下文变更了, 重置数据 62 | reset(); 63 | // 初始化程序 64 | setupShaders(); 65 | // 初始化顶点坐标 66 | setupCoordinates(); 67 | } 68 | 69 | void onSurfaceSizeChanged(int width, int height) { 70 | GLES20.glViewport(0, 0, width, height); 71 | // 配置纹理 72 | setupTexture(width, height); 73 | // 配置 fbo 74 | setupFbo(); 75 | } 76 | 77 | void bindFramebuffer() { 78 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebufferId); 79 | } 80 | 81 | void unbindFramebuffer() { 82 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 83 | } 84 | 85 | void drawToDisplay() { 86 | GLES20.glUseProgram(mProgramId); 87 | // 绑定纹理 88 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 89 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId); 90 | // 写入顶点坐标 91 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId); 92 | GLES20.glEnableVertexAttribArray(aVertexPosition); 93 | GLES20.glVertexAttribPointer(aVertexPosition, 2, GLES20.GL_FLOAT, false, 94 | 8, 0); 95 | // 写入纹理坐标 96 | GLES20.glEnableVertexAttribArray(aTexturePosition); 97 | GLES20.glVertexAttribPointer(aTexturePosition, 2, GLES20.GL_FLOAT, false, 98 | 8, mVertexCoordinate.length * 4); 99 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 100 | // 给 uTexture 赋值 101 | GLES20.glUniform1i(uTexture, 0); 102 | // 绘制到屏幕 103 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 104 | // 解绑纹理 105 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 106 | } 107 | 108 | int getFboTextureId() { 109 | return mTextureId; 110 | } 111 | 112 | private void reset() { 113 | this.mProgramId = 0; 114 | this.mVboId = 0; 115 | this.mTextureId = 0; 116 | this.mFramebufferId = 0; 117 | } 118 | 119 | private void setupShaders() { 120 | if (mProgramId != 0) { 121 | return; 122 | } 123 | mProgramId = GlUtil.createProgram(VERTEX_SHADER_STR, FRAGMENT_SHADER_STR); 124 | aVertexPosition = GLES20.glGetAttribLocation(mProgramId, "aVertexPosition"); 125 | aTexturePosition = GLES20.glGetAttribLocation(mProgramId, "aTexturePosition"); 126 | uTexture = GLES20.glGetUniformLocation(mProgramId, "uTexture"); 127 | } 128 | 129 | private void setupCoordinates() { 130 | if (mVboId != 0) { 131 | return; 132 | } 133 | // 创建 vbo 134 | int vboSize = 1; 135 | int[] vboIds = new int[vboSize]; 136 | GLES20.glGenBuffers(vboSize, vboIds, 0); 137 | // 将顶点坐标写入 vbo 138 | mVboId = vboIds[0]; 139 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId); 140 | // 开辟 VBO 空间 141 | GLES20.glBufferData( 142 | GLES20.GL_ARRAY_BUFFER, 143 | (mVertexCoordinate.length + mTextureCoordinate.length) * 4, 144 | null, 145 | GLES20.GL_STATIC_DRAW 146 | ); 147 | // 写入顶点坐标 148 | GLES20.glBufferSubData( 149 | GLES20.GL_ARRAY_BUFFER, 150 | 0, 151 | (mVertexCoordinate.length) * 4, 152 | mVertexBuffer 153 | ); 154 | // 写入纹理坐标 155 | GLES20.glBufferSubData( 156 | GLES20.GL_ARRAY_BUFFER, 157 | (mVertexCoordinate.length) * 4, 158 | (mTextureCoordinate.length) * 4, 159 | mTextureBuffer 160 | ); 161 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 162 | } 163 | 164 | private void setupTexture(int width, int height) { 165 | if (mTextureId == 0) { 166 | int[] textureIds = new int[1]; 167 | GLES20.glGenTextures(1, textureIds, 0); 168 | mTextureId = textureIds[0]; 169 | } 170 | // 绑定纹理 171 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 172 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId); 173 | // 设置纹理环绕方式 174 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); 175 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); 176 | // 设置纹理过滤方式 177 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 178 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 179 | // 创建一个空的纹理画布 180 | GLES20.glTexImage2D( 181 | GLES20.GL_TEXTURE_2D, 182 | 0, 183 | GLES20.GL_RGBA, 184 | width, height, 185 | 0, 186 | GLES20.GL_RGBA, 187 | GLES20.GL_UNSIGNED_BYTE, 188 | null 189 | ); 190 | // 解绑纹理 191 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 192 | } 193 | 194 | private void setupFbo() { 195 | if (mFramebufferId != 0) { 196 | return; 197 | } 198 | // 创建 fbo 199 | int[] fBoIds = new int[1]; 200 | GLES20.glGenBuffers(1, fBoIds, 0); 201 | mFramebufferId = fBoIds[0]; 202 | // 绑定 fbo 203 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebufferId); 204 | // 将纹理绑定到 FBO 上, 作为颜色附件 205 | GLES20.glFramebufferTexture2D( 206 | GLES20.GL_FRAMEBUFFER, 207 | GLES20.GL_COLOR_ATTACHMENT0, // 描述为颜色附件 208 | GLES20.GL_TEXTURE_2D, 209 | mTextureId, 210 | 0 211 | ); 212 | // 解绑 fbo 213 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /lib-scamera/src/main/utils/com/sharry/lib/camera/EglCore.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.graphics.SurfaceTexture; 4 | import android.opengl.EGL14; 5 | import android.opengl.EGLConfig; 6 | import android.opengl.EGLContext; 7 | import android.opengl.EGLDisplay; 8 | import android.opengl.EGLExt; 9 | import android.opengl.EGLSurface; 10 | import android.util.Log; 11 | import android.view.Surface; 12 | 13 | import androidx.annotation.IntDef; 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | 17 | import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; 18 | 19 | /** 20 | * An EGL helper class. 21 | *

22 | * The EGLContext must only be attached to one thread at a time. This class is not thread-safe. 23 | *

24 | * Get more detailshttps://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/gles/EglCore.java 25 | * 26 | * @author Sharry Contact me. 27 | * @version 1.0 28 | * @since 2019-07-29 29 | */ 30 | public class EglCore { 31 | 32 | public static final int EGL_VERSION_2 = 2; 33 | public static final int EGL_VERSION_3 = 3; 34 | 35 | @IntDef(value = { 36 | EGL_VERSION_2, 37 | EGL_VERSION_3 38 | }) 39 | private @interface EGLVersion { 40 | 41 | } 42 | 43 | private static final String TAG = EglCore.class.getSimpleName(); 44 | 45 | private final int mEGLVersion; 46 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 47 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 48 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 49 | 50 | public EglCore() { 51 | this(EGL_VERSION_2); 52 | } 53 | 54 | public EglCore(@EGLVersion int eglVersion) { 55 | mEGLVersion = eglVersion; 56 | } 57 | 58 | /** 59 | * Initialize EGL for a given configuration spec. 60 | * 61 | * @param surface native window 62 | * @param eglContext if null will create new context, false will use shared context 63 | */ 64 | public void initialize(@NonNull Surface surface, @Nullable EGLContext eglContext) { 65 | initializeInternal(surface, eglContext == null ? EGL14.EGL_NO_CONTEXT : eglContext); 66 | } 67 | 68 | /** 69 | * Initialize EGL for a given configuration spec. 70 | * 71 | * @param surfaceTexture native window 72 | * @param eglContext if null will create new context, false will use shared context 73 | */ 74 | public void initialize(@NonNull SurfaceTexture surfaceTexture, @Nullable EGLContext eglContext) { 75 | initializeInternal(surfaceTexture, eglContext == null ? EGL14.EGL_NO_CONTEXT : eglContext); 76 | } 77 | 78 | /** 79 | * Makes our EGL context current, using the supplied "draw" and "read" surfaces. 80 | */ 81 | public void makeCurrent() { 82 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 83 | throw new RuntimeException("eglMakeCurrent failed"); 84 | } 85 | } 86 | 87 | /** 88 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 89 | * 90 | * @return false on failure 91 | */ 92 | public boolean swapBuffers() { 93 | return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); 94 | } 95 | 96 | /** 97 | * Discards all resources held by this class, notably the EGL context. This must be 98 | * called from the thread where the context was created. 99 | *

100 | * On completion, no context will be current. 101 | */ 102 | public void release() { 103 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 104 | EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 105 | EGL14.EGL_NO_CONTEXT); 106 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 107 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); 108 | EGL14.eglTerminate(mEGLDisplay); 109 | } 110 | mEGLContext = EGL14.EGL_NO_CONTEXT; 111 | mEGLDisplay = EGL14.EGL_NO_DISPLAY; 112 | mEGLSurface = EGL14.EGL_NO_SURFACE; 113 | } 114 | 115 | /** 116 | * Gets the current EGLContext 117 | * 118 | * @return the current EGLContext 119 | */ 120 | public EGLContext getContext() { 121 | return mEGLContext; 122 | } 123 | 124 | /** 125 | * Copy from {@link android.opengl.GLSurfaceView#EglHelper} 126 | */ 127 | private void initializeInternal(Object nativeWindow, EGLContext sharedEglContext) { 128 | /* 129 | * Create a connection for system native window 130 | */ 131 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 132 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 133 | throw new RuntimeException("eglGetDisplay failed"); 134 | } 135 | 136 | /* 137 | * We can now initialize EGL for that display 138 | */ 139 | int[] version = new int[2]; 140 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 141 | mEGLDisplay = null; 142 | throw new RuntimeException("eglInitialize failed"); 143 | } 144 | 145 | /* 146 | * Create EGLConfig 147 | */ 148 | EGLConfig eglConfig = chooseConfig(); 149 | if (eglConfig == null) { 150 | throw new RuntimeException("Cannot find suitable config."); 151 | } 152 | 153 | /* 154 | * Create EGLContext 155 | */ 156 | int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLVersion, EGL14.EGL_NONE}; 157 | EGLContext eglContext = EGL14.eglCreateContext(mEGLDisplay, eglConfig, sharedEglContext, 158 | attrib_list, 0); 159 | if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { 160 | mEGLContext = eglContext; 161 | } else { 162 | throw new RuntimeException("Create EGLContext failed."); 163 | } 164 | 165 | /* 166 | * Create EGLSurface 167 | */ 168 | int[] surfaceAttribs = {EGL14.EGL_NONE}; 169 | mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, eglConfig, nativeWindow, 170 | surfaceAttribs, 0); 171 | if (mEGLSurface == null || mEGLSurface == EGL14.EGL_NO_SURFACE) { 172 | throw new RuntimeException("createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 173 | } 174 | 175 | /* 176 | * Bind context 177 | */ 178 | makeCurrent(); 179 | } 180 | 181 | /** 182 | * Finds a suitable EGLConfig. 183 | */ 184 | private EGLConfig chooseConfig() { 185 | int renderableType = EGL14.EGL_OPENGL_ES2_BIT; 186 | if (mEGLVersion >= 3) { 187 | renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; 188 | } 189 | // The actual surface is generally RGBA or RGBX, so situationally omitting alpha 190 | // doesn't really help. It can also lead to a huge performance hit on glReadPixels() 191 | // when reading into a GL_RGBA buffer. 192 | int[] attribList = { 193 | EGL14.EGL_RED_SIZE, 8, 194 | EGL14.EGL_GREEN_SIZE, 8, 195 | EGL14.EGL_BLUE_SIZE, 8, 196 | EGL14.EGL_ALPHA_SIZE, 8, 197 | //EGL14.EGL_DEPTH_SIZE, 16, 198 | //EGL14.EGL_STENCIL_SIZE, 8, 199 | EGL14.EGL_RENDERABLE_TYPE, renderableType, 200 | EGL14.EGL_NONE, 0, // placeholder for recordable [@-3] 201 | EGL14.EGL_NONE 202 | }; 203 | EGLConfig[] configs = new EGLConfig[1]; 204 | int[] numConfigs = new int[1]; 205 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 206 | 0, configs.length, 207 | numConfigs, 0)) { 208 | Log.w(TAG, "unable to find RGB8888 / " + mEGLVersion + " EGLConfig"); 209 | return null; 210 | } 211 | return configs[0]; 212 | } 213 | 214 | @Override 215 | protected void finalize() throws Throwable { 216 | try { 217 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 218 | // We're limited here -- finalizers don't run on the thread that holds 219 | // the EGL state, so if a surface or context is still current on another 220 | // thread we can't fully release it here. Exceptions thrown from here 221 | // are quietly discarded. Complain in the log file. 222 | Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked"); 223 | release(); 224 | } 225 | } finally { 226 | super.finalize(); 227 | } 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/PreviewRenderer.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.content.Context; 4 | import android.opengl.EGLContext; 5 | import android.opengl.GLES11Ext; 6 | import android.opengl.GLES20; 7 | import android.opengl.Matrix; 8 | import android.util.Log; 9 | 10 | import java.nio.FloatBuffer; 11 | 12 | import static android.opengl.GLES20.GL_FLOAT; 13 | import static android.opengl.GLES20.glGetUniformLocation; 14 | 15 | /** 16 | * @author Sharry Contact me. 17 | * @version 1.0 18 | * @since 2019-07-28 19 | */ 20 | public class PreviewRenderer implements IPreviewerRenderer { 21 | 22 | private static final String TAG = PreviewRenderer.class.getSimpleName(); 23 | 24 | private final float[] mVertexCoordinate = new float[]{ 25 | -1f, 1f, // 左上 26 | -1f, -1f, // 左下 27 | 1f, 1f, // 右上 28 | 1f, -1f // 右下 29 | }; 30 | private final float[] mTextureCoordinate = new float[]{ 31 | 0f, 1f, // 左上 32 | 0f, 0f, // 左下 33 | 1f, 1f, // 右上 34 | 1f, 0f // 右下 35 | }; 36 | private final FloatBuffer mVertexBuffer = GlUtil.createFloatBuffer(mVertexCoordinate); 37 | private final FloatBuffer mTextureBuffer = GlUtil.createFloatBuffer(mTextureCoordinate); 38 | private final Context mContext; 39 | private final PreviewerFramebufferRenderer mFramebufferRenderer; 40 | private EGLContext mEglContext; 41 | 42 | /** 43 | * 着色器相关 44 | */ 45 | private int mProgram; 46 | private int aVertexCoordinate; 47 | private int aTextureCoordinate; 48 | private int uTextureMatrix; 49 | private int uVertexMatrix; 50 | private int uTexture; 51 | 52 | /** 53 | * Vertex buffer object 相关 54 | */ 55 | private int mVboId; 56 | 57 | /** 58 | * Matrix 59 | */ 60 | private final float[] mProjectionMatrix = new float[16]; // 投影矩阵 61 | private final float[] mRotationMatrix = new float[16]; // 裁剪矩阵 62 | private final float[] mFinalMatrix = new float[16]; // 裁剪矩阵 63 | 64 | PreviewRenderer(Context context) { 65 | mContext = context; 66 | mFramebufferRenderer = new PreviewerFramebufferRenderer(); 67 | } 68 | 69 | @Override 70 | public void onEglContextCreated(EGLContext eglContext) { 71 | this.mFramebufferRenderer.onEglContextCreated(); 72 | this.mEglContext = eglContext; 73 | // 上下文变更了, 重置数据 74 | reset(); 75 | // 配置着色器 76 | setupShaders(); 77 | // 配置顶点和纹理坐标 78 | setupCoordinates(); 79 | } 80 | 81 | @Override 82 | public void onSurfaceSizeChanged(int width, int height) { 83 | mFramebufferRenderer.onSurfaceSizeChanged(width, height); 84 | GLES20.glViewport(0, 0, width, height); 85 | } 86 | 87 | @Override 88 | public void drawTexture(int OESTextureId, float[] textureMatrix) { 89 | mFramebufferRenderer.bindFramebuffer(); 90 | // 清屏 91 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 92 | GLES20.glClearColor(0f, 0f, 0f, 0f); 93 | // 激活着色器 94 | GLES20.glUseProgram(mProgram); 95 | // 绑定纹理 96 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 97 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, OESTextureId); 98 | 99 | /* 100 | 顶点着色器 101 | */ 102 | // 顶点坐标赋值 103 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId); 104 | GLES20.glEnableVertexAttribArray(aVertexCoordinate); 105 | GLES20.glVertexAttribPointer(aVertexCoordinate, 2, GL_FLOAT, false, 106 | 8, 0); 107 | // 纹理坐标赋值 108 | GLES20.glEnableVertexAttribArray(aTextureCoordinate); 109 | GLES20.glVertexAttribPointer(aTextureCoordinate, 2, GL_FLOAT, false, 110 | 8, mVertexCoordinate.length * 4); 111 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 112 | // 顶点变换矩阵赋值 113 | GLES20.glUniformMatrix4fv(uVertexMatrix, 1, false, mFinalMatrix, 0); 114 | // 纹理变换矩阵赋值 115 | GLES20.glUniformMatrix4fv(uTextureMatrix, 1, false, textureMatrix, 0); 116 | 117 | /* 118 | 片元着色器, 为 uTexture 赋值 119 | */ 120 | GLES20.glUniform1i(uTexture, 0); 121 | 122 | // 执行渲染管线 123 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 124 | 125 | // 解绑纹理 126 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); 127 | 128 | // 解绑 fbo 129 | mFramebufferRenderer.unbindFramebuffer(); 130 | 131 | // 输出到屏幕上 132 | mFramebufferRenderer.drawToDisplay(); 133 | } 134 | 135 | @Override 136 | public EGLContext getEGLContext() { 137 | return mEglContext; 138 | } 139 | 140 | @Override 141 | public int getPreviewerTextureId() { 142 | return mFramebufferRenderer.getFboTextureId(); 143 | } 144 | 145 | @Override 146 | public void resetMatrix() { 147 | Matrix.setIdentityM(mProjectionMatrix, 0); 148 | Matrix.setIdentityM(mRotationMatrix, 0); 149 | Matrix.setIdentityM(mFinalMatrix, 0); 150 | } 151 | 152 | @Override 153 | public void rotate(int degrees) { 154 | Matrix.rotateM(mRotationMatrix, 0, degrees, 0, 0, 1); 155 | } 156 | 157 | @Override 158 | public void centerCrop(boolean isLandscape, Size surfaceSize, Size textureSize) { 159 | // 设置正交投影 160 | float aspectPlane = surfaceSize.getWidth() / (float) surfaceSize.getHeight(); 161 | float aspectTexture = isLandscape ? textureSize.getWidth() / (float) textureSize.getHeight() 162 | : textureSize.getHeight() / (float) textureSize.getWidth(); 163 | float left, top, right, bottom; 164 | // 1. 纹理比例 > 投影平面比例 165 | if (aspectTexture > aspectPlane) { 166 | left = -aspectPlane / aspectTexture; 167 | right = -left; 168 | top = 1; 169 | bottom = -1; 170 | } 171 | // 2. 纹理比例 < 投影平面比例 172 | else { 173 | left = -1; 174 | right = 1; 175 | top = 1 / aspectPlane * aspectTexture; 176 | bottom = -top; 177 | } 178 | Matrix.orthoM( 179 | mProjectionMatrix, 0, 180 | left, right, bottom, top, 181 | 1, -1 182 | ); 183 | Log.e(TAG, "preview size = " + surfaceSize + ", camera size = " + textureSize); 184 | } 185 | 186 | @Override 187 | public void transformMatrix() { 188 | // 使裁剪矩阵合并旋转矩阵 189 | Matrix.multiplyMM(mFinalMatrix, 0, mProjectionMatrix, 0, 190 | mRotationMatrix, 0); 191 | } 192 | 193 | private void reset() { 194 | this.mProgram = 0; 195 | this.mVboId = 0; 196 | } 197 | 198 | private void setupShaders() { 199 | if (mProgram != 0) { 200 | return; 201 | } 202 | // 加载着色器 203 | String vertexSource = GlUtil.getGLResource(mContext, R.raw.camera_vertex_shader); 204 | String fragmentSource = GlUtil.getGLResource(mContext, R.raw.camera_fragment_shader); 205 | mProgram = GlUtil.createProgram(vertexSource, fragmentSource); 206 | // 加载 Program 中的变量 207 | aVertexCoordinate = GLES20.glGetAttribLocation(mProgram, "aVertexCoordinate"); 208 | aTextureCoordinate = GLES20.glGetAttribLocation(mProgram, "aTextureCoordinate"); 209 | uVertexMatrix = glGetUniformLocation(mProgram, "uVertexMatrix"); 210 | uTextureMatrix = glGetUniformLocation(mProgram, "uTextureMatrix"); 211 | uTexture = glGetUniformLocation(mProgram, "uTexture"); 212 | } 213 | 214 | private void setupCoordinates() { 215 | if (mVboId != 0) { 216 | return; 217 | } 218 | // 创建 vbo 219 | int vboSize = 1; 220 | int[] vboIds = new int[vboSize]; 221 | GLES20.glGenBuffers(vboSize, vboIds, 0); 222 | // 将顶点坐标写入 vbo 223 | mVboId = vboIds[0]; 224 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId); 225 | // 开辟 VBO 空间 226 | GLES20.glBufferData( 227 | GLES20.GL_ARRAY_BUFFER, 228 | (mVertexCoordinate.length + mTextureCoordinate.length) * 4, 229 | null, 230 | GLES20.GL_STATIC_DRAW 231 | ); 232 | // 写入顶点坐标 233 | GLES20.glBufferSubData( 234 | GLES20.GL_ARRAY_BUFFER, 235 | 0, 236 | (mVertexCoordinate.length) * 4, 237 | mVertexBuffer 238 | ); 239 | // 写入纹理坐标 240 | GLES20.glBufferSubData( 241 | GLES20.GL_ARRAY_BUFFER, 242 | (mVertexCoordinate.length) * 4, 243 | (mTextureCoordinate.length) * 4, 244 | mTextureBuffer 245 | ); 246 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /lib-scamera/src/main/previewer/com/sharry/lib/camera/GLTextureView.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.content.Context; 4 | import android.graphics.SurfaceTexture; 5 | import android.opengl.GLES11Ext; 6 | import android.opengl.GLES20; 7 | import android.os.Handler; 8 | import android.os.HandlerThread; 9 | import android.os.Message; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.TextureView; 13 | 14 | import androidx.annotation.NonNull; 15 | 16 | import java.lang.ref.WeakReference; 17 | 18 | import javax.microedition.khronos.opengles.GL10; 19 | 20 | /** 21 | * 利用 TextureView 实现对外来 SurfaceTexture 的加工绘制 22 | * 23 | * @author Sharry Contact me. 24 | * @version 1.0 25 | * @since 2019-07-27 26 | */ 27 | public class GLTextureView extends TextureView { 28 | 29 | private static final String TAG = GLTextureView.class.getSimpleName(); 30 | 31 | /** 32 | * 渲染器 33 | */ 34 | protected ITextureRenderer mRenderer; 35 | 36 | /** 37 | * 用于渲染的纹理 38 | */ 39 | private SurfaceTexture mBufferTexture; 40 | 41 | /** 42 | * 用于渲染的线程 43 | */ 44 | private RendererThread mRendererThread; 45 | 46 | public GLTextureView(Context context) { 47 | this(context, null); 48 | } 49 | 50 | public GLTextureView(Context context, AttributeSet attrs) { 51 | this(context, attrs, 0); 52 | } 53 | 54 | public GLTextureView(Context context, AttributeSet attrs, int defStyleAttr) { 55 | super(context, attrs, defStyleAttr); 56 | setSurfaceTextureListener(new SurfaceTextureListener() { 57 | @Override 58 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 59 | if (mRendererThread != null) { 60 | Log.e(TAG, "Renderer thread already launched."); 61 | return; 62 | } 63 | // do launch 64 | mRendererThread = new RendererThread("Renderer Thread", 65 | new WeakReference<>(GLTextureView.this)); 66 | mRendererThread.start(); 67 | // invoke renderer lifecycle sequence. 68 | if (mRenderer != null) { 69 | mRendererThread.handleRenderChanged(); 70 | } 71 | mRendererThread.handleSizeChanged(); 72 | if (mBufferTexture != null) { 73 | mRendererThread.handleTextureChanged(); 74 | } 75 | } 76 | 77 | @Override 78 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 79 | if (mRendererThread != null) { 80 | mRendererThread.handleSizeChanged(); 81 | } 82 | } 83 | 84 | @Override 85 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 86 | if (mRendererThread != null) { 87 | mRendererThread.quitSafely(); 88 | mRendererThread = null; 89 | } 90 | return true; 91 | } 92 | 93 | @Override 94 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 95 | 96 | } 97 | 98 | }); 99 | } 100 | 101 | /** 102 | * 设置渲染器 103 | */ 104 | public void setRenderer(@NonNull IPreviewerRenderer renderer) { 105 | if (mRenderer == renderer) { 106 | return; 107 | } 108 | this.mRenderer = renderer; 109 | if (mRendererThread != null) { 110 | mRendererThread.handleRenderChanged(); 111 | } 112 | } 113 | 114 | /** 115 | * 设置外部纹理数据 116 | */ 117 | public void setBufferTexture(@NonNull SurfaceTexture dataSource) { 118 | if (mBufferTexture == dataSource) { 119 | Log.i(TAG, "Data source not changed."); 120 | return; 121 | } 122 | // update data source 123 | this.mBufferTexture = dataSource; 124 | if (mRendererThread != null) { 125 | mRendererThread.handleTextureChanged(); 126 | } 127 | } 128 | 129 | private static class RendererThread extends HandlerThread 130 | implements SurfaceTexture.OnFrameAvailableListener, Handler.Callback { 131 | 132 | private static final int MSG_CREATE_EGL_CONTEXT = 0; 133 | private static final int MSG_RENDERER_CHANGED = 1; 134 | private static final int MSG_SURFACE_SIZE_CHANGED = 2; 135 | private static final int MSG_TEXTURE_CHANGED = 3; 136 | private static final int MSG_DRAW_FRAME = 4; 137 | 138 | private final WeakReference mWkRef; 139 | private final float[] mTextureMatrix = new float[16]; 140 | private final EglCore mEglCore = new EglCore(); 141 | private int mOESTextureId; 142 | private Handler mRendererHandler; 143 | 144 | private RendererThread(String name, WeakReference view) { 145 | super(name); 146 | mWkRef = view; 147 | } 148 | 149 | @Override 150 | public synchronized void start() { 151 | super.start(); 152 | mRendererHandler = new Handler(getLooper(), this); 153 | mRendererHandler.sendEmptyMessage(MSG_CREATE_EGL_CONTEXT); 154 | } 155 | 156 | @Override 157 | public boolean quitSafely() { 158 | release(); 159 | return super.quitSafely(); 160 | } 161 | 162 | @Override 163 | public boolean handleMessage(Message msg) { 164 | switch (msg.what) { 165 | // 创建 EGL 上下文 166 | case MSG_CREATE_EGL_CONTEXT: 167 | preformCreateEGL(); 168 | break; 169 | // 渲染器变更 170 | case MSG_RENDERER_CHANGED: 171 | performRenderChanged(); 172 | break; 173 | // 画布尺寸变更 174 | case MSG_SURFACE_SIZE_CHANGED: 175 | performSurfaceSizeChanged(); 176 | break; 177 | // 纹理变更 178 | case MSG_TEXTURE_CHANGED: 179 | performTextureChanged(); 180 | break; 181 | // 绘制数据帧 182 | case MSG_DRAW_FRAME: 183 | performDrawTexture(); 184 | break; 185 | default: 186 | break; 187 | } 188 | return false; 189 | } 190 | 191 | @Override 192 | public void onFrameAvailable(SurfaceTexture surfaceTexture) { 193 | if (mRendererHandler != null) { 194 | mRendererHandler.sendEmptyMessage(MSG_DRAW_FRAME); 195 | } 196 | } 197 | 198 | void handleRenderChanged() { 199 | if (mRendererHandler != null) { 200 | mRendererHandler.sendEmptyMessage(MSG_RENDERER_CHANGED); 201 | } 202 | } 203 | 204 | void handleSizeChanged() { 205 | if (mRendererHandler != null) { 206 | mRendererHandler.sendEmptyMessage(MSG_SURFACE_SIZE_CHANGED); 207 | } 208 | } 209 | 210 | void handleTextureChanged() { 211 | if (mRendererHandler != null) { 212 | mRendererHandler.sendEmptyMessage(MSG_TEXTURE_CHANGED); 213 | } 214 | } 215 | 216 | private void preformCreateEGL() { 217 | GLTextureView view = mWkRef.get(); 218 | if (view == null) { 219 | return; 220 | } 221 | // Create egl context 222 | mEglCore.initialize(view.getSurfaceTexture(), null); 223 | } 224 | 225 | private void performRenderChanged() { 226 | GLTextureView view = mWkRef.get(); 227 | if (view == null) { 228 | return; 229 | } 230 | view.mRenderer.onEglContextCreated(mEglCore.getContext()); 231 | } 232 | 233 | private void performSurfaceSizeChanged() { 234 | GLTextureView view = mWkRef.get(); 235 | if (view == null) { 236 | return; 237 | } 238 | ITextureRenderer renderer = view.mRenderer; 239 | renderer.onSurfaceSizeChanged(view.getWidth(), view.getHeight()); 240 | } 241 | 242 | private void performTextureChanged() { 243 | // 为这个纹理绑定 textureId 244 | GLTextureView view = mWkRef.get(); 245 | if (view == null) { 246 | return; 247 | } 248 | // 更新纹理数据 249 | SurfaceTexture bufferTexture = view.mBufferTexture; 250 | try { 251 | // 确保这个 Texture 没有绑定其他的纹理 id 252 | bufferTexture.detachFromGLContext(); 253 | } catch (Throwable e) { 254 | // ignore. 255 | } finally { 256 | /* 257 | CameraX 切换摄像头返回新的 SurfaceTexture 时, 会导致 SurfaceTexture 的 transform matrix 旋转角度改变, 从而引发跳闪 258 | 这里通过创建新的 textureId 解决 259 | */ 260 | // 创建纹理 261 | mOESTextureId = createOESTextureId(); 262 | // 绑定纹理 263 | bufferTexture.attachToGLContext(mOESTextureId); 264 | // 设置监听器 265 | bufferTexture.setOnFrameAvailableListener(this); 266 | } 267 | } 268 | 269 | private void performDrawTexture() { 270 | GLTextureView view = mWkRef.get(); 271 | if (view == null) { 272 | return; 273 | } 274 | // 设置当前的环境 275 | mEglCore.makeCurrent(); 276 | // 更新纹理数据 277 | SurfaceTexture bufferTexture = view.mBufferTexture; 278 | ITextureRenderer renderer = view.mRenderer; 279 | if (bufferTexture != null) { 280 | bufferTexture.updateTexImage(); 281 | bufferTexture.getTransformMatrix(mTextureMatrix); 282 | } 283 | // 执行渲染器的绘制 284 | if (renderer != null) { 285 | renderer.drawTexture(mOESTextureId, mTextureMatrix); 286 | } 287 | // 将 EGL 绘制的数据, 输出到 View 的 preview 中 288 | mEglCore.swapBuffers(); 289 | } 290 | 291 | private int createOESTextureId() { 292 | int[] tex = new int[1]; 293 | GLES20.glGenTextures(1, tex, 0); 294 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]); 295 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 296 | GL10.GL_TEXTURE_MIN_FILTER, (float) GL10.GL_NEAREST); 297 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 298 | GL10.GL_TEXTURE_MAG_FILTER, (float) GL10.GL_LINEAR); 299 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 300 | GL10.GL_TEXTURE_WRAP_S, (float) GL10.GL_CLAMP_TO_EDGE); 301 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 302 | GL10.GL_TEXTURE_WRAP_T, (float) GL10.GL_CLAMP_TO_EDGE); 303 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); 304 | return tex[0]; 305 | } 306 | 307 | private void release() { 308 | if (mRendererHandler != null) { 309 | mRendererHandler.removeMessages(MSG_CREATE_EGL_CONTEXT); 310 | mRendererHandler.removeMessages(MSG_SURFACE_SIZE_CHANGED); 311 | mRendererHandler.removeMessages(MSG_TEXTURE_CHANGED); 312 | mRendererHandler.removeMessages(MSG_DRAW_FRAME); 313 | } 314 | mEglCore.release(); 315 | } 316 | 317 | } 318 | 319 | } 320 | -------------------------------------------------------------------------------- /lib-scamera/src/main/device/com/sharry/lib/camera/Camera1Device.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.SurfaceTexture; 5 | import android.hardware.Camera; 6 | import android.util.Log; 7 | 8 | import androidx.collection.SparseArrayCompat; 9 | 10 | import java.util.List; 11 | import java.util.SortedSet; 12 | 13 | /** 14 | * Camera1 实现的相机引擎 15 | * 16 | * @author Sharry Contact me. 17 | * @version 1.0 18 | * @since 2019-04-24 19 | */ 20 | class Camera1Device extends AbsCameraDevice { 21 | 22 | private static final String TAG = Camera1Device.class.getSimpleName(); 23 | private static final SparseArrayCompat FLASH_MODES = new SparseArrayCompat<>(); 24 | 25 | static { 26 | FLASH_MODES.put(Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF); 27 | FLASH_MODES.put(Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON); 28 | FLASH_MODES.put(Constants.FLASH_TORCH, Camera.Parameters.FLASH_MODE_TORCH); 29 | FLASH_MODES.put(Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO); 30 | FLASH_MODES.put(Constants.FLASH_RED_EYE, Camera.Parameters.FLASH_MODE_RED_EYE); 31 | } 32 | 33 | private static final int MAGIC_TEXTURE_ID = 0; 34 | private static final int INVALID_CAMERA_ID = -1; 35 | 36 | private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo(); 37 | private final SizeMap mPreviewSizes = new SizeMap(); 38 | private final SizeMap mPictureSizes = new SizeMap(); 39 | private final SurfaceTexture mBufferTexture; 40 | 41 | private Camera mImpl; 42 | private Camera.Parameters mCameraParams; 43 | 44 | Camera1Device(CameraContext context, OnCameraReadyListener listener) { 45 | super(context, listener); 46 | mBufferTexture = new SurfaceTexture(MAGIC_TEXTURE_ID); 47 | } 48 | 49 | @Override 50 | public boolean isCameraOpened() { 51 | return mImpl != null; 52 | } 53 | 54 | @Override 55 | public void open() { 56 | // Stop preview first. 57 | close(); 58 | // 根据 Options 初始化相机 59 | startPreviewInternal(); 60 | } 61 | 62 | @Override 63 | public void close() { 64 | if (null != mImpl) { 65 | try { 66 | // 停止预览 67 | mImpl.stopPreview(); 68 | /* 69 | 移除回调, 否则会扔出: Camera is being used after Camera.release() was called 70 | */ 71 | mImpl.setPreviewCallback(null); 72 | mImpl.release(); 73 | mImpl = null; 74 | Log.i(TAG, "Camera release success."); 75 | } catch (Throwable e) { 76 | // ignore. 77 | } 78 | } 79 | } 80 | 81 | @Override 82 | public Bitmap takePicture() { 83 | close(); 84 | return null; 85 | } 86 | 87 | @Override 88 | public void notifyAutoFocusChanged() { 89 | // is previewing 90 | if (isCameraOpened()) { 91 | if (this.autoFocus == context.isAutoFocus()) { 92 | return; 93 | } 94 | this.autoFocus = context.isAutoFocus(); 95 | // resetMatrix params 96 | setAutoFocusInternal(autoFocus); 97 | mImpl.setParameters(mCameraParams); 98 | } 99 | // not previewing 100 | else { 101 | this.autoFocus = context.isAutoFocus(); 102 | } 103 | } 104 | 105 | @Override 106 | public void notifyFlashModeChanged() { 107 | // is previewing 108 | if (isCameraOpened()) { 109 | if (flashMode == context.getFlashMode()) { 110 | return; 111 | } 112 | // resetMatrix params 113 | if (setFlashInternal(context.getFlashMode())) { 114 | mImpl.setParameters(mCameraParams); 115 | } 116 | } 117 | // not previewing 118 | else { 119 | flashMode = context.getFlashMode(); 120 | } 121 | } 122 | 123 | /** 124 | * 开启预览真正的逻辑实现 125 | */ 126 | private void startPreviewInternal() { 127 | try { 128 | // 1. 打开相机 129 | int cameraId = chooseCamera(facing); 130 | mImpl = Camera.open(cameraId); 131 | // 2. 设置相机参数 132 | mCameraParams = mImpl.getParameters(); 133 | /* 134 | 3. 设置预览尺寸 135 | */ 136 | // 采集所有的预览尺寸 137 | mPreviewSizes.clear(); 138 | for (Camera.Size size : mCameraParams.getSupportedPreviewSizes()) { 139 | mPreviewSizes.add(new Size(size.width, size.height)); 140 | } 141 | // 获取用户期望的比例的集合 142 | SortedSet previewSizes = mPreviewSizes.sizes(aspectRatio); 143 | if (previewSizes == null) { 144 | // 用户期望的比例不存在, 获取默认比例 145 | previewSizes = mPreviewSizes.sizes(chooseDefaultAspectRatio()); 146 | } 147 | final Size previewSize = chooseOptimalPreviewSize(previewSizes); 148 | mCameraParams.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); 149 | 150 | /* 151 | 4. 设置拍照尺寸 152 | */ 153 | // 采集所有照片的尺寸 154 | mPictureSizes.clear(); 155 | for (Camera.Size size : mCameraParams.getSupportedPictureSizes()) { 156 | mPictureSizes.add(new Size(size.width, size.height)); 157 | } 158 | // 获取用户期望的比例集合 159 | SortedSet pictureSizes = mPictureSizes.sizes(aspectRatio); 160 | if (pictureSizes == null) { 161 | // 用户期望的尺寸不存在, 获取默认比例 162 | pictureSizes = mPreviewSizes.sizes(chooseDefaultAspectRatio()); 163 | } 164 | // 选择期望集合中, 尺寸最大的一个, 保证拍照后输出图像的清晰度 165 | Size pictureSize = pictureSizes.last(); 166 | mCameraParams.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); 167 | // 5. 设置拍摄后的图像输出的方向 168 | mCameraParams.setRotation(calcTakenPictureRotation(screenOrientationDegrees)); 169 | // 6. 处理自动对焦 170 | setAutoFocusInternal(autoFocus); 171 | // 7. 处理闪光灯变化 172 | setFlashInternal(flashMode); 173 | mImpl.setParameters(mCameraParams); 174 | // 8. 设置预览帧的图像的输出方向 175 | mImpl.setDisplayOrientation(calcPreviewFrameOrientation(screenOrientationDegrees)); 176 | // 9. 设置图像输出的画布 177 | mImpl.setPreviewTexture(mBufferTexture); 178 | // 10. 启动预览 179 | mImpl.startPreview(); 180 | // 6. 通知外界, Camera 数据准备好了 181 | listener.onCameraReady(mBufferTexture, previewSize, 0); 182 | Log.i(TAG, "Camera start preview success."); 183 | } catch (Throwable e) { 184 | Log.e(TAG, "Camera start preview failed.", e); 185 | close(); 186 | } 187 | } 188 | 189 | /** 190 | * 选择相机 id 191 | */ 192 | private int chooseCamera(int facing) { 193 | int cameraId = INVALID_CAMERA_ID; 194 | for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { 195 | Camera.getCameraInfo(i, mCameraInfo); 196 | if (mCameraInfo.facing == facing) { 197 | cameraId = i; 198 | break; 199 | } 200 | } 201 | return cameraId; 202 | } 203 | 204 | /** 205 | * 获取默认比例 206 | */ 207 | private AspectRatio chooseDefaultAspectRatio() { 208 | AspectRatio result = null; 209 | for (AspectRatio ratio : mPreviewSizes.ratios()) { 210 | result = ratio; 211 | if (AspectRatio.DEFAULT.equals(ratio)) { 212 | break; 213 | } 214 | } 215 | return result; 216 | } 217 | 218 | /** 219 | * 设置自动对焦 220 | *

221 | * it will modify {@link #mCameraParams}. 222 | */ 223 | private void setAutoFocusInternal(boolean autoFocus) { 224 | final List modes = mCameraParams.getSupportedFocusModes(); 225 | if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 226 | mCameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 227 | } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { 228 | mCameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); 229 | } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { 230 | mCameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); 231 | } else { 232 | mCameraParams.setFocusMode(modes.get(0)); 233 | } 234 | } 235 | 236 | /** 237 | * 设置闪光灯 238 | * 239 | * @return {@code true} if {@link #mCameraParams} was modified. 240 | */ 241 | private boolean setFlashInternal(int flash) { 242 | List modes = mCameraParams.getSupportedFlashModes(); 243 | String mode = FLASH_MODES.get(flash); 244 | if (modes != null && modes.contains(mode)) { 245 | mCameraParams.setFlashMode(mode); 246 | flashMode = flash; 247 | return true; 248 | } 249 | String currentMode = FLASH_MODES.get(flashMode); 250 | if (modes == null || !modes.contains(currentMode)) { 251 | mCameraParams.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 252 | flashMode = Constants.FLASH_OFF; 253 | return true; 254 | } 255 | return false; 256 | } 257 | 258 | /** 259 | * 选择最合适的预览尺寸 260 | */ 261 | private Size chooseOptimalPreviewSize(SortedSet sizes) { 262 | int desiredWidth; 263 | int desiredHeight; 264 | if (isLandscape(screenOrientationDegrees)) { 265 | desiredWidth = previewWidth; 266 | desiredHeight = previewHeight; 267 | } else { 268 | desiredWidth = previewHeight; 269 | desiredHeight = previewWidth; 270 | } 271 | Size result = null; 272 | for (Size size : sizes) { 273 | result = size; 274 | // Iterate from small to large 275 | if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { 276 | break; 277 | } 278 | } 279 | return result; 280 | } 281 | 282 | /** 283 | * Calculate camera rotate 284 | *

285 | * This calculation is applied to the output JPEG either via Exif Orientation tag 286 | * or by actually transforming the bitmap. (Determined by vendor camera API implementation) 287 | *

288 | * Note: This is not the same calculation as the display orientation 289 | * 290 | * @param screenOrientationDegrees Screen orientation in degrees 291 | * @return Number of degrees to rotate image in order for it to view correctly. 292 | */ 293 | private int calcTakenPictureRotation(int screenOrientationDegrees) { 294 | if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 295 | return (mCameraInfo.orientation + screenOrientationDegrees) % 360; 296 | } else { // back-facing 297 | final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0; 298 | return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360; 299 | } 300 | } 301 | 302 | /** 303 | * Calculate display orientation 304 | * https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int) 305 | *

306 | * This calculation is used for orienting the preview 307 | *

308 | * Note: This is not the same calculation as the camera rotate 309 | * 310 | * @param screenOrientationDegrees Screen orientation in degrees(anticlockwise) 311 | * @return Number of degrees required to rotate preview 312 | */ 313 | private int calcPreviewFrameOrientation(int screenOrientationDegrees) { 314 | int result; 315 | // front-facing 316 | if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 317 | result = (mCameraInfo.orientation + screenOrientationDegrees) % 360; 318 | // compensate the mirror 319 | result = (360 - result) % 360; 320 | } 321 | // back-facing 322 | else { 323 | result = (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360; 324 | } 325 | return result; 326 | } 327 | 328 | /** 329 | * Test if the supplied orientation is in landscape. 330 | * 331 | * @param screenOrientationDegrees Orientation in degrees (0,90,180,270) 332 | * @return True if in landscape, false if portrait 333 | */ 334 | private boolean isLandscape(int screenOrientationDegrees) { 335 | return (screenOrientationDegrees == Constants.LANDSCAPE_90 336 | || screenOrientationDegrees == Constants.LANDSCAPE_270); 337 | } 338 | 339 | } 340 | -------------------------------------------------------------------------------- /lib-scamera/src/main/api/com/sharry/lib/camera/SCameraView.java: -------------------------------------------------------------------------------- 1 | package com.sharry.lib.camera; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Bitmap; 7 | import android.graphics.SurfaceTexture; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.widget.FrameLayout; 11 | 12 | import androidx.annotation.IntDef; 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.lifecycle.LifecycleOwner; 16 | 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | 20 | /** 21 | * The facade handle device interaction with view. 22 | * 23 | * @author Sharry Contact me. 24 | * @version 1.0 25 | * @since 2019-08-05 26 | */ 27 | public class SCameraView extends FrameLayout implements 28 | ScreenOrientationDetector.OnDisplayChangedListener, ICameraDevice.OnCameraReadyListener { 29 | 30 | /** 31 | * The camera device faces the opposite direction as the device's screen. 32 | */ 33 | public static final int FACING_BACK = Constants.FACING_BACK; 34 | 35 | /** 36 | * The camera device faces the same direction as the device's screen. 37 | */ 38 | public static final int FACING_FRONT = Constants.FACING_FRONT; 39 | 40 | /** 41 | * Direction the camera faces relative to device screen. 42 | */ 43 | @IntDef({FACING_BACK, FACING_FRONT}) 44 | @Retention(RetentionPolicy.SOURCE) 45 | @interface Facing { 46 | } 47 | 48 | /** 49 | * Flash will not be fired. 50 | */ 51 | public static final int FLASH_OFF = Constants.FLASH_OFF; 52 | 53 | /** 54 | * Flash will always be fired during snapshot. 55 | */ 56 | public static final int FLASH_ON = Constants.FLASH_ON; 57 | 58 | /** 59 | * Constant emission of light during preview, auto-focus and snapshot. 60 | */ 61 | public static final int FLASH_TORCH = Constants.FLASH_TORCH; 62 | 63 | /** 64 | * Flash will be fired automatically when required. 65 | */ 66 | public static final int FLASH_AUTO = Constants.FLASH_AUTO; 67 | 68 | /** 69 | * Flash will be fired in red-eye reduction mode. 70 | */ 71 | public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE; 72 | 73 | /** 74 | * The mode for for the camera device's flash control 75 | */ 76 | @IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE}) 77 | @interface Flash { 78 | } 79 | 80 | /** 81 | * Control camera device 82 | */ 83 | private final ICameraDevice mDevice; 84 | 85 | /** 86 | * The context holder data 87 | */ 88 | private CameraContext mContext; 89 | 90 | /** 91 | * Control preview. 92 | */ 93 | private final IPreviewer mPreviewer; 94 | 95 | /** 96 | * Control display rotate 97 | */ 98 | private final ScreenOrientationDetector mScreenOrientationDetector; 99 | 100 | public SCameraView(@NonNull Context context) { 101 | this(context, null); 102 | } 103 | 104 | public SCameraView(@NonNull Context context, @Nullable AttributeSet attrs) { 105 | this(context, attrs, 0); 106 | } 107 | 108 | public SCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 109 | super(context, attrs, defStyleAttr); 110 | this.mContext = new CameraContext(context); 111 | this.mPreviewer = new Previewer(context, this); 112 | this.mScreenOrientationDetector = new ScreenOrientationDetector(context, this); 113 | if (Build.VERSION.SDK_INT > 21 && context instanceof LifecycleOwner) { 114 | this.mDevice = new CameraXDevice(mContext, this); 115 | // this.mDevice = new Camera1Device(mContext, this); 116 | } else { 117 | this.mDevice = new Camera1Device(mContext, this); 118 | } 119 | // Attributes 120 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SCameraView, defStyleAttr, 121 | R.style.Widget_CameraView); 122 | // set adjust view bounds 123 | setAdjustViewBounds(a.getBoolean(R.styleable.SCameraView_android_adjustViewBounds, false)); 124 | // set facing 125 | setFacing(a.getInt(R.styleable.SCameraView_facing, FACING_BACK)); 126 | // set aspect ratio 127 | String aspectRatio = a.getString(R.styleable.SCameraView_aspectRatio); 128 | setAspectRatio(aspectRatio != null ? AspectRatio.parse(aspectRatio) : AspectRatio.DEFAULT); 129 | // set auto focus 130 | setAutoFocus(a.getBoolean(R.styleable.SCameraView_autoFocus, true)); 131 | // set flash mode 132 | setFlash(a.getInt(R.styleable.SCameraView_flash, Constants.FLASH_AUTO)); 133 | a.recycle(); 134 | } 135 | 136 | @Override 137 | protected void onAttachedToWindow() { 138 | super.onAttachedToWindow(); 139 | if (!isInEditMode()) { 140 | mScreenOrientationDetector.enable(getDisplay()); 141 | } 142 | } 143 | 144 | @Override 145 | protected void onDetachedFromWindow() { 146 | if (!isInEditMode()) { 147 | mScreenOrientationDetector.disable(); 148 | } 149 | super.onDetachedFromWindow(); 150 | } 151 | 152 | @Override 153 | public void onDisplayOrientationChanged(int displayOrientation) { 154 | mContext.setScreenOrientationDegrees(displayOrientation); 155 | mDevice.notifyScreenOrientationChanged(); 156 | } 157 | 158 | @Override 159 | public void onCameraReady(@NonNull SurfaceTexture cameraTexture, @NonNull Size textureSize, int needDegrees) { 160 | mPreviewer.setDataSource(cameraTexture); 161 | mPreviewer.getRenderer().resetMatrix(); 162 | mPreviewer.getRenderer().rotate(needDegrees); 163 | mPreviewer.getRenderer().centerCrop( 164 | mScreenOrientationDetector.isLandscape(), 165 | mPreviewer.getSize(), 166 | textureSize 167 | ); 168 | mPreviewer.getRenderer().transformMatrix(); 169 | } 170 | 171 | @Override 172 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 173 | if (isInEditMode()) { 174 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 175 | return; 176 | } 177 | // Handle android:adjustViewBounds 178 | if (mContext.isAdjustViewBounds()) { 179 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 180 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 181 | // 宽为精确测量 182 | if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { 183 | // 根据比例计算高 184 | final AspectRatio ratio = getAspectRatio(); 185 | int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat()); 186 | if (heightMode == MeasureSpec.AT_MOST) { 187 | height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); 188 | } 189 | super.onMeasure(widthMeasureSpec, 190 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 191 | } 192 | // 高为精确测量 193 | else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { 194 | // 根据比例计算宽 195 | final AspectRatio ratio = getAspectRatio(); 196 | int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat()); 197 | if (widthMode == MeasureSpec.AT_MOST) { 198 | width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec)); 199 | } 200 | super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 201 | heightMeasureSpec); 202 | } else { 203 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 204 | } 205 | } else { 206 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 207 | return; 208 | } 209 | // Measure the PreviewView 210 | int width = getMeasuredWidth(); 211 | int height = getMeasuredHeight(); 212 | // 若为竖屏, 则颠倒一下比例, 方便计算 213 | AspectRatio ratio = getAspectRatio(); 214 | if (!mScreenOrientationDetector.isLandscape()) { 215 | ratio = ratio.inverse(); 216 | } 217 | if (height < width * ratio.getY() / ratio.getX()) { 218 | mPreviewer.getView().measure( 219 | MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 220 | MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(), 221 | MeasureSpec.EXACTLY)); 222 | } else { 223 | mPreviewer.getView().measure( 224 | MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(), 225 | MeasureSpec.EXACTLY), 226 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 227 | } 228 | } 229 | 230 | @Override 231 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 232 | super.onLayout(changed, left, top, right, bottom); 233 | mContext.setDesiredSize(mPreviewer.getSize()); 234 | mDevice.notifyDesiredSizeChanged(); 235 | } 236 | 237 | /** 238 | * Open a camera device and start showing camera preview. This is typically called from 239 | * {@link Activity#onResume}. 240 | */ 241 | public void startPreview() { 242 | post(new Runnable() { 243 | @Override 244 | public void run() { 245 | mDevice.open(); 246 | } 247 | }); 248 | } 249 | 250 | /** 251 | * Stop camera preview and close the device. This is typically called from 252 | * {@link Activity#onPause} 253 | */ 254 | public void stopPreview() { 255 | mDevice.close(); 256 | } 257 | 258 | /** 259 | * 获取照片 260 | */ 261 | @Nullable 262 | public Bitmap takePicture() { 263 | return mPreviewer.getBitmap(); 264 | } 265 | 266 | /** 267 | * Chooses camera by the direction it faces. 268 | * 269 | * @param facing The camera facing. Must be either {@link #FACING_BACK} or 270 | * {@link #FACING_FRONT}. 271 | */ 272 | public void setFacing(@Facing int facing) { 273 | mContext.setFacing(facing); 274 | mDevice.notifyFacingChanged(); 275 | } 276 | 277 | /** 278 | * Sets the aspect ratio of camera. 279 | * 280 | * @param ratio The {@link AspectRatio} to be set. 281 | */ 282 | public void setAspectRatio(@NonNull AspectRatio ratio) { 283 | if (mContext.getAspectRatio().equals(ratio)) { 284 | return; 285 | } 286 | mContext.setAspectRatio(ratio); 287 | mDevice.notifyAspectRatioChanged(); 288 | requestLayout(); 289 | } 290 | 291 | /** 292 | * Enables or disables the continuous auto-focus mode. When the current camera doesn't support 293 | * auto-focus, calling this method will be ignored. 294 | * 295 | * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to 296 | * disable it. 297 | */ 298 | public void setAutoFocus(boolean autoFocus) { 299 | mContext.setAutoFocus(autoFocus); 300 | mDevice.notifyAutoFocusChanged(); 301 | } 302 | 303 | /** 304 | * Sets the flash mode. 305 | * 306 | * @param flash The desired flash mode. 307 | */ 308 | public void setFlash(@Flash int flash) { 309 | mContext.setFlashMode(flash); 310 | mDevice.notifyFlashModeChanged(); 311 | } 312 | 313 | /** 314 | * @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to 315 | * preserve the aspect ratio of camera. 316 | */ 317 | public void setAdjustViewBounds(boolean adjustViewBounds) { 318 | if (mContext.isAdjustViewBounds() != adjustViewBounds) { 319 | mContext.setAdjustViewBounds(adjustViewBounds); 320 | requestLayout(); 321 | } 322 | } 323 | 324 | /** 325 | * Gets the direction that the current camera faces. 326 | * 327 | * @return The camera facing. 328 | */ 329 | public int getFacing() { 330 | return mContext.getFacing(); 331 | } 332 | 333 | /** 334 | * Gets the current aspect ratio of camera. 335 | * 336 | * @return The current {@link AspectRatio}. Default is 4:3. 337 | */ 338 | @NonNull 339 | public AspectRatio getAspectRatio() { 340 | return mContext.getAspectRatio(); 341 | } 342 | 343 | /** 344 | * Returns whether the continuous auto-focus mode is enabled. 345 | * 346 | * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is 347 | * disabled, or if it is not supported by the current camera. 348 | */ 349 | public boolean getAutoFocus() { 350 | return mContext.isAutoFocus(); 351 | } 352 | 353 | /** 354 | * Gets the current flash mode. 355 | * 356 | * @return The current flash mode. 357 | */ 358 | public int getFlash() { 359 | //noinspection WrongConstant 360 | return mContext.getFlashMode(); 361 | } 362 | 363 | /** 364 | * Returns whether the adjustViewBounds is enabled. 365 | * 366 | * @return {@code true} if the adjustViewBounds is enabled. {@code false} if it is disabled 367 | */ 368 | public boolean getAdjustViewBounds() { 369 | return mContext.getAdjustViewBounds(); 370 | } 371 | 372 | /** 373 | * Gets the previewer. 374 | * 375 | * @return The render view. 376 | */ 377 | public IPreviewer getPreviewer() { 378 | return mPreviewer; 379 | } 380 | 381 | } 382 | --------------------------------------------------------------------------------