提交 d13fe800 编写于 作者: 杨时权

【需求】新增加Camera2 Android采集接口。

上级 fb2ecda5
package com.ly.avfoundation.avfoundation.capture;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
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.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor;
import static android.hardware.camera2.params.SessionConfiguration.SESSION_REGULAR;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class VideoCapture2 {
public static final String TAG = "VideoCapture2";
/**
* ID of the current {@link CameraDevice}
*/
private String mCameraid;
/**
* A {@link CameraCaptureSession} for camera preview
*/
private CameraCaptureSession mSession;
/**
* A reference to the opened {@link CameraDevice}
*/
private CameraDevice mCameraDevice;
/**
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
startPreview();
Log.i(TAG, "onOpened device=" + cameraDevice);
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
if (mCameraDevice == cameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
Log.i(TAG, "onDisconnected device=" + cameraDevice);
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
cameraDevice.close();
Log.e(TAG, "onError device=" + cameraDevice + " error=" + i);
}
};
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mCameraHandlerThread;
/**
* A {@link Handler} for running tasks in the background
*/
private Handler mCameraHandler;
/**
* {@link CaptureRequest} generated by {@link #mCaptureRequestBuilder}
*/
private CaptureRequest mCaptureRequest = null;
/**
* {@link CaptureRequest.Builder} for the camera preview.
*/
private CaptureRequest.Builder mCaptureRequestBuilder = null;
public static class Configuration {
public int width;
public int height;
public int facingType;
public int angle;
public Configuration() {
this(1280, 720);
}
public Configuration(int w, int h) {
this(w, h, FacingType.FRONT);
}
public Configuration(int w, int h, int facingType) {
this.width = w;
this.height = h;
this.facingType = facingType;
}
}
/**
* A {@link Configuration} for preview config to {@link #mSession}
*/
private Configuration mConfiguration = null;
/**
* A {@link SurfaceTexture} fro preview render object
*/
private SurfaceTexture mSurfaceTexture = null;
/**
* The {@link Size} of camera preview.
*/
private Size mPreviewSize;
private int mSensorOrientation;
private Activity mActivity = null;
public static class FacingType {
public static final int FRONT = 0;
public static final int BACK = 1;
}
/**
* Compares two {@code Size}s based on their areas.
*/
static class CompareSizeByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
public interface OnPreviewSizeChangeListener {
void onPreviewSizeChange(int width, int height);
}
public OnPreviewSizeChangeListener mPreviewSizeChangeListener = null;
public void setPreviewSizeChangeListener(OnPreviewSizeChangeListener listener) {
mPreviewSizeChangeListener = listener;
}
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
};
public VideoCapture2(Activity activity) {
mActivity = activity;
startCameraThread();
}
public void openCamera(Configuration configuration) {
mConfiguration = configuration;
// setup camera to get cameraid
setupCamera(mConfiguration.width, mConfiguration.height, mConfiguration.facingType);
// create camera by cameraid
if (!openCamera()) {
throw new RuntimeException("open camera error!");
}
}
public void setSurfaceTexture(SurfaceTexture st) {
mSurfaceTexture = st;
startPreview();
}
public void releaseCamera() {
if (mSession != null) {
mSession.close();
mSession = null;
}
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
}
private void startCameraThread() {
mCameraHandlerThread = new HandlerThread("CameraThread");
mCameraHandlerThread.start();
mCameraHandler = new Handler(mCameraHandlerThread.getLooper());
}
private String setupCamera(int w, int h, int facingType) {
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
for (String id : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && characteristics.get(CameraCharacteristics.LENS_FACING) != facingType) {
continue;
}
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null)
continue;
int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
swappedDimensions = true;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
swappedDimensions = true;
}
break;
default:
Log.e(TAG, "Display rotation is invalid: " + displayRotation);
}
Point displaySize = new Point();
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatePreviewWidth = w;
int rotatePreviewHeight = h;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (swappedDimensions) {
rotatePreviewWidth = h;
rotatePreviewHeight = w;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
}
if (maxPreviewWidth >= 1920)
maxPreviewWidth = 1920;
if (maxPreviewHeight >= 720)
maxPreviewHeight = 720;
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizeByArea());
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatePreviewWidth, rotatePreviewHeight, maxPreviewWidth, maxPreviewHeight, largest);
mCameraid = id;
if (mPreviewSizeChangeListener != null)
mPreviewSizeChangeListener.onPreviewSizeChange(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Log.i(TAG, "setupCamera id=" + mCameraid + " previewW=" + mPreviewSize.getWidth() + " previewH=" + mPreviewSize.getHeight());
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return mCameraid;
}
public void startPreview() {
if (mCameraDevice == null || mSurfaceTexture == null) {
return;
}
try {
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface previewSurface = new Surface(mSurfaceTexture);
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mCaptureRequestBuilder.addTarget(previewSurface);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Executor executor = new Executor() {
@Override
public void execute(Runnable runnable) {
runnable.run();
}
};
CameraCaptureSession.StateCallback ccs = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (mCameraDevice == null) {
return;
}
try {
mSession = cameraCaptureSession;
// mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCaptureRequest = mCaptureRequestBuilder.build();
mSession.setRepeatingRequest(mCaptureRequest, mCaptureCallback, mCameraHandler);
} catch (CameraAccessException exp) {
exp.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG, "onConfigureFailed!!!");
}
};
OutputConfiguration configuration = new OutputConfiguration(previewSurface);
mCameraDevice.createCaptureSession(new SessionConfiguration(SESSION_REGULAR, Arrays.asList(configuration), executor, ccs));
}
} catch (CameraAccessException exp) {
exp.printStackTrace();
}
}
public boolean openCamera() {
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return false;
}
manager.openCamera(mCameraid, mStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
* is at least as large as the respective texture view size, and that is at most as large as the
* respective max size, and whose aspect ratio matches with the specified value. If such size
* doesn't exist, choose the largest one that is at most as large as the respective max size,
* and whose aspect ratio matches with the specified value.
*
* @param choices The list of sizes that the camera supports for the intended output
* class
* @param textureViewWidth The width of the texture view relative to sensor coordinate
* @param textureViewHeight The height of the texture view relative to sensor coordinate
* @param maxWidth The maximum width that can be chosen
* @param maxHeight The maximum height that can be chosen
* @param aspectRatio The aspect ratio
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
*/
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizeByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizeByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
}
......@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
......@@ -20,10 +21,11 @@ import androidx.fragment.app.Fragment;
import com.ly.avfoundation.R;
import com.ly.avfoundation.avfoundation.capture.VideoCapture;
import com.ly.avfoundation.avfoundation.capture.VideoCapture2;
import com.ly.avfoundation.avfoundation.render.CameraSurfaceRenderer;
public class CaptureFragment extends Fragment implements View.OnClickListener,
VideoCapture.OnPreviewSizeChangeListener, TextWatcher, CameraSurfaceRenderer.OnSurfaceRenderListener {
VideoCapture.OnPreviewSizeChangeListener, TextWatcher, CameraSurfaceRenderer.OnSurfaceRenderListener, VideoCapture2.OnPreviewSizeChangeListener {
private static final String TAG = "[CaptureFragment]";
private Context mActivity = null;
......@@ -31,6 +33,8 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
private VideoCapture mCapture;
private VideoCapture.Configuration mConfiguration;
private VideoCapture2 mCapture2;
private VideoCapture2.Configuration mConfiguration2;
private SurfaceTexture mSurfaceTexture;
private Boolean mStartCapture;
......@@ -43,6 +47,8 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
private Button mStopBtn;
private int mSelectInputType;
private Boolean mUseCamera2 = Boolean.TRUE;
public static CaptureFragment newInstance(Bundle saveInstanceState) {
Bundle bundle = new Bundle();
CaptureFragment fragment = new CaptureFragment();
......@@ -100,11 +106,15 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
});
mSelectInputType = VideoCapture.FactingType.FRONT;
mCapture = new VideoCapture();
mConfiguration = new VideoCapture.Configuration(0, 0, 0, 90);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mUseCamera2) {
mCapture2 = new VideoCapture2((Activity) mActivity);
mConfiguration2 = new VideoCapture2.Configuration(0, 0, 0);
} else {
mCapture = new VideoCapture();
mConfiguration = new VideoCapture.Configuration(0, 0, 0, 90);
}
mStartCapture = false;
return view;
}
......@@ -114,7 +124,6 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
// to open capture
if (mStartCapture) {
}
}
......@@ -124,7 +133,6 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
// to close capture
if (mStartCapture) {
}
}
......@@ -183,23 +191,50 @@ public class CaptureFragment extends Fragment implements View.OnClickListener,
width = Integer.parseInt(widthStr);
height = Integer.parseInt(heightStr);
mConfiguration.width = width;
mConfiguration.height = height;
mConfiguration.angle = 0;
mConfiguration.facingType = mSelectInputType;
mCapture.setPreviewSizeChangeListener(this);
mCapture.openCamera(mConfiguration);
mCapture.setSurfaceTexture(mSurfaceTexture);
if (mSelectInputType == VideoCapture.FactingType.FRONT) {
mRender.setAngle(90);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mUseCamera2) {
mConfiguration2.width = width;
mConfiguration2.height = height;
mConfiguration2.angle = 0;
switch (mSelectInputType) {
case VideoCapture.FactingType.FRONT:
mConfiguration2.facingType = VideoCapture2.FacingType.FRONT;
break;
case VideoCapture.FactingType.REAR:
mConfiguration2.facingType = VideoCapture2.FacingType.BACK;
break;
}
mCapture2.setPreviewSizeChangeListener(this);
mCapture2.openCamera(mConfiguration2);
mCapture2.setSurfaceTexture(mSurfaceTexture);
if (mSelectInputType == VideoCapture.FactingType.FRONT) {
mRender.setAngle(90);
} else {
mRender.setAngle(270);
}
} else {
mRender.setAngle(270);
mConfiguration.width = width;
mConfiguration.height = height;
mConfiguration.angle = 0;
mConfiguration.facingType = mSelectInputType;
mCapture.setPreviewSizeChangeListener(this);
mCapture.openCamera(mConfiguration);
mCapture.setSurfaceTexture(mSurfaceTexture);
if (mSelectInputType == VideoCapture.FactingType.FRONT) {
mRender.setAngle(90);
} else {
mRender.setAngle(270);
}
}
}
private void handleStopBtn() {
mStartCapture = Boolean.FALSE;
mCapture.releaseCamera();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mUseCamera2) {
mCapture2.releaseCamera();
} else {
mCapture.releaseCamera();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册