Compare commits
1 Commits
main
...
debug-base
Author | SHA1 | Date |
---|---|---|
Mattia Iavarone | 41e3e7c1f5 | 5 years ago |
@ -1 +0,0 @@ |
|||||||
Contributing guidelines are [hosted here](https://natario1.github.io/CameraView/extra/contributing). |
|
@ -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,119 +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: Build |
|
||||||
on: |
|
||||||
push: |
|
||||||
branches: |
|
||||||
- main |
|
||||||
pull_request: |
|
||||||
jobs: |
|
||||||
ANDROID_BASE_CHECKS: |
|
||||||
name: Base Checks |
|
||||||
runs-on: ubuntu-latest |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Perform base checks |
|
||||||
run: ./gradlew demo:assembleDebug cameraview:publishToDirectory --stacktrace |
|
||||||
ANDROID_UNIT_TESTS: |
|
||||||
name: Unit Tests |
|
||||||
runs-on: ubuntu-latest |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Execute unit tests |
|
||||||
run: ./gradlew cameraview:runUnitTests --stacktrace |
|
||||||
- name: Upload unit tests artifact |
|
||||||
uses: actions/upload-artifact@v1 |
|
||||||
with: |
|
||||||
name: unit_tests |
|
||||||
path: ./cameraview/build/coverage_input/unit_tests |
|
||||||
ANDROID_EMULATOR_TESTS: |
|
||||||
name: Emulator Tests |
|
||||||
runs-on: macos-latest |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
# TODO 29 fails due to Mockito issues, probably reproducible locally |
|
||||||
# 22-28 work (some of them, with SdkExclude restrictions) |
|
||||||
EMULATOR_API: [22, 23, 24, 25, 26, 27, 28] |
|
||||||
include: |
|
||||||
- EMULATOR_API: 28 |
|
||||||
EMULATOR_ARCH: x86_64 |
|
||||||
- EMULATOR_API: 27 |
|
||||||
EMULATOR_ARCH: x86_64 |
|
||||||
- EMULATOR_API: 26 |
|
||||||
EMULATOR_ARCH: x86_64 |
|
||||||
- EMULATOR_API: 25 |
|
||||||
EMULATOR_ARCH: x86 |
|
||||||
- EMULATOR_API: 24 |
|
||||||
EMULATOR_ARCH: x86 |
|
||||||
- EMULATOR_API: 23 |
|
||||||
EMULATOR_ARCH: x86 |
|
||||||
- EMULATOR_API: 22 |
|
||||||
EMULATOR_ARCH: x86 |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Execute emulator tests |
|
||||||
timeout-minutes: 30 |
|
||||||
uses: reactivecircus/android-emulator-runner@v2.21.0 |
|
||||||
with: |
|
||||||
api-level: ${{ matrix.EMULATOR_API }} |
|
||||||
arch: ${{ matrix.EMULATOR_ARCH }} |
|
||||||
disable-animations: true |
|
||||||
profile: Nexus 5X |
|
||||||
emulator-options: -no-snapshot -no-window -no-boot-anim -camera-back emulated -camera-front emulated -gpu swiftshader_indirect |
|
||||||
script: ./.github/workflows/emulator_script.sh |
|
||||||
- name: Upload emulator tests artifact |
|
||||||
uses: actions/upload-artifact@v1 |
|
||||||
with: |
|
||||||
name: emulator_tests_${{ matrix.EMULATOR_API }} |
|
||||||
path: ./cameraview/build/coverage_input/android_tests |
|
||||||
CODE_COVERAGE: |
|
||||||
name: Code Coverage Report |
|
||||||
runs-on: ubuntu-latest |
|
||||||
needs: [ANDROID_UNIT_TESTS, ANDROID_EMULATOR_TESTS] |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v2 |
|
||||||
- uses: actions/setup-java@v2 |
|
||||||
with: |
|
||||||
java-version: 11 |
|
||||||
distribution: temurin |
|
||||||
cache: gradle |
|
||||||
- name: Download unit tests artifact |
|
||||||
uses: actions/download-artifact@v1 |
|
||||||
with: |
|
||||||
name: unit_tests |
|
||||||
path: ./cameraview/build/coverage_input/unit_tests |
|
||||||
- name: Download emulator tests artifact |
|
||||||
uses: actions/download-artifact@v1 |
|
||||||
with: |
|
||||||
# 27 is the EMULATOR_API with less SdkExclude annotations, and should have |
|
||||||
# the best possible coverage. |
|
||||||
name: emulator_tests_27 |
|
||||||
path: ./cameraview/build/coverage_input/android_tests |
|
||||||
- name: Create merged coverage report |
|
||||||
run: ./gradlew cameraview:computeCoverage |
|
||||||
- name: Upload merged coverage report (GitHub) |
|
||||||
uses: actions/upload-artifact@v1 |
|
||||||
with: |
|
||||||
name: report |
|
||||||
path: ./cameraview/build/coverage_output/xml |
|
||||||
- name: Upload merged coverage report (Codecov) |
|
||||||
uses: codecov/codecov-action@v1 |
|
||||||
with: |
|
||||||
file: ./cameraview/build/coverage_output/xml/* |
|
||||||
fail_ci_if_error: true |
|
@ -1,23 +0,0 @@ |
|||||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions |
|
||||||
name: Deploy |
|
||||||
on: |
|
||||||
release: |
|
||||||
types: [published] |
|
||||||
jobs: |
|
||||||
MAVEN_UPLOAD: |
|
||||||
name: Maven Upload |
|
||||||
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: Perform maven upload |
|
||||||
run: ./gradlew publishToSonatype |
|
@ -1,14 +0,0 @@ |
|||||||
#!/usr/bin/env bash |
|
||||||
# Core |
|
||||||
ADB_TAGS="CameraView:I CameraCallbacks:I CameraOrchestrator:I CameraEngine:I" |
|
||||||
ADB_TAGS="$ADB_TAGS CameraUtils:I WorkerHandler:I" |
|
||||||
# Recorders |
|
||||||
ADB_TAGS="$ADB_TAGS VideoRecorder:I FullVideoRecorder:I SnapshotVideoRecorder:I" |
|
||||||
ADB_TAGS="$ADB_TAGS FullPictureRecorder:I SnapshotPictureRecorder:I DeviceEncoders:I" |
|
||||||
# Video encoders |
|
||||||
ADB_TAGS="$ADB_TAGS MediaEncoderEngine:I MediaEncoder:I AudioMediaEncoder:I VideoMediaEncoder:I TextureMediaEncoder:I" |
|
||||||
# Debugging |
|
||||||
ADB_TAGS="$ADB_TAGS CameraIntegrationTest:I MessageQueue:W MPEG4Writer:I" |
|
||||||
adb logcat -c |
|
||||||
adb logcat $ADB_TAGS *:E -v color & |
|
||||||
./gradlew cameraview:runAndroidTests |
|
@ -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,90 @@ |
|||||||
|
# https://github.com/andstatus/andstatus/blob/master/.travis.yml |
||||||
|
|
||||||
|
language: android |
||||||
|
|
||||||
|
branches: |
||||||
|
only: |
||||||
|
- master |
||||||
|
- /^v\d+\.\d+\.\d+$/ |
||||||
|
- /^v\d+\.\d+\.\d+-beta\d+$/ |
||||||
|
- /^v\d+\.\d+\.\d+-rc\d+$/ |
||||||
|
|
||||||
|
sudo: false |
||||||
|
|
||||||
|
jdk: |
||||||
|
- oraclejdk8 |
||||||
|
|
||||||
|
env: |
||||||
|
global: |
||||||
|
- EMULATOR_API=22 # 24 has some issues, probably some overlayed window |
||||||
|
- EMULATOR_ABI=x86_64 # seems to work with emulator v29. |
||||||
|
- EMULATOR_TAG=default # can be google_apis |
||||||
|
- PATH=$ANDROID_HOME:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH |
||||||
|
|
||||||
|
android: |
||||||
|
components: |
||||||
|
- tools |
||||||
|
- platform-tools |
||||||
|
- build-tools-28.0.3 |
||||||
|
- android-28 |
||||||
|
- doc-28 |
||||||
|
|
||||||
|
install: |
||||||
|
# Setup |
||||||
|
- echo $ANDROID_HOME # We assume this is correctly set when setting path |
||||||
|
- sdkmanager --list || true # Look at the packages |
||||||
|
- echo yes | sdkmanager "tools" # Ensure tools is updated |
||||||
|
- echo yes | sdkmanager "emulator" # Ensure emulator is present |
||||||
|
|
||||||
|
# Install emulator |
||||||
|
# The channel=4 line looks into canary which brings in v29. |
||||||
|
# The previous version v28 was broken: |
||||||
|
# https://travis-ci.community/t/android-emulators-not-starting-for-the-last-few-days-late-march-2019/2871/11?u=mikehardy |
||||||
|
- export EMULATOR="system-images;android-$EMULATOR_API;$EMULATOR_TAG;$EMULATOR_ABI" |
||||||
|
- echo yes | sdkmanager "platforms;android-$EMULATOR_API" # Install sdk for the emulator |
||||||
|
- echo yes | sdkmanager --channel=4 "$EMULATOR" # Install system image |
||||||
|
- sdkmanager --list || true # Check everything is updated |
||||||
|
|
||||||
|
# Create adn start emulator |
||||||
|
- echo no | avdmanager create avd -n test -k "$EMULATOR" -f # Create emulator virtual device |
||||||
|
- which emulator # ensure we are using the right emulator ($ANDROID_HOME/emulator/emulator) |
||||||
|
- emulator -avd test -no-window -no-accel -no-snapshot -camera-back emulated -camera-front emulated -memory 2048 -writable-system & # Launch |
||||||
|
- adb wait-for-device # Wait for adb process |
||||||
|
- adb remount # Mount as writable |
||||||
|
|
||||||
|
before_script: |
||||||
|
# Wait for emulator |
||||||
|
- android-wait-for-emulator # Wait for emulator ready to interact |
||||||
|
- adb shell settings put global window_animation_scale 0 & # Disable animations |
||||||
|
- adb shell settings put global transition_animation_scale 0 & # Disable animations |
||||||
|
- adb shell settings put global animator_duration_scale 0 & # Disable animations |
||||||
|
|
||||||
|
# Unlock and configure logs. |
||||||
|
# Would be great to use -v color to adb logcat but looks not supported on travis. |
||||||
|
- sleep 20 # Sleep 20 seconds just in case |
||||||
|
- adb shell input keyevent 82 & # Dispatch unlock event |
||||||
|
- adb logcat --help # See if this version supports color |
||||||
|
- adb logcat -c # Clear logcat |
||||||
|
- adb logcat Test:V TestRunner:V CameraView:V CameraController:V Camera1:V WorkerHandler:V THREAD_STATE:S *:E & |
||||||
|
# - export LOGCAT_PID=$! # Save PID of the logcat process. Should kill later with kill $LOGCAT_PID |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
script: |
||||||
|
- ./gradlew clean testDebugUnitTest connectedCheck mergedCoverageReport |
||||||
|
|
||||||
|
after_success: |
||||||
|
- bash <(curl -s https://codecov.io/bash) -s "*/build/reports/mergedCoverageReport/" |
||||||
|
|
||||||
|
cache: |
||||||
|
directories: |
||||||
|
- $HOME/.gradle |
||||||
|
- $HOME/.m2/repository |
||||||
|
|
||||||
|
deploy: |
||||||
|
provider: script |
||||||
|
script: ./gradlew bintrayUpload |
||||||
|
skip_cleanup: true |
||||||
|
on: |
||||||
|
branch: master |
||||||
|
tags: true |
@ -0,0 +1,158 @@ |
|||||||
|
For v2 changelogs, please take a look at the [website](https://natario1.github.io/CameraView/about/changelog.html). |
||||||
|
|
||||||
|
## v1.6.1 |
||||||
|
|
||||||
|
This is the last release before v2. |
||||||
|
|
||||||
|
- Fixed: crash when using TextureView in API 28, thanks to [@Keyrillanskiy][Keyrillanskiy] ([#297][297]) |
||||||
|
- Fixed: restore Frame Processor callbacks after taking videos, thanks to [@stefanJi][stefanJi] ([#344][344]) |
||||||
|
- Enhancement: when horizontal, camera now uses the last available orientation, thanks to [@aartikov][aartikov] ([#290][290]) |
||||||
|
- Changed: we now swallow exceptions during autoFocus that were happening unpredictably on some devices, thanks to [@mahdi-ninja][mahdi-ninja] ([#332][332]) |
||||||
|
https://github.com/natario1/CameraView/compare/v1.6.0...v1.6.1 |
||||||
|
|
||||||
|
## v1.6.0 |
||||||
|
|
||||||
|
- Lifecycle support. Use `setLifecycleOwner` instead of calling start, stop and destroy ([#265][265]) |
||||||
|
- Enhancement: provide synchronous version of CameraUtils.decodeBitmap thanks to [@athornz][athornz] ([#224][224]) |
||||||
|
- Enhancement: prevent possible context leak thanks to [@MatFl][MatFl] ([#245][245]) |
||||||
|
- Bug: fix crash when using default VideoCodec thanks to [@Namazed][Namazed] ([#264][264]) |
||||||
|
- Enhancement: CameraException.getReason() gives some insight about the error ([#265][265]) |
||||||
|
- Enhancement: Common crashes are now being posted to the error callback instead of crashing the app ([#265][265]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.5.1...v1.6.0 |
||||||
|
|
||||||
|
### v1.5.1 |
||||||
|
|
||||||
|
- Bug: byte array length for Frames was incorrect thanks to [@ssakhavi][ssakhavi] ([#205][205]) |
||||||
|
- Bug: gestures were crashing in some conditions ([#222][222]) |
||||||
|
- Bug: import correctly the ExifInterface library ([#222][222]) |
||||||
|
- Updated dependencies thanks to [@caleb-allen][caleb-allen] ([#190][190]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.5.0...v1.5.1 |
||||||
|
|
||||||
|
## v1.5.0 |
||||||
|
|
||||||
|
- New: set encoder for video recordings with `cameraVideoCodec` ([#174][174]) |
||||||
|
- New: set max duration for videos with `cameraVideoMaxDuration` ([#172][172]) |
||||||
|
- Enhancement: reduced lag with continuous gestures (ev, zoom) ([#170][170]) |
||||||
|
- Bug: tap to focus was crashing on some devices ([#167][167]) |
||||||
|
- Bug: capturePicture was breaking if followed by another event soon after ([#173][173]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.4.2...v1.5.0 |
||||||
|
|
||||||
|
### v1.4.2 |
||||||
|
|
||||||
|
- Add prefix to XML resources so they don't collide, thanks to [@RocketRider][RocketRider] ([#162][162]) |
||||||
|
- Add `videoMaxSize` API and XML attribute, to set max size video in bytes, thanks to [@chaitanyaraghav][chaitanyaraghav] ([#104][104]) |
||||||
|
- Improved the preview size selection, thanks to [@YeungKC][YeungKC] ([#133][133]) |
||||||
|
- Improved the playSounds attribute, was playing incorrectly, thanks to [@xp-vit][xp-vit] ([#143][143]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.4.1...v1.4.2 |
||||||
|
|
||||||
|
### v1.4.1 |
||||||
|
|
||||||
|
- Fixed a bug that would flip the front camera preview on some devices ([#112][112]) |
||||||
|
- Two new `CameraOptions` APIs: `o.getSupportedPictureSizes()` and `o.getSupportedPictureAspectRatios()` ([#101][101]) |
||||||
|
- Most controls (video quality, hdr, grid, session type, audio, white balance, flash, facing) now inherit |
||||||
|
from a base `Control` class ([#105][105]). This let us add new APIs: |
||||||
|
|
||||||
|
- `CameraView.set(Control)`: sets the control to the given value, e.g. `set(Flash.AUTO)` |
||||||
|
- `CameraOptions.supports(Control)`: returns true if the control is supported |
||||||
|
- `CameraOptions.getSupportedControls(Class<? extends Control>)`: returns list of supported controls of a given kind |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.4.0...v1.4.1 |
||||||
|
|
||||||
|
## v1.4.0 |
||||||
|
|
||||||
|
- CameraView is now completely thread-safe. All actions are asynchronous. ([#97][97]) |
||||||
|
This has some breaking drawbacks. Specifically, the `get` methods (e.g., `getWhiteBalance`) might |
||||||
|
not return the correct value while it is being changed. So don't trust them right after you have changed the value. |
||||||
|
Instead, always check the `CameraOptions` to see if the value you want is supported. |
||||||
|
- Added error handling ([#97][97]) in `CameraListener.onCameraError(CameraException)`. |
||||||
|
At the moment, all exceptions there are unrecoverable. When the method is called, the camera is showing |
||||||
|
a black preview. This is a good moment to show an error dialog to the user. |
||||||
|
You can also try to `start()` again but that is not guaranteed to work. |
||||||
|
- Long requested ability to set the picture output size ([#99][99]). Can be done through |
||||||
|
`CameraView.setPictureSize()` or through new XML attributes starting with `cameraPictureSize`. |
||||||
|
Please refer to docs about it. |
||||||
|
- Deprecated `toggleFacing`. It was unreliable and will be removed. |
||||||
|
- Deprecated `getCaptureSize`. Use `getPictureSize` instead. |
||||||
|
- Fixed bugs. |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.3.2...v1.4.0 |
||||||
|
|
||||||
|
### v1.3.2 |
||||||
|
|
||||||
|
- Fixed a memory leak thanks to [@andrewmunn][andrewmunn] ([#92][92]) |
||||||
|
- Reduced memory usage when using cropOutput thanks to [@RobertoMorelos][RobertoMorelos] ([#93][93]) |
||||||
|
- Improved efficiency for Frame processors, recycle buffers and Frames ([#94][94]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.3.1...v1.3.2 |
||||||
|
|
||||||
|
### v1.3.1 |
||||||
|
|
||||||
|
- Fixed a bug that would make setFacing and other APIs freeze the camera ([#86][86]) |
||||||
|
- Fixed ConcurrentModificationExceptions during CameraListener callbacks ([#88][88]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.3.0...v1.3.1 |
||||||
|
|
||||||
|
## v1.3.0 |
||||||
|
|
||||||
|
- Ability to inject frame processors to do your own visual tasks (barcodes, facial recognition etc.) ([#82][82]) |
||||||
|
- Ability to inject external loggers (e.g. Crashlytics) to listen for internal logging events ([#80][80]) |
||||||
|
- Improved CameraUtils.decodeBitmap, you can now pass maxWidth and maxHeight to avoid OOM ([#83][83]) |
||||||
|
- Updated dependencies thanks to [@v-gar][v-gar] ([#73][73]) |
||||||
|
|
||||||
|
https://github.com/natario1/CameraView/compare/v1.2.3...v1.3.0 |
||||||
|
|
||||||
|
[aartikov]: https://github.com/aartikov |
||||||
|
[athornz]: https://github.com/athornz |
||||||
|
[v-gar]: https://github.com/v-gar |
||||||
|
[andrewmunn]: https://github.com/andrewmunn |
||||||
|
[chaitanyaraghav]: https://github.com/chaitanyaraghav |
||||||
|
[YeungKC]: https://github.com/YeungKC |
||||||
|
[RobertoMorelos]: https://github.com/RobertoMorelos |
||||||
|
[RocketRider]: https://github.com/RocketRider |
||||||
|
[xp-vit]: https://github.com/xp-vit |
||||||
|
[caleb-allen]: https://github.com/caleb-allen |
||||||
|
[ssakhavi]: https://github.com/ssakhavi |
||||||
|
[MatFl]: https://github.com/MatFl |
||||||
|
[Namazed]: https://github.com/Namazed |
||||||
|
[Keyrillanskiy]: https://github.com/Keyrillanskiy |
||||||
|
[mahdi-ninja]: https://github.com/mahdi-ninja |
||||||
|
[stefanJi]: https://github.com/stefanJi |
||||||
|
|
||||||
|
[73]: https://github.com/natario1/CameraView/pull/73 |
||||||
|
[80]: https://github.com/natario1/CameraView/pull/80 |
||||||
|
[82]: https://github.com/natario1/CameraView/pull/82 |
||||||
|
[83]: https://github.com/natario1/CameraView/pull/83 |
||||||
|
[86]: https://github.com/natario1/CameraView/pull/86 |
||||||
|
[88]: https://github.com/natario1/CameraView/pull/88 |
||||||
|
[92]: https://github.com/natario1/CameraView/pull/92 |
||||||
|
[93]: https://github.com/natario1/CameraView/pull/93 |
||||||
|
[94]: https://github.com/natario1/CameraView/pull/94 |
||||||
|
[97]: https://github.com/natario1/CameraView/pull/97 |
||||||
|
[99]: https://github.com/natario1/CameraView/pull/99 |
||||||
|
[101]: https://github.com/natario1/CameraView/pull/101 |
||||||
|
[104]: https://github.com/natario1/CameraView/pull/104 |
||||||
|
[105]: https://github.com/natario1/CameraView/pull/105 |
||||||
|
[112]: https://github.com/natario1/CameraView/pull/112 |
||||||
|
[133]: https://github.com/natario1/CameraView/pull/133 |
||||||
|
[143]: https://github.com/natario1/CameraView/pull/143 |
||||||
|
[162]: https://github.com/natario1/CameraView/pull/162 |
||||||
|
[167]: https://github.com/natario1/CameraView/pull/167 |
||||||
|
[170]: https://github.com/natario1/CameraView/pull/170 |
||||||
|
[172]: https://github.com/natario1/CameraView/pull/172 |
||||||
|
[173]: https://github.com/natario1/CameraView/pull/173 |
||||||
|
[174]: https://github.com/natario1/CameraView/pull/174 |
||||||
|
[190]: https://github.com/natario1/CameraView/pull/190 |
||||||
|
[205]: https://github.com/natario1/CameraView/pull/205 |
||||||
|
[222]: https://github.com/natario1/CameraView/pull/222 |
||||||
|
[224]: https://github.com/natario1/CameraView/pull/224 |
||||||
|
[245]: https://github.com/natario1/CameraView/pull/245 |
||||||
|
[264]: https://github.com/natario1/CameraView/pull/264 |
||||||
|
[265]: https://github.com/natario1/CameraView/pull/265 |
||||||
|
[290]: https://github.com/natario1/CameraView/pull/290 |
||||||
|
[297]: https://github.com/natario1/CameraView/pull/297 |
||||||
|
[332]: https://github.com/natario1/CameraView/pull/332 |
||||||
|
[334]: https://github.com/natario1/CameraView/pull/334 |
@ -0,0 +1,29 @@ |
|||||||
|
buildscript { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
google() |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
classpath 'com.android.tools.build:gradle:3.4.2' |
||||||
|
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 = 28 |
||||||
|
minSdkVersion = 15 |
||||||
|
targetSdkVersion = 28 |
||||||
|
} |
||||||
|
|
||||||
|
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,256 @@ |
|||||||
|
apply plugin: 'com.android.library' |
||||||
|
apply plugin: 'com.github.dcendents.android-maven' |
||||||
|
apply plugin: 'com.jfrog.bintray' |
||||||
|
|
||||||
|
// Required by bintray |
||||||
|
version = '2.0.0-rc2' |
||||||
|
group = 'com.otaliastudios' |
||||||
|
|
||||||
|
//region android dependencies |
||||||
|
|
||||||
|
def travis = System.getenv("TRAVIS") |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion rootProject.ext.compileSdkVersion |
||||||
|
|
||||||
|
defaultConfig { |
||||||
|
minSdkVersion rootProject.ext.minSdkVersion |
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion |
||||||
|
versionCode 1 |
||||||
|
versionName project.version |
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||||
|
if (travis) { |
||||||
|
testInstrumentationRunnerArgument "notAnnotation", "com.otaliastudios.cameraview.DoNotRunOnTravis" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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.0.0' |
||||||
|
api 'androidx.lifecycle:lifecycle-common:2.1.0-alpha01' |
||||||
|
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 |
||||||
|
if (travis) { |
||||||
|
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' |
||||||
|
exclude '**/internal/**' // This excludes our internal folder! |
||||||
|
} |
||||||
|
|
||||||
|
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 mergedCoverageReport(type: JacocoReport) { |
||||||
|
dependsOn "testDebugUnitTest" |
||||||
|
dependsOn "connectedCheck" |
||||||
|
|
||||||
|
// 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 (travis) { |
||||||
|
// All these classes are tested by the integration tests that we are not able to |
||||||
|
// run on the travis 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/picture/**.*') |
||||||
|
classFilter.add('**/com/otaliastudios/cameraview/video/**.*') |
||||||
|
// TODO these below could be testable ALSO outside of the integration tests |
||||||
|
classFilter.add('**/com/otaliastudios/cameraview/video/encoding/**.*') |
||||||
|
} |
||||||
|
classDirectories = fileTree(dir: classDir, excludes: classFilter); |
||||||
|
|
||||||
|
reports.html.enabled = true |
||||||
|
reports.xml.enabled = true |
||||||
|
reports.xml.destination file("$reportsDirectory/mergedCoverageReport/report.xml") |
||||||
|
} |
||||||
|
|
||||||
|
//endregion |
||||||
|
|
||||||
|
// export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home |
||||||
|
// 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,36 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview; |
|
||||||
|
|
||||||
import android.opengl.EGL14; |
|
||||||
|
|
||||||
import com.otaliastudios.opengl.core.EglCore; |
|
||||||
import com.otaliastudios.opengl.surface.EglOffscreenSurface; |
|
||||||
import com.otaliastudios.opengl.surface.EglSurface; |
|
||||||
|
|
||||||
import org.junit.After; |
|
||||||
import org.junit.Before; |
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
public abstract class BaseEglTest extends BaseTest { |
|
||||||
|
|
||||||
protected final static int WIDTH = 100; |
|
||||||
protected final static int HEIGHT = 100; |
|
||||||
|
|
||||||
protected EglCore eglCore; |
|
||||||
protected EglSurface eglSurface; |
|
||||||
|
|
||||||
@Before |
|
||||||
public void setUp() { |
|
||||||
eglCore = new EglCore(EGL14.EGL_NO_CONTEXT, EglCore.FLAG_RECORDABLE); |
|
||||||
eglSurface = new EglOffscreenSurface(eglCore, WIDTH, HEIGHT); |
|
||||||
eglSurface.makeCurrent(); |
|
||||||
} |
|
||||||
|
|
||||||
@After |
|
||||||
public void tearDown() { |
|
||||||
eglSurface.release(); |
|
||||||
eglSurface = null; |
|
||||||
eglCore.release(); |
|
||||||
eglCore = null; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,17 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
|
||||||
|
/** |
||||||
|
* Thanks to a testInstrumentationRunnerArgument in our build file, we will not |
||||||
|
* execute these tests on Travis CI. |
||||||
|
* The {@link RetentionPolicy#RUNTIME} is needed! |
||||||
|
* |
||||||
|
* https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner
|
||||||
|
* |
||||||
|
*/ |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
public @interface DoNotRunOnTravis { |
||||||
|
String because() default ""; |
||||||
|
} |
@ -1,51 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.controls.Engine; |
|
||||||
import com.otaliastudios.cameraview.frame.Frame; |
|
||||||
import com.otaliastudios.cameraview.frame.FrameProcessor; |
|
||||||
import com.otaliastudios.cameraview.tools.Op; |
|
||||||
import com.otaliastudios.cameraview.tools.Retry; |
|
||||||
import com.otaliastudios.cameraview.tools.SdkExclude; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.LargeTest; |
|
||||||
import androidx.test.filters.RequiresDevice; |
|
||||||
|
|
||||||
import java.util.Collection; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
|
|
||||||
/** |
|
||||||
* These tests work great on real devices, and are the only way to test actual CameraEngine |
|
||||||
* implementation - we really need to open the camera device. |
|
||||||
* Unfortunately they fail unreliably on emulated devices, due to some bug with the |
|
||||||
* emulated camera controller. |
|
||||||
*/ |
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@LargeTest |
|
||||||
// @RequiresDevice
|
|
||||||
public class Camera1IntegrationTest extends CameraIntegrationTest<Camera1Engine> { |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
protected Engine getEngine() { |
|
||||||
return Engine.CAMERA1; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected long getMeteringTimeoutMillis() { |
|
||||||
return Camera1Engine.AUTOFOCUS_END_DELAY_MILLIS; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void testFrameProcessing_maxSize() { |
|
||||||
// Camera1Engine does not support different sizes.
|
|
||||||
// super.testFrameProcessing_maxSize();
|
|
||||||
} |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine; |
|
||||||
|
|
||||||
import android.hardware.camera2.CameraCharacteristics; |
|
||||||
import android.hardware.camera2.CaptureRequest; |
|
||||||
import android.hardware.camera2.TotalCaptureResult; |
|
||||||
import android.os.Handler; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.controls.Engine; |
|
||||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
|
||||||
import com.otaliastudios.cameraview.engine.action.BaseAction; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.LargeTest; |
|
||||||
import androidx.test.filters.RequiresDevice; |
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch; |
|
||||||
|
|
||||||
/** |
|
||||||
* These tests work great on real devices, and are the only way to test actual CameraEngine |
|
||||||
* implementation - we really need to open the camera device. |
|
||||||
* Unfortunately they fail unreliably on emulated devices, due to some bug with the |
|
||||||
* emulated camera controller. |
|
||||||
*/ |
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@LargeTest |
|
||||||
// @RequiresDevice
|
|
||||||
public class Camera2IntegrationTest extends CameraIntegrationTest<Camera2Engine> { |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
protected Engine getEngine() { |
|
||||||
return Engine.CAMERA2; |
|
||||||
} |
|
||||||
|
|
||||||
/* @Override |
|
||||||
protected void onOpenSync() { |
|
||||||
super.onOpenSync(); |
|
||||||
// Extra wait for the first frame to be dispatched.
|
|
||||||
// This is because various classes require getLastResult to be non-null
|
|
||||||
// and that's typically the case in a real app.
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1); |
|
||||||
new BaseAction() { |
|
||||||
@Override |
|
||||||
public void onCaptureCompleted(@NonNull ActionHolder holder, |
|
||||||
@NonNull CaptureRequest request, |
|
||||||
@NonNull TotalCaptureResult result) { |
|
||||||
super.onCaptureCompleted(holder, request, result); |
|
||||||
latch.countDown(); |
|
||||||
setState(STATE_COMPLETED); |
|
||||||
} |
|
||||||
}.start(controller); |
|
||||||
try { latch.await(); } catch (InterruptedException ignore) {} |
|
||||||
} */ |
|
||||||
|
|
||||||
@Override |
|
||||||
protected long getMeteringTimeoutMillis() { |
|
||||||
return Camera2Engine.METER_TIMEOUT; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* setMaxDuration can crash on legacy devices (most emulator are), and I don't see |
|
||||||
* any way to fix this in code. They shouldn't use Camera2 at all. |
|
||||||
* @return true if possible. |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
protected boolean canSetVideoMaxDuration() { |
|
||||||
if (!super.canSetVideoMaxDuration()) return false; |
|
||||||
boolean shouldOpen = !camera.isOpened(); |
|
||||||
if (shouldOpen) openSync(true); |
|
||||||
boolean result = controller.readCharacteristic( |
|
||||||
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) |
|
||||||
!= CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
|
||||||
if (shouldOpen) closeSync(true); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void testFrameProcessing_freezeRelease() { |
|
||||||
// Camera2 Frames are not freezable.
|
|
||||||
// super.testFrameProcessing_freezeRelease();
|
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,30 @@ |
|||||||
|
package com.otaliastudios.cameraview.engine; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.DoNotRunOnTravis; |
||||||
|
import com.otaliastudios.cameraview.controls.Engine; |
||||||
|
|
||||||
|
import org.junit.Ignore; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||||
|
import androidx.test.filters.LargeTest; |
||||||
|
|
||||||
|
/** |
||||||
|
* These tests work great on real devices, and are the only way to test actual CameraEngine |
||||||
|
* implementation - we really need to open the camera device. |
||||||
|
* Unfortunately they fail unreliably on emulated devices, due to some bug with the |
||||||
|
* emulated camera controller. |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@LargeTest |
||||||
|
@DoNotRunOnTravis(because = "These do work but fail on CI emulators, due to bugs in the Camera1 emulated devices.") |
||||||
|
public class CameraIntegration1Test extends CameraIntegrationTest { |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
protected Engine getEngine() { |
||||||
|
return Engine.CAMERA1; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package com.otaliastudios.cameraview.engine; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.DoNotRunOnTravis; |
||||||
|
import com.otaliastudios.cameraview.controls.Engine; |
||||||
|
|
||||||
|
import org.junit.Ignore; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||||
|
import androidx.test.filters.LargeTest; |
||||||
|
|
||||||
|
/** |
||||||
|
* These tests work great on real devices, and are the only way to test actual CameraEngine |
||||||
|
* implementation - we really need to open the camera device. |
||||||
|
* Unfortunately they fail unreliably on emulated devices, due to some bug with the |
||||||
|
* emulated camera controller. |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@LargeTest |
||||||
|
@DoNotRunOnTravis(because = "These do work but fail on CI emulators.") |
||||||
|
public class CameraIntegration2Test extends CameraIntegrationTest { |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
protected Engine getEngine() { |
||||||
|
return Engine.CAMERA2; |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -1,101 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine.mappers; |
|
||||||
|
|
||||||
|
|
||||||
import android.hardware.Camera; |
|
||||||
import android.util.Pair; |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.controls.Facing; |
|
||||||
import com.otaliastudios.cameraview.controls.Flash; |
|
||||||
import com.otaliastudios.cameraview.controls.Hdr; |
|
||||||
import com.otaliastudios.cameraview.controls.WhiteBalance; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.Set; |
|
||||||
|
|
||||||
import static android.hardware.camera2.CameraMetadata.*; |
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class Camera2MapperTest extends BaseTest { |
|
||||||
|
|
||||||
private Camera2Mapper mapper = Camera2Mapper.get(); |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testMap() { |
|
||||||
List<Pair<Integer, Integer>> values = mapper.mapFlash(Flash.OFF); |
|
||||||
assertEquals(2, values.size()); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_OFF))); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_OFF))); |
|
||||||
values = mapper.mapFlash(Flash.TORCH); |
|
||||||
assertEquals(2, values.size()); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_TORCH))); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_TORCH))); |
|
||||||
values = mapper.mapFlash(Flash.AUTO); |
|
||||||
assertEquals(2, values.size()); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH, FLASH_MODE_OFF))); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE, FLASH_MODE_OFF))); |
|
||||||
values = mapper.mapFlash(Flash.ON); |
|
||||||
assertEquals(1, values.size()); |
|
||||||
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_ALWAYS_FLASH, FLASH_MODE_OFF))); |
|
||||||
|
|
||||||
assertEquals(mapper.mapFacing(Facing.BACK), LENS_FACING_BACK); |
|
||||||
assertEquals(mapper.mapFacing(Facing.FRONT), LENS_FACING_FRONT); |
|
||||||
|
|
||||||
assertEquals(mapper.mapHdr(Hdr.OFF), CONTROL_SCENE_MODE_DISABLED); |
|
||||||
assertEquals(mapper.mapHdr(Hdr.ON), CONTROL_SCENE_MODE_HDR); |
|
||||||
|
|
||||||
assertEquals(mapper.mapWhiteBalance(WhiteBalance.AUTO), CONTROL_AWB_MODE_AUTO); |
|
||||||
assertEquals(mapper.mapWhiteBalance(WhiteBalance.DAYLIGHT), CONTROL_AWB_MODE_DAYLIGHT); |
|
||||||
assertEquals(mapper.mapWhiteBalance(WhiteBalance.CLOUDY), CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); |
|
||||||
assertEquals(mapper.mapWhiteBalance(WhiteBalance.INCANDESCENT), CONTROL_AWB_MODE_INCANDESCENT); |
|
||||||
assertEquals(mapper.mapWhiteBalance(WhiteBalance.FLUORESCENT), CONTROL_AWB_MODE_FLUORESCENT); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
public void testUnmap() { |
|
||||||
Set<Flash> values; |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_OFF); |
|
||||||
assertEquals(values.size(), 2); |
|
||||||
assertTrue(values.contains(Flash.OFF)); |
|
||||||
assertTrue(values.contains(Flash.TORCH)); |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_ON); |
|
||||||
assertEquals(values.size(), 2); |
|
||||||
assertTrue(values.contains(Flash.OFF)); |
|
||||||
assertTrue(values.contains(Flash.TORCH)); |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_ALWAYS_FLASH); |
|
||||||
assertEquals(values.size(), 1); |
|
||||||
assertTrue(values.contains(Flash.ON)); |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH); |
|
||||||
assertEquals(values.size(), 1); |
|
||||||
assertTrue(values.contains(Flash.AUTO)); |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); |
|
||||||
assertEquals(values.size(), 1); |
|
||||||
assertTrue(values.contains(Flash.AUTO)); |
|
||||||
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_EXTERNAL_FLASH); |
|
||||||
assertEquals(values.size(), 0); |
|
||||||
|
|
||||||
assertEquals(Facing.BACK, mapper.unmapFacing(LENS_FACING_BACK)); |
|
||||||
assertEquals(Facing.FRONT, mapper.unmapFacing(LENS_FACING_FRONT)); |
|
||||||
|
|
||||||
assertEquals(Hdr.OFF, mapper.unmapHdr(CONTROL_SCENE_MODE_DISABLED)); |
|
||||||
assertEquals(Hdr.ON, mapper.unmapHdr(CONTROL_SCENE_MODE_HDR)); |
|
||||||
|
|
||||||
assertEquals(WhiteBalance.AUTO, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_AUTO)); |
|
||||||
assertEquals(WhiteBalance.DAYLIGHT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_DAYLIGHT)); |
|
||||||
assertEquals(WhiteBalance.CLOUDY, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_CLOUDY_DAYLIGHT)); |
|
||||||
assertEquals(WhiteBalance.INCANDESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_INCANDESCENT)); |
|
||||||
assertEquals(WhiteBalance.FLUORESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_FLUORESCENT)); |
|
||||||
} |
|
||||||
} |
|
@ -1,172 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import android.opengl.GLES20; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseEglTest; |
|
||||||
import com.otaliastudios.cameraview.internal.GlTextureDrawer; |
|
||||||
import com.otaliastudios.opengl.program.GlTextureProgram; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.junit.Assert.assertNull; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.Mockito.never; |
|
||||||
import static org.mockito.Mockito.spy; |
|
||||||
import static org.mockito.Mockito.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class BaseFilterTest extends BaseEglTest { |
|
||||||
|
|
||||||
public static class TestFilter extends BaseFilter implements TwoParameterFilter { |
|
||||||
|
|
||||||
private float param1; |
|
||||||
private float param2; |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String getFragmentShader() { |
|
||||||
return createDefaultFragmentShader(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void setParameter1(float value) { |
|
||||||
param1 = value; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void setParameter2(float value) { |
|
||||||
param2 = value; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public float getParameter1() { |
|
||||||
return param1; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public float getParameter2() { |
|
||||||
return param2; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private TestFilter filter; |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCreateDefaultFragmentShader() { |
|
||||||
filter = new TestFilter(); |
|
||||||
filter.fragmentTextureCoordinateName = "XXX"; |
|
||||||
String defaultFragmentShader = filter.createDefaultFragmentShader(); |
|
||||||
assertNotNull(defaultFragmentShader); |
|
||||||
assertTrue(defaultFragmentShader.contains(filter.fragmentTextureCoordinateName)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCreateDefaultVertexShader() { |
|
||||||
filter = new TestFilter(); |
|
||||||
filter.vertexModelViewProjectionMatrixName = "AAA"; |
|
||||||
filter.vertexPositionName = "BBB"; |
|
||||||
filter.vertexTextureCoordinateName = "CCC"; |
|
||||||
filter.vertexTransformMatrixName = "DDD"; |
|
||||||
filter.fragmentTextureCoordinateName = "EEE"; |
|
||||||
String defaultVertexShader = filter.createDefaultVertexShader(); |
|
||||||
assertNotNull(defaultVertexShader); |
|
||||||
assertTrue(defaultVertexShader.contains(filter.vertexModelViewProjectionMatrixName)); |
|
||||||
assertTrue(defaultVertexShader.contains(filter.vertexPositionName)); |
|
||||||
assertTrue(defaultVertexShader.contains(filter.vertexTextureCoordinateName)); |
|
||||||
assertTrue(defaultVertexShader.contains(filter.vertexTransformMatrixName)); |
|
||||||
assertTrue(defaultVertexShader.contains(filter.fragmentTextureCoordinateName)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnProgramCreate() { |
|
||||||
filter = new TestFilter(); |
|
||||||
int handle = GlTextureProgram.create(filter.getVertexShader(), filter.getFragmentShader()); |
|
||||||
filter.onCreate(handle); |
|
||||||
assertNotNull(filter.program); |
|
||||||
filter.onDestroy(); |
|
||||||
assertNull(filter.program); |
|
||||||
GLES20.glDeleteProgram(handle); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testDraw_whenInvalid() { |
|
||||||
filter = spy(new TestFilter()); |
|
||||||
float[] matrix = new float[16]; |
|
||||||
filter.draw(0L, matrix); |
|
||||||
verify(filter, never()).onPreDraw(0L, matrix); |
|
||||||
verify(filter, never()).onDraw(0L); |
|
||||||
verify(filter, never()).onPostDraw(0L); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testDraw() { |
|
||||||
// Use a drawer which cares about GL setup.
|
|
||||||
filter = spy(new TestFilter()); |
|
||||||
GlTextureDrawer drawer = new GlTextureDrawer(); |
|
||||||
drawer.setFilter(filter); |
|
||||||
|
|
||||||
float[] matrix = drawer.getTextureTransform(); |
|
||||||
drawer.draw(0L); |
|
||||||
verify(filter, times(1)).onPreDraw(0L, matrix); |
|
||||||
verify(filter, times(1)).onDraw(0L); |
|
||||||
verify(filter, times(1)).onPostDraw(0L); |
|
||||||
|
|
||||||
drawer.release(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test(expected = RuntimeException.class) |
|
||||||
public void testOnCopy_invalid() { |
|
||||||
// Anonymous inner classes do not have a public constructor.
|
|
||||||
Filter filter = new BaseFilter() { |
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String getFragmentShader() { |
|
||||||
return "whatever"; |
|
||||||
} |
|
||||||
}; |
|
||||||
filter.copy(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnCopy() { |
|
||||||
filter = new TestFilter(); |
|
||||||
BaseFilter other = filter.copy(); |
|
||||||
assertTrue(other instanceof TestFilter); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCopy_withSize() { |
|
||||||
filter = new TestFilter(); |
|
||||||
filter.setSize(WIDTH, HEIGHT); |
|
||||||
BaseFilter other = filter.copy(); |
|
||||||
assertEquals(WIDTH, other.size.getWidth()); |
|
||||||
assertEquals(HEIGHT, other.size.getHeight()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCopy_withParameter1() { |
|
||||||
filter = new TestFilter(); |
|
||||||
filter.setParameter1(0.5F); |
|
||||||
TestFilter other = (TestFilter) filter.copy(); |
|
||||||
assertEquals(filter.getParameter1(), other.getParameter1(), 0.001F); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCopy_withParameter2() { |
|
||||||
filter = new TestFilter(); |
|
||||||
filter.setParameter2(0.5F); |
|
||||||
TestFilter other = (TestFilter) filter.copy(); |
|
||||||
assertEquals(filter.getParameter2(), other.getParameter2(), 0.001F); |
|
||||||
} |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import android.content.res.TypedArray; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.R; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertNotNull; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class FilterParserTest extends BaseTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFallback() { |
|
||||||
TypedArray array = mock(TypedArray.class); |
|
||||||
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(false); |
|
||||||
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(null); |
|
||||||
FilterParser parser = new FilterParser(array); |
|
||||||
assertNotNull(parser.getFilter()); |
|
||||||
assertTrue(parser.getFilter() instanceof NoFilter); |
|
||||||
} |
|
||||||
@Test |
|
||||||
public void testConstructor() { |
|
||||||
TypedArray array = mock(TypedArray.class); |
|
||||||
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(true); |
|
||||||
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(MyFilter.class.getName()); |
|
||||||
FilterParser parser = new FilterParser(array); |
|
||||||
assertNotNull(parser.getFilter()); |
|
||||||
assertTrue(parser.getFilter() instanceof MyFilter); |
|
||||||
} |
|
||||||
|
|
||||||
public static class MyFilter extends BaseFilter { |
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String getFragmentShader() { |
|
||||||
return createDefaultFragmentShader(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class FiltersTest extends BaseTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testNewInstance() { |
|
||||||
// At least tests that all our default filters have a no-args constructor.
|
|
||||||
Filters[] filtersArray = Filters.values(); |
|
||||||
for (Filters filters : filtersArray) { |
|
||||||
Filter filter = filters.newInstance(); |
|
||||||
assertNotNull(filter); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,212 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import android.opengl.GLES20; |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseEglTest; |
|
||||||
import com.otaliastudios.cameraview.filters.AutoFixFilter; |
|
||||||
import com.otaliastudios.cameraview.filters.BrightnessFilter; |
|
||||||
import com.otaliastudios.cameraview.filters.DuotoneFilter; |
|
||||||
import com.otaliastudios.cameraview.filters.VignetteFilter; |
|
||||||
import com.otaliastudios.cameraview.internal.GlTextureDrawer; |
|
||||||
import com.otaliastudios.opengl.program.GlProgram; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
import org.mockito.invocation.InvocationOnMock; |
|
||||||
import org.mockito.stubbing.Answer; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertFalse; |
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.junit.Assert.assertNull; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.ArgumentMatchers.any; |
|
||||||
import static org.mockito.ArgumentMatchers.anyInt; |
|
||||||
import static org.mockito.ArgumentMatchers.eq; |
|
||||||
import static org.mockito.Mockito.atLeastOnce; |
|
||||||
import static org.mockito.Mockito.doAnswer; |
|
||||||
import static org.mockito.Mockito.never; |
|
||||||
import static org.mockito.Mockito.spy; |
|
||||||
import static org.mockito.Mockito.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class MultiFilterTest extends BaseEglTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testConstructor1() { |
|
||||||
MultiFilter multiFilter = new MultiFilter( |
|
||||||
new DuotoneFilter(), |
|
||||||
new AutoFixFilter() |
|
||||||
); |
|
||||||
assertEquals(2, multiFilter.filters.size()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testConstructor2() { |
|
||||||
List<Filter> filters = new ArrayList<>(); |
|
||||||
filters.add(new DuotoneFilter()); |
|
||||||
filters.add(new AutoFixFilter()); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filters); |
|
||||||
assertEquals(2, multiFilter.filters.size()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testAddFilter() { |
|
||||||
MultiFilter multiFilter = new MultiFilter(); |
|
||||||
assertEquals(0, multiFilter.filters.size()); |
|
||||||
multiFilter.addFilter(new DuotoneFilter()); |
|
||||||
assertEquals(1, multiFilter.filters.size()); |
|
||||||
multiFilter.addFilter(new AutoFixFilter()); |
|
||||||
assertEquals(2, multiFilter.filters.size()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testAddFilter_multi() { |
|
||||||
MultiFilter multiFilter = new MultiFilter(new DuotoneFilter()); |
|
||||||
assertEquals(1, multiFilter.filters.size()); |
|
||||||
MultiFilter other = new MultiFilter( |
|
||||||
new AutoFixFilter(), |
|
||||||
new BrightnessFilter(), |
|
||||||
new VignetteFilter()); |
|
||||||
multiFilter.addFilter(other); |
|
||||||
assertEquals(4, multiFilter.filters.size()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testSetSize() { |
|
||||||
DuotoneFilter filter = new DuotoneFilter(); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
MultiFilter.State state = multiFilter.states.get(filter); |
|
||||||
assertNotNull(state); |
|
||||||
assertNull(state.size); |
|
||||||
multiFilter.setSize(WIDTH, HEIGHT); |
|
||||||
assertNotNull(state.size); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCopy() { |
|
||||||
DuotoneFilter filter = spy(new DuotoneFilter()); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
MultiFilter multiFilterCopy = (MultiFilter) multiFilter.copy(); |
|
||||||
assertEquals(1, multiFilterCopy.filters.size()); |
|
||||||
verify(filter, times(1)).onCopy(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testParameter1() { |
|
||||||
DuotoneFilter filter = new DuotoneFilter(); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
float desired = 0.21582F; // whatever
|
|
||||||
multiFilter.setParameter1(desired); |
|
||||||
assertEquals(desired, multiFilter.getParameter1(), 0.001F); |
|
||||||
assertEquals(desired, filter.getParameter1(), 0.001F); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testParameter2() { |
|
||||||
DuotoneFilter filter = new DuotoneFilter(); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
float desired = 0.21582F; // whatever
|
|
||||||
multiFilter.setParameter2(desired); |
|
||||||
assertEquals(desired, multiFilter.getParameter2(), 0.001F); |
|
||||||
assertEquals(desired, filter.getParameter2(), 0.001F); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnCreate_isLazy() { |
|
||||||
DuotoneFilter filter = spy(new DuotoneFilter()); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
|
|
||||||
int program = GlProgram.create(multiFilter.getVertexShader(), |
|
||||||
multiFilter.getFragmentShader()); |
|
||||||
multiFilter.onCreate(program); |
|
||||||
verify(filter, never()).onCreate(anyInt()); |
|
||||||
|
|
||||||
multiFilter.onDestroy(); |
|
||||||
GLES20.glDeleteProgram(program); |
|
||||||
verify(filter, never()).onDestroy(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testDraw_simple() { |
|
||||||
DuotoneFilter filter = spy(new DuotoneFilter()); |
|
||||||
MultiFilter multiFilter = new MultiFilter(filter); |
|
||||||
multiFilter.setSize(WIDTH, HEIGHT); |
|
||||||
GlTextureDrawer drawer = new GlTextureDrawer(); |
|
||||||
drawer.setFilter(multiFilter); |
|
||||||
float[] matrix = drawer.getTextureTransform(); |
|
||||||
drawer.draw(0L); |
|
||||||
drawer.release(); |
|
||||||
|
|
||||||
// The child should have experienced the whole lifecycle.
|
|
||||||
verify(filter, atLeastOnce()).getVertexShader(); |
|
||||||
verify(filter, atLeastOnce()).getFragmentShader(); |
|
||||||
verify(filter, atLeastOnce()).setSize(anyInt(), anyInt()); |
|
||||||
verify(filter, times(1)).onCreate(anyInt()); |
|
||||||
verify(filter, times(1)).draw(0L, matrix); |
|
||||||
verify(filter, times(1)).onDestroy(); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testDraw_multi() { |
|
||||||
// Want to test that when filter1 is drawn, the current framebuffer is a
|
|
||||||
// non-default one. When filter2 is drawn, the current framebuffer is 0.
|
|
||||||
final DuotoneFilter filter1 = spy(new DuotoneFilter()); |
|
||||||
final DuotoneFilter filter2 = spy(new DuotoneFilter()); |
|
||||||
final MultiFilter multiFilter = new MultiFilter(filter1, filter2); |
|
||||||
multiFilter.setSize(WIDTH, HEIGHT); |
|
||||||
GlTextureDrawer drawer = new GlTextureDrawer(); |
|
||||||
drawer.setFilter(multiFilter); |
|
||||||
float[] matrix = drawer.getTextureTransform(); |
|
||||||
final int[] result = new int[1]; |
|
||||||
|
|
||||||
doAnswer(new Answer() { |
|
||||||
@Override |
|
||||||
public Object answer(InvocationOnMock invocation) { |
|
||||||
MultiFilter.State state = multiFilter.states.get(filter1); |
|
||||||
assertNotNull(state); |
|
||||||
assertTrue(state.isProgramCreated); |
|
||||||
assertTrue(state.isFramebufferCreated); |
|
||||||
|
|
||||||
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); |
|
||||||
// assertTrue(result[0] != 0);
|
|
||||||
return null; |
|
||||||
} |
|
||||||
}).when(filter1).draw(0L, matrix); |
|
||||||
|
|
||||||
// Note: second filter is drawn with the identity matrix!
|
|
||||||
doAnswer(new Answer() { |
|
||||||
@Override |
|
||||||
public Object answer(InvocationOnMock invocation) { |
|
||||||
// The last filter has no FBO / texture.
|
|
||||||
MultiFilter.State state = multiFilter.states.get(filter2); |
|
||||||
assertNotNull(state); |
|
||||||
assertTrue(state.isProgramCreated); |
|
||||||
assertFalse(state.isFramebufferCreated); |
|
||||||
|
|
||||||
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); |
|
||||||
assertEquals(0, result[0]); |
|
||||||
return null; |
|
||||||
|
|
||||||
} |
|
||||||
}).when(filter2).draw(eq(0L), any(float[].class)); |
|
||||||
|
|
||||||
drawer.draw(0L); |
|
||||||
drawer.release(); |
|
||||||
|
|
||||||
// Verify that both are drawn.
|
|
||||||
verify(filter1, times(1)).draw(0L, matrix); |
|
||||||
verify(filter2, times(1)).draw(eq(0L), any(float[].class)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class NoFilterTest extends BaseTest { |
|
||||||
|
|
||||||
public static class DummyFilter extends BaseFilter { |
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public String getFragmentShader() { |
|
||||||
return "whatever"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetFragmentShader() { |
|
||||||
NoFilter filter = new NoFilter(); |
|
||||||
String defaultFragmentShader = new DummyFilter().createDefaultFragmentShader(); |
|
||||||
assertEquals(defaultFragmentShader, filter.getFragmentShader()); |
|
||||||
} |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.filter; |
|
||||||
|
|
||||||
|
|
||||||
import android.content.res.TypedArray; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.R; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertNotNull; |
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class SimpleFilterTest extends BaseTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetFragmentShader() { |
|
||||||
String shader = "shader"; |
|
||||||
SimpleFilter filter = new SimpleFilter(shader); |
|
||||||
assertEquals(shader, filter.getFragmentShader()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCopy() { |
|
||||||
String shader = "shader"; |
|
||||||
SimpleFilter filter = new SimpleFilter(shader); |
|
||||||
BaseFilter copy = filter.copy(); |
|
||||||
assertTrue(copy instanceof SimpleFilter); |
|
||||||
SimpleFilter simpleCopy = (SimpleFilter) copy; |
|
||||||
assertEquals(shader, simpleCopy.getFragmentShader()); |
|
||||||
} |
|
||||||
} |
|
@ -1,112 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.frame; |
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.ImageFormat; |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.engine.offset.Angles; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
|
|
||||||
import org.junit.After; |
|
||||||
import org.junit.Before; |
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.mockito.Matchers.any; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.never; |
|
||||||
import static org.mockito.Mockito.reset; |
|
||||||
import static org.mockito.Mockito.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class ByteBufferFrameManagerTest extends BaseTest { |
|
||||||
|
|
||||||
private final Angles angles = new Angles(); |
|
||||||
private ByteBufferFrameManager.BufferCallback callback; |
|
||||||
|
|
||||||
@Before |
|
||||||
public void setUp() { |
|
||||||
callback = mock(ByteBufferFrameManager.BufferCallback.class); |
|
||||||
} |
|
||||||
|
|
||||||
@After |
|
||||||
public void tearDown() { |
|
||||||
callback = null; |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testAllocate() { |
|
||||||
ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
verify(callback, times(1)).onBufferAvailable(any(byte[].class)); |
|
||||||
reset(callback); |
|
||||||
|
|
||||||
manager = new ByteBufferFrameManager(5, callback); |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
verify(callback, times(5)).onBufferAvailable(any(byte[].class)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnFrameReleased_alreadyFull() { |
|
||||||
ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
int length = manager.getFrameBytes(); |
|
||||||
|
|
||||||
Frame frame1 = manager.getFrame(new byte[length], 0); |
|
||||||
assertNotNull(frame1); |
|
||||||
// Since frame1 is already taken and poolSize = 1, getFrame() would return null.
|
|
||||||
// To create a new frame, freeze the first one.
|
|
||||||
Frame frame2 = frame1.freeze(); |
|
||||||
// Now release the first frame so it goes back into the pool.
|
|
||||||
manager.onFrameReleased(frame1, (byte[]) frame1.getData()); |
|
||||||
reset(callback); |
|
||||||
// Release the second. The pool is already full, so onBufferAvailable should not be called
|
|
||||||
// since this Frame instance will NOT be reused.
|
|
||||||
manager.onFrameReleased(frame2, (byte[]) frame2.getData()); |
|
||||||
verify(callback, never()).onBufferAvailable((byte[]) frame2.getData()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnFrameReleased_sameLength() { |
|
||||||
ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
int length = manager.getFrameBytes(); |
|
||||||
|
|
||||||
// A camera preview frame comes. Request a frame.
|
|
||||||
byte[] picture = new byte[length]; |
|
||||||
Frame frame = manager.getFrame(picture, 0); |
|
||||||
assertNotNull(frame); |
|
||||||
|
|
||||||
// Release the frame and ensure that onBufferAvailable is called.
|
|
||||||
reset(callback); |
|
||||||
manager.onFrameReleased(frame, (byte[]) frame.getData()); |
|
||||||
verify(callback, times(1)).onBufferAvailable(picture); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testOnFrameReleased_differentLength() { |
|
||||||
ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
int length = manager.getFrameBytes(); |
|
||||||
|
|
||||||
// A camera preview frame comes. Request a frame.
|
|
||||||
byte[] picture = new byte[length]; |
|
||||||
Frame frame = manager.getFrame(picture, 0); |
|
||||||
assertNotNull(frame); |
|
||||||
|
|
||||||
// Don't release the frame. Change the allocation size.
|
|
||||||
manager.setUp(ImageFormat.NV16, new Size(15, 15), angles); |
|
||||||
|
|
||||||
// Now release the old frame and ensure that onBufferAvailable is NOT called,
|
|
||||||
// because the released data has wrong length.
|
|
||||||
manager.onFrameReleased(frame, (byte[]) frame.getData()); |
|
||||||
reset(callback); |
|
||||||
verify(callback, never()).onBufferAvailable(picture); |
|
||||||
} |
|
||||||
} |
|
@ -1,73 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.frame; |
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.ImageFormat; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.engine.offset.Angles; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.junit.Assert.assertNull; |
|
||||||
import static org.mockito.Matchers.any; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class FrameManagerTest extends BaseTest { |
|
||||||
|
|
||||||
private final Angles angles = new Angles(); |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFrameRecycling() { |
|
||||||
// A 1-pool manager will always recycle the same frame.
|
|
||||||
FrameManager<String> manager = new FrameManager<String>(1, String.class) { |
|
||||||
@Override |
|
||||||
protected void onFrameDataReleased(@NonNull String data, boolean recycled) { } |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
protected String onCloneFrameData(@NonNull String data) { |
|
||||||
return data; |
|
||||||
} |
|
||||||
}; |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
|
|
||||||
Frame first = manager.getFrame("foo", 0); |
|
||||||
assertNotNull(first); |
|
||||||
first.release(); |
|
||||||
Frame second = manager.getFrame("bar", 0); |
|
||||||
assertNotNull(second); |
|
||||||
second.release(); |
|
||||||
assertEquals(first, second); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetFrame() { |
|
||||||
FrameManager<String> manager = new FrameManager<String>(1, String.class) { |
|
||||||
@Override |
|
||||||
protected void onFrameDataReleased(@NonNull String data, boolean recycled) { } |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
protected String onCloneFrameData(@NonNull String data) { |
|
||||||
return data; |
|
||||||
} |
|
||||||
}; |
|
||||||
manager.setUp(ImageFormat.NV21, new Size(50, 50), angles); |
|
||||||
|
|
||||||
Frame first = manager.getFrame("foo", 0); |
|
||||||
assertNotNull(first); |
|
||||||
Frame second = manager.getFrame("bar", 0); |
|
||||||
assertNull(second); |
|
||||||
} |
|
||||||
} |
|
@ -1,219 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
|
||||||
|
|
||||||
|
|
||||||
import android.media.MediaCodecInfo; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.MediumTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
|
|
||||||
import org.junit.After; |
|
||||||
import org.junit.Before; |
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertFalse; |
|
||||||
import static org.junit.Assert.assertNotEquals; |
|
||||||
import static org.junit.Assert.assertNotNull; |
|
||||||
import static org.junit.Assert.assertNull; |
|
||||||
import static org.junit.Assert.assertSame; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@MediumTest |
|
||||||
public class DeviceEncodersTest extends BaseTest { |
|
||||||
|
|
||||||
// This is guaranteed to work, see
|
|
||||||
// https://developer.android.com/guide/topics/media/media-formats
|
|
||||||
private final static Size GUARANTEED_SIZE = new Size(176, 144); |
|
||||||
|
|
||||||
private boolean enabled; |
|
||||||
|
|
||||||
@Before |
|
||||||
public void setUp() { |
|
||||||
enabled = DeviceEncoders.ENABLED; |
|
||||||
} |
|
||||||
|
|
||||||
@After |
|
||||||
public void tearDown() { |
|
||||||
DeviceEncoders.ENABLED = enabled; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
private DeviceEncoders create() { |
|
||||||
return new DeviceEncoders(DeviceEncoders.MODE_RESPECT_ORDER, |
|
||||||
"video/avc", |
|
||||||
"audio/mp4a-latm", |
|
||||||
0, |
|
||||||
0); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetDeviceEncoders() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
List<MediaCodecInfo> infos = deviceEncoders.getDeviceEncoders(); |
|
||||||
for (MediaCodecInfo info : infos) { |
|
||||||
assertTrue(info.isEncoder()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testIsHardwareEncoder() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
assertFalse(deviceEncoders.isHardwareEncoder("OMX.google.encoder")); |
|
||||||
assertTrue(deviceEncoders.isHardwareEncoder("OMX.other.encoder")); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFindDeviceEncoder() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders(); |
|
||||||
MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, |
|
||||||
"video/avc", DeviceEncoders.MODE_RESPECT_ORDER, 0); |
|
||||||
assertNotNull(encoder); |
|
||||||
List<String> encoderTypes = Arrays.asList(encoder.getSupportedTypes()); |
|
||||||
assertTrue(encoderTypes.contains("video/avc")); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetVideoEncoder() { |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
assertNotNull(deviceEncoders.getVideoEncoder()); |
|
||||||
} |
|
||||||
|
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
assertNull(deviceEncoders.getVideoEncoder()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetAudioEncoder() { |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
assertNotNull(deviceEncoders.getAudioEncoder()); |
|
||||||
} |
|
||||||
|
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
assertNull(deviceEncoders.getAudioEncoder()); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedVideoSize_disabled() { |
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
Size input = new Size(GUARANTEED_SIZE.getWidth(), GUARANTEED_SIZE.getHeight()); |
|
||||||
Size output = deviceEncoders.getSupportedVideoSize(input); |
|
||||||
assertSame(input, output); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedVideoSize_scalesDown() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
Size input = new Size( |
|
||||||
GUARANTEED_SIZE.getWidth() * 1000, |
|
||||||
GUARANTEED_SIZE.getHeight() * 1000); |
|
||||||
try { |
|
||||||
Size output = deviceEncoders.getSupportedVideoSize(input); |
|
||||||
assertTrue(AspectRatio.of(input).matches(output, 0.01F)); |
|
||||||
} catch (RuntimeException e) { |
|
||||||
// The scaled down size happens to be not supported.
|
|
||||||
// I see no way of testing this easily if we're not sure of supported ranges.
|
|
||||||
// This depends highly on the alignment since scaling down, while keeping AR,
|
|
||||||
// can change the alignment and require width / height changes.
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedVideoSize_aligns() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
Size input = new Size(GUARANTEED_SIZE.getWidth() + 1, |
|
||||||
GUARANTEED_SIZE.getHeight() + 1); |
|
||||||
Size output = deviceEncoders.getSupportedVideoSize(input); |
|
||||||
assertTrue(output.getWidth() <= input.getWidth()); |
|
||||||
assertTrue(output.getHeight() <= input.getHeight()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedVideoBitRate_disabled() { |
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
int input = 1000; |
|
||||||
int output = deviceEncoders.getSupportedVideoBitRate(input); |
|
||||||
assertEquals(input, output); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedVideoBitRate_enabled() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
// Ensure it's clamped: we can pass a negative value and check it's >= 0.
|
|
||||||
int input = -1000; |
|
||||||
int output = deviceEncoders.getSupportedVideoBitRate(input); |
|
||||||
assertNotEquals(input, output); |
|
||||||
assertTrue(output >= 0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedAudioBitRate_disabled() { |
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
int input = 1000; |
|
||||||
int output = deviceEncoders.getSupportedAudioBitRate(input); |
|
||||||
assertEquals(input, output); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedAudioBitRate_enabled() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
// Ensure it's clamped: we can pass a negative value and check it's >= 0.
|
|
||||||
int input = -1000; |
|
||||||
int output = deviceEncoders.getSupportedAudioBitRate(input); |
|
||||||
assertNotEquals(input, output); |
|
||||||
assertTrue(output >= 0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedFrameRate_disabled() { |
|
||||||
DeviceEncoders.ENABLED = false; |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
int input = 1000; |
|
||||||
int output = deviceEncoders.getSupportedVideoFrameRate(GUARANTEED_SIZE, input); |
|
||||||
assertEquals(input, output); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetSupportedFrameRate_enabled() { |
|
||||||
DeviceEncoders deviceEncoders = create(); |
|
||||||
if (DeviceEncoders.ENABLED) { |
|
||||||
// Ensure it's clamped: we can pass a negative value and check it's >= 0.
|
|
||||||
int input = -10; |
|
||||||
Size inputSize = deviceEncoders.getSupportedVideoSize(GUARANTEED_SIZE); |
|
||||||
int output = deviceEncoders.getSupportedVideoFrameRate(inputSize, input); |
|
||||||
assertNotEquals(input, output); |
|
||||||
assertTrue(output >= 0); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,109 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
|
||||||
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
import android.view.OrientationEventListener; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.internal.OrientationHelper; |
|
||||||
|
|
||||||
import org.junit.After; |
|
||||||
import org.junit.Before; |
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.*; |
|
||||||
import static org.mockito.Mockito.*; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class OrientationHelperTest extends BaseTest { |
|
||||||
|
|
||||||
private OrientationHelper helper; |
|
||||||
private OrientationHelper.Callback callback; |
|
||||||
|
|
||||||
@Before |
|
||||||
public void setUp() { |
|
||||||
uiSync(new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
callback = mock(OrientationHelper.Callback.class); |
|
||||||
helper = new OrientationHelper(getContext(), callback); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@After |
|
||||||
public void tearDown() { |
|
||||||
callback = null; |
|
||||||
helper = null; |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testEnable() { |
|
||||||
// On some API levels, enable() needs to be run on the UI thread.
|
|
||||||
uiSync(new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
assertEquals(helper.getLastDisplayOffset(), -1); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), -1); |
|
||||||
|
|
||||||
helper.enable(); |
|
||||||
assertNotEquals(helper.getLastDisplayOffset(), -1); // Don't know about device orientation.
|
|
||||||
|
|
||||||
// Ensure nothing bad if called twice.
|
|
||||||
helper.enable(); |
|
||||||
assertNotEquals(helper.getLastDisplayOffset(), -1); |
|
||||||
|
|
||||||
helper.disable(); |
|
||||||
assertEquals(helper.getLastDisplayOffset(), -1); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), -1); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testRotation() { |
|
||||||
// On some API levels, enable() needs to be run on the UI thread.
|
|
||||||
uiSync(new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
// Sometimes (on some APIs) the helper will trigger an update to 0
|
|
||||||
// right after enabling. But that's fine for us, times(1) will be OK either way.
|
|
||||||
helper.enable(); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 0); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(10); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 0); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(-10); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 0); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(44); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 0); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(360); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 0); |
|
||||||
|
|
||||||
// Callback called just once.
|
|
||||||
verify(callback, times(1)).onDeviceOrientationChanged(0); |
|
||||||
|
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(90); |
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(91); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 90); |
|
||||||
verify(callback, times(1)).onDeviceOrientationChanged(90); |
|
||||||
|
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(180); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 180); |
|
||||||
verify(callback, times(1)).onDeviceOrientationChanged(180); |
|
||||||
|
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(270); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 270); |
|
||||||
verify(callback, times(1)).onDeviceOrientationChanged(270); |
|
||||||
|
|
||||||
// It is still 270 after ORIENTATION_UNKNOWN
|
|
||||||
helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); |
|
||||||
assertEquals(helper.getLastDeviceOrientation(), 270); |
|
||||||
verify(callback, times(1)).onDeviceOrientationChanged(270); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -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; |
||||||
|
|
@ -0,0 +1,102 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.graphics.Canvas; |
||||||
|
import android.graphics.Color; |
||||||
|
import android.graphics.ImageFormat; |
||||||
|
import android.graphics.Paint; |
||||||
|
import android.graphics.PorterDuff; |
||||||
|
import android.graphics.Rect; |
||||||
|
import android.graphics.YuvImage; |
||||||
|
import android.media.Image; |
||||||
|
import android.media.ImageReader; |
||||||
|
import android.opengl.GLES20; |
||||||
|
import android.os.Handler; |
||||||
|
import android.os.Looper; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||||
|
import androidx.test.filters.SmallTest; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.BaseTest; |
||||||
|
import com.otaliastudios.cameraview.internal.egl.EglCore; |
||||||
|
import com.otaliastudios.cameraview.internal.egl.EglViewport; |
||||||
|
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface; |
||||||
|
import com.otaliastudios.cameraview.size.AspectRatio; |
||||||
|
import com.otaliastudios.cameraview.size.Size; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertNotEquals; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class ImageHelperTest extends BaseTest { |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private Image getImage() { |
||||||
|
ImageReader reader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1); |
||||||
|
Surface readerSurface = reader.getSurface(); |
||||||
|
final Op<Image> imageOp = new Op<>(true); |
||||||
|
reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { |
||||||
|
@Override |
||||||
|
public void onImageAvailable(ImageReader reader) { |
||||||
|
Image image = reader.acquireLatestImage(); |
||||||
|
if (image != null) imageOp.end(image); |
||||||
|
} |
||||||
|
}, new Handler(Looper.getMainLooper())); |
||||||
|
|
||||||
|
// Write on reader surface.
|
||||||
|
Canvas readerCanvas = readerSurface.lockCanvas(null); |
||||||
|
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); |
||||||
|
paint.setColor(Color.RED); |
||||||
|
readerCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); |
||||||
|
readerCanvas.drawCircle(50, 50, 50, paint); |
||||||
|
readerSurface.unlockCanvasAndPost(readerCanvas); |
||||||
|
|
||||||
|
// Wait
|
||||||
|
Image image = imageOp.await(5000); |
||||||
|
assertNotNull(image); |
||||||
|
return image; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testImage() { |
||||||
|
Image image = getImage(); |
||||||
|
int width = image.getWidth(); |
||||||
|
int height = image.getHeight(); |
||||||
|
int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); |
||||||
|
int sizeBits = width * height * bitsPerPixel; |
||||||
|
int sizeBytes = (int) Math.ceil(sizeBits / 8.0d); |
||||||
|
byte[] bytes = new byte[sizeBytes]; |
||||||
|
ImageHelper.convertToNV21(image, bytes); |
||||||
|
image.close(); |
||||||
|
|
||||||
|
// Read the image
|
||||||
|
YuvImage yuvImage = new YuvImage(bytes, ImageFormat.NV21, width, height, null); |
||||||
|
ByteArrayOutputStream jpegStream = new ByteArrayOutputStream(); |
||||||
|
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, jpegStream); |
||||||
|
byte[] jpegByteArray = jpegStream.toByteArray(); |
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length); |
||||||
|
assertNotNull(bitmap); |
||||||
|
|
||||||
|
// Wanted to do assertions on the color here but it doesn't work. There must be an issue
|
||||||
|
// with how we are drawing the image in this test, since in real camera, the algorithm works well.
|
||||||
|
// So for now let's just test that nothing crashes during this process.
|
||||||
|
// int color = bitmap.getPixel(bitmap.getWidth() - 1, bitmap.getHeight() - 1);
|
||||||
|
// assertEquals(Color.red(color), 255, 5);
|
||||||
|
// assertEquals(Color.green(color), 0, 5);
|
||||||
|
// assertEquals(Color.blue(color), 0, 5);
|
||||||
|
// assertEquals(Color.alpha(color), 0, 5);
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal.utils; |
||||||
|
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||||
|
import androidx.test.filters.SmallTest; |
||||||
|
import android.view.OrientationEventListener; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.BaseTest; |
||||||
|
|
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class OrientationHelperTest extends BaseTest { |
||||||
|
|
||||||
|
private OrientationHelper helper; |
||||||
|
private OrientationHelper.Callback callback; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
uiSync(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
callback = mock(OrientationHelper.Callback.class); |
||||||
|
helper = new OrientationHelper(getContext(), callback); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@After |
||||||
|
public void tearDown() { |
||||||
|
callback = null; |
||||||
|
helper = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testEnable() { |
||||||
|
assertNotNull(helper.mListener); |
||||||
|
assertEquals(helper.getDisplayOffset(), -1); |
||||||
|
assertEquals(helper.getDeviceOrientation(), -1); |
||||||
|
|
||||||
|
helper.enable(getContext()); |
||||||
|
assertNotNull(helper.mListener); |
||||||
|
assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
|
||||||
|
|
||||||
|
// Ensure nothing bad if called twice.
|
||||||
|
helper.enable(getContext()); |
||||||
|
assertNotNull(helper.mListener); |
||||||
|
assertNotEquals(helper.getDisplayOffset(), -1); |
||||||
|
|
||||||
|
helper.disable(); |
||||||
|
assertNotNull(helper.mListener); |
||||||
|
assertEquals(helper.getDisplayOffset(), -1); |
||||||
|
assertEquals(helper.getDeviceOrientation(), -1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testRotation() { |
||||||
|
|
||||||
|
// Sometimes (on some APIs) the helper will trigger an update to 0
|
||||||
|
// right after enabling. But that's fine for us, times(1) will be OK either way.
|
||||||
|
helper.enable(getContext()); |
||||||
|
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 0); |
||||||
|
helper.mListener.onOrientationChanged(10); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 0); |
||||||
|
helper.mListener.onOrientationChanged(-10); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 0); |
||||||
|
helper.mListener.onOrientationChanged(44); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 0); |
||||||
|
helper.mListener.onOrientationChanged(360); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 0); |
||||||
|
|
||||||
|
// Callback called just once.
|
||||||
|
verify(callback, times(1)).onDeviceOrientationChanged(0); |
||||||
|
|
||||||
|
helper.mListener.onOrientationChanged(90); |
||||||
|
helper.mListener.onOrientationChanged(91); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 90); |
||||||
|
verify(callback, times(1)).onDeviceOrientationChanged(90); |
||||||
|
|
||||||
|
helper.mListener.onOrientationChanged(180); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 180); |
||||||
|
verify(callback, times(1)).onDeviceOrientationChanged(180); |
||||||
|
|
||||||
|
helper.mListener.onOrientationChanged(270); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 270); |
||||||
|
verify(callback, times(1)).onDeviceOrientationChanged(270); |
||||||
|
|
||||||
|
// It is still 270 after ORIENTATION_UNKNOWN
|
||||||
|
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); |
||||||
|
assertEquals(helper.getDeviceOrientation(), 270); |
||||||
|
verify(callback, times(1)).onDeviceOrientationChanged(270); |
||||||
|
} |
||||||
|
} |
@ -1,137 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.metering; |
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.content.res.TypedArray; |
|
||||||
import android.graphics.PointF; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.graphics.RectF; |
|
||||||
import android.view.View; |
|
||||||
import android.view.ViewGroup; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
|
||||||
import com.otaliastudios.cameraview.R; |
|
||||||
import com.otaliastudios.cameraview.markers.AutoFocusMarker; |
|
||||||
import com.otaliastudios.cameraview.markers.AutoFocusTrigger; |
|
||||||
import com.otaliastudios.cameraview.markers.MarkerParser; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
import org.mockito.invocation.InvocationOnMock; |
|
||||||
import org.mockito.stubbing.Answer; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertNotNull; |
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertNull; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.ArgumentMatchers.any; |
|
||||||
import static org.mockito.Mockito.atLeastOnce; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class MeteringRegionsTest extends BaseTest { |
|
||||||
|
|
||||||
private final Size bounds = new Size(1000, 1000); |
|
||||||
|
|
||||||
private void checkRegion(@NonNull MeteringRegion region, @NonNull PointF center, int weight) { |
|
||||||
assertEquals(center.x, region.mRegion.centerX(), 0.01F); |
|
||||||
assertEquals(center.y, region.mRegion.centerY(), 0.01F); |
|
||||||
assertEquals(weight, region.mWeight); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFromPoint() { |
|
||||||
PointF center = new PointF(500, 500); |
|
||||||
MeteringRegions regions = MeteringRegions.fromPoint(bounds, center); |
|
||||||
assertEquals(2, regions.mRegions.size()); |
|
||||||
MeteringRegion first = regions.mRegions.get(0); |
|
||||||
MeteringRegion second = regions.mRegions.get(1); |
|
||||||
checkRegion(first, center, MeteringRegion.MAX_WEIGHT); |
|
||||||
checkRegion(second, center, |
|
||||||
Math.round(MeteringRegions.BLUR_FACTOR_WEIGHT * MeteringRegion.MAX_WEIGHT)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFromArea() { |
|
||||||
RectF area = new RectF(400, 400, 600, 600); |
|
||||||
MeteringRegions regions = MeteringRegions.fromArea(bounds, area); |
|
||||||
assertEquals(1, regions.mRegions.size()); |
|
||||||
MeteringRegion region = regions.mRegions.get(0); |
|
||||||
checkRegion(region, new PointF(area.centerX(), area.centerY()), MeteringRegion.MAX_WEIGHT); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testFromArea_withBlur() { |
|
||||||
RectF area = new RectF(400, 400, 600, 600); |
|
||||||
MeteringRegions regions = MeteringRegions.fromArea(bounds, area, |
|
||||||
MeteringRegion.MAX_WEIGHT, true); |
|
||||||
assertEquals(2, regions.mRegions.size()); |
|
||||||
MeteringRegion first = regions.mRegions.get(0); |
|
||||||
MeteringRegion second = regions.mRegions.get(1); |
|
||||||
PointF center = new PointF(area.centerX(), area.centerY()); |
|
||||||
checkRegion(first, center, MeteringRegion.MAX_WEIGHT); |
|
||||||
checkRegion(second, center, |
|
||||||
Math.round(MeteringRegions.BLUR_FACTOR_WEIGHT * MeteringRegion.MAX_WEIGHT)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testTransform() { |
|
||||||
MeteringTransform transform = mock(MeteringTransform.class); |
|
||||||
when(transform.transformMeteringPoint(any(PointF.class))).then(new Answer<PointF>() { |
|
||||||
@Override |
|
||||||
public PointF answer(InvocationOnMock invocation) { |
|
||||||
PointF in = invocation.getArgument(0); |
|
||||||
// This will swap x and y coordinates
|
|
||||||
//noinspection SuspiciousNameCombination
|
|
||||||
return new PointF(in.y, in.x); |
|
||||||
} |
|
||||||
}); |
|
||||||
RectF area = new RectF(0, 0, 100, 500); // tall area
|
|
||||||
RectF expected = new RectF(0, 0, 500, 100); // wide area
|
|
||||||
MeteringRegions regions = MeteringRegions.fromArea(bounds, area); |
|
||||||
MeteringRegions transformed = regions.transform(transform); |
|
||||||
verify(transform, times(4)).transformMeteringPoint(any(PointF.class)); |
|
||||||
assertEquals(1, transformed.mRegions.size()); |
|
||||||
assertEquals(expected, transformed.mRegions.get(0).mRegion); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGet() { |
|
||||||
MeteringTransform<Integer> transform = new MeteringTransform<Integer>() { |
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public PointF transformMeteringPoint(@NonNull PointF point) { |
|
||||||
return point; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public Integer transformMeteringRegion(@NonNull RectF region, int weight) { |
|
||||||
return weight; |
|
||||||
} |
|
||||||
}; |
|
||||||
MeteringRegions regions = MeteringRegions.fromArea(bounds, |
|
||||||
new RectF(400, 400, 600, 600), |
|
||||||
900, |
|
||||||
true); |
|
||||||
assertEquals(2, regions.mRegions.size()); |
|
||||||
List<Integer> result = regions.get(1, transform); |
|
||||||
assertEquals(1, result.size()); |
|
||||||
assertEquals(900, (int) result.get(0)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,78 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.overlay; |
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.Canvas; |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
|
||||||
import androidx.test.filters.SmallTest; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseEglTest; |
|
||||||
import com.otaliastudios.cameraview.internal.GlTextureDrawer; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
|
|
||||||
import org.hamcrest.BaseMatcher; |
|
||||||
import org.hamcrest.Description; |
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse; |
|
||||||
import static org.junit.Assert.assertThat; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.ArgumentMatchers.any; |
|
||||||
import static org.mockito.ArgumentMatchers.eq; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.spy; |
|
||||||
import static org.mockito.Mockito.times; |
|
||||||
import static org.mockito.Mockito.verify; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class OverlayDrawerTest extends BaseEglTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testDraw() { |
|
||||||
Overlay overlay = mock(Overlay.class); |
|
||||||
OverlayDrawer drawer = new OverlayDrawer(overlay, new Size(WIDTH, HEIGHT)); |
|
||||||
drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); |
|
||||||
verify(overlay, times(1)).drawOn( |
|
||||||
eq(Overlay.Target.PICTURE_SNAPSHOT), |
|
||||||
any(Canvas.class)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testGetTransform() { |
|
||||||
// We'll check that the transform is not all zeros, which is highly unlikely
|
|
||||||
// (the default transform should be the identity matrix)
|
|
||||||
OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); |
|
||||||
drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); |
|
||||||
assertThat(drawer.getTransform(), new BaseMatcher<float[]>() { |
|
||||||
public void describeTo(Description description) { } |
|
||||||
public boolean matches(Object item) { |
|
||||||
float[] array = (float[]) item; |
|
||||||
for (float value : array) { |
|
||||||
if (value != 0.0F) return true; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testRender() { |
|
||||||
OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); |
|
||||||
drawer.mTextureDrawer = spy(drawer.mTextureDrawer); |
|
||||||
|
|
||||||
drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); |
|
||||||
drawer.render(0L); |
|
||||||
verify(drawer.mTextureDrawer, times(1)).draw(0L); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testRelease() { |
|
||||||
OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); |
|
||||||
GlTextureDrawer textureDrawer = spy(drawer.mTextureDrawer); |
|
||||||
drawer.mTextureDrawer = textureDrawer; |
|
||||||
drawer.release(); |
|
||||||
verify(textureDrawer, times(1)).release(); |
|
||||||
} |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
public class Emulator { |
|
||||||
public static boolean isEmulator() { |
|
||||||
// From Android's RequiresDeviceFilter
|
|
||||||
return Build.HARDWARE.equals("goldfish") |
|
||||||
|| Build.HARDWARE.equals("ranchu") |
|
||||||
|| Build.HARDWARE.equals("gce_x86"); |
|
||||||
} |
|
||||||
} |
|
@ -1,149 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.google.android.gms.tasks.OnSuccessListener; |
|
||||||
import com.google.android.gms.tasks.Task; |
|
||||||
import com.otaliastudios.cameraview.controls.Control; |
|
||||||
|
|
||||||
import org.mockito.Mockito; |
|
||||||
import org.mockito.invocation.InvocationOnMock; |
|
||||||
import org.mockito.stubbing.Answer; |
|
||||||
import org.mockito.stubbing.Stubber; |
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
|
|
||||||
/** |
|
||||||
* A naive implementation of {@link java.util.concurrent.CountDownLatch} |
|
||||||
* to help in testing. |
|
||||||
*/ |
|
||||||
public class Op<T> { |
|
||||||
|
|
||||||
public class Controller { |
|
||||||
private int mToBeIgnored; |
|
||||||
|
|
||||||
private Controller() { } |
|
||||||
|
|
||||||
/** Op owner method: notifies the action started. */ |
|
||||||
public void start() { |
|
||||||
if (!isListening()) mToBeIgnored++; |
|
||||||
} |
|
||||||
|
|
||||||
/** Op owner method: notifies the action ended. */ |
|
||||||
public void end(T result) { |
|
||||||
if (mToBeIgnored > 0) { |
|
||||||
mToBeIgnored--; |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (isListening()) { // Should be always true.
|
|
||||||
mResult = result; |
|
||||||
mLatch.countDown(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void from(@NonNull Task<T> task) { |
|
||||||
start(); |
|
||||||
task.addOnSuccessListener(new OnSuccessListener<T>() { |
|
||||||
@Override |
|
||||||
public void onSuccess(T result) { |
|
||||||
end(result); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public Stubber from(final int invocationArgument) { |
|
||||||
return Mockito.doAnswer(new Answer() { |
|
||||||
@Override |
|
||||||
public Object answer(InvocationOnMock invocation) { |
|
||||||
//noinspection unchecked
|
|
||||||
T o = (T) invocation.getArguments()[invocationArgument]; |
|
||||||
start(); |
|
||||||
end(o); |
|
||||||
return null; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private CountDownLatch mLatch; |
|
||||||
private Controller mController = new Controller(); |
|
||||||
private T mResult; |
|
||||||
|
|
||||||
/** |
|
||||||
* Listeners should: |
|
||||||
* - call {@link #listen()} to notify they are interested in the next action |
|
||||||
* - call {@link #await()} to know when the action is performed. |
|
||||||
* |
|
||||||
* Op owners should: |
|
||||||
* - call {@link Controller#start()} when task started |
|
||||||
* - call {@link Controller#end(Object)} when task ends |
|
||||||
*/ |
|
||||||
public Op() { |
|
||||||
this(true); |
|
||||||
} |
|
||||||
|
|
||||||
public Op(boolean startListening) { |
|
||||||
if (startListening) listen(); |
|
||||||
} |
|
||||||
|
|
||||||
public Op(@NonNull Task<T> task) { |
|
||||||
listen(); |
|
||||||
controller().from(task); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isListening() { |
|
||||||
return mLatch != null; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listener method: notifies we are interested in the next action. |
|
||||||
*/ |
|
||||||
public void listen() { |
|
||||||
if (isListening()) throw new RuntimeException("Should not happen."); |
|
||||||
mResult = null; |
|
||||||
mLatch = new CountDownLatch(1); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listener method: waits for next task action to end. |
|
||||||
* @param millis milliseconds |
|
||||||
* @return the action result |
|
||||||
*/ |
|
||||||
public T await(long millis) { |
|
||||||
return await(millis, TimeUnit.MILLISECONDS); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listener method: waits 1 minute for next task action to end. |
|
||||||
* @return the action result |
|
||||||
*/ |
|
||||||
public T await() { |
|
||||||
return await(1, TimeUnit.MINUTES); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listener method: waits for next task action to end. |
|
||||||
* @param time time |
|
||||||
* @param unit the time unit |
|
||||||
* @return the action result |
|
||||||
*/ |
|
||||||
private T await(long time, @NonNull TimeUnit unit) { |
|
||||||
try { |
|
||||||
mLatch.await(time, unit); |
|
||||||
} catch (Exception e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
T result = mResult; |
|
||||||
mResult = null; |
|
||||||
mLatch = null; |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
public Controller controller() { |
|
||||||
return mController; |
|
||||||
} |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import java.lang.annotation.Retention; |
|
||||||
import java.lang.annotation.RetentionPolicy; |
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME) |
|
||||||
public @interface Retry { |
|
||||||
boolean emulatorOnly() default false; |
|
||||||
} |
|
@ -1,52 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
|
|
||||||
import org.junit.rules.TestRule; |
|
||||||
import org.junit.runner.Description; |
|
||||||
import org.junit.runners.model.Statement; |
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger; |
|
||||||
|
|
||||||
public class RetryRule implements TestRule { |
|
||||||
|
|
||||||
private final static String TAG = RetryRule.class.getSimpleName(); |
|
||||||
private final static CameraLogger LOG = CameraLogger.create(TAG); |
|
||||||
|
|
||||||
private AtomicInteger retries; |
|
||||||
|
|
||||||
public RetryRule(int retries) { |
|
||||||
this.retries = new AtomicInteger(retries); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Statement apply(final Statement base, final Description description) { |
|
||||||
return new Statement() { |
|
||||||
@Override |
|
||||||
public void evaluate() throws Throwable { |
|
||||||
Retry retry = description.getAnnotation(Retry.class); |
|
||||||
if (retry == null || retry.emulatorOnly() && !Emulator.isEmulator()) { |
|
||||||
base.evaluate(); |
|
||||||
} else { |
|
||||||
Throwable caught = null; |
|
||||||
while (retries.getAndDecrement() > 0) { |
|
||||||
try { |
|
||||||
base.evaluate(); |
|
||||||
return; |
|
||||||
} catch (Throwable throwable) { |
|
||||||
LOG.e("[RETRY] Test failed.", retries.get(), |
|
||||||
"retries available..."); |
|
||||||
LOG.e("*******************************************************"); |
|
||||||
caught = throwable; |
|
||||||
} |
|
||||||
} |
|
||||||
if (caught != null) { |
|
||||||
throw caught; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import java.lang.annotation.ElementType; |
|
||||||
import java.lang.annotation.Retention; |
|
||||||
import java.lang.annotation.RetentionPolicy; |
|
||||||
import java.lang.annotation.Target; |
|
||||||
|
|
||||||
/** |
|
||||||
* Like {@link androidx.test.filters.SdkSuppress}, but negative. |
|
||||||
*/ |
|
||||||
@Retention(RetentionPolicy.RUNTIME) |
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD}) |
|
||||||
public @interface SdkExclude { |
|
||||||
/** The minimum API level to drop (inclusive) */ |
|
||||||
int minSdkVersion() default 1; |
|
||||||
/** The maximum API level to drop (inclusive) */ |
|
||||||
int maxSdkVersion() default Integer.MAX_VALUE; |
|
||||||
/** Whether this filter only applies to emulators */ |
|
||||||
boolean emulatorOnly() default false; |
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.test.internal.runner.filters.ParentFilter; |
|
||||||
|
|
||||||
import org.junit.runner.Description; |
|
||||||
|
|
||||||
/** |
|
||||||
* Filter for {@link SdkExclude}, based on |
|
||||||
* {@link androidx.test.internal.runner.TestRequestBuilder}'s SdkSuppressFilter. |
|
||||||
*/ |
|
||||||
public class SdkExcludeFilter extends ParentFilter { |
|
||||||
|
|
||||||
protected boolean evaluateTest(Description description) { |
|
||||||
SdkExclude annotation = getAnnotationForTest(description); |
|
||||||
if (annotation != null) { |
|
||||||
if ((!annotation.emulatorOnly() || Emulator.isEmulator()) |
|
||||||
&& Build.VERSION.SDK_INT >= annotation.minSdkVersion() |
|
||||||
&& Build.VERSION.SDK_INT <= annotation.maxSdkVersion()) { |
|
||||||
return false; // exclude the test
|
|
||||||
} |
|
||||||
return true; // run the test
|
|
||||||
} |
|
||||||
return true; // no annotation, run the test
|
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private SdkExclude getAnnotationForTest(Description description) { |
|
||||||
final SdkExclude s = description.getAnnotation(SdkExclude.class); |
|
||||||
if (s != null) { |
|
||||||
return s; |
|
||||||
} |
|
||||||
final Class<?> testClass = description.getTestClass(); |
|
||||||
if (testClass != null) { |
|
||||||
return testClass.getAnnotation(SdkExclude.class); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String describe() { |
|
||||||
return "Skip tests annotated with SdkExclude"; |
|
||||||
} |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
import java.lang.annotation.ElementType; |
|
||||||
import java.lang.annotation.Retention; |
|
||||||
import java.lang.annotation.RetentionPolicy; |
|
||||||
import java.lang.annotation.Target; |
|
||||||
|
|
||||||
/** |
|
||||||
* Like {@link androidx.test.filters.SdkSuppress}, but with emulatorOnly(). |
|
||||||
*/ |
|
||||||
@Retention(RetentionPolicy.RUNTIME) |
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD}) |
|
||||||
public @interface SdkInclude { |
|
||||||
/** The minimum API level to run (inclusive) */ |
|
||||||
int minSdkVersion() default 1; |
|
||||||
/** The maximum API level to run (inclusive) */ |
|
||||||
int maxSdkVersion() default Integer.MAX_VALUE; |
|
||||||
/** Whether this filter only applies to emulators */ |
|
||||||
boolean emulatorOnly() default false; |
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.tools; |
|
||||||
|
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.test.internal.runner.filters.ParentFilter; |
|
||||||
|
|
||||||
import org.junit.runner.Description; |
|
||||||
|
|
||||||
/** |
|
||||||
* Filter for {@link SdkInclude}, based on |
|
||||||
* {@link androidx.test.internal.runner.TestRequestBuilder}'s SdkSuppressFilter. |
|
||||||
*/ |
|
||||||
public class SdkIncludeFilter extends ParentFilter { |
|
||||||
|
|
||||||
protected boolean evaluateTest(Description description) { |
|
||||||
SdkInclude annotation = getAnnotationForTest(description); |
|
||||||
if (annotation != null) { |
|
||||||
if ((!annotation.emulatorOnly() || Emulator.isEmulator()) |
|
||||||
&& Build.VERSION.SDK_INT >= annotation.minSdkVersion() |
|
||||||
&& Build.VERSION.SDK_INT <= annotation.maxSdkVersion()) { |
|
||||||
return true; // run the test
|
|
||||||
} |
|
||||||
return false; // drop the test
|
|
||||||
} |
|
||||||
return true; // no annotation, run the test
|
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private SdkInclude getAnnotationForTest(Description description) { |
|
||||||
final SdkInclude s = description.getAnnotation(SdkInclude.class); |
|
||||||
if (s != null) { |
|
||||||
return s; |
|
||||||
} |
|
||||||
final Class<?> testClass = description.getTestClass(); |
|
||||||
if (testClass != null) { |
|
||||||
return testClass.getAnnotation(SdkInclude.class); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String describe() { |
|
||||||
return "Skip tests annotated with SdkInclude"; |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -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,52 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.controls; |
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.CameraView; |
|
||||||
|
|
||||||
/** |
|
||||||
* Format of the picture results for pictures that are taken with {@link CameraView#takePicture()}. |
|
||||||
* This does not apply to picture snapshots. |
|
||||||
* |
|
||||||
* @see CameraView#setPictureFormat(PictureFormat) |
|
||||||
*/ |
|
||||||
public enum PictureFormat implements Control { |
|
||||||
|
|
||||||
/** |
|
||||||
* The picture result data will be a JPEG file. |
|
||||||
* This value is always supported. |
|
||||||
*/ |
|
||||||
JPEG(0), |
|
||||||
|
|
||||||
/** |
|
||||||
* The picture result data will be a DNG file. |
|
||||||
* This is only supported with the {@link Engine#CAMERA2} engine and only on |
|
||||||
* specific devices. Please check {@link CameraOptions#getSupportedPictureFormats()}. |
|
||||||
*/ |
|
||||||
DNG(1); |
|
||||||
|
|
||||||
static final PictureFormat DEFAULT = JPEG; |
|
||||||
|
|
||||||
private int value; |
|
||||||
|
|
||||||
PictureFormat(int value) { |
|
||||||
this.value = value; |
|
||||||
} |
|
||||||
|
|
||||||
int value() { |
|
||||||
return value; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
static PictureFormat fromValue(int value) { |
|
||||||
PictureFormat[] list = PictureFormat.values(); |
|
||||||
for (PictureFormat action : list) { |
|
||||||
if (action.value() == value) { |
|
||||||
return action; |
|
||||||
} |
|
||||||
} |
|
||||||
return DEFAULT; |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,978 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine; |
|
||||||
|
|
||||||
import android.location.Location; |
|
||||||
|
|
||||||
import androidx.annotation.CallSuper; |
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.annotation.VisibleForTesting; |
|
||||||
|
|
||||||
import com.google.android.gms.tasks.Task; |
|
||||||
import com.google.android.gms.tasks.Tasks; |
|
||||||
import com.otaliastudios.cameraview.CameraException; |
|
||||||
import com.otaliastudios.cameraview.CameraOptions; |
|
||||||
import com.otaliastudios.cameraview.PictureResult; |
|
||||||
import com.otaliastudios.cameraview.VideoResult; |
|
||||||
import com.otaliastudios.cameraview.controls.Audio; |
|
||||||
import com.otaliastudios.cameraview.controls.AudioCodec; |
|
||||||
import com.otaliastudios.cameraview.controls.Facing; |
|
||||||
import com.otaliastudios.cameraview.controls.Flash; |
|
||||||
import com.otaliastudios.cameraview.controls.Hdr; |
|
||||||
import com.otaliastudios.cameraview.controls.Mode; |
|
||||||
import com.otaliastudios.cameraview.controls.PictureFormat; |
|
||||||
import com.otaliastudios.cameraview.controls.VideoCodec; |
|
||||||
import com.otaliastudios.cameraview.controls.WhiteBalance; |
|
||||||
import com.otaliastudios.cameraview.engine.offset.Angles; |
|
||||||
import com.otaliastudios.cameraview.engine.offset.Reference; |
|
||||||
import com.otaliastudios.cameraview.engine.orchestrator.CameraState; |
|
||||||
import com.otaliastudios.cameraview.frame.FrameManager; |
|
||||||
import com.otaliastudios.cameraview.overlay.Overlay; |
|
||||||
import com.otaliastudios.cameraview.picture.PictureRecorder; |
|
||||||
import com.otaliastudios.cameraview.preview.CameraPreview; |
|
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
|
||||||
import com.otaliastudios.cameraview.size.Size; |
|
||||||
import com.otaliastudios.cameraview.size.SizeSelector; |
|
||||||
import com.otaliastudios.cameraview.size.SizeSelectors; |
|
||||||
import com.otaliastudios.cameraview.video.VideoRecorder; |
|
||||||
|
|
||||||
import java.io.File; |
|
||||||
import java.io.FileDescriptor; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Abstract implementation of {@link CameraEngine} that helps in common tasks. |
|
||||||
*/ |
|
||||||
public abstract class CameraBaseEngine extends CameraEngine { |
|
||||||
|
|
||||||
protected final static int ALLOWED_ZOOM_OPS = 20; |
|
||||||
protected final static int ALLOWED_EV_OPS = 20; |
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") protected CameraPreview mPreview; |
|
||||||
@SuppressWarnings("WeakerAccess") protected CameraOptions mCameraOptions; |
|
||||||
@SuppressWarnings("WeakerAccess") protected PictureRecorder mPictureRecorder; |
|
||||||
@SuppressWarnings("WeakerAccess") protected VideoRecorder mVideoRecorder; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Size mCaptureSize; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Size mFrameProcessingSize; |
|
||||||
@SuppressWarnings("WeakerAccess") protected int mFrameProcessingFormat; |
|
||||||
@SuppressWarnings("WeakerAccess") protected boolean mHasFrameProcessors; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Flash mFlash; |
|
||||||
@SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance; |
|
||||||
@SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec; |
|
||||||
@SuppressWarnings("WeakerAccess") protected AudioCodec mAudioCodec; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Hdr mHdr; |
|
||||||
@SuppressWarnings("WeakerAccess") protected PictureFormat mPictureFormat; |
|
||||||
@SuppressWarnings("WeakerAccess") protected Location mLocation; |
|
||||||
@SuppressWarnings("WeakerAccess") protected float mZoomValue; |
|
||||||
@SuppressWarnings("WeakerAccess") protected float mExposureCorrectionValue; |
|
||||||
@SuppressWarnings("WeakerAccess") protected boolean mPlaySounds; |
|
||||||
@SuppressWarnings("WeakerAccess") protected boolean mPictureMetering; |
|
||||||
@SuppressWarnings("WeakerAccess") protected boolean mPictureSnapshotMetering; |
|
||||||
@SuppressWarnings("WeakerAccess") protected float mPreviewFrameRate; |
|
||||||
@SuppressWarnings("WeakerAccess") private boolean mPreviewFrameRateExact; |
|
||||||
|
|
||||||
private FrameManager mFrameManager; |
|
||||||
private final Angles mAngles = new Angles(); |
|
||||||
@Nullable private SizeSelector mPreviewStreamSizeSelector; |
|
||||||
private SizeSelector mPictureSizeSelector; |
|
||||||
private SizeSelector mVideoSizeSelector; |
|
||||||
private Facing mFacing; |
|
||||||
private Mode mMode; |
|
||||||
private Audio mAudio; |
|
||||||
private long mVideoMaxSize; |
|
||||||
private int mVideoMaxDuration; |
|
||||||
private int mVideoBitRate; |
|
||||||
private int mAudioBitRate; |
|
||||||
private long mAutoFocusResetDelayMillis; |
|
||||||
private int mSnapshotMaxWidth; // in REF_VIEW like SizeSelectors
|
|
||||||
private int mSnapshotMaxHeight; // in REF_VIEW like SizeSelectors
|
|
||||||
private int mFrameProcessingMaxWidth; // in REF_VIEW like SizeSelectors
|
|
||||||
private int mFrameProcessingMaxHeight; // in REF_VIEW like SizeSelectors
|
|
||||||
private int mFrameProcessingPoolSize; |
|
||||||
private Overlay mOverlay; |
|
||||||
|
|
||||||
// Ops used for testing.
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mZoomTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mExposureCorrectionTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mFlashTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mWhiteBalanceTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mHdrTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mLocationTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPlaySoundsTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPreviewFrameRateTask |
|
||||||
= Tasks.forResult(null); |
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected CameraBaseEngine(@NonNull Callback callback) { |
|
||||||
super(callback); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Called at construction time to get a frame manager that can later be |
|
||||||
* accessed through {@link #getFrameManager()}. |
|
||||||
* @param poolSize pool size |
|
||||||
* @return a frame manager |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
protected abstract FrameManager instantiateFrameManager(int poolSize); |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Angles getAngles() { |
|
||||||
return mAngles; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public FrameManager getFrameManager() { |
|
||||||
if (mFrameManager == null) { |
|
||||||
mFrameManager = instantiateFrameManager(mFrameProcessingPoolSize); |
|
||||||
} |
|
||||||
return mFrameManager; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final CameraOptions getCameraOptions() { |
|
||||||
return mCameraOptions; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPreview(@NonNull CameraPreview cameraPreview) { |
|
||||||
if (mPreview != null) mPreview.setSurfaceCallback(null); |
|
||||||
mPreview = cameraPreview; |
|
||||||
mPreview.setSurfaceCallback(this); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final CameraPreview getPreview() { |
|
||||||
return mPreview; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setOverlay(@Nullable Overlay overlay) { |
|
||||||
mOverlay = overlay; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Overlay getOverlay() { |
|
||||||
return mOverlay; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) { |
|
||||||
mPreviewStreamSizeSelector = selector; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final SizeSelector getPreviewStreamSizeSelector() { |
|
||||||
return mPreviewStreamSizeSelector; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPictureSizeSelector(@NonNull SizeSelector selector) { |
|
||||||
mPictureSizeSelector = selector; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final SizeSelector getPictureSizeSelector() { |
|
||||||
return mPictureSizeSelector; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setVideoSizeSelector(@NonNull SizeSelector selector) { |
|
||||||
mVideoSizeSelector = selector; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final SizeSelector getVideoSizeSelector() { |
|
||||||
return mVideoSizeSelector; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setVideoMaxSize(long videoMaxSizeBytes) { |
|
||||||
mVideoMaxSize = videoMaxSizeBytes; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final long getVideoMaxSize() { |
|
||||||
return mVideoMaxSize; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setVideoMaxDuration(int videoMaxDurationMillis) { |
|
||||||
mVideoMaxDuration = videoMaxDurationMillis; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getVideoMaxDuration() { |
|
||||||
return mVideoMaxDuration; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setVideoCodec(@NonNull VideoCodec codec) { |
|
||||||
mVideoCodec = codec; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final VideoCodec getVideoCodec() { |
|
||||||
return mVideoCodec; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setVideoBitRate(int videoBitRate) { |
|
||||||
mVideoBitRate = videoBitRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getVideoBitRate() { |
|
||||||
return mVideoBitRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setAudioCodec(@NonNull AudioCodec codec) { |
|
||||||
mAudioCodec = codec; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final AudioCodec getAudioCodec() { |
|
||||||
return mAudioCodec; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setAudioBitRate(int audioBitRate) { |
|
||||||
mAudioBitRate = audioBitRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getAudioBitRate() { |
|
||||||
return mAudioBitRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setSnapshotMaxWidth(int maxWidth) { |
|
||||||
mSnapshotMaxWidth = maxWidth; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getSnapshotMaxWidth() { |
|
||||||
return mSnapshotMaxWidth; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setSnapshotMaxHeight(int maxHeight) { |
|
||||||
mSnapshotMaxHeight = maxHeight; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getSnapshotMaxHeight() { |
|
||||||
return mSnapshotMaxHeight; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setFrameProcessingMaxWidth(int maxWidth) { |
|
||||||
mFrameProcessingMaxWidth = maxWidth; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getFrameProcessingMaxWidth() { |
|
||||||
return mFrameProcessingMaxWidth; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setFrameProcessingMaxHeight(int maxHeight) { |
|
||||||
mFrameProcessingMaxHeight = maxHeight; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getFrameProcessingMaxHeight() { |
|
||||||
return mFrameProcessingMaxHeight; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getFrameProcessingFormat() { |
|
||||||
return mFrameProcessingFormat; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setFrameProcessingPoolSize(int poolSize) { |
|
||||||
mFrameProcessingPoolSize = poolSize; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final int getFrameProcessingPoolSize() { |
|
||||||
return mFrameProcessingPoolSize; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setAutoFocusResetDelay(long delayMillis) { |
|
||||||
mAutoFocusResetDelayMillis = delayMillis; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final long getAutoFocusResetDelay() { |
|
||||||
return mAutoFocusResetDelayMillis; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Helper function for subclasses. |
|
||||||
* @return true if AF should be reset |
|
||||||
*/ |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected final boolean shouldResetAutoFocus() { |
|
||||||
return mAutoFocusResetDelayMillis > 0 && mAutoFocusResetDelayMillis != Long.MAX_VALUE; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets a new facing value. This will restart the engine session (if there's any) |
|
||||||
* so that we can open the new facing camera. |
|
||||||
* @param facing facing |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public final void setFacing(final @NonNull Facing facing) { |
|
||||||
final Facing old = mFacing; |
|
||||||
if (facing != old) { |
|
||||||
mFacing = facing; |
|
||||||
getOrchestrator().scheduleStateful("facing", CameraState.ENGINE, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
if (collectCameraInfo(facing)) { |
|
||||||
restart(); |
|
||||||
} else { |
|
||||||
mFacing = old; |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Facing getFacing() { |
|
||||||
return mFacing; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets a new audio value that will be used for video recordings. |
|
||||||
* @param audio desired audio |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public final void setAudio(@NonNull Audio audio) { |
|
||||||
if (mAudio != audio) { |
|
||||||
if (isTakingVideo()) { |
|
||||||
LOG.w("Audio setting was changed while recording. " + |
|
||||||
"Changes will take place starting from next video"); |
|
||||||
} |
|
||||||
mAudio = audio; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Audio getAudio() { |
|
||||||
return mAudio; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sets the desired mode (either picture or video). |
|
||||||
* @param mode desired mode. |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public final void setMode(@NonNull Mode mode) { |
|
||||||
if (mode != mMode) { |
|
||||||
mMode = mode; |
|
||||||
getOrchestrator().scheduleStateful("mode", CameraState.ENGINE, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
restart(); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Mode getMode() { |
|
||||||
return mMode; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final float getZoomValue() { |
|
||||||
return mZoomValue; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final float getExposureCorrectionValue() { |
|
||||||
return mExposureCorrectionValue; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Flash getFlash() { |
|
||||||
return mFlash; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final WhiteBalance getWhiteBalance() { |
|
||||||
return mWhiteBalance; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final Hdr getHdr() { |
|
||||||
return mHdr; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Location getLocation() { |
|
||||||
return mLocation; |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@Override |
|
||||||
public final PictureFormat getPictureFormat() { |
|
||||||
return mPictureFormat; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPreviewFrameRateExact(boolean previewFrameRateExact) { |
|
||||||
mPreviewFrameRateExact = previewFrameRateExact; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean getPreviewFrameRateExact() { |
|
||||||
return mPreviewFrameRateExact; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final float getPreviewFrameRate() { |
|
||||||
return mPreviewFrameRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean hasFrameProcessors() { |
|
||||||
return mHasFrameProcessors; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPictureMetering(boolean enable) { |
|
||||||
mPictureMetering = enable; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean getPictureMetering() { |
|
||||||
return mPictureMetering; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void setPictureSnapshotMetering(boolean enable) { |
|
||||||
mPictureSnapshotMetering = enable; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean getPictureSnapshotMetering() { |
|
||||||
return mPictureSnapshotMetering; |
|
||||||
} |
|
||||||
|
|
||||||
//region Picture and video control
|
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean isTakingPicture() { |
|
||||||
return mPictureRecorder != null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public /* final */ void takePicture(final @NonNull PictureResult.Stub stub) { |
|
||||||
// Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
|
|
||||||
final boolean metering = mPictureMetering; |
|
||||||
getOrchestrator().scheduleStateful("take picture", CameraState.BIND, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
LOG.i("takePicture:", "running. isTakingPicture:", isTakingPicture()); |
|
||||||
if (isTakingPicture()) return; |
|
||||||
if (mMode == Mode.VIDEO) { |
|
||||||
throw new IllegalStateException("Can't take hq pictures while in VIDEO mode"); |
|
||||||
} |
|
||||||
stub.isSnapshot = false; |
|
||||||
stub.location = mLocation; |
|
||||||
stub.facing = mFacing; |
|
||||||
stub.format = mPictureFormat; |
|
||||||
onTakePicture(stub, metering); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The snapshot size is the {@link #getPreviewStreamSize(Reference)}, but cropped based on the |
|
||||||
* view/surface aspect ratio. |
|
||||||
* @param stub a picture stub |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public /* final */ void takePictureSnapshot(final @NonNull PictureResult.Stub stub) { |
|
||||||
// Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
|
|
||||||
final boolean metering = mPictureSnapshotMetering; |
|
||||||
getOrchestrator().scheduleStateful("take picture snapshot", CameraState.BIND, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
LOG.i("takePictureSnapshot:", "running. isTakingPicture:", isTakingPicture()); |
|
||||||
if (isTakingPicture()) return; |
|
||||||
stub.location = mLocation; |
|
||||||
stub.isSnapshot = true; |
|
||||||
stub.facing = mFacing; |
|
||||||
stub.format = PictureFormat.JPEG; |
|
||||||
// Leave the other parameters to subclasses.
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); |
|
||||||
onTakePictureSnapshot(stub, ratio, metering); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPictureShutter(boolean didPlaySound) { |
|
||||||
getCallback().dispatchOnPictureShutter(!didPlaySound); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPictureResult(@Nullable PictureResult.Stub result, @Nullable Exception error) { |
|
||||||
mPictureRecorder = null; |
|
||||||
if (result != null && result.data != null) { |
|
||||||
getCallback().dispatchOnPictureTaken(result); |
|
||||||
} else { |
|
||||||
LOG.e("onPictureResult", "result or data is null: something went wrong.", error); |
|
||||||
getCallback().dispatchError(new CameraException(error, |
|
||||||
CameraException.REASON_PICTURE_FAILED)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final boolean isTakingVideo() { |
|
||||||
return mVideoRecorder != null && mVideoRecorder.isRecording(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void takeVideo(final @NonNull VideoResult.Stub stub, |
|
||||||
final @Nullable File file, |
|
||||||
final @Nullable FileDescriptor fileDescriptor) { |
|
||||||
getOrchestrator().scheduleStateful("take video", CameraState.BIND, new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
LOG.i("takeVideo:", "running. isTakingVideo:", isTakingVideo()); |
|
||||||
if (isTakingVideo()) return; |
|
||||||
if (mMode == Mode.PICTURE) { |
|
||||||
throw new IllegalStateException("Can't record video while in PICTURE mode"); |
|
||||||
} |
|
||||||
if (file != null) { |
|
||||||
stub.file = file; |
|
||||||
} else if (fileDescriptor != null) { |
|
||||||
stub.fileDescriptor = fileDescriptor; |
|
||||||
} else { |
|
||||||
throw new IllegalStateException("file and fileDescriptor are both null."); |
|
||||||
} |
|
||||||
stub.isSnapshot = false; |
|
||||||
stub.videoCodec = mVideoCodec; |
|
||||||
stub.audioCodec = mAudioCodec; |
|
||||||
stub.location = mLocation; |
|
||||||
stub.facing = mFacing; |
|
||||||
stub.audio = mAudio; |
|
||||||
stub.maxSize = mVideoMaxSize; |
|
||||||
stub.maxDuration = mVideoMaxDuration; |
|
||||||
stub.videoBitRate = mVideoBitRate; |
|
||||||
stub.audioBitRate = mAudioBitRate; |
|
||||||
onTakeVideo(stub); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param stub a video stub |
|
||||||
* @param file the output file |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public final void takeVideoSnapshot(@NonNull final VideoResult.Stub stub, |
|
||||||
@NonNull final File file) { |
|
||||||
getOrchestrator().scheduleStateful("take video snapshot", CameraState.BIND, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
LOG.i("takeVideoSnapshot:", "running. isTakingVideo:", isTakingVideo()); |
|
||||||
stub.file = file; |
|
||||||
stub.isSnapshot = true; |
|
||||||
stub.videoCodec = mVideoCodec; |
|
||||||
stub.audioCodec = mAudioCodec; |
|
||||||
stub.location = mLocation; |
|
||||||
stub.facing = mFacing; |
|
||||||
stub.videoBitRate = mVideoBitRate; |
|
||||||
stub.audioBitRate = mAudioBitRate; |
|
||||||
stub.audio = mAudio; |
|
||||||
stub.maxSize = mVideoMaxSize; |
|
||||||
stub.maxDuration = mVideoMaxDuration; |
|
||||||
//noinspection ConstantConditions
|
|
||||||
AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); |
|
||||||
onTakeVideoSnapshot(stub, ratio); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public final void stopVideo() { |
|
||||||
getOrchestrator().schedule("stop video", true, new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
LOG.i("stopVideo", "running. isTakingVideo?", isTakingVideo()); |
|
||||||
onStopVideo(); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected void onStopVideo() { |
|
||||||
if (mVideoRecorder != null) { |
|
||||||
mVideoRecorder.stop(false); |
|
||||||
// Do not null this, so we respond correctly to isTakingVideo(),
|
|
||||||
// which checks for recorder presence and recorder.isRecording().
|
|
||||||
// It will be nulled in onVideoResult.
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@CallSuper |
|
||||||
@Override |
|
||||||
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { |
|
||||||
mVideoRecorder = null; |
|
||||||
if (result != null) { |
|
||||||
getCallback().dispatchOnVideoTaken(result); |
|
||||||
} else { |
|
||||||
LOG.e("onVideoResult", "result is null: something went wrong.", exception); |
|
||||||
getCallback().dispatchError(new CameraException(exception, |
|
||||||
CameraException.REASON_VIDEO_FAILED)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onVideoRecordingStart() { |
|
||||||
getCallback().dispatchOnVideoRecordingStart(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onVideoRecordingEnd() { |
|
||||||
getCallback().dispatchOnVideoRecordingEnd(); |
|
||||||
} |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
protected abstract void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering); |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
protected abstract void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, |
|
||||||
@NonNull AspectRatio outputRatio, |
|
||||||
boolean doMetering); |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
protected abstract void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, |
|
||||||
@NonNull AspectRatio outputRatio); |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
protected abstract void onTakeVideo(@NonNull VideoResult.Stub stub); |
|
||||||
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region Size / Surface
|
|
||||||
|
|
||||||
@Override |
|
||||||
public final void onSurfaceChanged() { |
|
||||||
LOG.i("onSurfaceChanged:", "Size is", getPreviewSurfaceSize(Reference.VIEW)); |
|
||||||
getOrchestrator().scheduleStateful("surface changed", CameraState.BIND, |
|
||||||
new Runnable() { |
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
// Compute a new camera preview size and apply.
|
|
||||||
Size newSize = computePreviewStreamSize(); |
|
||||||
if (newSize.equals(mPreviewStreamSize)) { |
|
||||||
LOG.i("onSurfaceChanged:", |
|
||||||
"The computed preview size is identical. No op."); |
|
||||||
} else { |
|
||||||
LOG.i("onSurfaceChanged:", |
|
||||||
"Computed a new preview size. Calling onPreviewStreamSizeChanged()."); |
|
||||||
mPreviewStreamSize = newSize; |
|
||||||
onPreviewStreamSizeChanged(); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The preview stream size has changed. At this point, some engine might want to |
|
||||||
* simply call {@link #restartPreview()}, others to {@link #restartBind()}. |
|
||||||
* |
|
||||||
* It basically depends on the step at which the preview stream size is actually used. |
|
||||||
*/ |
|
||||||
@EngineThread |
|
||||||
protected abstract void onPreviewStreamSizeChanged(); |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Size getPictureSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { |
|
||||||
Size size = mCaptureSize; |
|
||||||
if (size == null || mMode == Mode.VIDEO) return null; |
|
||||||
return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Size getVideoSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { |
|
||||||
Size size = mCaptureSize; |
|
||||||
if (size == null || mMode == Mode.PICTURE) return null; |
|
||||||
return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Size getPreviewStreamSize(@NonNull Reference reference) { |
|
||||||
Size size = mPreviewStreamSize; |
|
||||||
if (size == null) return null; |
|
||||||
return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; |
|
||||||
} |
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue") |
|
||||||
@Nullable |
|
||||||
private Size getPreviewSurfaceSize(@NonNull Reference reference) { |
|
||||||
CameraPreview preview = mPreview; |
|
||||||
if (preview == null) return null; |
|
||||||
return getAngles().flip(Reference.VIEW, reference) ? preview.getSurfaceSize().flip() |
|
||||||
: preview.getSurfaceSize(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the snapshot size, but not cropped with the view dimensions, which |
|
||||||
* is what we will do before creating the snapshot. However, cropping is done at various |
|
||||||
* levels so we don't want to perform the op here. |
|
||||||
* |
|
||||||
* The base snapshot size is based on PreviewStreamSize (later cropped with view ratio). Why? |
|
||||||
* One might be tempted to say that it's the SurfaceSize (which already matches the view ratio). |
|
||||||
* |
|
||||||
* The camera sensor will capture preview frames with PreviewStreamSize and that's it. Then they |
|
||||||
* are hardware-scaled by the preview surface, but this does not affect the snapshot, as the |
|
||||||
* snapshot recorder simply creates another surface. |
|
||||||
* |
|
||||||
* Done tests to ensure that this is true, by using |
|
||||||
* 1. small SurfaceSize and biggest() PreviewStreamSize: output is not low quality |
|
||||||
* 2. big SurfaceSize and smallest() PreviewStreamSize: output is low quality |
|
||||||
* In both cases the result.size here was set to the biggest of the two. |
|
||||||
* |
|
||||||
* I could not find the same evidence for videos, but I would say that the same things should |
|
||||||
* apply, despite the capturing mechanism being different. |
|
||||||
* |
|
||||||
* @param reference the reference system |
|
||||||
* @return the uncropped snapshot size |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public final Size getUncroppedSnapshotSize(@NonNull Reference reference) { |
|
||||||
Size baseSize = getPreviewStreamSize(reference); |
|
||||||
if (baseSize == null) return null; |
|
||||||
boolean flip = getAngles().flip(reference, Reference.VIEW); |
|
||||||
int maxWidth = flip ? mSnapshotMaxHeight : mSnapshotMaxWidth; |
|
||||||
int maxHeight = flip ? mSnapshotMaxWidth : mSnapshotMaxHeight; |
|
||||||
if (maxWidth <= 0) maxWidth = Integer.MAX_VALUE; |
|
||||||
if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE; |
|
||||||
float baseRatio = AspectRatio.of(baseSize).toFloat(); |
|
||||||
float maxValuesRatio = AspectRatio.of(maxWidth, maxHeight).toFloat(); |
|
||||||
if (maxValuesRatio >= baseRatio) { |
|
||||||
// Height is the real constraint.
|
|
||||||
int outHeight = Math.min(baseSize.getHeight(), maxHeight); |
|
||||||
int outWidth = (int) Math.floor((float) outHeight * baseRatio); |
|
||||||
return new Size(outWidth, outHeight); |
|
||||||
} else { |
|
||||||
// Width is the real constraint.
|
|
||||||
int outWidth = Math.min(baseSize.getWidth(), maxWidth); |
|
||||||
int outHeight = (int) Math.floor((float) outWidth / baseRatio); |
|
||||||
return new Size(outWidth, outHeight); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This is called either on cameraView.start(), or when the underlying surface changes. |
|
||||||
* It is possible that in the first call the preview surface has not already computed its |
|
||||||
* dimensions. |
|
||||||
* But when it does, the {@link CameraPreview.SurfaceCallback} should be called, |
|
||||||
* and this should be refreshed. |
|
||||||
* |
|
||||||
* @return the capture size |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected final Size computeCaptureSize() { |
|
||||||
return computeCaptureSize(mMode); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected final Size computeCaptureSize(@NonNull Mode mode) { |
|
||||||
// We want to pass stuff into the REF_VIEW reference, not the sensor one.
|
|
||||||
// This is already managed by CameraOptions, so we just flip again at the end.
|
|
||||||
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); |
|
||||||
SizeSelector selector; |
|
||||||
Collection<Size> sizes; |
|
||||||
if (mode == Mode.PICTURE) { |
|
||||||
selector = mPictureSizeSelector; |
|
||||||
sizes = mCameraOptions.getSupportedPictureSizes(); |
|
||||||
} else { |
|
||||||
selector = mVideoSizeSelector; |
|
||||||
sizes = mCameraOptions.getSupportedVideoSizes(); |
|
||||||
} |
|
||||||
selector = SizeSelectors.or(selector, SizeSelectors.biggest()); |
|
||||||
List<Size> list = new ArrayList<>(sizes); |
|
||||||
Size result = selector.select(list).get(0); |
|
||||||
if (!list.contains(result)) { |
|
||||||
throw new RuntimeException("SizeSelectors must not return Sizes other than " + |
|
||||||
"those in the input list."); |
|
||||||
} |
|
||||||
LOG.i("computeCaptureSize:", "result:", result, "flip:", flip, "mode:", mode); |
|
||||||
if (flip) result = result.flip(); // Go back to REF_SENSOR
|
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This is called anytime {@link #computePreviewStreamSize()} is called. |
|
||||||
* This means that it should be called during the binding process, when |
|
||||||
* we can be sure that the camera is available (engineState == STARTED). |
|
||||||
* @return a list of available sizes for preview |
|
||||||
*/ |
|
||||||
@EngineThread |
|
||||||
@NonNull |
|
||||||
protected abstract List<Size> getPreviewStreamAvailableSizes(); |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
@NonNull |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected final Size computePreviewStreamSize() { |
|
||||||
@NonNull List<Size> previewSizes = getPreviewStreamAvailableSizes(); |
|
||||||
// These sizes come in REF_SENSOR. Since there is an external selector involved,
|
|
||||||
// we must convert all of them to REF_VIEW, then flip back when returning.
|
|
||||||
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); |
|
||||||
List<Size> sizes = new ArrayList<>(previewSizes.size()); |
|
||||||
for (Size size : previewSizes) { |
|
||||||
sizes.add(flip ? size.flip() : size); |
|
||||||
} |
|
||||||
|
|
||||||
// Create our own default selector, which will be used if the external
|
|
||||||
// mPreviewStreamSizeSelector is null, or if it fails in finding a size.
|
|
||||||
Size targetMinSize = getPreviewSurfaceSize(Reference.VIEW); |
|
||||||
if (targetMinSize == null) { |
|
||||||
throw new IllegalStateException("targetMinSize should not be null here."); |
|
||||||
} |
|
||||||
AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); |
|
||||||
if (flip) targetRatio = targetRatio.flip(); |
|
||||||
LOG.i("computePreviewStreamSize:", |
|
||||||
"targetRatio:", targetRatio, |
|
||||||
"targetMinSize:", targetMinSize); |
|
||||||
SizeSelector matchRatio = SizeSelectors.and( // Match this aspect ratio and sort by biggest
|
|
||||||
SizeSelectors.aspectRatio(targetRatio, 0), |
|
||||||
SizeSelectors.biggest()); |
|
||||||
SizeSelector matchSize = SizeSelectors.and( // Bigger than this size, and sort by smallest
|
|
||||||
SizeSelectors.minHeight(targetMinSize.getHeight()), |
|
||||||
SizeSelectors.minWidth(targetMinSize.getWidth()), |
|
||||||
SizeSelectors.smallest()); |
|
||||||
SizeSelector matchAll = SizeSelectors.or( |
|
||||||
SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
|
|
||||||
matchSize, // If couldn't match aspect ratio, at least respect the size
|
|
||||||
matchRatio, // If couldn't respect size, at least match aspect ratio
|
|
||||||
SizeSelectors.biggest() // If couldn't match any, take the biggest.
|
|
||||||
); |
|
||||||
|
|
||||||
// Apply the external selector with this as a fallback,
|
|
||||||
// and return a size in REF_SENSOR reference.
|
|
||||||
SizeSelector selector; |
|
||||||
if (mPreviewStreamSizeSelector != null) { |
|
||||||
selector = SizeSelectors.or(mPreviewStreamSizeSelector, matchAll); |
|
||||||
} else { |
|
||||||
selector = matchAll; |
|
||||||
} |
|
||||||
Size result = selector.select(sizes).get(0); |
|
||||||
if (!sizes.contains(result)) { |
|
||||||
throw new RuntimeException("SizeSelectors must not return Sizes other than " + |
|
||||||
"those in the input list."); |
|
||||||
} |
|
||||||
if (flip) result = result.flip(); |
|
||||||
LOG.i("computePreviewStreamSize:", "result:", result, "flip:", flip); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This is called anytime {@link #computeFrameProcessingSize()} is called. |
|
||||||
* Implementors can return null if frame processor size is not selectable |
|
||||||
* @return a list of available sizes for frame processing |
|
||||||
*/ |
|
||||||
@EngineThread |
|
||||||
@NonNull |
|
||||||
protected abstract List<Size> getFrameProcessingAvailableSizes(); |
|
||||||
|
|
||||||
@EngineThread |
|
||||||
@NonNull |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
protected final Size computeFrameProcessingSize() { |
|
||||||
@NonNull List<Size> frameSizes = getFrameProcessingAvailableSizes(); |
|
||||||
// These sizes come in REF_SENSOR. Since there is an external selector involved,
|
|
||||||
// we must convert all of them to REF_VIEW, then flip back when returning.
|
|
||||||
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); |
|
||||||
List<Size> sizes = new ArrayList<>(frameSizes.size()); |
|
||||||
for (Size size : frameSizes) { |
|
||||||
sizes.add(flip ? size.flip() : size); |
|
||||||
} |
|
||||||
AspectRatio targetRatio = AspectRatio.of( |
|
||||||
mPreviewStreamSize.getWidth(), |
|
||||||
mPreviewStreamSize.getHeight()); |
|
||||||
if (flip) targetRatio = targetRatio.flip(); |
|
||||||
int maxWidth = mFrameProcessingMaxWidth; |
|
||||||
int maxHeight = mFrameProcessingMaxHeight; |
|
||||||
if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) maxWidth = 640; |
|
||||||
if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) maxHeight = 640; |
|
||||||
Size targetMaxSize = new Size(maxWidth, maxHeight); |
|
||||||
LOG.i("computeFrameProcessingSize:", |
|
||||||
"targetRatio:", targetRatio, |
|
||||||
"targetMaxSize:", targetMaxSize); |
|
||||||
SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); |
|
||||||
SizeSelector matchSize = SizeSelectors.and( |
|
||||||
SizeSelectors.maxHeight(targetMaxSize.getHeight()), |
|
||||||
SizeSelectors.maxWidth(targetMaxSize.getWidth()), |
|
||||||
SizeSelectors.biggest()); |
|
||||||
SizeSelector matchAll = SizeSelectors.or( |
|
||||||
SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
|
|
||||||
matchSize, // If couldn't match aspect ratio, at least respect the size
|
|
||||||
SizeSelectors.smallest() // If couldn't match any, take the smallest.
|
|
||||||
); |
|
||||||
Size result = matchAll.select(sizes).get(0); |
|
||||||
if (!sizes.contains(result)) { |
|
||||||
throw new RuntimeException("SizeSelectors must not return Sizes other than " + |
|
||||||
"those in the input list."); |
|
||||||
} |
|
||||||
if (flip) result = result.flip(); |
|
||||||
LOG.i("computeFrameProcessingSize:", "result:", result, "flip:", flip); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
//endregion
|
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that some action is being executed on the {@link CameraEngine} thread. |
|
||||||
*/ |
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
public @interface EngineThread {} |
|
@ -0,0 +1,221 @@ |
|||||||
|
package com.otaliastudios.cameraview.engine; |
||||||
|
|
||||||
|
import android.hardware.Camera; |
||||||
|
import android.hardware.camera2.CameraCharacteristics; |
||||||
|
import android.os.Build; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.controls.Control; |
||||||
|
import com.otaliastudios.cameraview.controls.Engine; |
||||||
|
import com.otaliastudios.cameraview.controls.Facing; |
||||||
|
import com.otaliastudios.cameraview.controls.Flash; |
||||||
|
import com.otaliastudios.cameraview.controls.Hdr; |
||||||
|
import com.otaliastudios.cameraview.controls.WhiteBalance; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.RequiresApi; |
||||||
|
|
||||||
|
/** |
||||||
|
* A Mapper maps camera engine constants to CameraView constants. |
||||||
|
*/ |
||||||
|
public abstract class Mapper { |
||||||
|
|
||||||
|
private static Mapper CAMERA1; |
||||||
|
private static Mapper CAMERA2; |
||||||
|
|
||||||
|
public static Mapper get(@NonNull Engine engine) { |
||||||
|
if (engine == Engine.CAMERA1) { |
||||||
|
if (CAMERA1 == null) CAMERA1 = new Camera1Mapper(); |
||||||
|
return CAMERA1; |
||||||
|
} else if (engine == Engine.CAMERA2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||||
|
if (CAMERA2 == null) CAMERA2 = new Camera2Mapper(); |
||||||
|
return CAMERA2; |
||||||
|
} else { |
||||||
|
throw new IllegalArgumentException("Unknown engine or unsupported API level."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Mapper() {} |
||||||
|
|
||||||
|
public abstract <T> T map(Flash flash); |
||||||
|
|
||||||
|
public abstract <T> T map(Facing facing); |
||||||
|
|
||||||
|
public abstract <T> T map(WhiteBalance whiteBalance); |
||||||
|
|
||||||
|
public abstract <T> T map(Hdr hdr); |
||||||
|
|
||||||
|
public abstract <T> Flash unmapFlash(T cameraConstant); |
||||||
|
|
||||||
|
public abstract <T> Facing unmapFacing(T cameraConstant); |
||||||
|
|
||||||
|
public abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant); |
||||||
|
|
||||||
|
public abstract <T> Hdr unmapHdr(T cameraConstant); |
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") |
||||||
|
protected <C extends Control, T> C reverseLookup(HashMap<C, T> map, T object) { |
||||||
|
for (C value : map.keySet()) { |
||||||
|
if (object.equals(map.get(value))) { |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") |
||||||
|
protected <C extends Control, T> C reverseListLookup(HashMap<C, List<T>> map, T object) { |
||||||
|
for (C value : map.keySet()) { |
||||||
|
List<T> list = map.get(value); |
||||||
|
if (list == null) continue; |
||||||
|
for (T candidate : list) { |
||||||
|
if (object.equals(candidate)) { |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private static class Camera1Mapper extends Mapper { |
||||||
|
|
||||||
|
private static final HashMap<Flash, String> FLASH = new HashMap<>(); |
||||||
|
private static final HashMap<WhiteBalance, String> WB = new HashMap<>(); |
||||||
|
private static final HashMap<Facing, Integer> FACING = new HashMap<>(); |
||||||
|
private static final HashMap<Hdr, String> HDR = new HashMap<>(); |
||||||
|
|
||||||
|
static { |
||||||
|
FLASH.put(Flash.OFF, Camera.Parameters.FLASH_MODE_OFF); |
||||||
|
FLASH.put(Flash.ON, Camera.Parameters.FLASH_MODE_ON); |
||||||
|
FLASH.put(Flash.AUTO, Camera.Parameters.FLASH_MODE_AUTO); |
||||||
|
FLASH.put(Flash.TORCH, Camera.Parameters.FLASH_MODE_TORCH); |
||||||
|
FACING.put(Facing.BACK, Camera.CameraInfo.CAMERA_FACING_BACK); |
||||||
|
FACING.put(Facing.FRONT, Camera.CameraInfo.CAMERA_FACING_FRONT); |
||||||
|
WB.put(WhiteBalance.AUTO, Camera.Parameters.WHITE_BALANCE_AUTO); |
||||||
|
WB.put(WhiteBalance.INCANDESCENT, Camera.Parameters.WHITE_BALANCE_INCANDESCENT); |
||||||
|
WB.put(WhiteBalance.FLUORESCENT, Camera.Parameters.WHITE_BALANCE_FLUORESCENT); |
||||||
|
WB.put(WhiteBalance.DAYLIGHT, Camera.Parameters.WHITE_BALANCE_DAYLIGHT); |
||||||
|
WB.put(WhiteBalance.CLOUDY, Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT); |
||||||
|
HDR.put(Hdr.OFF, Camera.Parameters.SCENE_MODE_AUTO); |
||||||
|
if (Build.VERSION.SDK_INT >= 17) { |
||||||
|
HDR.put(Hdr.ON, Camera.Parameters.SCENE_MODE_HDR); |
||||||
|
} else { |
||||||
|
HDR.put(Hdr.ON, "hdr"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(Flash flash) { |
||||||
|
return (T) FLASH.get(flash); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(Facing facing) { |
||||||
|
return (T) FACING.get(facing); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(WhiteBalance whiteBalance) { |
||||||
|
return (T) WB.get(whiteBalance); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(Hdr hdr) { |
||||||
|
return (T) HDR.get(hdr); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Flash unmapFlash(T cameraConstant) { |
||||||
|
return reverseLookup(FLASH, (String) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Facing unmapFacing(T cameraConstant) { |
||||||
|
return reverseLookup(FACING, (Integer) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { |
||||||
|
return reverseLookup(WB, (String) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Hdr unmapHdr(T cameraConstant) { |
||||||
|
return reverseLookup(HDR, (String) cameraConstant); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||||
|
private static class Camera2Mapper extends Mapper { |
||||||
|
|
||||||
|
private static final HashMap<Flash, List<Integer>> FLASH = new HashMap<>(); |
||||||
|
private static final HashMap<Facing, Integer> FACING = new HashMap<>(); |
||||||
|
private static final HashMap<WhiteBalance, Integer> WB = new HashMap<>(); |
||||||
|
private static final HashMap<Hdr, Integer> HDR = new HashMap<>(); |
||||||
|
|
||||||
|
static { |
||||||
|
// OFF and TORCH have also a second condition - to CameraCharacteristics.CONTROL_FLASH_MODE - but that does not
|
||||||
|
// fit into the Mapper interface. TODO review this
|
||||||
|
FLASH.put(Flash.OFF, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); |
||||||
|
FLASH.put(Flash.TORCH, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON, CameraCharacteristics.CONTROL_AE_MODE_OFF)); |
||||||
|
FLASH.put(Flash.AUTO, Arrays.asList(CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH, CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE)); |
||||||
|
FLASH.put(Flash.ON, Collections.singletonList(CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH)); |
||||||
|
FACING.put(Facing.BACK, CameraCharacteristics.LENS_FACING_BACK); |
||||||
|
FACING.put(Facing.FRONT, CameraCharacteristics.LENS_FACING_FRONT); |
||||||
|
WB.put(WhiteBalance.AUTO, CameraCharacteristics.CONTROL_AWB_MODE_AUTO); |
||||||
|
WB.put(WhiteBalance.CLOUDY, CameraCharacteristics.CONTROL_AWB_MODE_CLOUDY_DAYLIGHT); |
||||||
|
WB.put(WhiteBalance.DAYLIGHT, CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT); |
||||||
|
WB.put(WhiteBalance.FLUORESCENT, CameraCharacteristics.CONTROL_AWB_MODE_FLUORESCENT); |
||||||
|
WB.put(WhiteBalance.INCANDESCENT, CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT); |
||||||
|
HDR.put(Hdr.OFF, CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED); |
||||||
|
HDR.put(Hdr.ON, 18 /* CameraCharacteristics.CONTROL_SCENE_MODE_HDR */); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions") |
||||||
|
@Override |
||||||
|
public <T> T map(Flash flash) { |
||||||
|
return (T) FLASH.get(flash); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(Facing facing) { |
||||||
|
return (T) FACING.get(facing); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(WhiteBalance whiteBalance) { |
||||||
|
return (T) WB.get(whiteBalance); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> T map(Hdr hdr) { |
||||||
|
return (T) HDR.get(hdr); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Flash unmapFlash(T cameraConstant) { |
||||||
|
return reverseListLookup(FLASH, (Integer) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Facing unmapFacing(T cameraConstant) { |
||||||
|
return reverseLookup(FACING, (Integer) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> WhiteBalance unmapWhiteBalance(T cameraConstant) { |
||||||
|
return reverseLookup(WB, (Integer) cameraConstant); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> Hdr unmapHdr(T cameraConstant) { |
||||||
|
return reverseLookup(HDR, (Integer) cameraConstant); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,170 @@ |
|||||||
|
package com.otaliastudios.cameraview.engine; |
||||||
|
|
||||||
|
import com.google.android.gms.tasks.Continuation; |
||||||
|
import com.google.android.gms.tasks.OnFailureListener; |
||||||
|
import com.google.android.gms.tasks.SuccessContinuation; |
||||||
|
import com.google.android.gms.tasks.Task; |
||||||
|
import com.google.android.gms.tasks.Tasks; |
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
import java.util.concurrent.Executor; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.annotation.VisibleForTesting; |
||||||
|
|
||||||
|
/** |
||||||
|
* Represents one of the steps in the {@link CameraEngine} setup: for example, the engine step, |
||||||
|
* the bind-to-surface step, and the preview step. |
||||||
|
* |
||||||
|
* A step is something that can be setup (started) or torn down (stopped), and |
||||||
|
* steps can of course depend onto each other. |
||||||
|
* |
||||||
|
* The purpose of this class is to manage the step state (stopping, stopped, starting or started) |
||||||
|
* and, more importantly, to perform START and STOP operations in such a way that they do not |
||||||
|
* overlap. For example, if we're stopping, we're wait for stop to finish before starting again. |
||||||
|
* |
||||||
|
* This is an important condition for simplifying the engine code. |
||||||
|
* Since Camera1, the only requirement was basically to use a single thread. |
||||||
|
* Since Camera2, which has an asynchronous API, further care must be used. |
||||||
|
* |
||||||
|
* For this reason, we use Google's {@link Task} abstraction and only start new operations |
||||||
|
* once the previous one has ended. |
||||||
|
* |
||||||
|
* <strong>This class is NOT thread safe!</string> |
||||||
|
*/ |
||||||
|
class Step { |
||||||
|
|
||||||
|
private static final String TAG = Step.class.getSimpleName(); |
||||||
|
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||||
|
|
||||||
|
interface Callback { |
||||||
|
@NonNull |
||||||
|
Executor getExecutor(); |
||||||
|
void handleException(@NonNull Exception exception); |
||||||
|
} |
||||||
|
|
||||||
|
static final int STATE_STOPPING = -1; |
||||||
|
static final int STATE_STOPPED = 0; |
||||||
|
static final int STATE_STARTING = 1; |
||||||
|
static final int STATE_STARTED = 2; |
||||||
|
|
||||||
|
private int state = STATE_STOPPED; |
||||||
|
|
||||||
|
// To avoid dirty scenarios (e.g. calling stopXXX while XXX is starting),
|
||||||
|
// and since every operation can be asynchronous, we use some tasks for each step.
|
||||||
|
private Task<Void> task = Tasks.forResult(null); |
||||||
|
|
||||||
|
private final String name; |
||||||
|
private final Callback callback; |
||||||
|
|
||||||
|
Step(@NonNull String name, @NonNull Callback callback) { |
||||||
|
this.name = name.toUpperCase(); |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
int getState() { |
||||||
|
return state; |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting void setState(int newState) { |
||||||
|
state = newState; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
String getStateName() { |
||||||
|
switch (state) { |
||||||
|
case STATE_STOPPING: return name + "_STATE_STOPPING"; |
||||||
|
case STATE_STOPPED: return name + "_STATE_STOPPED"; |
||||||
|
case STATE_STARTING: return name + "_STATE_STARTING"; |
||||||
|
case STATE_STARTED: return name + "_STATE_STARTED"; |
||||||
|
} |
||||||
|
return "null"; |
||||||
|
} |
||||||
|
|
||||||
|
boolean isStoppingOrStopped() { |
||||||
|
return state == STATE_STOPPING || state == STATE_STOPPED; |
||||||
|
} |
||||||
|
|
||||||
|
boolean isStartedOrStarting() { |
||||||
|
return state == STATE_STARTING || state == STATE_STARTED; |
||||||
|
} |
||||||
|
|
||||||
|
boolean isStarted() { |
||||||
|
return state == STATE_STARTED; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
Task<Void> getTask() { |
||||||
|
return task; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings({"SameParameterValue", "UnusedReturnValue"}) |
||||||
|
Task<Void> doStart(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op) { |
||||||
|
return doStart(swallowExceptions, op, null); |
||||||
|
} |
||||||
|
|
||||||
|
Task<Void> doStart(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op, final @Nullable Runnable onStarted) { |
||||||
|
LOG.i(name, "doStart", "Called. Enqueuing."); |
||||||
|
task = task.continueWithTask(callback.getExecutor(), new Continuation<Void, Task<Void>>() { |
||||||
|
@Override |
||||||
|
public Task<Void> then(@NonNull Task<Void> task) throws Exception { |
||||||
|
LOG.i(name, "doStart", "About to start. Setting state to STARTING"); |
||||||
|
setState(STATE_STARTING); |
||||||
|
return op.call().addOnFailureListener(callback.getExecutor(), new OnFailureListener() { |
||||||
|
@Override |
||||||
|
public void onFailure(@NonNull Exception e) { |
||||||
|
LOG.w(name, "doStart", "Failed with error", e, "Setting state to STOPPED"); |
||||||
|
setState(STATE_STOPPED); |
||||||
|
if (!swallowExceptions) callback.handleException(e); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}).onSuccessTask(callback.getExecutor(), new SuccessContinuation<Void, Void>() { |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Task<Void> then(@Nullable Void aVoid) { |
||||||
|
LOG.i(name, "doStart", "Succeeded! Setting state to STARTED"); |
||||||
|
setState(STATE_STARTED); |
||||||
|
if (onStarted != null) onStarted.run(); |
||||||
|
return Tasks.forResult(null); |
||||||
|
} |
||||||
|
}); |
||||||
|
return task; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue") |
||||||
|
Task<Void> doStop(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op) { |
||||||
|
return doStop(swallowExceptions, op, null); |
||||||
|
} |
||||||
|
|
||||||
|
Task<Void> doStop(final boolean swallowExceptions, final @NonNull Callable<Task<Void>> op, final @Nullable Runnable onStopped) { |
||||||
|
LOG.i(name, "doStop", "Called. Enqueuing."); |
||||||
|
task = task.continueWithTask(callback.getExecutor(), new Continuation<Void, Task<Void>>() { |
||||||
|
@Override |
||||||
|
public Task<Void> then(@NonNull Task<Void> task) throws Exception { |
||||||
|
LOG.i(name, "doStop", "About to stop. Setting state to STOPPING"); |
||||||
|
state = STATE_STOPPING; |
||||||
|
return op.call().addOnFailureListener(callback.getExecutor(), new OnFailureListener() { |
||||||
|
@Override |
||||||
|
public void onFailure(@NonNull Exception e) { |
||||||
|
LOG.w(name, "doStop", "Failed with error", e, "Setting state to STOPPED"); |
||||||
|
state = STATE_STOPPED; |
||||||
|
if (!swallowExceptions) callback.handleException(e); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}).onSuccessTask(callback.getExecutor(), new SuccessContinuation<Void, Void>() { |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Task<Void> then(@Nullable Void aVoid) { |
||||||
|
LOG.i(name, "doStop", "Succeeded! Setting state to STOPPED"); |
||||||
|
state = STATE_STOPPED; |
||||||
|
if (onStopped != null) onStopped.run(); |
||||||
|
return Tasks.forResult(null); |
||||||
|
} |
||||||
|
}); |
||||||
|
return task; |
||||||
|
} |
||||||
|
} |
@ -1,92 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine.action; |
|
||||||
|
|
||||||
import android.hardware.camera2.CameraCaptureSession; |
|
||||||
import android.hardware.camera2.CameraCaptureSession.CaptureCallback; |
|
||||||
import android.hardware.camera2.CaptureRequest; |
|
||||||
import android.hardware.camera2.CaptureResult; |
|
||||||
import android.hardware.camera2.TotalCaptureResult; |
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
|
|
||||||
/** |
|
||||||
* The Action class encapsulates logic for completing an action in a Camera2 environment. |
|
||||||
* In this case, we are often interested in constantly receiving the {@link CaptureResult} |
|
||||||
* and {@link CaptureRequest} callbacks, as well as applying changes to a |
|
||||||
* {@link CaptureRequest.Builder} and having them applied to the sensor. |
|
||||||
* |
|
||||||
* The Action class receives the given callbacks and can operate over the engine |
|
||||||
* through the {@link ActionHolder} object. |
|
||||||
* |
|
||||||
* Each Action operates on a given state in a given moment. This base class offers the |
|
||||||
* {@link #STATE_COMPLETED} state which is common to all actions. |
|
||||||
* |
|
||||||
* See {@link BaseAction} for a base implementation. |
|
||||||
*/ |
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
|
||||||
public interface Action { |
|
||||||
|
|
||||||
int STATE_COMPLETED = Integer.MAX_VALUE; |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the current state. |
|
||||||
* @return the state |
|
||||||
*/ |
|
||||||
int getState(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Starts this action. |
|
||||||
* @param holder the holder |
|
||||||
*/ |
|
||||||
void start(@NonNull ActionHolder holder); |
|
||||||
|
|
||||||
/** |
|
||||||
* Aborts this action. |
|
||||||
* @param holder the holder |
|
||||||
*/ |
|
||||||
void abort(@NonNull ActionHolder holder); |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds an {@link ActionCallback} to receive state |
|
||||||
* change events. |
|
||||||
* @param callback a callback |
|
||||||
*/ |
|
||||||
void addCallback(@NonNull ActionCallback callback); |
|
||||||
|
|
||||||
/** |
|
||||||
* Removes a previously added callback. |
|
||||||
* @param callback a callback |
|
||||||
*/ |
|
||||||
void removeCallback(@NonNull ActionCallback callback); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called from {@link CaptureCallback#onCaptureStarted(CameraCaptureSession, CaptureRequest, |
|
||||||
* long, long)}. |
|
||||||
* @param holder the holder |
|
||||||
* @param request the request |
|
||||||
*/ |
|
||||||
void onCaptureStarted(@NonNull ActionHolder holder, @NonNull CaptureRequest request); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called from {@link CaptureCallback#onCaptureProgressed(CameraCaptureSession, CaptureRequest, |
|
||||||
* CaptureResult)}. |
|
||||||
* @param holder the holder |
|
||||||
* @param request the request |
|
||||||
* @param result the result |
|
||||||
*/ |
|
||||||
void onCaptureProgressed(@NonNull ActionHolder holder, |
|
||||||
@NonNull CaptureRequest request, |
|
||||||
@NonNull CaptureResult result); |
|
||||||
|
|
||||||
/** |
|
||||||
* Called from {@link CaptureCallback#onCaptureCompleted(CameraCaptureSession, CaptureRequest, |
|
||||||
* TotalCaptureResult)}. |
|
||||||
* @param holder the holder |
|
||||||
* @param request the request |
|
||||||
* @param result the result |
|
||||||
*/ |
|
||||||
void onCaptureCompleted(@NonNull ActionHolder holder, |
|
||||||
@NonNull CaptureRequest request, |
|
||||||
@NonNull TotalCaptureResult result); |
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine.action; |
|
||||||
|
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
|
|
||||||
/** |
|
||||||
* A callback for {@link Action} state changes. |
|
||||||
* See the action class. |
|
||||||
* |
|
||||||
* See also {@link CompletionCallback}. |
|
||||||
*/ |
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
|
||||||
public interface ActionCallback { |
|
||||||
|
|
||||||
/** |
|
||||||
* Action state has just changed. |
|
||||||
* @param action action |
|
||||||
* @param state new state |
|
||||||
*/ |
|
||||||
void onActionStateChanged(@NonNull Action action, int state); |
|
||||||
} |
|
@ -1,83 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.engine.action; |
|
||||||
|
|
||||||
import android.hardware.camera2.CameraAccessException; |
|
||||||
import android.hardware.camera2.CameraCharacteristics; |
|
||||||
import android.hardware.camera2.CaptureRequest; |
|
||||||
import android.hardware.camera2.CaptureResult; |
|
||||||
import android.hardware.camera2.TotalCaptureResult; |
|
||||||
import android.os.Build; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
import androidx.annotation.Nullable; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
|
|
||||||
/** |
|
||||||
* The holder of {@link Action}. |
|
||||||
* |
|
||||||
* This class should keep a list or set of currently running actions, and offers |
|
||||||
* to them the base Camera2 objects that are needed to apply changes. |
|
||||||
* |
|
||||||
* This class, or an holder of it, should also forward the capture callbacks |
|
||||||
* to all {@link Action}s. See {@link com.otaliastudios.cameraview.engine.Camera2Engine} for |
|
||||||
* our implementation. |
|
||||||
*/ |
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
|
||||||
public interface ActionHolder { |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a new action |
|
||||||
* @param action action |
|
||||||
*/ |
|
||||||
void addAction(@NonNull Action action); |
|
||||||
|
|
||||||
/** |
|
||||||
* Removes a previously added action |
|
||||||
* @param action action |
|
||||||
*/ |
|
||||||
void removeAction(@NonNull Action action); |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the {@link CameraCharacteristics} of the current |
|
||||||
* camera device. |
|
||||||
* @param action action |
|
||||||
* @return characteristics |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
CameraCharacteristics getCharacteristics(@NonNull Action action); |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the latest {@link TotalCaptureResult}. Can be used |
|
||||||
* by actions to start querying the state before receiving their |
|
||||||
* first frame. |
|
||||||
* @param action action |
|
||||||
* @return last result |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
TotalCaptureResult getLastResult(@NonNull Action action); |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the current {@link CaptureRequest.Builder} so that |
|
||||||
* actions can apply changes to it and later submit them. |
|
||||||
* @param action action |
|
||||||
* @return the builder |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
CaptureRequest.Builder getBuilder(@NonNull Action action); |
|
||||||
|
|
||||||
/** |
|
||||||
* Applies the current builder (as per {@link #getBuilder(Action)}) |
|
||||||
* as a repeating request on the preview. |
|
||||||
* @param source action |
|
||||||
*/ |
|
||||||
void applyBuilder(@NonNull Action source); |
|
||||||
|
|
||||||
/** |
|
||||||
* Applies the given builder as a single capture request. |
|
||||||
* Callers can catch the exception and choose what to do. |
|
||||||
* @param source action |
|
||||||
* @param builder builder |
|
||||||
* @throws CameraAccessException camera exception |
|
||||||
*/ |
|
||||||
void applyBuilder(@NonNull Action source, @NonNull CaptureRequest.Builder builder) |
|
||||||
throws CameraAccessException; |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue