├── 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 |
--------------------------------------------------------------------------------