Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Mattia Iavarone | b6947c6267 | 5 years ago |
@ -1,14 +1,10 @@ |
|||||||
### Before you go |
### Before you go |
||||||
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first so that |
Unless this is a simple fix (typos, bugs with obvious solution), please open an issue first. |
||||||
we can discuss the best approach to address the problem. Without a reference issue and discussion, |
If the edited files were covered by tests, updated tests are required for merging. Please look into the tests folders and make sure you cover new code. |
||||||
unfortunately, this PR will likely be ignored. |
|
||||||
|
|
||||||
If the edited files were covered by tests, updated tests are required for merging. |
- Fixes ... (*issue number*) |
||||||
Please look into the tests folders and make sure you cover new code. |
|
||||||
|
|
||||||
- Fixes: ... (*issue number*) |
|
||||||
- Tests: ... (*yes/no*) |
- Tests: ... (*yes/no*) |
||||||
- Docs updated: ... (*yes/no*) |
- Docs updated: ... (*yes/no*) |
||||||
|
|
||||||
### Solution |
### Solution |
||||||
If applicable, describe briefly how the issue was addressed. |
If applicable, briefly describe how the issue was addressed. |
||||||
|
@ -1,25 +0,0 @@ |
|||||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions |
|
||||||
# Renaming ? Change the README badge. |
|
||||||
name: Snapshot |
|
||||||
on: |
|
||||||
push: |
|
||||||
branches: |
|
||||||
- main |
|
||||||
jobs: |
|
||||||
SNAPSHOT: |
|
||||||
name: Publish Snapshot |
|
||||||
runs-on: ubuntu-latest |
|
||||||
env: |
|
||||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }} |
|
||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} |
|
||||||
SONATYPE_USER: ${{ secrets.SONATYPE_USER }} |
|
||||||
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Publish sonatype snapshot |
|
||||||
run: ./gradlew publishToSonatypeSnapshot |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="runAndroidTests" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value="runAndroidTests" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" value="" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -1,23 +0,0 @@ |
|||||||
<component name="ProjectRunConfigurationManager"> |
|
||||||
<configuration default="false" name="runUnitTests" type="GradleRunConfiguration" factoryName="Gradle"> |
|
||||||
<ExternalSystemSettings> |
|
||||||
<option name="executionName" /> |
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$/cameraview" /> |
|
||||||
<option name="externalSystemIdString" value="GRADLE" /> |
|
||||||
<option name="scriptParameters" value="" /> |
|
||||||
<option name="taskDescriptions"> |
|
||||||
<list /> |
|
||||||
</option> |
|
||||||
<option name="taskNames"> |
|
||||||
<list> |
|
||||||
<option value="runUnitTests" /> |
|
||||||
</list> |
|
||||||
</option> |
|
||||||
<option name="vmOptions" value="" /> |
|
||||||
</ExternalSystemSettings> |
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess> |
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> |
|
||||||
<DebugAllEnabled>false</DebugAllEnabled> |
|
||||||
<method v="2" /> |
|
||||||
</configuration> |
|
||||||
</component> |
|
@ -0,0 +1,29 @@ |
|||||||
|
buildscript { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
google() |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
classpath 'com.android.tools.build:gradle:3.5.3' |
||||||
|
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' |
||||||
|
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
allprojects { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
google() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ext { |
||||||
|
compileSdkVersion = 29 |
||||||
|
minSdkVersion = 15 |
||||||
|
targetSdkVersion = 29 |
||||||
|
} |
||||||
|
|
||||||
|
task clean(type: Delete) { |
||||||
|
delete rootProject.buildDir |
||||||
|
} |
@ -1,30 +0,0 @@ |
|||||||
|
|
||||||
buildscript { |
|
||||||
|
|
||||||
extra["minSdkVersion"] = 15 |
|
||||||
extra["compileSdkVersion"] = 31 |
|
||||||
extra["targetSdkVersion"] = 31 |
|
||||||
|
|
||||||
repositories { |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
classpath("com.android.tools.build:gradle:7.0.3") |
|
||||||
classpath("io.deepmedia.tools:publisher:0.6.0") |
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
allprojects { |
|
||||||
repositories { |
|
||||||
google() |
|
||||||
mavenCentral() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
tasks.register("clean", Delete::class) { |
|
||||||
delete(buildDir) |
|
||||||
} |
|
@ -0,0 +1,270 @@ |
|||||||
|
apply plugin: 'com.android.library' |
||||||
|
apply plugin: 'com.github.dcendents.android-maven' |
||||||
|
apply plugin: 'com.jfrog.bintray' |
||||||
|
|
||||||
|
// Required by bintray |
||||||
|
version = '2.6.0' |
||||||
|
group = 'com.otaliastudios' |
||||||
|
|
||||||
|
//region android dependencies |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion rootProject.ext.compileSdkVersion |
||||||
|
|
||||||
|
defaultConfig { |
||||||
|
minSdkVersion rootProject.ext.minSdkVersion |
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion |
||||||
|
versionCode 1 |
||||||
|
versionName project.version |
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||||
|
testInstrumentationRunnerArgument "filter", "" + |
||||||
|
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," + |
||||||
|
"com.otaliastudios.cameraview.tools.SdkIncludeFilter" |
||||||
|
} |
||||||
|
|
||||||
|
buildTypes { |
||||||
|
debug { |
||||||
|
testCoverageEnabled true |
||||||
|
} |
||||||
|
|
||||||
|
release { |
||||||
|
minifyEnabled false |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
testImplementation 'junit:junit:4.12' |
||||||
|
testImplementation 'org.mockito:mockito-inline:2.28.2' |
||||||
|
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.2.0' |
||||||
|
androidTestImplementation 'androidx.test:rules:1.2.0' |
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1' |
||||||
|
androidTestImplementation 'org.mockito:mockito-android:2.28.2' |
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
||||||
|
|
||||||
|
api 'androidx.exifinterface:exifinterface:1.1.0' |
||||||
|
api 'androidx.lifecycle:lifecycle-common:2.1.0' |
||||||
|
api 'com.google.android.gms:play-services-tasks:17.0.0' |
||||||
|
implementation 'androidx.annotation:annotation:1.1.0' |
||||||
|
} |
||||||
|
|
||||||
|
//endregion |
||||||
|
|
||||||
|
//region bintray |
||||||
|
|
||||||
|
// install is a task defined by the Gradle Maven plugin, which is used to |
||||||
|
// publish a maven repo to a local repository. (we actually use the android version of the plugin, |
||||||
|
// com.github.dcendents.android-maven, to support AARs) |
||||||
|
// https://docs.gradle.org/current/userguide/maven_plugin.html#sec:maven_tasks |
||||||
|
install { |
||||||
|
// The repositories property is common to all tasks of type Upload and returns the repositories |
||||||
|
// into which we will upload data. https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Upload.html#org.gradle.api.tasks.Upload:repositories |
||||||
|
// It returns a RepositoryHandler: https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html |
||||||
|
repositories { |
||||||
|
// The maven plugin adds a mavenInstaller property to the RepositoryHandler which can be used to |
||||||
|
// add and configure a local maven repository cache. |
||||||
|
// https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html#N11785 |
||||||
|
mavenInstaller { |
||||||
|
// The object here extends PomFilterContainer so we can configure the pom file here. |
||||||
|
// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/PomFilterContainer.html#pom-groovy.lang.Closure- |
||||||
|
pom { |
||||||
|
// Now we are inside a MavenPom object that can be configured. We get the project and configure. |
||||||
|
// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/maven/MavenPom.html |
||||||
|
project { |
||||||
|
name 'CameraView' |
||||||
|
description 'A well documented, high-level Android interface that makes capturing pictures ' + |
||||||
|
'and videos easy, addressing most of the common issues and needs.' |
||||||
|
url 'https://github.com/natario1/CameraView' |
||||||
|
packaging 'aar' |
||||||
|
groupId project.group |
||||||
|
artifactId 'cameraview' |
||||||
|
version project.version |
||||||
|
licenses { |
||||||
|
license { |
||||||
|
name 'The Apache Software License, Version 2.0' |
||||||
|
url 'http://www.apache.org/licenses/LICENSE-2.0.txt' |
||||||
|
} |
||||||
|
} |
||||||
|
scm { |
||||||
|
connection 'https://github.com/natario1/CameraView.git' |
||||||
|
developerConnection 'https://github.com/natario1/CameraView.git' |
||||||
|
url 'https://github.com/natario1/CameraView' |
||||||
|
|
||||||
|
} |
||||||
|
developers { |
||||||
|
developer { |
||||||
|
id = 'natario' |
||||||
|
name 'Mattia Iavarone' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
def bintrayUser |
||||||
|
def bintrayKey |
||||||
|
def isCI = System.getenv("TRAVIS") |
||||||
|
if (isCI) { |
||||||
|
bintrayUser = System.getenv("BINTRAY_USER") |
||||||
|
bintrayKey = System.getenv("BINTRAY_KEY") |
||||||
|
} else { |
||||||
|
Properties props = new Properties() |
||||||
|
props.load(project.rootProject.file('local.properties').newDataInputStream()) |
||||||
|
bintrayUser = props.getProperty('bintray.user') |
||||||
|
bintrayKey = props.get('bintray.key') |
||||||
|
} |
||||||
|
|
||||||
|
bintray { |
||||||
|
// https://github.com/bintray/gradle-bintray-plugin |
||||||
|
user = bintrayUser |
||||||
|
key = bintrayKey |
||||||
|
configurations = ['archives'] |
||||||
|
pkg { |
||||||
|
repo = 'android' |
||||||
|
name = 'CameraView' |
||||||
|
licenses = ['Apache-2.0'] |
||||||
|
vcsUrl = 'https://github.com/natario1/CameraView.git' |
||||||
|
publish = true |
||||||
|
override = true |
||||||
|
version { |
||||||
|
name = project.version |
||||||
|
desc = 'CameraView v. '+project.version |
||||||
|
released = new Date() |
||||||
|
vcsTag = 'v'+project.version |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//endregion |
||||||
|
|
||||||
|
//region javadoc and sources |
||||||
|
|
||||||
|
// From official sample https://github.com/bintray/bintray-examples/blob/master/gradle-aar-example/build.gradle |
||||||
|
task sourcesJar(type: Jar) { |
||||||
|
classifier = 'sources' |
||||||
|
from android.sourceSets.main.java.sourceFiles |
||||||
|
} |
||||||
|
|
||||||
|
task javadoc(type: Javadoc) { |
||||||
|
source = android.sourceSets.main.java.srcDirs |
||||||
|
classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) |
||||||
|
classpath += project.files("${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar") |
||||||
|
project.android.libraryVariants.all { variant -> |
||||||
|
if (variant.name == 'release') { |
||||||
|
classpath += files(variant.javaCompile.classpath) |
||||||
|
} |
||||||
|
} |
||||||
|
exclude '**/BuildConfig.java' |
||||||
|
exclude '**/R.java' |
||||||
|
// This excludes our internal folder, which is nice, but also creates |
||||||
|
// errors anytime we reference excluded classes in public classes javadocs, |
||||||
|
// which is unfortunate. There might be a fix but I don't know any. |
||||||
|
// exclude '**/internal/**' |
||||||
|
} |
||||||
|
|
||||||
|
task javadocJar(type: Jar, dependsOn: javadoc) { |
||||||
|
classifier = 'javadoc' |
||||||
|
from javadoc.destinationDir |
||||||
|
} |
||||||
|
|
||||||
|
artifacts { |
||||||
|
archives javadocJar |
||||||
|
archives sourcesJar |
||||||
|
} |
||||||
|
|
||||||
|
//endregion |
||||||
|
|
||||||
|
//region code coverage |
||||||
|
|
||||||
|
// 1. running androidTests with connectedCheck will generate an .ec file |
||||||
|
// in build/outputs/code-coverage/connected, plus the XML result in |
||||||
|
// in build/reports/coverage/debug/report.xml . |
||||||
|
|
||||||
|
// 2. running unit tests with testDebugUnitTest will just generate the .exec file. |
||||||
|
// The JacocoReport task from the jacoco plugin can create the XML report out of it. |
||||||
|
|
||||||
|
// to have a unified report, we just pass both the .exec and the .ec file |
||||||
|
// to the jacoco task, so we get a unified XML report with total coverage. |
||||||
|
// Reference: https://medium.com/@rafael_toledo/setting-up-an-unified-coverage-report-in-android-with-jacoco-robolectric-and-espresso-ffe239aaf3fa |
||||||
|
|
||||||
|
apply plugin: 'jacoco' |
||||||
|
|
||||||
|
def reportsDirectory = "$buildDir/reports/" |
||||||
|
jacoco { |
||||||
|
toolVersion = "0.8.1" |
||||||
|
reportsDir = file(reportsDirectory) |
||||||
|
} |
||||||
|
|
||||||
|
task createCoverageReports() { |
||||||
|
dependsOn "testDebugUnitTest" |
||||||
|
dependsOn "connectedCheck" |
||||||
|
} |
||||||
|
|
||||||
|
task mergeCoverageReports(type: JacocoReport) { |
||||||
|
// Let this be called without running the tests. |
||||||
|
// dependsOn "createCoverageReports" |
||||||
|
// However, ensure we have the compiled .class files. |
||||||
|
dependsOn "compileDebugSources" |
||||||
|
|
||||||
|
// Merge unit tests and android tests data |
||||||
|
executionData = fileTree(dir: "$buildDir", includes: [ |
||||||
|
"jacoco/testDebugUnitTest.exec", // Unit tests |
||||||
|
"outputs/code_coverage/debugAndroidTest/connected/*coverage.ec" // Android tests |
||||||
|
]) |
||||||
|
|
||||||
|
// Sources |
||||||
|
sourceDirectories = files(android.sourceSets.main.java.sourceFiles) |
||||||
|
additionalSourceDirs = files([ // Add BuildConfig and R. |
||||||
|
"$buildDir/generated/source/buildConfig/debug", |
||||||
|
"$buildDir/generated/source/r/debug" |
||||||
|
]) |
||||||
|
|
||||||
|
// Classes (.class files) |
||||||
|
// Not everything in the filter relates to CameraView, |
||||||
|
// but let's keep a generic filter |
||||||
|
def classDir = "$buildDir/intermediates/javac/debug" |
||||||
|
def classFilter = [ |
||||||
|
'**/R.class', |
||||||
|
'**/R$*.class', |
||||||
|
'**/BuildConfig.*', |
||||||
|
'**/Manifest*.*', |
||||||
|
'android/**', |
||||||
|
'androidx/**', |
||||||
|
'com/google/**', |
||||||
|
'**/*$ViewInjector*.*', |
||||||
|
'**/Dagger*Component.class', |
||||||
|
'**/Dagger*Component$Builder.class', |
||||||
|
'**/*Module_*Factory.class', |
||||||
|
] |
||||||
|
if (isCI) { |
||||||
|
// All these classes are tested by the integration tests that we are not able to |
||||||
|
// run on the CI emulator. |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/CameraEngine**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/Camera1Engine**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/Camera2Engine**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/action/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/lock/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/engine/meter/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/picture/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/video/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/orchestrator/**.*') |
||||||
|
// classFilter.add('**/com/otaliastudios/cameraview/video/encoding/**.*') |
||||||
|
} |
||||||
|
// We don't test OpenGL filters. |
||||||
|
classFilter.add('**/com/otaliastudios/cameraview/filters/**.*') |
||||||
|
|
||||||
|
classDirectories = fileTree(dir: classDir, excludes: classFilter); |
||||||
|
|
||||||
|
reports.html.enabled = true |
||||||
|
reports.xml.enabled = true |
||||||
|
reports.xml.destination file("$reportsDirectory/mergedCoverageReport/report.xml") |
||||||
|
} |
||||||
|
|
||||||
|
//endregion |
||||||
|
|
||||||
|
// To deploy ./gradlew bintrayUpload |
||||||
|
|
@ -1,137 +0,0 @@ |
|||||||
import io.deepmedia.tools.publisher.common.License |
|
||||||
import io.deepmedia.tools.publisher.common.Release |
|
||||||
import io.deepmedia.tools.publisher.common.GithubScm |
|
||||||
|
|
||||||
plugins { |
|
||||||
id("com.android.library") |
|
||||||
id("kotlin-android") |
|
||||||
id("io.deepmedia.tools.publisher") |
|
||||||
id("jacoco") |
|
||||||
} |
|
||||||
|
|
||||||
android { |
|
||||||
compileSdk = property("compileSdkVersion") as Int |
|
||||||
defaultConfig { |
|
||||||
minSdk = property("minSdkVersion") as Int |
|
||||||
targetSdk = property("targetSdkVersion") as Int |
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
|
||||||
testInstrumentationRunnerArguments["filter"] = "" + |
|
||||||
"com.otaliastudios.cameraview.tools.SdkExcludeFilter," + |
|
||||||
"com.otaliastudios.cameraview.tools.SdkIncludeFilter" |
|
||||||
} |
|
||||||
buildTypes["debug"].isTestCoverageEnabled = true |
|
||||||
buildTypes["release"].isMinifyEnabled = false |
|
||||||
} |
|
||||||
|
|
||||||
dependencies { |
|
||||||
testImplementation("junit:junit:4.13.1") |
|
||||||
testImplementation("org.mockito:mockito-inline:2.28.2") |
|
||||||
|
|
||||||
androidTestImplementation("androidx.test:runner:1.4.0") |
|
||||||
androidTestImplementation("androidx.test:rules:1.4.0") |
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.3") |
|
||||||
androidTestImplementation("org.mockito:mockito-android:2.28.2") |
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") |
|
||||||
|
|
||||||
api("androidx.exifinterface:exifinterface:1.3.3") |
|
||||||
api("androidx.lifecycle:lifecycle-common:2.3.1") |
|
||||||
api("com.google.android.gms:play-services-tasks:17.2.1") |
|
||||||
implementation("androidx.annotation:annotation:1.2.0") |
|
||||||
implementation("com.otaliastudios.opengl:egloo:0.6.1") |
|
||||||
} |
|
||||||
|
|
||||||
// Publishing |
|
||||||
|
|
||||||
publisher { |
|
||||||
project.description = "A well documented, high-level Android interface that makes capturing " + |
|
||||||
"pictures and videos easy, addressing all of the common issues and needs. " + |
|
||||||
"Real-time filters, gestures, watermarks, frame processing, RAW, output of any size." |
|
||||||
project.artifact = "cameraview" |
|
||||||
project.group = "com.otaliastudios" |
|
||||||
project.url = "https://github.com/natario1/CameraView" |
|
||||||
project.scm = GithubScm("natario1", "CameraView") |
|
||||||
project.addLicense(License.APACHE_2_0) |
|
||||||
project.addDeveloper("natario1", "mat.iavarone@gmail.com") |
|
||||||
release.sources = Release.SOURCES_AUTO |
|
||||||
release.docs = Release.DOCS_AUTO |
|
||||||
release.version = "2.7.2" |
|
||||||
|
|
||||||
directory() |
|
||||||
|
|
||||||
sonatype { |
|
||||||
auth.user = "SONATYPE_USER" |
|
||||||
auth.password = "SONATYPE_PASSWORD" |
|
||||||
signing.key = "SIGNING_KEY" |
|
||||||
signing.password = "SIGNING_PASSWORD" |
|
||||||
} |
|
||||||
|
|
||||||
sonatype("snapshot") { |
|
||||||
repository = io.deepmedia.tools.publisher.sonatype.Sonatype.OSSRH_SNAPSHOT_1 |
|
||||||
release.version = "latest-SNAPSHOT" |
|
||||||
auth.user = "SONATYPE_USER" |
|
||||||
auth.password = "SONATYPE_PASSWORD" |
|
||||||
signing.key = "SIGNING_KEY" |
|
||||||
signing.password = "SIGNING_PASSWORD" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Code Coverage |
|
||||||
val buildDir = project.buildDir.absolutePath |
|
||||||
val coverageInputDir = "$buildDir/coverage_input" // changing? change github workflow |
|
||||||
val coverageOutputDir = "$buildDir/coverage_output" // changing? change github workflow |
|
||||||
|
|
||||||
// Run unit tests, with coverage enabled in the android { } configuration. |
|
||||||
// Output will be an .exec file in build/jacoco. |
|
||||||
tasks.register("runUnitTests") { // changing name? change github workflow |
|
||||||
dependsOn("testDebugUnitTest") |
|
||||||
doLast { |
|
||||||
copy { |
|
||||||
from("$buildDir/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec") |
|
||||||
into("$coverageInputDir/unit_tests") // changing? change github workflow |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Run android tests with coverage. |
|
||||||
tasks.register("runAndroidTests") { // changing name? change github workflow |
|
||||||
dependsOn("connectedDebugAndroidTest") |
|
||||||
doLast { |
|
||||||
copy { |
|
||||||
from("$buildDir/outputs/code_coverage/debugAndroidTest/connected") |
|
||||||
include("*coverage.ec") |
|
||||||
into("$coverageInputDir/android_tests") // changing? change github workflow |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Merge the two with a jacoco task. |
|
||||||
jacoco { toolVersion = "0.8.5" } |
|
||||||
tasks.register("computeCoverage", JacocoReport::class) { |
|
||||||
dependsOn("compileDebugSources") // Compile sources, needed below |
|
||||||
executionData.from(fileTree(coverageInputDir)) |
|
||||||
sourceDirectories.from(android.sourceSets["main"].java.srcDirs) |
|
||||||
additionalSourceDirs.from("$buildDir/generated/source/buildConfig/debug") |
|
||||||
additionalSourceDirs.from("$buildDir/generated/source/r/debug") |
|
||||||
classDirectories.from(fileTree("$buildDir/intermediates/javac/debug") { |
|
||||||
// Not everything here is relevant for CameraView, but let's keep it generic |
|
||||||
exclude( |
|
||||||
"**/R.class", |
|
||||||
"**/R$*.class", |
|
||||||
"**/BuildConfig.*", |
|
||||||
"**/Manifest*.*", |
|
||||||
"android/**", |
|
||||||
"androidx/**", |
|
||||||
"com/google/**", |
|
||||||
"**/*\$ViewInjector*.*", |
|
||||||
"**/Dagger*Component.class", |
|
||||||
"**/Dagger*Component\$Builder.class", |
|
||||||
"**/*Module_*Factory.class", |
|
||||||
// We don"t test OpenGL filters. |
|
||||||
"**/com/otaliastudios/cameraview/filters/**.*" |
|
||||||
) |
|
||||||
}) |
|
||||||
reports.html.required.set(true) |
|
||||||
reports.xml.required.set(true) |
|
||||||
reports.html.outputLocation.set(file("$coverageOutputDir/html")) |
|
||||||
reports.xml.outputLocation.set(file("$coverageOutputDir/xml/report.xml")) |
|
||||||
} |
|
@ -1,10 +1,9 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
|
|
||||||
import android.graphics.Rect; |
import android.graphics.Rect; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
import com.otaliastudios.cameraview.BaseTest; |
||||||
import com.otaliastudios.cameraview.internal.CropHelper; |
|
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
import com.otaliastudios.cameraview.size.AspectRatio; |
||||||
import com.otaliastudios.cameraview.size.Size; |
import com.otaliastudios.cameraview.size.Size; |
||||||
|
|
@ -1,10 +1,9 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
|
|
||||||
import com.google.android.gms.tasks.Task; |
import com.google.android.gms.tasks.Task; |
||||||
import com.google.android.gms.tasks.Tasks; |
import com.google.android.gms.tasks.Tasks; |
||||||
import com.otaliastudios.cameraview.BaseTest; |
import com.otaliastudios.cameraview.BaseTest; |
||||||
import com.otaliastudios.cameraview.internal.WorkerHandler; |
|
||||||
import com.otaliastudios.cameraview.tools.Op; |
import com.otaliastudios.cameraview.tools.Op; |
||||||
|
|
||||||
import androidx.annotation.NonNull; |
import androidx.annotation.NonNull; |
@ -1,63 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.controls; |
|
||||||
|
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraView; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constants for selecting the encoder of audio recordings. |
|
||||||
* https://developer.android.com/guide/topics/media/media-formats.html#audio-formats
|
|
||||||
* |
|
||||||
* @see CameraView#setAudioCodec(AudioCodec) |
|
||||||
*/ |
|
||||||
public enum AudioCodec implements Control { |
|
||||||
|
|
||||||
/** |
|
||||||
* Let the device choose its codec. |
|
||||||
*/ |
|
||||||
DEVICE_DEFAULT(0), |
|
||||||
|
|
||||||
/** |
|
||||||
* The AAC codec. |
|
||||||
*/ |
|
||||||
AAC(1), |
|
||||||
|
|
||||||
/** |
|
||||||
* The HE_AAC codec. |
|
||||||
*/ |
|
||||||
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) |
|
||||||
HE_AAC(2), |
|
||||||
|
|
||||||
/** |
|
||||||
* The AAC_ELD codec. |
|
||||||
*/ |
|
||||||
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN) |
|
||||||
AAC_ELD(3); |
|
||||||
|
|
||||||
static final AudioCodec DEFAULT = DEVICE_DEFAULT; |
|
||||||
|
|
||||||
private int value; |
|
||||||
|
|
||||||
AudioCodec(int value) { |
|
||||||
this.value = value; |
|
||||||
} |
|
||||||
|
|
||||||
int value() { |
|
||||||
return value; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
static AudioCodec fromValue(int value) { |
|
||||||
AudioCodec[] list = AudioCodec.values(); |
|
||||||
for (AudioCodec action : list) { |
|
||||||
if (action.value() == value) { |
|
||||||
return action; |
|
||||||
} |
|
||||||
} |
|
||||||
return DEFAULT; |
|
||||||
} |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
import android.util.Log; |
|
||||||
import android.util.Range; |
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
|
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
@RequiresApi(21) |
|
||||||
public class FpsRangeValidator { |
|
||||||
|
|
||||||
private final static CameraLogger LOG = CameraLogger.create("FpsRangeValidator"); |
|
||||||
private final static Map<String, List<Range<Integer>>> sIssues = new HashMap<>(); |
|
||||||
|
|
||||||
static { |
|
||||||
sIssues.put("Google Pixel 4", Arrays.asList(new Range<>(15, 60))); |
|
||||||
sIssues.put("Google Pixel 4a", Arrays.asList(new Range<>(15, 60))); |
|
||||||
sIssues.put("Google Pixel 4 XL", Arrays.asList(new Range<>(15, 60))); |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean validate(Range<Integer> range) { |
|
||||||
LOG.i("Build.MODEL:", Build.MODEL, "Build.BRAND:", Build.BRAND, "Build.MANUFACTURER:", Build.MANUFACTURER); |
|
||||||
String descriptor = Build.MANUFACTURER + " " + Build.MODEL; |
|
||||||
List<Range<Integer>> ranges = sIssues.get(descriptor); |
|
||||||
if (ranges != null && ranges.contains(range)) { |
|
||||||
LOG.i("Dropping range:", range); |
|
||||||
return false; |
|
||||||
} |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
@ -1,97 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
|
||||||
|
|
||||||
|
|
||||||
import android.opengl.GLES11Ext; |
|
||||||
import android.opengl.GLES20; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
import com.otaliastudios.cameraview.filter.Filter; |
|
||||||
import com.otaliastudios.cameraview.filter.NoFilter; |
|
||||||
import com.otaliastudios.opengl.core.Egloo; |
|
||||||
import com.otaliastudios.opengl.program.GlProgram; |
|
||||||
import com.otaliastudios.opengl.texture.GlTexture; |
|
||||||
|
|
||||||
|
|
||||||
public class GlTextureDrawer { |
|
||||||
|
|
||||||
private final static String TAG = GlTextureDrawer.class.getSimpleName(); |
|
||||||
private final static CameraLogger LOG = CameraLogger.create(TAG); |
|
||||||
|
|
||||||
private final static int TEXTURE_TARGET = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; |
|
||||||
private final static int TEXTURE_UNIT = GLES20.GL_TEXTURE0; |
|
||||||
|
|
||||||
private final GlTexture mTexture; |
|
||||||
private float[] mTextureTransform = Egloo.IDENTITY_MATRIX.clone(); |
|
||||||
|
|
||||||
@NonNull |
|
||||||
private Filter mFilter = new NoFilter(); |
|
||||||
private Filter mPendingFilter = null; |
|
||||||
private int mProgramHandle = -1; |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
public GlTextureDrawer() { |
|
||||||
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET)); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("unused") |
|
||||||
public GlTextureDrawer(int textureId) { |
|
||||||
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET, textureId)); |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
public GlTextureDrawer(@NonNull GlTexture texture) { |
|
||||||
mTexture = texture; |
|
||||||
} |
|
||||||
|
|
||||||
public void setFilter(@NonNull Filter filter) { |
|
||||||
mPendingFilter = filter; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public GlTexture getTexture() { |
|
||||||
return mTexture; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public float[] getTextureTransform() { |
|
||||||
return mTextureTransform; |
|
||||||
} |
|
||||||
|
|
||||||
public void setTextureTransform(@NonNull float[] textureTransform) { |
|
||||||
mTextureTransform = textureTransform; |
|
||||||
} |
|
||||||
|
|
||||||
public void draw(final long timestampUs) { |
|
||||||
if (mPendingFilter != null) { |
|
||||||
release(); |
|
||||||
mFilter = mPendingFilter; |
|
||||||
mPendingFilter = null; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
if (mProgramHandle == -1) { |
|
||||||
mProgramHandle = GlProgram.create( |
|
||||||
mFilter.getVertexShader(), |
|
||||||
mFilter.getFragmentShader()); |
|
||||||
mFilter.onCreate(mProgramHandle); |
|
||||||
Egloo.checkGlError("program creation"); |
|
||||||
} |
|
||||||
|
|
||||||
GLES20.glUseProgram(mProgramHandle); |
|
||||||
Egloo.checkGlError("glUseProgram(handle)"); |
|
||||||
mTexture.bind(); |
|
||||||
mFilter.draw(timestampUs, mTextureTransform); |
|
||||||
mTexture.unbind(); |
|
||||||
GLES20.glUseProgram(0); |
|
||||||
Egloo.checkGlError("glUseProgram(0)"); |
|
||||||
} |
|
||||||
|
|
||||||
public void release() { |
|
||||||
if (mProgramHandle == -1) return; |
|
||||||
mFilter.onDestroy(); |
|
||||||
GLES20.glDeleteProgram(mProgramHandle); |
|
||||||
mProgramHandle = -1; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,96 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
|
import android.opengl.GLES20; |
||||||
|
import android.opengl.Matrix; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.ByteOrder; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
|
||||||
|
public class GlUtils { |
||||||
|
|
||||||
|
private final static String TAG = GlUtils.class.getSimpleName(); |
||||||
|
private final static CameraLogger LOG = CameraLogger.create(TAG); |
||||||
|
|
||||||
|
// Identity matrix for general use.
|
||||||
|
public static final float[] IDENTITY_MATRIX = new float[16]; |
||||||
|
static { |
||||||
|
Matrix.setIdentityM(IDENTITY_MATRIX, 0); |
||||||
|
} |
||||||
|
|
||||||
|
public static void checkError(@NonNull String opName) { |
||||||
|
int error = GLES20.glGetError(); |
||||||
|
if (error != GLES20.GL_NO_ERROR) { |
||||||
|
String message = LOG.e("Error during", opName, "glError 0x", |
||||||
|
Integer.toHexString(error)); |
||||||
|
throw new RuntimeException(message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void checkLocation(int location, @NonNull String name) { |
||||||
|
if (location < 0) { |
||||||
|
String message = LOG.e("Unable to locate", name, "in program"); |
||||||
|
throw new RuntimeException(message); |
||||||
|
} |
||||||
|
} |
||||||
|
// Compiles the given shader, returns a handle.
|
||||||
|
@SuppressWarnings("WeakerAccess") |
||||||
|
public static int loadShader(int shaderType, @NonNull String source) { |
||||||
|
int shader = GLES20.glCreateShader(shaderType); |
||||||
|
checkError("glCreateShader type=" + shaderType); |
||||||
|
GLES20.glShaderSource(shader, source); |
||||||
|
GLES20.glCompileShader(shader); |
||||||
|
int[] compiled = new int[1]; |
||||||
|
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); |
||||||
|
if (compiled[0] == 0) { |
||||||
|
LOG.e("Could not compile shader", shaderType, ":", |
||||||
|
GLES20.glGetShaderInfoLog(shader)); |
||||||
|
GLES20.glDeleteShader(shader); |
||||||
|
shader = 0; |
||||||
|
} |
||||||
|
return shader; |
||||||
|
} |
||||||
|
|
||||||
|
// Creates a program with given vertex shader and pixel shader.
|
||||||
|
public static int createProgram(@NonNull String vertexSource, @NonNull String fragmentSource) { |
||||||
|
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); |
||||||
|
if (vertexShader == 0) return 0; |
||||||
|
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); |
||||||
|
if (pixelShader == 0) return 0; |
||||||
|
|
||||||
|
int program = GLES20.glCreateProgram(); |
||||||
|
checkError("glCreateProgram"); |
||||||
|
if (program == 0) { |
||||||
|
LOG.e("Could not create program"); |
||||||
|
} |
||||||
|
GLES20.glAttachShader(program, vertexShader); |
||||||
|
checkError("glAttachShader"); |
||||||
|
GLES20.glAttachShader(program, pixelShader); |
||||||
|
checkError("glAttachShader"); |
||||||
|
GLES20.glLinkProgram(program); |
||||||
|
int[] linkStatus = new int[1]; |
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); |
||||||
|
if (linkStatus[0] != GLES20.GL_TRUE) { |
||||||
|
LOG.e("Could not link program:", GLES20.glGetProgramInfoLog(program)); |
||||||
|
GLES20.glDeleteProgram(program); |
||||||
|
program = 0; |
||||||
|
} |
||||||
|
return program; |
||||||
|
} |
||||||
|
|
||||||
|
// Allocates a direct float buffer, and populates it with the float array data.
|
||||||
|
public static FloatBuffer floatBuffer(@NonNull float[] coords) { |
||||||
|
// Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
|
||||||
|
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4); |
||||||
|
bb.order(ByteOrder.nativeOrder()); |
||||||
|
FloatBuffer fb = bb.asFloatBuffer(); |
||||||
|
fb.put(coords); |
||||||
|
fb.position(0); |
||||||
|
return fb; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,244 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Google Inc. All rights reserved. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.otaliastudios.cameraview.internal.egl; |
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.opengl.EGL14; |
||||||
|
import android.opengl.EGLSurface; |
||||||
|
import android.opengl.GLES20; |
||||||
|
import android.os.Build; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
import com.otaliastudios.cameraview.internal.GlUtils; |
||||||
|
|
||||||
|
import java.io.BufferedOutputStream; |
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.ByteOrder; |
||||||
|
|
||||||
|
/** |
||||||
|
* Common base class for EGL surfaces. |
||||||
|
* <p> |
||||||
|
* There can be multiple surfaces associated with a single context. |
||||||
|
*/ |
||||||
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
||||||
|
public class EglBaseSurface { |
||||||
|
protected static final String TAG = EglBaseSurface.class.getSimpleName(); |
||||||
|
private final static CameraLogger LOG = CameraLogger.create(TAG); |
||||||
|
|
||||||
|
// EglCore object we're associated with. It may be associated with multiple surfaces.
|
||||||
|
protected EglCore mEglCore; |
||||||
|
|
||||||
|
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||||
|
private int mWidth = -1; |
||||||
|
private int mHeight = -1; |
||||||
|
|
||||||
|
public EglBaseSurface(EglCore eglCore) { |
||||||
|
mEglCore = eglCore; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a window surface. |
||||||
|
* <p> |
||||||
|
* @param surface May be a Surface or SurfaceTexture. |
||||||
|
*/ |
||||||
|
public void createWindowSurface(Object surface) { |
||||||
|
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||||
|
throw new IllegalStateException("surface already created"); |
||||||
|
} |
||||||
|
mEGLSurface = mEglCore.createWindowSurface(surface); |
||||||
|
|
||||||
|
// Don't cache width/height here, because the size of the underlying surface can change
|
||||||
|
// out from under us (see e.g. HardwareScalerActivity).
|
||||||
|
//mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
|
||||||
|
//mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an off-screen surface. |
||||||
|
*/ |
||||||
|
public void createOffscreenSurface(int width, int height) { |
||||||
|
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
||||||
|
throw new IllegalStateException("surface already created"); |
||||||
|
} |
||||||
|
mEGLSurface = mEglCore.createOffscreenSurface(width, height); |
||||||
|
mWidth = width; |
||||||
|
mHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the surface's width, in pixels. |
||||||
|
* <p> |
||||||
|
* If this is called on a window surface, and the underlying surface is in the process |
||||||
|
* of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" |
||||||
|
* callback). The size should match after the next buffer swap. |
||||||
|
*/ |
||||||
|
public int getWidth() { |
||||||
|
if (mWidth < 0) { |
||||||
|
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); |
||||||
|
} else { |
||||||
|
return mWidth; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the surface's height, in pixels. |
||||||
|
*/ |
||||||
|
public int getHeight() { |
||||||
|
if (mHeight < 0) { |
||||||
|
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); |
||||||
|
} else { |
||||||
|
return mHeight; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Release the EGL surface. |
||||||
|
*/ |
||||||
|
public void releaseEglSurface() { |
||||||
|
mEglCore.releaseSurface(mEGLSurface); |
||||||
|
mEGLSurface = EGL14.EGL_NO_SURFACE; |
||||||
|
mWidth = mHeight = -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes our EGL context and surface current. |
||||||
|
*/ |
||||||
|
public void makeCurrent() { |
||||||
|
mEglCore.makeCurrent(mEGLSurface); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes our EGL context and surface current for drawing, using the supplied surface |
||||||
|
* for reading. |
||||||
|
*/ |
||||||
|
public void makeCurrentReadFrom(EglBaseSurface readSurface) { |
||||||
|
mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls eglSwapBuffers. Use this to "publish" the current frame. |
||||||
|
* |
||||||
|
* @return false on failure |
||||||
|
*/ |
||||||
|
public boolean swapBuffers() { |
||||||
|
boolean result = mEglCore.swapBuffers(mEGLSurface); |
||||||
|
if (!result) { |
||||||
|
Log.d(TAG, "WARNING: swapBuffers() failed"); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends the presentation time stamp to EGL. |
||||||
|
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
|
||||||
|
* |
||||||
|
* @param nsecs Timestamp, in nanoseconds. |
||||||
|
*/ |
||||||
|
public void setPresentationTime(long nsecs) { |
||||||
|
mEglCore.setPresentationTime(mEGLSurface, nsecs); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Saves the EGL surface to a file. |
||||||
|
* <p> |
||||||
|
* Expects that this object's EGL surface is current. |
||||||
|
*/ |
||||||
|
public void saveFrameToFile(File file) throws IOException { |
||||||
|
if (!mEglCore.isCurrent(mEGLSurface)) { |
||||||
|
throw new RuntimeException("Expected EGL context/surface is not current"); |
||||||
|
} |
||||||
|
|
||||||
|
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
|
||||||
|
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
|
||||||
|
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
|
||||||
|
// Bitmap "copy pixels" method wants the same format GL provides.
|
||||||
|
//
|
||||||
|
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
|
||||||
|
// here often.
|
||||||
|
//
|
||||||
|
// Making this even more interesting is the upside-down nature of GL, which means
|
||||||
|
// our output will look upside down relative to what appears on screen if the
|
||||||
|
// typical GL conventions are used.
|
||||||
|
|
||||||
|
String filename = file.toString(); |
||||||
|
|
||||||
|
int width = getWidth(); |
||||||
|
int height = getHeight(); |
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); |
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN); |
||||||
|
GLES20.glReadPixels(0, 0, width, height, |
||||||
|
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); |
||||||
|
GlUtils.checkError("glReadPixels"); |
||||||
|
buf.rewind(); |
||||||
|
|
||||||
|
BufferedOutputStream bos = null; |
||||||
|
try { |
||||||
|
bos = new BufferedOutputStream(new FileOutputStream(filename)); |
||||||
|
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
||||||
|
bmp.copyPixelsFromBuffer(buf); |
||||||
|
bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); |
||||||
|
bmp.recycle(); |
||||||
|
} finally { |
||||||
|
if (bos != null) bos.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Saves the EGL surface to given format. |
||||||
|
* <p> |
||||||
|
* Expects that this object's EGL surface is current. |
||||||
|
*/ |
||||||
|
public byte[] saveFrameTo(Bitmap.CompressFormat compressFormat) { |
||||||
|
if (!mEglCore.isCurrent(mEGLSurface)) { |
||||||
|
throw new RuntimeException("Expected EGL context/surface is not current"); |
||||||
|
} |
||||||
|
|
||||||
|
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
|
||||||
|
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
|
||||||
|
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
|
||||||
|
// Bitmap "copy pixels" method wants the same format GL provides.
|
||||||
|
//
|
||||||
|
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
|
||||||
|
// here often.
|
||||||
|
//
|
||||||
|
// Making this even more interesting is the upside-down nature of GL, which means
|
||||||
|
// our output will look upside down relative to what appears on screen if the
|
||||||
|
// typical GL conventions are used.
|
||||||
|
|
||||||
|
int width = getWidth(); |
||||||
|
int height = getHeight(); |
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); |
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN); |
||||||
|
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, |
||||||
|
buf); |
||||||
|
GlUtils.checkError("glReadPixels"); |
||||||
|
buf.rewind(); |
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length); |
||||||
|
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
||||||
|
bmp.copyPixelsFromBuffer(buf); |
||||||
|
bmp.compress(compressFormat, 90, bos); |
||||||
|
bmp.recycle(); |
||||||
|
return bos.toByteArray(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,364 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Google Inc. All rights reserved. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.otaliastudios.cameraview.internal.egl; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.opengl.EGL14; |
||||||
|
import android.opengl.EGLConfig; |
||||||
|
import android.opengl.EGLContext; |
||||||
|
import android.opengl.EGLDisplay; |
||||||
|
import android.opengl.EGLExt; |
||||||
|
import android.opengl.EGLSurface; |
||||||
|
import android.os.Build; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
import android.util.Log; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
/** |
||||||
|
* -- from grafika -- |
||||||
|
* |
||||||
|
* Core EGL state (display, context, config). |
||||||
|
* <p> |
||||||
|
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe. |
||||||
|
*/ |
||||||
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
||||||
|
public final class EglCore { |
||||||
|
private static final String TAG = EglCore.class.getSimpleName(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor flag: surface must be recordable. This discourages EGL from using a |
||||||
|
* pixel format that cannot be converted efficiently to something usable by the video |
||||||
|
* encoder. |
||||||
|
*/ |
||||||
|
public static final int FLAG_RECORDABLE = 0x01; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this |
||||||
|
* flag, GLES2 is used. |
||||||
|
*/ |
||||||
|
public static final int FLAG_TRY_GLES3 = 0x02; |
||||||
|
|
||||||
|
// Android-specific extension.
|
||||||
|
private static final int EGL_RECORDABLE_ANDROID = 0x3142; |
||||||
|
|
||||||
|
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||||
|
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||||
|
private EGLConfig mEGLConfig = null; |
||||||
|
private int mGlVersion = -1; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Prepares EGL display and context. |
||||||
|
* <p> |
||||||
|
* Equivalent to EglCore(null, 0). |
||||||
|
*/ |
||||||
|
public EglCore() { |
||||||
|
this(null, 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Prepares EGL display and context. |
||||||
|
* <p> |
||||||
|
* @param sharedContext The context to share, or null if sharing is not desired. |
||||||
|
* @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. |
||||||
|
*/ |
||||||
|
public EglCore(EGLContext sharedContext, int flags) { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
throw new RuntimeException("EGL already set up"); |
||||||
|
} |
||||||
|
|
||||||
|
if (sharedContext == null) { |
||||||
|
sharedContext = EGL14.EGL_NO_CONTEXT; |
||||||
|
} |
||||||
|
|
||||||
|
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); |
||||||
|
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
||||||
|
throw new RuntimeException("unable to get EGL14 display"); |
||||||
|
} |
||||||
|
int[] version = new int[2]; |
||||||
|
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { |
||||||
|
mEGLDisplay = null; |
||||||
|
throw new RuntimeException("unable to initialize EGL14"); |
||||||
|
} |
||||||
|
|
||||||
|
// Try to get a GLES3 context, if requested.
|
||||||
|
if ((flags & FLAG_TRY_GLES3) != 0) { |
||||||
|
//Log.d(TAG, "Trying GLES 3");
|
||||||
|
EGLConfig config = getConfig(flags, 3); |
||||||
|
if (config != null) { |
||||||
|
int[] attrib3_list = { |
||||||
|
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, |
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, |
||||||
|
attrib3_list, 0); |
||||||
|
|
||||||
|
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { |
||||||
|
//Log.d(TAG, "Got GLES 3 config");
|
||||||
|
mEGLConfig = config; |
||||||
|
mEGLContext = context; |
||||||
|
mGlVersion = 3; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
|
||||||
|
//Log.d(TAG, "Trying GLES 2");
|
||||||
|
EGLConfig config = getConfig(flags, 2); |
||||||
|
if (config == null) { |
||||||
|
throw new RuntimeException("Unable to find a suitable EGLConfig"); |
||||||
|
} |
||||||
|
int[] attrib2_list = { |
||||||
|
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, |
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, |
||||||
|
attrib2_list, 0); |
||||||
|
checkEglError("eglCreateContext"); |
||||||
|
mEGLConfig = config; |
||||||
|
mEGLContext = context; |
||||||
|
mGlVersion = 2; |
||||||
|
} |
||||||
|
|
||||||
|
// Confirm with query.
|
||||||
|
int[] values = new int[1]; |
||||||
|
EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, |
||||||
|
values, 0); |
||||||
|
// Log.d(TAG, "EGLContext created, client version " + values[0]);
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds a suitable EGLConfig. |
||||||
|
* |
||||||
|
* @param flags Bit flags from constructor. |
||||||
|
* @param version Must be 2 or 3. |
||||||
|
*/ |
||||||
|
private EGLConfig getConfig(int flags, int version) { |
||||||
|
int renderableType = EGL14.EGL_OPENGL_ES2_BIT; |
||||||
|
if (version >= 3) { |
||||||
|
renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; |
||||||
|
} |
||||||
|
|
||||||
|
// The actual surface is generally RGBA or RGBX, so situationally omitting alpha
|
||||||
|
// doesn't really help. It can also lead to a huge performance hit on glReadPixels()
|
||||||
|
// when reading into a GL_RGBA buffer.
|
||||||
|
int[] attribList = { |
||||||
|
EGL14.EGL_RED_SIZE, 8, |
||||||
|
EGL14.EGL_GREEN_SIZE, 8, |
||||||
|
EGL14.EGL_BLUE_SIZE, 8, |
||||||
|
EGL14.EGL_ALPHA_SIZE, 8, |
||||||
|
//EGL14.EGL_DEPTH_SIZE, 16,
|
||||||
|
//EGL14.EGL_STENCIL_SIZE, 8,
|
||||||
|
EGL14.EGL_RENDERABLE_TYPE, renderableType, |
||||||
|
EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
|
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
if ((flags & FLAG_RECORDABLE) != 0) { |
||||||
|
attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; |
||||||
|
attribList[attribList.length - 2] = 1; |
||||||
|
} |
||||||
|
EGLConfig[] configs = new EGLConfig[1]; |
||||||
|
int[] numConfigs = new int[1]; |
||||||
|
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, |
||||||
|
configs.length, numConfigs, 0)) { |
||||||
|
Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); |
||||||
|
return null; |
||||||
|
} |
||||||
|
return configs[0]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Discards all resources held by this class, notably the EGL context. This must be |
||||||
|
* called from the thread where the context was created. |
||||||
|
* <p> |
||||||
|
* On completion, no context will be current. |
||||||
|
*/ |
||||||
|
public void release() { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
// Android is unusual in that it uses a reference-counted EGLDisplay. So for
|
||||||
|
// every eglInitialize() we need an eglTerminate().
|
||||||
|
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, |
||||||
|
EGL14.EGL_NO_CONTEXT); |
||||||
|
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); |
||||||
|
EGL14.eglReleaseThread(); |
||||||
|
EGL14.eglTerminate(mEGLDisplay); |
||||||
|
} |
||||||
|
|
||||||
|
mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
||||||
|
mEGLContext = EGL14.EGL_NO_CONTEXT; |
||||||
|
mEGLConfig = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void finalize() throws Throwable { |
||||||
|
try { |
||||||
|
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
||||||
|
// We're limited here -- finalizers don't run on the thread that holds
|
||||||
|
// the EGL state, so if a surface or context is still current on another
|
||||||
|
// thread we can't fully release it here. Exceptions thrown from here
|
||||||
|
// are quietly discarded. Complain in the log file.
|
||||||
|
Log.w(TAG, "WARNING: EglCore was not explicitly released! " + |
||||||
|
"State may be leaked"); |
||||||
|
release(); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
super.finalize(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's |
||||||
|
* still current in a context. |
||||||
|
*/ |
||||||
|
public void releaseSurface(EGLSurface eglSurface) { |
||||||
|
EGL14.eglDestroySurface(mEGLDisplay, eglSurface); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an EGL surface associated with a Surface. |
||||||
|
* <p> |
||||||
|
* If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute. |
||||||
|
*/ |
||||||
|
public EGLSurface createWindowSurface(Object surface) { |
||||||
|
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { |
||||||
|
throw new RuntimeException("invalid surface: " + surface); |
||||||
|
} |
||||||
|
|
||||||
|
// Create a window surface, and attach it to the Surface we received.
|
||||||
|
int[] surfaceAttribs = { |
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, |
||||||
|
surfaceAttribs, 0); |
||||||
|
checkEglError("eglCreateWindowSurface"); |
||||||
|
if (eglSurface == null) { |
||||||
|
throw new RuntimeException("surface was null"); |
||||||
|
} |
||||||
|
return eglSurface; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an EGL surface associated with an offscreen buffer. |
||||||
|
*/ |
||||||
|
public EGLSurface createOffscreenSurface(int width, int height) { |
||||||
|
int[] surfaceAttribs = { |
||||||
|
EGL14.EGL_WIDTH, width, |
||||||
|
EGL14.EGL_HEIGHT, height, |
||||||
|
EGL14.EGL_NONE |
||||||
|
}; |
||||||
|
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, |
||||||
|
surfaceAttribs, 0); |
||||||
|
checkEglError("eglCreatePbufferSurface"); |
||||||
|
if (eglSurface == null) { |
||||||
|
throw new RuntimeException("surface was null"); |
||||||
|
} |
||||||
|
return eglSurface; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes our EGL context current, using the supplied surface for both "draw" and "read". |
||||||
|
*/ |
||||||
|
public void makeCurrent(EGLSurface eglSurface) { |
||||||
|
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
||||||
|
// called makeCurrent() before create?
|
||||||
|
// Log.d(TAG, "NOTE: makeCurrent w/o display");
|
||||||
|
} |
||||||
|
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { |
||||||
|
throw new RuntimeException("eglMakeCurrent failed"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes our EGL context current, using the supplied "draw" and "read" surfaces. |
||||||
|
*/ |
||||||
|
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { |
||||||
|
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
||||||
|
// called makeCurrent() before create?
|
||||||
|
Log.d(TAG, "NOTE: makeCurrent w/o display"); |
||||||
|
} |
||||||
|
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { |
||||||
|
throw new RuntimeException("eglMakeCurrent(draw,read) failed"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes no context current. |
||||||
|
*/ |
||||||
|
public void makeNothingCurrent() { |
||||||
|
if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, |
||||||
|
EGL14.EGL_NO_CONTEXT)) { |
||||||
|
throw new RuntimeException("eglMakeCurrent failed"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls eglSwapBuffers. Use this to "publish" the current frame. |
||||||
|
* |
||||||
|
* @return false on failure |
||||||
|
*/ |
||||||
|
public boolean swapBuffers(EGLSurface eglSurface) { |
||||||
|
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. |
||||||
|
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
|
||||||
|
*/ |
||||||
|
public void setPresentationTime(EGLSurface eglSurface, long nsecs) { |
||||||
|
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if our context and the specified surface are current. |
||||||
|
*/ |
||||||
|
public boolean isCurrent(EGLSurface eglSurface) { |
||||||
|
return mEGLContext.equals(EGL14.eglGetCurrentContext()) && |
||||||
|
eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a simple surface query. |
||||||
|
*/ |
||||||
|
public int querySurface(EGLSurface eglSurface, int what) { |
||||||
|
int[] value = new int[1]; |
||||||
|
EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); |
||||||
|
return value[0]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queries a string value. |
||||||
|
*/ |
||||||
|
public String queryString(int what) { |
||||||
|
return EGL14.eglQueryString(mEGLDisplay, what); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the GLES version this context is configured for (currently 2 or 3). |
||||||
|
*/ |
||||||
|
public int getGlVersion() { |
||||||
|
return mGlVersion; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks for EGL errors. Throws an exception if an error has been raised. |
||||||
|
*/ |
||||||
|
private void checkEglError(String msg) { |
||||||
|
int error; |
||||||
|
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { |
||||||
|
throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal.egl; |
||||||
|
|
||||||
|
|
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES20; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
import com.otaliastudios.cameraview.filter.Filter; |
||||||
|
import com.otaliastudios.cameraview.filter.NoFilter; |
||||||
|
import com.otaliastudios.cameraview.internal.GlUtils; |
||||||
|
|
||||||
|
|
||||||
|
public class EglViewport { |
||||||
|
|
||||||
|
private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName()); |
||||||
|
|
||||||
|
private int mProgramHandle = -1; |
||||||
|
private int mTextureTarget; |
||||||
|
private int mTextureUnit; |
||||||
|
|
||||||
|
private Filter mFilter; |
||||||
|
private Filter mPendingFilter; |
||||||
|
|
||||||
|
public EglViewport() { |
||||||
|
this(new NoFilter()); |
||||||
|
} |
||||||
|
|
||||||
|
public EglViewport(@NonNull Filter filter) { |
||||||
|
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; |
||||||
|
mTextureUnit = GLES20.GL_TEXTURE0; |
||||||
|
mFilter = filter; |
||||||
|
createProgram(); |
||||||
|
} |
||||||
|
|
||||||
|
private void createProgram() { |
||||||
|
mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), |
||||||
|
mFilter.getFragmentShader()); |
||||||
|
mFilter.onCreate(mProgramHandle); |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
if (mProgramHandle != -1) { |
||||||
|
mFilter.onDestroy(); |
||||||
|
GLES20.glDeleteProgram(mProgramHandle); |
||||||
|
mProgramHandle = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int createTexture() { |
||||||
|
int[] textures = new int[1]; |
||||||
|
GLES20.glGenTextures(1, textures, 0); |
||||||
|
GlUtils.checkError("glGenTextures"); |
||||||
|
|
||||||
|
int texId = textures[0]; |
||||||
|
GLES20.glActiveTexture(mTextureUnit); |
||||||
|
GLES20.glBindTexture(mTextureTarget, texId); |
||||||
|
GlUtils.checkError("glBindTexture " + texId); |
||||||
|
|
||||||
|
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, |
||||||
|
GLES20.GL_NEAREST); |
||||||
|
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, |
||||||
|
GLES20.GL_LINEAR); |
||||||
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, |
||||||
|
GLES20.GL_CLAMP_TO_EDGE); |
||||||
|
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, |
||||||
|
GLES20.GL_CLAMP_TO_EDGE); |
||||||
|
GlUtils.checkError("glTexParameter"); |
||||||
|
|
||||||
|
return texId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFilter(@NonNull Filter filter) { |
||||||
|
// TODO see if this is needed. If setFilter is always called from the correct GL thread,
|
||||||
|
// we don't need to wait for a new draw call (which might not even happen).
|
||||||
|
mPendingFilter = filter; |
||||||
|
} |
||||||
|
|
||||||
|
public void drawFrame(long timestampUs, int textureId, float[] textureMatrix) { |
||||||
|
if (mPendingFilter != null) { |
||||||
|
release(); |
||||||
|
mFilter = mPendingFilter; |
||||||
|
mPendingFilter = null; |
||||||
|
createProgram(); |
||||||
|
} |
||||||
|
|
||||||
|
GlUtils.checkError("draw start"); |
||||||
|
|
||||||
|
// Select the program and the active texture.
|
||||||
|
GLES20.glUseProgram(mProgramHandle); |
||||||
|
GlUtils.checkError("glUseProgram"); |
||||||
|
GLES20.glActiveTexture(mTextureUnit); |
||||||
|
GLES20.glBindTexture(mTextureTarget, textureId); |
||||||
|
|
||||||
|
// Draw.
|
||||||
|
mFilter.draw(timestampUs, textureMatrix); |
||||||
|
|
||||||
|
// Release.
|
||||||
|
GLES20.glBindTexture(mTextureTarget, 0); |
||||||
|
GLES20.glUseProgram(0); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2013 Google Inc. All rights reserved. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.otaliastudios.cameraview.internal.egl; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.os.Build; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Recordable EGL window surface. |
||||||
|
* <p> |
||||||
|
* It's good practice to explicitly release() the surface, preferably from a "finally" block. |
||||||
|
*/ |
||||||
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
||||||
|
public class EglWindowSurface extends EglBaseSurface { |
||||||
|
private Surface mSurface; |
||||||
|
private boolean mReleaseSurface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Associates an EGL surface with the native window surface. |
||||||
|
* <p> |
||||||
|
* Set releaseSurface to true if you want the Surface to be released when release() is |
||||||
|
* called. This is convenient, but can interfere with framework classes that expect to |
||||||
|
* manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the |
||||||
|
* surfaceDestroyed() callback won't fire). |
||||||
|
*/ |
||||||
|
public EglWindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { |
||||||
|
super(eglCore); |
||||||
|
createWindowSurface(surface); |
||||||
|
mSurface = surface; |
||||||
|
mReleaseSurface = releaseSurface; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Associates an EGL surface with the SurfaceTexture. |
||||||
|
*/ |
||||||
|
public EglWindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { |
||||||
|
super(eglCore); |
||||||
|
createWindowSurface(surfaceTexture); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Associates an EGL surface with the Surface. |
||||||
|
*/ |
||||||
|
public EglWindowSurface(EglCore eglCore, Surface surface) { |
||||||
|
super(eglCore); |
||||||
|
createWindowSurface(surface); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Releases any resources associated with the EGL surface (and, if configured to do so, |
||||||
|
* with the Surface as well). |
||||||
|
* <p> |
||||||
|
* Does not require that the surface's EGL context be current. |
||||||
|
*/ |
||||||
|
public void release() { |
||||||
|
releaseEglSurface(); |
||||||
|
if (mSurface != null) { |
||||||
|
if (mReleaseSurface) { |
||||||
|
mSurface.release(); |
||||||
|
} |
||||||
|
mSurface = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
||||||
import android.media.CamcorderProfile; |
import android.media.CamcorderProfile; |
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import android.graphics.Rect; |
import android.graphics.Rect; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface; |
import androidx.exifinterface.media.ExifInterface; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.size.Size; |
import com.otaliastudios.cameraview.size.Size; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
import android.os.Handler; |
import android.os.Handler; |
||||||
import android.os.HandlerThread; |
import android.os.HandlerThread; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue