@ -0,0 +1 @@ |
|||||||
|
/build |
@ -0,0 +1,25 @@ |
|||||||
|
apply plugin: 'com.android.application' |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion 25 |
||||||
|
buildToolsVersion "25.0.2" |
||||||
|
defaultConfig { |
||||||
|
applicationId "com.wonderkiln.camerakit.demo" |
||||||
|
minSdkVersion 21 |
||||||
|
targetSdkVersion 25 |
||||||
|
versionCode 1 |
||||||
|
versionName "1.0" |
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |
||||||
|
} |
||||||
|
buildTypes { |
||||||
|
release { |
||||||
|
minifyEnabled false |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
compile 'com.android.support:appcompat-v7:25.0.1' |
||||||
|
compile project(':camerakit') |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
# Add project specific ProGuard rules here. |
||||||
|
# By default, the flags in this file are appended to flags specified |
||||||
|
# in /Users/dylanmcintyre/Library/Android/sdk/tools/proguard/proguard-android.txt |
||||||
|
# You can edit the include path and order by changing the proguardFiles |
||||||
|
# directive in build.gradle. |
||||||
|
# |
||||||
|
# For more details, see |
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html |
||||||
|
|
||||||
|
# Add any project specific keep options here: |
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following |
||||||
|
# and specify the fully qualified class name to the JavaScript interface |
||||||
|
# class: |
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||||
|
# public *; |
||||||
|
#} |
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for |
||||||
|
# debugging stack traces. |
||||||
|
#-keepattributes SourceFile,LineNumberTable |
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to |
||||||
|
# hide the original source file name. |
||||||
|
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,26 @@ |
|||||||
|
package com.wonderkiln.camerakit.demo; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.support.test.InstrumentationRegistry; |
||||||
|
import android.support.test.runner.AndroidJUnit4; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Instrumentation test, which will execute on an Android device. |
||||||
|
* |
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
public class ExampleInstrumentedTest { |
||||||
|
@Test |
||||||
|
public void useAppContext() throws Exception { |
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext(); |
||||||
|
|
||||||
|
assertEquals("com.wonderkiln.camerakit.demo", appContext.getPackageName()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
package="com.wonderkiln.camerakit.demo"> |
||||||
|
|
||||||
|
<application |
||||||
|
android:allowBackup="true" |
||||||
|
android:icon="@mipmap/ic_launcher" |
||||||
|
android:label="@string/app_name" |
||||||
|
android:roundIcon="@mipmap/ic_launcher_round" |
||||||
|
android:supportsRtl="true" |
||||||
|
android:theme="@style/AppTheme"> |
||||||
|
<activity android:name=".MainActivity"> |
||||||
|
<intent-filter> |
||||||
|
<action android:name="android.intent.action.MAIN" /> |
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" /> |
||||||
|
</intent-filter> |
||||||
|
</activity> |
||||||
|
</application> |
||||||
|
|
||||||
|
</manifest> |
@ -0,0 +1,14 @@ |
|||||||
|
package com.wonderkiln.camerakit.demo; |
||||||
|
|
||||||
|
import android.support.v7.app.AppCompatActivity; |
||||||
|
import android.os.Bundle; |
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_main); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout> |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<resources> |
||||||
|
<color name="colorPrimary">#3F51B5</color> |
||||||
|
<color name="colorPrimaryDark">#303F9F</color> |
||||||
|
<color name="colorAccent">#FF4081</color> |
||||||
|
</resources> |
@ -0,0 +1,3 @@ |
|||||||
|
<resources> |
||||||
|
<string name="app_name">CameraKit-Android</string> |
||||||
|
</resources> |
@ -0,0 +1,11 @@ |
|||||||
|
<resources> |
||||||
|
|
||||||
|
<!-- Base application theme. --> |
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> |
||||||
|
<!-- Customize your theme here. --> |
||||||
|
<item name="colorPrimary">@color/colorPrimary</item> |
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> |
||||||
|
<item name="colorAccent">@color/colorAccent</item> |
||||||
|
</style> |
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,17 @@ |
|||||||
|
package com.wonderkiln.camerakit.demo; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Example local unit test, which will execute on the development machine (host). |
||||||
|
* |
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
public class ExampleUnitTest { |
||||||
|
@Test |
||||||
|
public void addition_isCorrect() throws Exception { |
||||||
|
assertEquals(4, 2 + 2); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
||||||
|
|
||||||
|
buildscript { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
dependencies { |
||||||
|
classpath 'com.android.tools.build:gradle:2.3.0-beta1' |
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong |
||||||
|
// in the individual module build.gradle files |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
allprojects { |
||||||
|
repositories { |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
task clean(type: Delete) { |
||||||
|
delete rootProject.buildDir |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
/build |
@ -0,0 +1,26 @@ |
|||||||
|
apply plugin: 'com.android.library' |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion 25 |
||||||
|
buildToolsVersion "25.0.2" |
||||||
|
|
||||||
|
defaultConfig { |
||||||
|
minSdkVersion 21 |
||||||
|
targetSdkVersion 25 |
||||||
|
versionCode 1 |
||||||
|
versionName "1.0" |
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |
||||||
|
|
||||||
|
} |
||||||
|
buildTypes { |
||||||
|
release { |
||||||
|
minifyEnabled false |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
|
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
# Add project specific ProGuard rules here. |
||||||
|
# By default, the flags in this file are appended to flags specified |
||||||
|
# in /Users/dylanmcintyre/Library/Android/sdk/tools/proguard/proguard-android.txt |
||||||
|
# You can edit the include path and order by changing the proguardFiles |
||||||
|
# directive in build.gradle. |
||||||
|
# |
||||||
|
# For more details, see |
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html |
||||||
|
|
||||||
|
# Add any project specific keep options here: |
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following |
||||||
|
# and specify the fully qualified class name to the JavaScript interface |
||||||
|
# class: |
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||||
|
# public *; |
||||||
|
#} |
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for |
||||||
|
# debugging stack traces. |
||||||
|
#-keepattributes SourceFile,LineNumberTable |
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to |
||||||
|
# hide the original source file name. |
||||||
|
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,26 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.support.test.InstrumentationRegistry; |
||||||
|
import android.support.test.runner.AndroidJUnit4; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Instrumentation test, which will execute on an Android device. |
||||||
|
* |
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
public class ExampleInstrumentedTest { |
||||||
|
@Test |
||||||
|
public void useAppContext() throws Exception { |
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext(); |
||||||
|
|
||||||
|
assertEquals("com.wonderkiln.camerakit.test", appContext.getPackageName()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
package="com.wonderkiln.camerakit"> |
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" /> |
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" /> |
||||||
|
<uses-feature |
||||||
|
android:name="android.hardware.camera.autofocus" |
||||||
|
android:required="false" /> |
||||||
|
|
||||||
|
<application /> |
||||||
|
|
||||||
|
</manifest> |
@ -0,0 +1,170 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.v4.util.SparseArrayCompat; |
||||||
|
|
||||||
|
public class AspectRatio implements Comparable<AspectRatio>, Parcelable { |
||||||
|
|
||||||
|
private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache |
||||||
|
= new SparseArrayCompat<>(16); |
||||||
|
|
||||||
|
private final int mX; |
||||||
|
private final int mY; |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. |
||||||
|
* The values {@code x} and {@code} will be reduced by their greatest common divider. |
||||||
|
* |
||||||
|
* @param x The width |
||||||
|
* @param y The height |
||||||
|
* @return An instance of {@link AspectRatio} |
||||||
|
*/ |
||||||
|
public static AspectRatio of(int x, int y) { |
||||||
|
int gcd = gcd(x, y); |
||||||
|
x /= gcd; |
||||||
|
y /= gcd; |
||||||
|
SparseArrayCompat<AspectRatio> arrayX = sCache.get(x); |
||||||
|
if (arrayX == null) { |
||||||
|
AspectRatio ratio = new AspectRatio(x, y); |
||||||
|
arrayX = new SparseArrayCompat<>(); |
||||||
|
arrayX.put(y, ratio); |
||||||
|
sCache.put(x, arrayX); |
||||||
|
return ratio; |
||||||
|
} else { |
||||||
|
AspectRatio ratio = arrayX.get(y); |
||||||
|
if (ratio == null) { |
||||||
|
ratio = new AspectRatio(x, y); |
||||||
|
arrayX.put(y, ratio); |
||||||
|
} |
||||||
|
return ratio; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". |
||||||
|
* |
||||||
|
* @param s The string representation of the aspect ratio |
||||||
|
* @return The aspect ratio |
||||||
|
* @throws IllegalArgumentException when the format is incorrect. |
||||||
|
*/ |
||||||
|
public static AspectRatio parse(String s) { |
||||||
|
int position = s.indexOf(':'); |
||||||
|
if (position == -1) { |
||||||
|
throw new IllegalArgumentException("Malformed aspect ratio: " + s); |
||||||
|
} |
||||||
|
try { |
||||||
|
int x = Integer.parseInt(s.substring(0, position)); |
||||||
|
int y = Integer.parseInt(s.substring(position + 1)); |
||||||
|
return AspectRatio.of(x, y); |
||||||
|
} catch (NumberFormatException e) { |
||||||
|
throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private AspectRatio(int x, int y) { |
||||||
|
mX = x; |
||||||
|
mY = y; |
||||||
|
} |
||||||
|
|
||||||
|
public int getX() { |
||||||
|
return mX; |
||||||
|
} |
||||||
|
|
||||||
|
public int getY() { |
||||||
|
return mY; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean matches(Size size) { |
||||||
|
int gcd = gcd(size.getWidth(), size.getHeight()); |
||||||
|
int x = size.getWidth() / gcd; |
||||||
|
int y = size.getHeight() / gcd; |
||||||
|
return mX == x && mY == y; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (o == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o instanceof AspectRatio) { |
||||||
|
AspectRatio ratio = (AspectRatio) o; |
||||||
|
return mX == ratio.mX && mY == ratio.mY; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return mX + ":" + mY; |
||||||
|
} |
||||||
|
|
||||||
|
public float toFloat() { |
||||||
|
return (float) mX / mY; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
|
||||||
|
return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compareTo(@NonNull AspectRatio another) { |
||||||
|
if (equals(another)) { |
||||||
|
return 0; |
||||||
|
} else if (toFloat() - another.toFloat() > 0) { |
||||||
|
return 1; |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return The inverse of this {@link AspectRatio}. |
||||||
|
*/ |
||||||
|
public AspectRatio inverse() { |
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return AspectRatio.of(mY, mX); |
||||||
|
} |
||||||
|
|
||||||
|
private static int gcd(int a, int b) { |
||||||
|
while (b != 0) { |
||||||
|
int c = b; |
||||||
|
b = a % b; |
||||||
|
a = c; |
||||||
|
} |
||||||
|
return a; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int describeContents() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||||
|
dest.writeInt(mX); |
||||||
|
dest.writeInt(mY); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Parcelable.Creator<AspectRatio> CREATOR |
||||||
|
= new Parcelable.Creator<AspectRatio>() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public AspectRatio createFromParcel(Parcel source) { |
||||||
|
int x = source.readInt(); |
||||||
|
int y = source.readInt(); |
||||||
|
return AspectRatio.of(x, y); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AspectRatio[] newArray(int size) { |
||||||
|
return new AspectRatio[size]; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,539 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.annotation.TargetApi; |
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.ImageFormat; |
||||||
|
import android.hardware.camera2.CameraAccessException; |
||||||
|
import android.hardware.camera2.CameraCaptureSession; |
||||||
|
import android.hardware.camera2.CameraCharacteristics; |
||||||
|
import android.hardware.camera2.CameraDevice; |
||||||
|
import android.hardware.camera2.CameraManager; |
||||||
|
import android.hardware.camera2.CaptureRequest; |
||||||
|
import android.hardware.camera2.TotalCaptureResult; |
||||||
|
import android.hardware.camera2.params.StreamConfigurationMap; |
||||||
|
import android.media.Image; |
||||||
|
import android.media.ImageReader; |
||||||
|
import android.media.MediaRecorder; |
||||||
|
import android.os.Handler; |
||||||
|
import android.os.HandlerThread; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.util.Log; |
||||||
|
import android.util.SparseIntArray; |
||||||
|
import android.view.Surface; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.SortedSet; |
||||||
|
import java.util.TreeSet; |
||||||
|
import java.util.concurrent.Semaphore; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
import static android.content.ContentValues.TAG; |
||||||
|
|
||||||
|
@TargetApi(21) |
||||||
|
public class Camera2 extends CameraViewImpl { |
||||||
|
|
||||||
|
private static final SparseIntArray INTERNAL_FACINGS = new SparseIntArray(); |
||||||
|
|
||||||
|
static { |
||||||
|
INTERNAL_FACINGS.put(Constants.FACING_BACK, CameraCharacteristics.LENS_FACING_BACK); |
||||||
|
INTERNAL_FACINGS.put(Constants.FACING_FRONT, CameraCharacteristics.LENS_FACING_FRONT); |
||||||
|
} |
||||||
|
|
||||||
|
private CameraManager mCameraManager; |
||||||
|
private CameraDevice mCamera; |
||||||
|
private CameraCaptureSession mCaptureSession; |
||||||
|
private CaptureRequest.Builder mPreviewRequestBuilder; |
||||||
|
private ImageReader mImageReader; |
||||||
|
private MediaRecorder mMediaRecorder; |
||||||
|
|
||||||
|
private Semaphore mCameraOpenCloseLock; |
||||||
|
|
||||||
|
private int mFacing; |
||||||
|
private int mFlash; |
||||||
|
private int mDisplayOrientation; |
||||||
|
private String mCameraId; |
||||||
|
private CameraCharacteristics mCameraCharacteristics; |
||||||
|
|
||||||
|
private SortedSet<Size> mPreviewSizes; |
||||||
|
private SortedSet<Size> mCaptureSizes; |
||||||
|
|
||||||
|
private HandlerThread mBackgroundThread; |
||||||
|
private Handler mBackgroundHandler; |
||||||
|
|
||||||
|
Camera2(Context context, CameraListener cameraListener, PreviewImpl preview) { |
||||||
|
super(cameraListener, preview); |
||||||
|
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
||||||
|
mPreview.setCallback(new PreviewImpl.Callback() { |
||||||
|
@Override |
||||||
|
public void onSurfaceChanged() { |
||||||
|
startCaptureSession(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
mCameraOpenCloseLock = new Semaphore(1); |
||||||
|
|
||||||
|
mPreviewSizes = new TreeSet<>(); |
||||||
|
mCaptureSizes = new TreeSet<>(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
View getView() { |
||||||
|
return mPreview.getView(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void start() { |
||||||
|
if (chooseCameraIdByFacing()) { |
||||||
|
startBackgroundThread(); |
||||||
|
collectCameraInfo(); |
||||||
|
prepareImageReader(); |
||||||
|
startOpeningCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void stop() { |
||||||
|
try { |
||||||
|
mCameraOpenCloseLock.acquire(); |
||||||
|
if (mCaptureSession != null) { |
||||||
|
mCaptureSession.close(); |
||||||
|
mCaptureSession = null; |
||||||
|
} |
||||||
|
if (mCamera != null) { |
||||||
|
mCamera.close(); |
||||||
|
mCamera = null; |
||||||
|
} |
||||||
|
if (mImageReader != null) { |
||||||
|
mImageReader.close(); |
||||||
|
mImageReader = null; |
||||||
|
} |
||||||
|
} catch (InterruptedException e) { |
||||||
|
throw new RuntimeException("Interrupted while trying to lock camera closing.", e); |
||||||
|
} finally { |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
|
||||||
|
if (mBackgroundThread != null) { |
||||||
|
stopBackgroundThread(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
boolean isCameraOpened() { |
||||||
|
return mCamera != null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setFacing(int facing) { |
||||||
|
int internalFacing = INTERNAL_FACINGS.get(facing); |
||||||
|
if (mFacing == internalFacing) return; |
||||||
|
this.mFacing = internalFacing; |
||||||
|
if (isCameraOpened()) { |
||||||
|
stop(); |
||||||
|
start(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
int getFacing() { |
||||||
|
return mFacing; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setFlash(int flash) { |
||||||
|
if (mFlash == flash) return; |
||||||
|
int fallback = flash; |
||||||
|
mFlash = flash; |
||||||
|
if (mPreviewRequestBuilder != null) { |
||||||
|
updateFlash(mPreviewRequestBuilder); |
||||||
|
if (mCaptureSession != null) { |
||||||
|
try { |
||||||
|
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
mFlash = fallback; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
int getFlash() { |
||||||
|
return mFlash; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void capturePicture() { |
||||||
|
if (mFacing == Constants.FACING_BACK) { |
||||||
|
lockFocus(); |
||||||
|
} else { |
||||||
|
captureStillPicture(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void captureStill() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void startVideo() { |
||||||
|
if (mCamera == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void endVideo() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setDisplayOrientation(int displayOrientation) { |
||||||
|
mDisplayOrientation = displayOrientation; |
||||||
|
mPreview.setDisplayOrientation(mDisplayOrientation); |
||||||
|
} |
||||||
|
|
||||||
|
void updateFlash(CaptureRequest.Builder builder) { |
||||||
|
switch (mFlash) { |
||||||
|
case Constants.FLASH_OFF: |
||||||
|
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); |
||||||
|
builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); |
||||||
|
break; |
||||||
|
case Constants.FLASH_ON: |
||||||
|
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); |
||||||
|
builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); |
||||||
|
break; |
||||||
|
case Constants.FLASH_AUTO: |
||||||
|
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); |
||||||
|
builder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean chooseCameraIdByFacing() { |
||||||
|
try { |
||||||
|
int internalFacing = mFacing; |
||||||
|
final String[] ids = mCameraManager.getCameraIdList(); |
||||||
|
if (ids.length == 0) { // No camera
|
||||||
|
throw new RuntimeException("No camera available."); |
||||||
|
} |
||||||
|
for (String id : ids) { |
||||||
|
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); |
||||||
|
Integer level = characteristics.get( |
||||||
|
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
||||||
|
if (level == null || |
||||||
|
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING); |
||||||
|
if (internal == null) { |
||||||
|
throw new NullPointerException("Unexpected state: LENS_FACING null"); |
||||||
|
} |
||||||
|
if (internal == internalFacing) { |
||||||
|
mCameraId = id; |
||||||
|
mCameraCharacteristics = characteristics; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
// Not found
|
||||||
|
mCameraId = ids[0]; |
||||||
|
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); |
||||||
|
Integer level = mCameraCharacteristics.get( |
||||||
|
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); |
||||||
|
if (level == null || |
||||||
|
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING); |
||||||
|
if (internal == null) { |
||||||
|
throw new NullPointerException("Unexpected state: LENS_FACING null"); |
||||||
|
} |
||||||
|
for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) { |
||||||
|
if (INTERNAL_FACINGS.valueAt(i) == internal) { |
||||||
|
mFacing = INTERNAL_FACINGS.keyAt(i); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
// The operation can reach here when the only camera device is an external one.
|
||||||
|
// We treat it as facing back.
|
||||||
|
mFacing = Constants.FACING_BACK; |
||||||
|
return true; |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
throw new RuntimeException("Failed to get a list of camera devices", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void collectCameraInfo() { |
||||||
|
StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
||||||
|
if (map == null) { |
||||||
|
throw new IllegalStateException("Failed to get configuration map: " + mCameraId); |
||||||
|
} |
||||||
|
|
||||||
|
mPreviewSizes.clear(); |
||||||
|
for (android.util.Size size : map.getOutputSizes(mPreview.getOutputClass())) { |
||||||
|
mPreviewSizes.add(new Size(size.getWidth(), size.getHeight())); |
||||||
|
} |
||||||
|
|
||||||
|
mCaptureSizes.clear(); |
||||||
|
for (android.util.Size size : map.getOutputSizes(ImageFormat.JPEG)) { |
||||||
|
mCaptureSizes.add(new Size(size.getWidth(), size.getHeight())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Size getOptimalPreviewSize() { |
||||||
|
int surfaceLonger, surfaceShorter; |
||||||
|
final int surfaceWidth = mPreview.getWidth(); |
||||||
|
final int surfaceHeight = mPreview.getHeight(); |
||||||
|
if (surfaceWidth < surfaceHeight) { |
||||||
|
surfaceLonger = surfaceHeight; |
||||||
|
surfaceShorter = surfaceWidth; |
||||||
|
} else { |
||||||
|
surfaceLonger = surfaceWidth; |
||||||
|
surfaceShorter = surfaceHeight; |
||||||
|
} |
||||||
|
// Pick the smallest of those big enough.
|
||||||
|
for (Size size : mPreviewSizes) { |
||||||
|
if (size.getWidth() >= surfaceLonger && size.getHeight() >= surfaceShorter) { |
||||||
|
return size; |
||||||
|
} |
||||||
|
} |
||||||
|
// If no size is big enough, pick the largest one.
|
||||||
|
return mPreviewSizes.last(); |
||||||
|
} |
||||||
|
|
||||||
|
private void prepareImageReader() { |
||||||
|
Size previewSize = getOptimalPreviewSize(); |
||||||
|
AspectRatio aspectRatio = AspectRatio.of(previewSize.getWidth(), previewSize.getHeight()); |
||||||
|
Size bestSize = findSizeClosestTo(1500000, aspectRatio, mCaptureSizes); |
||||||
|
mImageReader = ImageReader.newInstance(bestSize.getWidth(), bestSize.getHeight(), ImageFormat.JPEG, 1); |
||||||
|
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); |
||||||
|
} |
||||||
|
|
||||||
|
private Size findSizeClosestTo(int targetLength, AspectRatio targetAspectRatio, SortedSet<Size> sizes) { |
||||||
|
int closestDistance = Integer.MAX_VALUE; |
||||||
|
Size closestSize = null; |
||||||
|
for (Size size : sizes) { |
||||||
|
if (targetAspectRatio.matches(size)) { |
||||||
|
int length = size.getWidth() * size.getHeight(); |
||||||
|
int distance = Math.abs(targetLength - length); |
||||||
|
if (closestSize == null) { |
||||||
|
closestSize = size; |
||||||
|
} else { |
||||||
|
if (distance < closestDistance) { |
||||||
|
closestSize = size; |
||||||
|
closestDistance = length; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return closestSize; |
||||||
|
} |
||||||
|
|
||||||
|
private void startBackgroundThread() { |
||||||
|
mBackgroundThread = new HandlerThread("CameraBackground"); |
||||||
|
mBackgroundThread.start(); |
||||||
|
mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); |
||||||
|
} |
||||||
|
|
||||||
|
private void stopBackgroundThread() { |
||||||
|
mBackgroundThread.quitSafely(); |
||||||
|
try { |
||||||
|
mBackgroundThread.join(); |
||||||
|
mBackgroundThread = null; |
||||||
|
mBackgroundHandler = null; |
||||||
|
} catch (InterruptedException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("MissingPermission") |
||||||
|
private void startOpeningCamera() { |
||||||
|
try { |
||||||
|
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { |
||||||
|
throw new RuntimeException("Time out waiting to lock camera opening."); |
||||||
|
} |
||||||
|
mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
throw new RuntimeException("Failed to open camera: " + mCameraId, e); |
||||||
|
} catch (InterruptedException e) { |
||||||
|
throw new RuntimeException("Time out waiting to lock camera opening."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void startCaptureSession() { |
||||||
|
if (!isCameraOpened() || !mPreview.isReady() || mImageReader == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Size previewSize = getOptimalPreviewSize(); |
||||||
|
mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight()); |
||||||
|
Surface surface = mPreview.getSurface(); |
||||||
|
try { |
||||||
|
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); |
||||||
|
mPreviewRequestBuilder.addTarget(surface); |
||||||
|
mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), mSessionCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
throw new RuntimeException("Failed to start camera session"); |
||||||
|
} catch (IllegalStateException e) { |
||||||
|
startOpeningCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void lockFocus() { |
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); |
||||||
|
try { |
||||||
|
mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING); |
||||||
|
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
Log.e(TAG, "Failed to lock focus.", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void captureStillPicture() { |
||||||
|
try { |
||||||
|
CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); |
||||||
|
captureRequestBuilder.addTarget(mImageReader.getSurface()); |
||||||
|
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE)); |
||||||
|
updateFlash(captureRequestBuilder); |
||||||
|
// Calculate JPEG orientation.
|
||||||
|
@SuppressWarnings("ConstantConditions") |
||||||
|
int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
||||||
|
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, |
||||||
|
(sensorOrientation + |
||||||
|
mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) + |
||||||
|
360) % 360); |
||||||
|
// Stop preview and capture a still picture.
|
||||||
|
mCaptureSession.stopRepeating(); |
||||||
|
mCaptureSession.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() { |
||||||
|
@Override |
||||||
|
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { |
||||||
|
unlockFocus(); |
||||||
|
} |
||||||
|
}, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
Log.e(TAG, "Cannot capture a still picture.", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void unlockFocus() { |
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); |
||||||
|
try { |
||||||
|
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); |
||||||
|
//updateAutoFocus();
|
||||||
|
updateFlash(mPreviewRequestBuilder); |
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE); |
||||||
|
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); |
||||||
|
mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
Log.e(TAG, "Failed to restart camera preview.", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private final CameraDevice.StateCallback mCameraDeviceCallback = new CameraDevice.StateCallback() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onOpened(@NonNull CameraDevice camera) { |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
mCamera = camera; |
||||||
|
getCameraListener().onCameraOpened(); |
||||||
|
startCaptureSession(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClosed(@NonNull CameraDevice camera) { |
||||||
|
getCameraListener().onCameraClosed(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDisconnected(@NonNull CameraDevice camera) { |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
mCamera.close(); |
||||||
|
mCamera = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(@NonNull CameraDevice camera, int error) { |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
Log.e(TAG, "onError: " + camera.getId() + " (" + error + ")"); |
||||||
|
mCamera = null; |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
private final CameraCaptureSession.StateCallback mSessionCallback = new CameraCaptureSession.StateCallback() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onConfigured(@NonNull CameraCaptureSession session) { |
||||||
|
if (mCamera == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
mCaptureSession = session; |
||||||
|
updateFlash(mPreviewRequestBuilder); |
||||||
|
try { |
||||||
|
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e); |
||||||
|
} catch (IllegalStateException e) { |
||||||
|
Log.e(TAG, "Failed to start camera preview.", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onConfigureFailed(@NonNull CameraCaptureSession session) { |
||||||
|
Log.e(TAG, "Failed to configure capture session."); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onClosed(@NonNull CameraCaptureSession session) { |
||||||
|
if (mCaptureSession != null && mCaptureSession.equals(session)) { |
||||||
|
mCaptureSession = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
private PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onPrecaptureRequired() { |
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); |
||||||
|
setState(STATE_PRECAPTURE); |
||||||
|
try { |
||||||
|
mCaptureSession.capture(mPreviewRequestBuilder.build(), this, mBackgroundHandler); |
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
Log.e(TAG, "Failed to run precapture sequence.", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onReady() { |
||||||
|
captureStillPicture(); |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onImageAvailable(ImageReader reader) { |
||||||
|
try (Image image = reader.acquireNextImage()) { |
||||||
|
Image.Plane[] planes = image.getPlanes(); |
||||||
|
if (planes.length > 0) { |
||||||
|
ByteBuffer buffer = planes[0].getBuffer(); |
||||||
|
byte[] data = new byte[buffer.remaining()]; |
||||||
|
buffer.get(data); |
||||||
|
getCameraListener().onPictureTaken(data); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
public abstract class CameraListener { |
||||||
|
|
||||||
|
public void onCameraOpened() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void onCameraClosed() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void onPictureTaken(File picture) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void onPictureTaken(byte[] picture) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void onVideoTaken(File video) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,254 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.os.Parcel; |
||||||
|
import android.os.Parcelable; |
||||||
|
import android.support.annotation.IntDef; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
import android.support.v4.hardware.display.DisplayManagerCompat; |
||||||
|
import android.support.v4.os.ParcelableCompat; |
||||||
|
import android.support.v4.os.ParcelableCompatCreatorCallbacks; |
||||||
|
import android.support.v4.view.ViewCompat; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.view.Display; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
|
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
|
||||||
|
public class CameraView extends FrameLayout { |
||||||
|
|
||||||
|
public static final int FACING_BACK = Constants.FACING_BACK; |
||||||
|
|
||||||
|
public static final int FACING_FRONT = Constants.FACING_FRONT; |
||||||
|
|
||||||
|
@IntDef({FACING_BACK, FACING_FRONT}) |
||||||
|
@Retention(RetentionPolicy.SOURCE) |
||||||
|
public @interface Facing { |
||||||
|
} |
||||||
|
|
||||||
|
public static final int FLASH_OFF = Constants.FLASH_OFF; |
||||||
|
|
||||||
|
public static final int FLASH_ON = Constants.FLASH_ON; |
||||||
|
|
||||||
|
public static final int FLASH_AUTO = Constants.FLASH_AUTO; |
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE) |
||||||
|
@IntDef({FLASH_OFF, FLASH_ON, FLASH_AUTO}) |
||||||
|
public @interface Flash { |
||||||
|
} |
||||||
|
|
||||||
|
public static final int PICTURE_MODE_QUALITY = Constants.PICTURE_MODE_QUALITY; |
||||||
|
public static final int PICTURE_MODE_SPEED = Constants.PICTURE_MODE_SPEED; |
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE) |
||||||
|
@IntDef({PICTURE_MODE_QUALITY, PICTURE_MODE_SPEED}) |
||||||
|
public @interface PictureMode { |
||||||
|
} |
||||||
|
|
||||||
|
private int mFacing; |
||||||
|
private int mDefaultFacing; |
||||||
|
|
||||||
|
private int mFlash; |
||||||
|
private int mDefaultFlash; |
||||||
|
|
||||||
|
private int mPictureMode; |
||||||
|
|
||||||
|
private CameraListener mCameraListener; |
||||||
|
private DisplayOrientationDetector mDisplayOrientationDetector; |
||||||
|
|
||||||
|
private CameraViewImpl mCameraImpl; |
||||||
|
|
||||||
|
public CameraView(@NonNull Context context) { |
||||||
|
super(context, null); |
||||||
|
} |
||||||
|
|
||||||
|
public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
|
||||||
|
final PreviewImpl preview = new TextureViewPreview(context, this); |
||||||
|
mCameraImpl = new Camera2(context, mCameraListener, preview); |
||||||
|
|
||||||
|
setFacing(mFacing); |
||||||
|
setFlash(mFlash); |
||||||
|
|
||||||
|
mDisplayOrientationDetector = new DisplayOrientationDetector(context) { |
||||||
|
@Override |
||||||
|
public void onDisplayOrientationChanged(int displayOrientation) { |
||||||
|
mCameraImpl.setDisplayOrientation(displayOrientation); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onAttachedToWindow() { |
||||||
|
super.onAttachedToWindow(); |
||||||
|
mDisplayOrientationDetector.enable( |
||||||
|
ViewCompat.isAttachedToWindow(this) |
||||||
|
? DisplayManagerCompat.getInstance(getContext()).getDisplay(Display.DEFAULT_DISPLAY) |
||||||
|
: null |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onDetachedFromWindow() { |
||||||
|
mDisplayOrientationDetector.disable(); |
||||||
|
super.onDetachedFromWindow(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Parcelable onSaveInstanceState() { |
||||||
|
SavedState state = new SavedState(super.onSaveInstanceState()); |
||||||
|
state.facing = mFacing; |
||||||
|
state.flash = mFlash; |
||||||
|
return state; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onRestoreInstanceState(Parcelable state) { |
||||||
|
if (!(state instanceof SavedState)) { |
||||||
|
super.onRestoreInstanceState(state); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
SavedState ss = (SavedState) state; |
||||||
|
super.onRestoreInstanceState(ss.getSuperState()); |
||||||
|
setFacing(ss.facing); |
||||||
|
setFlash(ss.flash); |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
mCameraImpl.start(); |
||||||
|
} |
||||||
|
|
||||||
|
public void stop() { |
||||||
|
if (mCameraImpl != null) { |
||||||
|
mCameraImpl.stop(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setFacing(@Facing int facing) { |
||||||
|
this.mFacing = facing; |
||||||
|
if (mCameraImpl != null) { |
||||||
|
mCameraImpl.setFacing(facing); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Facing |
||||||
|
public int toggleFacing() { |
||||||
|
switch (mFacing) { |
||||||
|
case FACING_BACK: |
||||||
|
setFacing(FACING_FRONT); |
||||||
|
break; |
||||||
|
|
||||||
|
case FACING_FRONT: |
||||||
|
setFacing(FACING_BACK); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return mFacing; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFlash(@Flash int flash) { |
||||||
|
this.mFlash = flash; |
||||||
|
if (mCameraImpl != null) { |
||||||
|
mCameraImpl.setFlash(flash); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Flash |
||||||
|
public int toggleFlash() { |
||||||
|
switch (mFlash) { |
||||||
|
case FLASH_OFF: |
||||||
|
setFlash(FLASH_ON); |
||||||
|
break; |
||||||
|
|
||||||
|
case FLASH_ON: |
||||||
|
setFlash(FLASH_AUTO); |
||||||
|
break; |
||||||
|
|
||||||
|
case FLASH_AUTO: |
||||||
|
setFlash(FLASH_OFF); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return mFlash; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPictureMode(@PictureMode int pictureMode) { |
||||||
|
this.mPictureMode = pictureMode; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCameraListener(CameraListener cameraListener) { |
||||||
|
this.mCameraListener = cameraListener; |
||||||
|
mCameraImpl.setCameraListener(cameraListener); |
||||||
|
} |
||||||
|
|
||||||
|
public void capturePicture() { |
||||||
|
switch (mPictureMode) { |
||||||
|
case PICTURE_MODE_QUALITY: |
||||||
|
mCameraImpl.capturePicture(); |
||||||
|
break; |
||||||
|
case PICTURE_MODE_SPEED: |
||||||
|
mCameraImpl.captureStill(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void startRecordingVideo() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void stopRecordingVideo() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
protected static class SavedState extends BaseSavedState { |
||||||
|
|
||||||
|
@Facing |
||||||
|
private int facing; |
||||||
|
|
||||||
|
@Flash |
||||||
|
private int flash; |
||||||
|
|
||||||
|
@SuppressWarnings("WrongConstant") |
||||||
|
public SavedState(Parcel source, ClassLoader loader) { |
||||||
|
super(source); |
||||||
|
facing = source.readInt(); |
||||||
|
flash = source.readInt(); |
||||||
|
} |
||||||
|
|
||||||
|
public SavedState(Parcelable superState) { |
||||||
|
super(superState); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void writeToParcel(Parcel out, int flags) { |
||||||
|
super.writeToParcel(out, flags); |
||||||
|
out.writeInt(facing); |
||||||
|
out.writeInt(flash); |
||||||
|
} |
||||||
|
|
||||||
|
public static final Parcelable.Creator<SavedState> CREATOR = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public SavedState createFromParcel(Parcel in, ClassLoader loader) { |
||||||
|
return new SavedState(in, loader); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SavedState[] newArray(int size) { |
||||||
|
return new SavedState[size]; |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
abstract class CameraViewImpl { |
||||||
|
|
||||||
|
protected CameraListener mCameraListener; |
||||||
|
|
||||||
|
protected final PreviewImpl mPreview; |
||||||
|
|
||||||
|
CameraViewImpl(CameraListener callback, PreviewImpl preview) { |
||||||
|
mCameraListener = callback; |
||||||
|
mPreview = preview; |
||||||
|
} |
||||||
|
|
||||||
|
View getView() { |
||||||
|
return mPreview.getView(); |
||||||
|
} |
||||||
|
|
||||||
|
abstract void start(); |
||||||
|
|
||||||
|
abstract void stop(); |
||||||
|
|
||||||
|
abstract boolean isCameraOpened(); |
||||||
|
|
||||||
|
abstract void setFacing(int facing); |
||||||
|
|
||||||
|
abstract int getFacing(); |
||||||
|
|
||||||
|
abstract void setFlash(int flash); |
||||||
|
|
||||||
|
abstract int getFlash(); |
||||||
|
|
||||||
|
abstract void capturePicture(); |
||||||
|
|
||||||
|
abstract void captureStill(); |
||||||
|
|
||||||
|
abstract void startVideo(); |
||||||
|
|
||||||
|
abstract void endVideo(); |
||||||
|
|
||||||
|
abstract void setDisplayOrientation(int displayOrientation); |
||||||
|
|
||||||
|
void setCameraListener(CameraListener cameraListener) { |
||||||
|
this.mCameraListener = cameraListener; |
||||||
|
} |
||||||
|
|
||||||
|
protected CameraListener getCameraListener() { |
||||||
|
return mCameraListener != null ? mCameraListener : new CameraListener() {}; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
public class Constants { |
||||||
|
|
||||||
|
public static final int FACING_BACK = 0; |
||||||
|
public static final int FACING_FRONT = 1; |
||||||
|
|
||||||
|
public static final int FLASH_OFF = 0; |
||||||
|
public static final int FLASH_ON = 1; |
||||||
|
public static final int FLASH_AUTO = 2; |
||||||
|
|
||||||
|
public static final int PICTURE_MODE_QUALITY = 0; |
||||||
|
public static final int PICTURE_MODE_SPEED = 1; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.util.SparseIntArray; |
||||||
|
import android.view.Display; |
||||||
|
import android.view.OrientationEventListener; |
||||||
|
import android.view.Surface; |
||||||
|
|
||||||
|
abstract class DisplayOrientationDetector { |
||||||
|
|
||||||
|
private final OrientationEventListener mOrientationEventListener; |
||||||
|
|
||||||
|
static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); |
||||||
|
|
||||||
|
static { |
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); |
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); |
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); |
||||||
|
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); |
||||||
|
} |
||||||
|
|
||||||
|
private Display mDisplay; |
||||||
|
|
||||||
|
private int mLastKnownDisplayOrientation = 0; |
||||||
|
|
||||||
|
public DisplayOrientationDetector(Context context) { |
||||||
|
mOrientationEventListener = new OrientationEventListener(context) { |
||||||
|
|
||||||
|
private int mLastKnownRotation = -1; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onOrientationChanged(int orientation) { |
||||||
|
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || |
||||||
|
mDisplay == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
final int rotation = mDisplay.getRotation(); |
||||||
|
if (mLastKnownRotation != rotation) { |
||||||
|
mLastKnownRotation = rotation; |
||||||
|
dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation)); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public void enable(Display display) { |
||||||
|
mDisplay = display; |
||||||
|
mOrientationEventListener.enable(); |
||||||
|
// Immediately dispatch the first callback
|
||||||
|
dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation())); |
||||||
|
} |
||||||
|
|
||||||
|
public void disable() { |
||||||
|
mOrientationEventListener.disable(); |
||||||
|
mDisplay = null; |
||||||
|
} |
||||||
|
|
||||||
|
public int getLastKnownDisplayOrientation() { |
||||||
|
return mLastKnownDisplayOrientation; |
||||||
|
} |
||||||
|
|
||||||
|
void dispatchOnDisplayOrientationChanged(int displayOrientation) { |
||||||
|
mLastKnownDisplayOrientation = displayOrientation; |
||||||
|
onDisplayOrientationChanged(displayOrientation); |
||||||
|
} |
||||||
|
|
||||||
|
public abstract void onDisplayOrientationChanged(int displayOrientation); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.hardware.camera2.CameraCaptureSession; |
||||||
|
import android.hardware.camera2.CaptureRequest; |
||||||
|
import android.hardware.camera2.CaptureResult; |
||||||
|
import android.hardware.camera2.TotalCaptureResult; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
|
||||||
|
abstract class PictureCaptureCallback extends CameraCaptureSession.CaptureCallback { |
||||||
|
|
||||||
|
static final int STATE_PREVIEW = 0; |
||||||
|
static final int STATE_LOCKING = 1; |
||||||
|
static final int STATE_LOCKED = 2; |
||||||
|
static final int STATE_PRECAPTURE = 3; |
||||||
|
static final int STATE_WAITING = 4; |
||||||
|
static final int STATE_CAPTURING = 5; |
||||||
|
|
||||||
|
private int mState; |
||||||
|
|
||||||
|
PictureCaptureCallback() { |
||||||
|
} |
||||||
|
|
||||||
|
void setState(int state) { |
||||||
|
mState = state; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { |
||||||
|
process(partialResult); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { |
||||||
|
process(result); |
||||||
|
} |
||||||
|
|
||||||
|
private void process(@NonNull CaptureResult result) { |
||||||
|
switch (mState) { |
||||||
|
case STATE_LOCKING: { |
||||||
|
Integer af = result.get(CaptureResult.CONTROL_AF_STATE); |
||||||
|
if (af == null) { |
||||||
|
break; |
||||||
|
} |
||||||
|
if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || |
||||||
|
af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { |
||||||
|
Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); |
||||||
|
if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { |
||||||
|
setState(STATE_CAPTURING); |
||||||
|
onReady(); |
||||||
|
} else { |
||||||
|
setState(STATE_LOCKED); |
||||||
|
onPrecaptureRequired(); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
case STATE_PRECAPTURE: { |
||||||
|
Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); |
||||||
|
if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || |
||||||
|
ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED || |
||||||
|
ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { |
||||||
|
setState(STATE_WAITING); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
case STATE_WAITING: { |
||||||
|
Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); |
||||||
|
if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { |
||||||
|
setState(STATE_CAPTURING); |
||||||
|
onReady(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public abstract void onReady(); |
||||||
|
|
||||||
|
public abstract void onPrecaptureRequired(); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.view.Surface; |
||||||
|
import android.view.SurfaceHolder; |
||||||
|
import android.view.View; |
||||||
|
|
||||||
|
abstract class PreviewImpl { |
||||||
|
|
||||||
|
interface Callback { |
||||||
|
void onSurfaceChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
private Callback mCallback; |
||||||
|
|
||||||
|
private int mWidth; |
||||||
|
|
||||||
|
private int mHeight; |
||||||
|
|
||||||
|
void setCallback(Callback callback) { |
||||||
|
mCallback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
abstract Surface getSurface(); |
||||||
|
|
||||||
|
abstract View getView(); |
||||||
|
|
||||||
|
abstract Class getOutputClass(); |
||||||
|
|
||||||
|
abstract void setDisplayOrientation(int displayOrientation); |
||||||
|
|
||||||
|
abstract boolean isReady(); |
||||||
|
|
||||||
|
protected void dispatchSurfaceChanged() { |
||||||
|
mCallback.onSurfaceChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
SurfaceHolder getSurfaceHolder() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
SurfaceTexture getSurfaceTexture() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
void setBufferSize(int width, int height) { |
||||||
|
} |
||||||
|
|
||||||
|
void setSize(int width, int height) { |
||||||
|
mWidth = width; |
||||||
|
mHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
int getWidth() { |
||||||
|
return mWidth; |
||||||
|
} |
||||||
|
|
||||||
|
int getHeight() { |
||||||
|
return mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.support.annotation.NonNull; |
||||||
|
|
||||||
|
public class Size implements Comparable<Size> { |
||||||
|
|
||||||
|
private final int mWidth; |
||||||
|
private final int mHeight; |
||||||
|
|
||||||
|
public Size(int width, int height) { |
||||||
|
mWidth = width; |
||||||
|
mHeight = height; |
||||||
|
} |
||||||
|
|
||||||
|
public int getWidth() { |
||||||
|
return mWidth; |
||||||
|
} |
||||||
|
|
||||||
|
public int getHeight() { |
||||||
|
return mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (o == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o instanceof Size) { |
||||||
|
Size size = (Size) o; |
||||||
|
return mWidth == size.mWidth && mHeight == size.mHeight; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return mWidth + "x" + mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
|
||||||
|
return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compareTo(@NonNull Size another) { |
||||||
|
return mWidth * mHeight - another.mWidth * another.mHeight; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,130 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import android.annotation.TargetApi; |
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.Matrix; |
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.view.Surface; |
||||||
|
import android.view.TextureView; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
|
||||||
|
@TargetApi(14) |
||||||
|
public class TextureViewPreview extends PreviewImpl { |
||||||
|
|
||||||
|
private final TextureView mTextureView; |
||||||
|
|
||||||
|
private int mDisplayOrientation; |
||||||
|
|
||||||
|
TextureViewPreview(Context context, ViewGroup parent) { |
||||||
|
final View view = View.inflate(context, R.layout.texture_view, parent); |
||||||
|
mTextureView = (TextureView) view.findViewById(R.id.texture_view); |
||||||
|
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { |
||||||
|
setSize(width, height); |
||||||
|
configureTransform(); |
||||||
|
dispatchSurfaceChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { |
||||||
|
setSize(width, height); |
||||||
|
configureTransform(); |
||||||
|
dispatchSurfaceChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { |
||||||
|
setSize(0, 0); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) { |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setCallback(Callback callback) { |
||||||
|
super.setCallback(callback); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
Surface getSurface() { |
||||||
|
return new Surface(mTextureView.getSurfaceTexture()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
View getView() { |
||||||
|
return mTextureView; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
Class getOutputClass() { |
||||||
|
return SurfaceTexture.class; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void setDisplayOrientation(int displayOrientation) { |
||||||
|
mDisplayOrientation = displayOrientation; |
||||||
|
configureTransform(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
boolean isReady() { |
||||||
|
return mTextureView.getSurfaceTexture() != null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void dispatchSurfaceChanged() { |
||||||
|
super.dispatchSurfaceChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
SurfaceTexture getSurfaceTexture() { |
||||||
|
return mTextureView.getSurfaceTexture(); |
||||||
|
} |
||||||
|
|
||||||
|
@TargetApi(15) |
||||||
|
@Override |
||||||
|
void setBufferSize(int width, int height) { |
||||||
|
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
void configureTransform() { |
||||||
|
Matrix matrix = new Matrix(); |
||||||
|
if (mDisplayOrientation % 180 == 90) { |
||||||
|
final int width = getWidth(); |
||||||
|
final int height = getHeight(); |
||||||
|
// Rotate the camera preview when the screen is landscape.
|
||||||
|
matrix.setPolyToPoly( |
||||||
|
new float[]{ |
||||||
|
0.f, 0.f, // top left
|
||||||
|
width, 0.f, // top right
|
||||||
|
0.f, height, // bottom left
|
||||||
|
width, height, // bottom right
|
||||||
|
}, 0, |
||||||
|
mDisplayOrientation == 90 ? |
||||||
|
// Clockwise
|
||||||
|
new float[]{ |
||||||
|
0.f, height, // top left
|
||||||
|
0.f, 0.f, // top right
|
||||||
|
width, height, // bottom left
|
||||||
|
width, 0.f, // bottom right
|
||||||
|
} : // mDisplayOrientation == 270
|
||||||
|
// Counter-clockwise
|
||||||
|
new float[]{ |
||||||
|
width, 0.f, // top left
|
||||||
|
width, height, // top right
|
||||||
|
0.f, 0.f, // bottom left
|
||||||
|
0.f, height, // bottom right
|
||||||
|
}, 0, |
||||||
|
4); |
||||||
|
} |
||||||
|
mTextureView.setTransform(matrix); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"> |
||||||
|
|
||||||
|
<TextureView |
||||||
|
android:id="@+id/texture_view" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
|
||||||
|
</merge> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<resources> |
||||||
|
|
||||||
|
<declare-styleable name="CameraView"> |
||||||
|
|
||||||
|
</declare-styleable> |
||||||
|
|
||||||
|
</resources> |
@ -0,0 +1,3 @@ |
|||||||
|
<resources> |
||||||
|
<string name="app_name">CameraKit</string> |
||||||
|
</resources> |
@ -0,0 +1,17 @@ |
|||||||
|
package com.wonderkiln.camerakit; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* Example local unit test, which will execute on the development machine (host). |
||||||
|
* |
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> |
||||||
|
*/ |
||||||
|
public class ExampleUnitTest { |
||||||
|
@Test |
||||||
|
public void addition_isCorrect() throws Exception { |
||||||
|
assertEquals(4, 2 + 2); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
# Project-wide Gradle settings. |
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users: |
||||||
|
# Gradle settings configured through the IDE *will override* |
||||||
|
# any settings specified in this file. |
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit |
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html |
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process. |
||||||
|
# The setting is particularly useful for tweaking memory settings. |
||||||
|
org.gradle.jvmargs=-Xmx1536m |
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode. |
||||||
|
# This option should only be used with decoupled projects. More details, visit |
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects |
||||||
|
# org.gradle.parallel=true |
@ -0,0 +1,6 @@ |
|||||||
|
#Tue Jan 17 16:25:25 EST 2017 |
||||||
|
distributionBase=GRADLE_USER_HOME |
||||||
|
distributionPath=wrapper/dists |
||||||
|
zipStoreBase=GRADLE_USER_HOME |
||||||
|
zipStorePath=wrapper/dists |
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip |
@ -0,0 +1,160 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
############################################################################## |
||||||
|
## |
||||||
|
## Gradle start up script for UN*X |
||||||
|
## |
||||||
|
############################################################################## |
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||||
|
DEFAULT_JVM_OPTS="" |
||||||
|
|
||||||
|
APP_NAME="Gradle" |
||||||
|
APP_BASE_NAME=`basename "$0"` |
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||||
|
MAX_FD="maximum" |
||||||
|
|
||||||
|
warn ( ) { |
||||||
|
echo "$*" |
||||||
|
} |
||||||
|
|
||||||
|
die ( ) { |
||||||
|
echo |
||||||
|
echo "$*" |
||||||
|
echo |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false'). |
||||||
|
cygwin=false |
||||||
|
msys=false |
||||||
|
darwin=false |
||||||
|
case "`uname`" in |
||||||
|
CYGWIN* ) |
||||||
|
cygwin=true |
||||||
|
;; |
||||||
|
Darwin* ) |
||||||
|
darwin=true |
||||||
|
;; |
||||||
|
MINGW* ) |
||||||
|
msys=true |
||||||
|
;; |
||||||
|
esac |
||||||
|
|
||||||
|
# Attempt to set APP_HOME |
||||||
|
# Resolve links: $0 may be a link |
||||||
|
PRG="$0" |
||||||
|
# Need this for relative symlinks. |
||||||
|
while [ -h "$PRG" ] ; do |
||||||
|
ls=`ls -ld "$PRG"` |
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||||
|
if expr "$link" : '/.*' > /dev/null; then |
||||||
|
PRG="$link" |
||||||
|
else |
||||||
|
PRG=`dirname "$PRG"`"/$link" |
||||||
|
fi |
||||||
|
done |
||||||
|
SAVED="`pwd`" |
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null |
||||||
|
APP_HOME="`pwd -P`" |
||||||
|
cd "$SAVED" >/dev/null |
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM. |
||||||
|
if [ -n "$JAVA_HOME" ] ; then |
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||||
|
# IBM's JDK on AIX uses strange locations for the executables |
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||||
|
else |
||||||
|
JAVACMD="$JAVA_HOME/bin/java" |
||||||
|
fi |
||||||
|
if [ ! -x "$JAVACMD" ] ; then |
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the |
||||||
|
location of your Java installation." |
||||||
|
fi |
||||||
|
else |
||||||
|
JAVACMD="java" |
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the |
||||||
|
location of your Java installation." |
||||||
|
fi |
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can. |
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then |
||||||
|
MAX_FD_LIMIT=`ulimit -H -n` |
||||||
|
if [ $? -eq 0 ] ; then |
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||||
|
MAX_FD="$MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
ulimit -n $MAX_FD |
||||||
|
if [ $? -ne 0 ] ; then |
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||||
|
fi |
||||||
|
else |
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
fi |
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock |
||||||
|
if $darwin; then |
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||||
|
fi |
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java |
||||||
|
if $cygwin ; then |
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath |
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||||
|
SEP="" |
||||||
|
for dir in $ROOTDIRSRAW ; do |
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||||
|
SEP="|" |
||||||
|
done |
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))" |
||||||
|
# Add a user-defined pattern to the cygpath arguments |
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||||
|
fi |
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||||
|
i=0 |
||||||
|
for arg in "$@" ; do |
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||||
|
else |
||||||
|
eval `echo args$i`="\"$arg\"" |
||||||
|
fi |
||||||
|
i=$((i+1)) |
||||||
|
done |
||||||
|
case $i in |
||||||
|
(0) set -- ;; |
||||||
|
(1) set -- "$args0" ;; |
||||||
|
(2) set -- "$args0" "$args1" ;; |
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;; |
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||||
|
esac |
||||||
|
fi |
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules |
||||||
|
function splitJvmOpts() { |
||||||
|
JVM_OPTS=("$@") |
||||||
|
} |
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS |
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" |
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
@ -0,0 +1,90 @@ |
|||||||
|
@if "%DEBUG%" == "" @echo off |
||||||
|
@rem ########################################################################## |
||||||
|
@rem |
||||||
|
@rem Gradle startup script for Windows |
||||||
|
@rem |
||||||
|
@rem ########################################################################## |
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell |
||||||
|
if "%OS%"=="Windows_NT" setlocal |
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||||
|
set DEFAULT_JVM_OPTS= |
||||||
|
|
||||||
|
set DIRNAME=%~dp0 |
||||||
|
if "%DIRNAME%" == "" set DIRNAME=. |
||||||
|
set APP_BASE_NAME=%~n0 |
||||||
|
set APP_HOME=%DIRNAME% |
||||||
|
|
||||||
|
@rem Find java.exe |
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome |
||||||
|
|
||||||
|
set JAVA_EXE=java.exe |
||||||
|
%JAVA_EXE% -version >NUL 2>&1 |
||||||
|
if "%ERRORLEVEL%" == "0" goto init |
||||||
|
|
||||||
|
echo. |
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||||
|
echo. |
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the |
||||||
|
echo location of your Java installation. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:findJavaFromJavaHome |
||||||
|
set JAVA_HOME=%JAVA_HOME:"=% |
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init |
||||||
|
|
||||||
|
echo. |
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
||||||
|
echo. |
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the |
||||||
|
echo location of your Java installation. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:init |
||||||
|
@rem Get command-line arguments, handling Windowz variants |
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args |
||||||
|
|
||||||
|
:win9xME_args |
||||||
|
@rem Slurp the command line arguments. |
||||||
|
set CMD_LINE_ARGS= |
||||||
|
set _SKIP=2 |
||||||
|
|
||||||
|
:win9xME_args_slurp |
||||||
|
if "x%~1" == "x" goto execute |
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%* |
||||||
|
goto execute |
||||||
|
|
||||||
|
:4NT_args |
||||||
|
@rem Get arguments from the 4NT Shell from JP Software |
||||||
|
set CMD_LINE_ARGS=%$ |
||||||
|
|
||||||
|
:execute |
||||||
|
@rem Setup the command line |
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
||||||
|
|
||||||
|
@rem Execute Gradle |
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
||||||
|
|
||||||
|
:end |
||||||
|
@rem End local scope for the variables with windows NT shell |
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd |
||||||
|
|
||||||
|
:fail |
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
||||||
|
rem the _cmd.exe /c_ return code! |
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
||||||
|
exit /b 1 |
||||||
|
|
||||||
|
:mainEnd |
||||||
|
if "%OS%"=="Windows_NT" endlocal |
||||||
|
|
||||||
|
:omega |
@ -0,0 +1 @@ |
|||||||
|
include ':app', ':camerakit' |