Java tutorial
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wlanjie.streaming.camera; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.opengl.Matrix; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; 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.os.ParcelableCompat; import android.support.v4.os.ParcelableCompatCreatorCallbacks; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.wlanjie.streaming.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Set; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class CameraView extends FrameLayout implements GLSurfaceView.Renderer { /** The camera device faces the opposite direction as the device's screen. */ public static final int FACING_BACK = Constants.FACING_BACK; /** The camera device faces the same direction as the device's screen. */ public static final int FACING_FRONT = Constants.FACING_FRONT; /** Direction the camera faces relative to device screen. */ @IntDef({ FACING_BACK, FACING_FRONT }) @Retention(RetentionPolicy.SOURCE) public @interface Facing { } /** Flash will not be fired. */ public static final int FLASH_OFF = Constants.FLASH_OFF; /** Flash will always be fired during snapshot. */ public static final int FLASH_ON = Constants.FLASH_ON; /** Constant emission of light during preview, auto-focus and snapshot. */ public static final int FLASH_TORCH = Constants.FLASH_TORCH; /** Flash will be fired automatically when required. */ public static final int FLASH_AUTO = Constants.FLASH_AUTO; /** Flash will be fired in red-eye reduction mode. */ public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE; /** The mode for for the camera device's flash control */ @IntDef({ FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE }) public @interface Flash { } final CameraViewImpl mImpl; private final CallbackBridge mCallbacks; private boolean mAdjustViewBounds; private final DisplayOrientationDetector mDisplayOrientationDetector; final GLSurfaceView mGLSurfaceView; private int mSurfaceWidth; private int mSurfaceHeight; private Handler mHandler; private Callback mCallback; private EglCore mEglCore; private int mTextureId; private SurfaceTexture mSurfaceTexture; private float[] mProjectionMatrix = new float[16]; private float[] mSurfaceMatrix = new float[16]; private float[] mTransformMatrix = new float[16]; public CameraView(Context context) { this(context, null); } public CameraView(Context context, AttributeSet attrs) { this(context, attrs, 0); } @SuppressWarnings("WrongConstant") public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // Internal setup final View view = View.inflate(context, R.layout.texture_view, this); mGLSurfaceView = (GLSurfaceView) view.findViewById(R.id.gl_surface_view); mGLSurfaceView.setEGLContextClientVersion(2); mGLSurfaceView.setRenderer(this); mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); mTextureId = OpenGLUtils.getExternalOESTextureID(); mSurfaceTexture = new SurfaceTexture(mTextureId); mCallbacks = new CallbackBridge(); if (Build.VERSION.SDK_INT < 21) { mImpl = new Camera1(mCallbacks); } else if (Build.VERSION.SDK_INT < 23) { mImpl = new Camera2(mCallbacks, context); } else { mImpl = new Camera2Api23(mCallbacks, context); } mImpl.setPreviewSurface(mSurfaceTexture); // Attributes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr, R.style.Widget_CameraView); mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false); setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK)); String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio); if (aspectRatio != null) { setAspectRatio(AspectRatio.parse(aspectRatio)); } else { setAspectRatio(Constants.DEFAULT_ASPECT_RATIO); } setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO)); a.recycle(); // Display orientation detector mDisplayOrientationDetector = new DisplayOrientationDetector(context) { @Override public void onDisplayOrientationChanged(int displayOrientation) { mImpl.setDisplayOrientation(displayOrientation); } }; } private ByteBuffer mFrameBuffer; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { GLES20.glDisable(GL10.GL_DITHER); GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); HandlerThread thread = new HandlerThread("glDraw"); thread.start(); mHandler = new Handler(thread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); IntBuffer buffer = mEglCore.getRgbaBuffer(); mFrameBuffer.asIntBuffer().put(buffer.array()); if (mCallback != null) { mCallback.onPreviewFrame(CameraView.this, mFrameBuffer.array()); } } }; mEglCore = new EglCore(getResources()); mEglCore.init(); mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { mGLSurfaceView.requestRender(); } }); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mSurfaceWidth = width; mSurfaceHeight = height; mFrameBuffer = ByteBuffer.allocate(width * height * 4); mCallbacks.onPreview(width, height); mEglCore.onInputSizeChanged(width, height); GLES20.glViewport(0, 0, width, height); mEglCore.onDisplaySizeChange(width, height); float outputAspectRatio = width > height ? (float) width / height : (float) height / width; float aspectRatio = outputAspectRatio / outputAspectRatio; if (width > height) { Matrix.orthoM(mProjectionMatrix, 0, -1.0f, 1.0f, -aspectRatio, aspectRatio, -1.0f, 1.0f); } else { Matrix.orthoM(mProjectionMatrix, 0, -aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f); } } @Override public void onDrawFrame(GL10 gl) { GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); mSurfaceTexture.updateTexImage(); mSurfaceTexture.getTransformMatrix(mSurfaceMatrix); Matrix.multiplyMM(mTransformMatrix, 0, mSurfaceMatrix, 0, mProjectionMatrix, 0); mEglCore.setTextureTransformMatrix(mTransformMatrix); mEglCore.onDrawFrame(mTextureId); mHandler.sendEmptyMessage(0); } public int getSurfaceWidth() { return mSurfaceWidth; } public int getSurfaceHeight() { return mSurfaceHeight; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mDisplayOrientationDetector.enable(ViewCompat2.getDisplay(this)); } @Override protected void onDetachedFromWindow() { mDisplayOrientationDetector.disable(); super.onDetachedFromWindow(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Handle android:adjustViewBounds if (mAdjustViewBounds) { if (!isCameraOpened()) { mCallbacks.reserveRequestLayoutOnOpen(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { final AspectRatio ratio = getAspectRatio(); assert ratio != null; int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat()); if (heightMode == MeasureSpec.AT_MOST) { height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec)); } super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { final AspectRatio ratio = getAspectRatio(); assert ratio != null; int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat()); if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec)); } super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightMeasureSpec); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } // Measure the TextureView int width = getMeasuredWidth(); int height = getMeasuredHeight(); AspectRatio ratio = getAspectRatio(); if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) { ratio = ratio.inverse(); } assert ratio != null; if (height < width * ratio.getY() / ratio.getX()) { mGLSurfaceView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(), MeasureSpec.EXACTLY)); } else { mGLSurfaceView.measure( MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } } @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); state.facing = getFacing(); state.ratio = getAspectRatio(); state.autoFocus = getAutoFocus(); state.flash = getFlash(); 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); setAspectRatio(ss.ratio); // setAutoFocus(ss.autoFocus); setFlash(ss.flash); } /** * Open a camera device and start showing camera preview. This is typically called from * {@link Activity#onResume()}. */ public void start() { mImpl.setSize(getWidth(), getHeight()); mImpl.start(); mImpl.startPreview(getWidth(), getHeight()); } /** * Stop camera preview and close the device. This is typically called from * {@link Activity#onPause()}. */ public void stop() { mImpl.stop(); mEglCore.destroy(); } /** * @return {@code true} if the camera is opened. */ public boolean isCameraOpened() { return mImpl.isCameraOpened(); } /** * Add a new callback. * * @param callback The {@link Callback} to add. * @see #removeCallback(Callback) */ public void addCallback(@NonNull Callback callback) { mCallback = callback; mCallbacks.add(callback); } /** * Remove a callback. * * @param callback The {@link Callback} to remove. * @see #addCallback(Callback) */ public void removeCallback(@NonNull Callback callback) { mCallbacks.remove(callback); } /** * @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to * preserve the aspect ratio of camera. * @see #getAdjustViewBounds() */ public void setAdjustViewBounds(boolean adjustViewBounds) { if (mAdjustViewBounds != adjustViewBounds) { mAdjustViewBounds = adjustViewBounds; requestLayout(); } } /** * @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of * camera. * @see #setAdjustViewBounds(boolean) */ public boolean getAdjustViewBounds() { return mAdjustViewBounds; } /** * Chooses camera by the direction it faces. * * @param facing The camera facing. Must be either {@link #FACING_BACK} or * {@link #FACING_FRONT}. */ public void setFacing(@Facing int facing) { mImpl.setFacing(facing); } /** * Gets the direction that the current camera faces. * * @return The camera facing. */ @Facing public int getFacing() { //noinspection WrongConstant return mImpl.getFacing(); } /** * Gets all the aspect ratios supported by the current camera. */ public Set<AspectRatio> getSupportedAspectRatios() { return mImpl.getSupportedAspectRatios(); } /** * Sets the aspect ratio of camera. * * @param ratio The {@link AspectRatio} to be set. */ public void setAspectRatio(@NonNull AspectRatio ratio) { mImpl.setAspectRatio(ratio); } /** * Gets the current aspect ratio of camera. * * @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet. */ @Nullable public AspectRatio getAspectRatio() { return mImpl.getAspectRatio(); } /** * Enables or disables the continuous auto-focus mode. When the current camera doesn't support * auto-focus, calling this method will be ignored. * * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to * disable it. */ // public void setAutoFocus(boolean autoFocus) { // mImpl.setAutoFocus(autoFocus); // } /** * Returns whether the continuous auto-focus mode is enabled. * * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is * disabled, or if it is not supported by the current camera. */ public boolean getAutoFocus() { return mImpl.getAutoFocus(); } /** * Sets the flash mode. * * @param flash The desired flash mode. */ public void setFlash(@Flash int flash) { mImpl.setFlash(flash); } /** * Gets the current flash mode. * * @return The current flash mode. */ @Flash public int getFlash() { //noinspection WrongConstant return mImpl.getFlash(); } class CallbackBridge implements CameraCallback { private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private boolean mRequestLayoutOnOpen; CallbackBridge() { } public void add(Callback callback) { mCallbacks.add(callback); } public void remove(Callback callback) { mCallbacks.remove(callback); } @Override public void onCameraOpened(int previewWidth, int previewHeight) { if (mRequestLayoutOnOpen) { mRequestLayoutOnOpen = false; requestLayout(); } for (Callback callback : mCallbacks) { callback.onCameraOpened(CameraView.this, previewWidth, previewHeight); } } @Override public void onCameraClosed() { for (Callback callback : mCallbacks) { callback.onCameraClosed(CameraView.this); } } @Override public void onPreviewFrame(byte[] data) { for (Callback callback : mCallbacks) { callback.onPreviewFrame(CameraView.this, data); } } @Override public void onPreview(int previewWidth, int previewHeight) { for (Callback callback : mCallbacks) { callback.onPreviewSize(previewWidth, previewHeight); } } public void reserveRequestLayoutOnOpen() { mRequestLayoutOnOpen = true; } } protected static class SavedState extends BaseSavedState { @Facing int facing; AspectRatio ratio; boolean autoFocus; @Flash int flash; @SuppressWarnings("WrongConstant") public SavedState(Parcel source, ClassLoader loader) { super(source); facing = source.readInt(); ratio = source.readParcelable(loader); autoFocus = source.readByte() != 0; 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.writeParcelable(ratio, 0); out.writeByte((byte) (autoFocus ? 1 : 0)); out.writeInt(flash); } public static final 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]; } }); } /** * Callback for monitoring events about {@link CameraView}. */ @SuppressWarnings("UnusedParameters") public abstract static class Callback { /** * Called when camera is opened. * * @param cameraView The associated {@link CameraView}. */ public void onCameraOpened(CameraView cameraView, int previewWidth, int previewHeight) { } /** * Called when camera is closed. * * @param cameraView The associated {@link CameraView}. */ public void onCameraClosed(CameraView cameraView) { } /** * Called when a picture is taken. * * @param cameraView The associated {@link CameraView}. * @param data JPEG data. */ public void onPreviewFrame(CameraView cameraView, byte[] data) { } public void onPreviewSize(int width, int height) { } } }