本章基于前章基础上功能补全和代码优化
1 上代码
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
package com.unity3d.player;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.RectF;
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.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
// The command line arguments are passed as a string, separated by spaces
// UnityPlayerActivity calls this from 'onCreate'
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
// @param cmdLine the current command line arguments, may be null
// @return the modified command line string or null
protected String updateUnityCommandLineArguments(String cmdLine)
{
return cmdLine;
}
// Setup activity layout
@Override protected void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
getIntent().putExtra("unity", cmdLine);
mUnityPlayer = new UnityPlayer(this, this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
applyForPermission(); // add by eh
}
// When Unity player unloaded move task to background
@Override public void onUnityPlayerUnloaded() {
moveTaskToBack(true);
}
// Callback before Unity player process is killed
@Override public void onUnityPlayerQuitted() {
}
@Override protected void onNewIntent(Intent intent)
{
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent);
mUnityPlayer.newIntent(intent);
}
// Quit Unity
@Override protected void onDestroy ()
{
mUnityPlayer.destroy();
super.onDestroy();
}
// Pause Unity
@Override protected void onPause()
{
super.onPause();
mUnityPlayer.pause();
}
// Resume Unity
@Override protected void onResume()
{
super.onResume();
mUnityPlayer.resume();
}
// Low Memory Unity
@Override public void onLowMemory()
{
super.onLowMemory();
mUnityPlayer.lowMemory();
}
// Trim Memory Unity
@Override public void onTrimMemory(int level)
{
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
{
mUnityPlayer.lowMemory();
}
}
// This ensures the layout will be correct.
@Override public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
// Notify Unity of the focus change.
@Override public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
//增加的代码开始处
final private static String TAG = UnityPlayerActivity.class.getName() ;
//供unity 调用 接口
public void OnClickBtn1(){
Log.d(TAG,"android OnClickBtn1");
if(isOpenFlatingWindow()){
addFloatingWindowView(); //增加悬浮窗后
}else{
openCamera();
}
}
//供unity 调用 接口
public void OnClickBtn2(){
Log.d(TAG,"android OnClickBtn2");
if(isOpenFlatingWindow()){
releaseFloatingWindow();
}else{
releaseCamera();
}
}
//供unity 调用 接口 暂时无用
public void OnClickBtn3(){
Log.d(TAG,"android OnClickBtn3");
// myActivity.addFloatingWindowView();
exchangeCamera(); //切换相机
}
/
//增加camera2 摄像头
private CameraManager mCameraManager;
private CameraDevice.StateCallback mStateCallback;
private CameraDevice mCameraDevice;
private TextureView mTextureView;
private CameraCaptureSession mCaptureSession;
private CaptureRequest mPreviewRequest;
private int mCameraSensorOrientation = 0 ; //摄像头方向
private int mCameraFacing = CameraCharacteristics.LENS_FACING_FRONT ; //默认使用前置摄像头
private int mDisplayRotation = 0; //getDisplay().defaultDisplay.rotation //手机方向
private String cameraIdFront = ""; //相机ID
private Size[] cameraSizeFront ; //相机支持的尺寸
private CameraCharacteristics cameraCharacteristicsFront ; //
private static final int RECV_MAX_IMAGES = 2 ; //同时最多接收2张图片
private ImageReader mImageReader;
private TextureView.SurfaceTextureListener surfaceTextureListener ;
private ImageReader.OnImageAvailableListener imageAvailableListener;
private boolean mFlashSupported =false; //判断是否支持闪关灯
android.util.Range<Integer> mAverageFPS =null ; //中间帧率
private HandlerThread mBackgroundThread; //用于运行不应阻塞UI的任务的附加线程
private Handler mBackgroundHandler; //用于在后台运行任务的{@link Handler}
//悬浮窗相关
private WindowManager windowManager = null;
private WindowManager.LayoutParams layoutParams = null;
// public static final int DMS_INPUT_IMG_W = 640;
// public static final int DMS_INPUT_IMG_H = 480;
public static final int PREVIEW_WIDTH = 640 ; //预览的宽度
public static final int PREVIEW_HEIGHT = 480 ; //预览的高度
public static final int DEFAULT_SCREEN_X = 300 ; //默认开始位置
public static final int DEFAULT_SCREEN_Y = 300 ; //默认开始位置
private Size mPreviewSize = null; //预览的尺寸 默认 PREVIEW_WIDTH PREVIEW_HEIGHT
private View mPreviewdisplayView = null; //预览窗口
private Surface mPreviewSurface; //预览窗口surface
// LayoutInflater floatingWindowlayoutInflater = null ; //悬浮窗口布局填充器 //放这里也可以,用临时变量也可以的
private final boolean bOpenFloatingWindowFlag = true ; //是否开启悬浮窗口 //不开启只做数据分析
//
//权限 相关
private final int REQUEST_CODE_CONTACT = 1;
final String[] AppPermissions = {
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
// Manifest.permission.READ_EXTERNAL_STORAGE,
// Manifest.permission.READ_PHONE_STATE,
Manifest.permission.INTERNET,
Manifest.permission.CAMERA,
//Manifest.permission.RECORD_AUDIO,
};
///
//是否开启悬浮窗口
private boolean isOpenFlatingWindow() {return bOpenFloatingWindowFlag;}
//创建悬浮窗布局
void createFloatingWindowLayout(){
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); //在哪都能有, 下面只有在Activity及继续类里有
// windowManager = getWindowManager() ; //上面1句 跟这句 是一样的 Activity里 自带
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; //TYPE_APPLICATION_SUB_PANEL ; //TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = getPreviewWidth(); // PREVIEW_WIDTH; //窗口宽度
layoutParams.height = getPreviewHeight();//PREVIEW_HEIGHT; //窗口高度
layoutParams.x = DEFAULT_SCREEN_X; //开始的位置
layoutParams.y = DEFAULT_SCREEN_Y; //开始的位置
}
//更新预览框大小 当默认的窗口尺寸不存在时 重新设置个尺寸
private void updateFloatingWindowLayout(int w,int h){
if(layoutParams != null){
// layoutParams.width = getPreviewWidth(); // PREVIEW_WIDTH; //窗口宽度
//layoutParams.height = getPreviewHeight();//PREVIEW_HEIGHT; //窗口高度
layoutParams.width = w ; //窗口宽度
layoutParams.height = h ;//窗口高度
}
}
//增加悬浮窗口
void addFloatingWindowView(){
LayoutInflater floatingWindowlayoutInflater = LayoutInflater.from(this);
mPreviewdisplayView = floatingWindowlayoutInflater.inflate(R.layout.camera_display, null);
mPreviewdisplayView.setOnTouchListener(new FloatingOnTouchListener());
mTextureView = mPreviewdisplayView.findViewById(R.id.preview);
/
//因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
Matrix matrix = configureTransform(w,h,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
if(matrix != null) mTextureView.setTransform(matrix);
/
mTextureView.setSurfaceTextureListener(surfaceTextureListener);
windowManager.addView(mPreviewdisplayView, layoutParams);
}
//释放悬浮窗口 并停止 相机
void releaseFloatingWindow(){
if(windowManager != null && mPreviewdisplayView !=null){
windowManager.removeView(mPreviewdisplayView);
mPreviewdisplayView = null ;
}
releaseCamera();
}
private void doSomeThing(){
if(initCameraInfo()){
initSurfaceTextureListener(); //监听SurfaceTexture
initImageAvailableListener(); //图像监听,也可以放到createImageReader前
createFloatingWindowLayout(); //创建布局
}
}
//申请权限
private void applyForPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (String str : AppPermissions) {
if (ContextCompat.checkSelfPermission(this, str) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, AppPermissions, REQUEST_CODE_CONTACT);
return;
}
}
//do some thing
doSomeThing();
}else{
//使用的比Build.VERSION_CODES.M大 所以这里不可能走到
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_CONTACT) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//do some thing
doSomeThing();
} else if (grantResults.length == 0) {
// 已知魅族手机会进这个结果
} else {
Toast.makeText(this, "您拒绝了相关权限,无法使用!", Toast.LENGTH_SHORT).show();
finish();
}
}
}
///相机相关//
//启动相机前 开启
private void startBackgroundThread() {
if (mBackgroundThread == null || mBackgroundHandler == null) {
Log.v(TAG, "startBackgroundThread");
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
//手动停止 或 onDestroy 停止
private void stopBackgroundThread() {
Log.v(TAG, "stopBackgroundThread");
if(mBackgroundThread != null){
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//释放 与切换
private void releaseCamera() {
if(mCaptureSession!=null){
mCaptureSession.close();
mCaptureSession = null ;
}
if(mCameraDevice !=null){
mCameraDevice.close();
mCameraDevice = null;
}
if(mImageReader !=null){
mImageReader.close();
mImageReader = null ;
}
stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()
}
private Handler getBackgroundHandler(){
return mBackgroundHandler ;
}
//切换相机 //直接点击事件下调用即可
private void exchangeCamera() {
if(windowManager == null) return;
//前后置相机切换
mCameraFacing = mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT?CameraCharacteristics.LENS_FACING_BACK:CameraCharacteristics.LENS_FACING_FRONT ;
releaseCamera();
initCameraInfo() ;
if(mTextureView != null){
/
//因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
Matrix matrix = configureTransform(w,h,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
if(matrix != null) mTextureView.setTransform(matrix);
/
}
openCamera();
}
///
//初始化Surface 监听
void initSurfaceTextureListener(){
surfaceTextureListener= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
// SurfaceTexture texture = mTextureView.getSurfaceTexture(); //也可以的
SurfaceTexture texture = surfaceTexture ; //感觉只有一个TextureView surfaceTexture == mTextureView.getSurfaceTexture() 用那个都可以
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
texture.setDefaultBufferSize(w, h); //这里会重新设置尺寸
mPreviewSurface = new Surface(texture);
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
};
}
// private boolean initWindowManager(Context context){
// if(windowManager == null){
// if (context instanceof Activity) {
// Activity activity = (Activity) context;
// windowManager = activity.getWindowManager();
// } else {
// windowManager = (WindowManager) context.getSystemService(UnityPlayerActivity.WINDOW_SERVICE);
// }
// }
// return windowManager !=null ;
// }
// //得到屏幕旋转角度
// private int getDisplayRotation(Context context) {
// if(initWindowManager(context))
// {
// int rotation = windowManager.getDefaultDisplay().getRotation();
// switch (rotation) {
// case Surface.ROTATION_0:
// return 0;
// case Surface.ROTATION_90:
// return 90;
// case Surface.ROTATION_180:
// return 180;
// case Surface.ROTATION_270:
// return 270;
// }
// }
// return 0;
// }
//
// //得到屏幕信息
// private Display getDisplay(Context context) {
// if(initWindowManager(context)) return windowManager.getDefaultDisplay() ;
// return null;
// }
//预览图像旋转度根据屏幕旋(横竖)转度 //displayRotation 1 3 横屏 0 2 竖屏
private Matrix configureTransform(final int viewWidth, final int viewHeight,final int previewWidth,final int previewHeight,final int displayRotation) {
int rotation = displayRotation ;
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, previewHeight, previewWidth);
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
Matrix matrix = new Matrix();
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / previewHeight,
(float) viewWidth / previewWidth);
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
return matrix ;
}
return null ;
}
// private void configureTransform(int viewWidth, int viewHeight) {
// if (null == mTextureView || null == mPreviewSize || null == windowManager) {
// return;
// }
// // int rotation = getWindowManager().getDefaultDisplay().getRotation();
// int rotation = windowManager.getDefaultDisplay().getRotation();
// Matrix matrix = new Matrix();
// RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
// RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
// float centerX = viewRect.centerX();
// float centerY = viewRect.centerY();
// if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
// bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
// matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
// float scale = Math.max(
// (float) viewHeight / mPreviewSize.getHeight(),
// (float) viewWidth / mPreviewSize.getWidth());
// matrix.postScale(scale, scale, centerX, centerY);
// matrix.postRotate(90 * (rotation - 2), centerX, centerY);
// }
// mTextureView.setTransform(matrix);
// }
private class CompareSizeByArea implements java.util.Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight()
- (long) rhs.getWidth() * rhs.getHeight());
}
}
private int getPreviewWidth(){
return mPreviewSize!=null?mPreviewSize.getWidth():PREVIEW_WIDTH ;
}
private int getPreviewHeight(){
return mPreviewSize!=null?mPreviewSize.getHeight():PREVIEW_HEIGHT;
}
//是否支持闪光灯模式
private boolean isFlashSupported(){
return mFlashSupported ;
}
//初始化相机
private boolean initCameraInfo() {
mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
try {
String [] carmeraids = mCameraManager.getCameraIdList() ;
for(int i =0 ;i<carmeraids.length ;i++){
CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(carmeraids[i]);
// cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)== mCameraFacing) { //前置
cameraIdFront = carmeraids[i] ;
cameraCharacteristicsFront = cameraCharacteristics ;
cameraSizeFront = cameraCharacteristics .get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(ImageReader.class);
break;
}
}
}catch (CameraAccessException e){
Log.e(TAG,e.getMessage());
return false;
}
// int supportLevel = cameraCharacteristicsFront.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
// if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
// // mActivity.toast("相机硬件不支持新特性")
// }
int supportLevel = cameraCharacteristicsFront.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if(!isHardwareLevelSupported(supportLevel,CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)){ //希望支持
return false ;
}
//判断是否支持闪光灯
Boolean available = cameraCharacteristicsFront.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
StreamConfigurationMap map = cameraCharacteristicsFront.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size [] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
Size largest = Collections.max(Arrays.asList(outputSizes), new CompareSizeByArea());
// 获取预览画面的尺寸
if(!(outputSizes!= null && outputSizes.length > 0)){
return false;
}
boolean bFound = false ;
for(Size s :outputSizes){
if(s.getWidth() == PREVIEW_WIDTH && s.getHeight() == PREVIEW_HEIGHT){
bFound = true ; //
break;
}
}
if(bFound){
mPreviewSize = new Size(PREVIEW_WIDTH,PREVIEW_HEIGHT);
}else {
//这里取中间值不怎么好,最好得是找一个接近默认值比的宽高度,有旦继续优化
int average = (int)(outputSizes.length/2); //平均值 偶数个 取大的
mPreviewSize = outputSizes[average] ;//map.getOutputSizes(SurfaceTexture.class)[0]; //0 为最大尺寸, 感觉不合理,所以取平均值
updateFloatingWindowLayout(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //重新更新下
}
//帧率
//Key[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
android.util.Range<Integer>[] ranges = cameraCharacteristicsFront.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if(ranges != null && ranges.length > 0){
//取中间值 从低往高 0 帧率最低
int average = (int)(ranges.length/2); //平均值 偶数个 取大的
mAverageFPS = ranges[average]; //中间值
}
return true ;
}
// 对于Camera2采集系统来说,每个摄像头都有一个支持等级: 从高到底为 越高越好
// INFO_SUPPORTED_HARDWARE_LEVEL_3 支持YUV再处理和原始数据采集功能,并且具备先进的功能。
// INFO_SUPPORTED_HARDWARE_LEVEL_FULL支持先进的摄像头功能。
// INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED向后兼容模式,底层等同于Camera1的实现。
// INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY 随机赠送的功能支持,支持性不足。
// 总的来说如果摄像头等级是LEVEL_3和LEVEL_FULL才建议使用Camera2进行采集,否则推荐采用兼容性更好的Camera1进行视频采集。
//判断是否支持要求的等级
// Returns true if the device supports the required hardware level, or better.
boolean isHardwareLevelSupported(int supportLevel , int requiredLevel) {
final int[] sortedHwLevels = {
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
};
int deviceLevel = supportLevel ; CameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (requiredLevel == deviceLevel) {
return true;
}
for (int sortedlevel : sortedHwLevels) {
if (sortedlevel == requiredLevel) {
return true;
} else if (sortedlevel == deviceLevel) {
return false;
}
}
return false; // Should never reach here
}
//初始化
private void initImageAvailableListener(){
imageAvailableListener = new ImageReader.OnImageAvailableListener(){
@Override
public void onImageAvailable(ImageReader var1){
Image image = var1.acquireLatestImage() ;
if(image== null){
return;
}
//得到图片 转成nv21格式
byte [] date = YUV_420_888toNV21(image);
// ByteBuffer buffer = image.getPlanes()[0].getBuffer();
// int length = buffer.remaining();
// var bytes = ByteArray(length);
// 转成 Bitmap
// ByteBuffer buffer = image.getPlanes()[0].getBuffer();
// byte[] b = new byte[buffer.capacity()];
// buffer.get(b, 0 , b.length);
// Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
image.close();
}
};
}
private void createCameraDeviceCallback(){
mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
mCameraDevice = null;
}
};
}
public void openCamera(){
mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
startBackgroundThread(); //开启
createCameraDeviceCallback() ; //相机状态监听
createImageReader(); //图像解析
try {
mCameraManager.openCamera(cameraIdFront, mStateCallback, getBackgroundHandler()); //"0" 前置 "1" 后置 // handler null 主线程处理 否 创建个独立线程区处理
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//创建分析
private void createImageReader() {
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
}
//mPreviewSize.getWidth()
mImageReader = ImageReader.newInstance(getPreviewWidth(), getPreviewHeight(),
ImageFormat.YUV_420_888, RECV_MAX_IMAGES);
// imageReader = ImageReader.newInstance(1280, 720, ImageFormat.JPEG, 2)
mImageReader.setOnImageAvailableListener(imageAvailableListener, getBackgroundHandler());
}
//创建预览会话
private void createCameraPreviewSession() {
try {
final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
if(isOpenFlatingWindow()){ //是否开启悬浮窗口
previewRequestBuilder.addTarget(mPreviewSurface);
}
previewRequestBuilder.addTarget(mImageReader.getSurface());
//这几句可以放到 // 放到设置预览时连续捕获图像数据前 也可以
// 设置自动对焦模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
if(isFlashSupported()){ //如果支持就开启
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
//设置帧率
if(mAverageFPS!=null){
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mAverageFPS);
}
// 开始显示相机预览
mPreviewRequest = previewRequestBuilder.build();
//
// width 和 height 与 surfaceview 的相同
//ImageReader 用于接收处理(拍照、录像)的相机流
//SurfaceView 用于接收预览的相机流
// ImageReader mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
List<Surface> surfaces = new ArrayList<Surface>();
if(isOpenFlatingWindow()) { //是否开启悬浮窗口
surfaces.add(mPreviewSurface);
}
surfaces.add(mImageReader.getSurface());
// surfaces.add(surfaceView.getHolder().getSurface());
// surfaces.add(mImageReader.getSurface());
// Arrays.asList(surface)
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
try {
//这几句可以放到 // 放到设置预览时连续捕获图像数据 也可以
// 设置自动对焦模式
// previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// // 设置自动曝光模式
// if(isFlashSupported()){ //如果支持就开启
// previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// }
// //设置帧率
// if(mAverageFPS!=null){
// previewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mAverageFPS);
// }
// // 开始显示相机预览
// mPreviewRequest = previewRequestBuilder.build();
//
// 设置预览时连续捕获图像数据
//listener 这里不是拍照,预览的无需回调,拍照就需要了
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Log.e("MainActivity", "onConfigureFailed");
}
}, getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
数据格式处理//
//数据转化 抄别人的 //
//Planar格式(P)的处理
private static ByteBuffer getuvBufferWithoutPaddingP(ByteBuffer uBuffer, ByteBuffer vBuffer, int width, int height, int rowStride, int pixelStride){
int pos = 0;
byte []byteArray = new byte[height*width/2];
for (int row=0; row<height/2; row++) {
for (int col=0; col<width/2; col++) {
int vuPos = col*pixelStride + row*rowStride;
byteArray[pos++] = vBuffer.get(vuPos);
byteArray[pos++] = uBuffer.get(vuPos);
}
}
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
//Semi-Planar格式(SP)的处理和y通道的数据
private static ByteBuffer getBufferWithoutPadding(ByteBuffer buffer, int width, int rowStride, int times,boolean isVbuffer){
if(width == rowStride) return buffer; //没有buffer,不用处理。
int bufferPos = buffer.position();
int cap = buffer.capacity();
byte []byteArray = new byte[times*width];
int pos = 0;
//对于y平面,要逐行赋值的次数就是height次。对于uv交替的平面,赋值的次数是height/2次
for (int i=0;i<times;i++) {
buffer.position(bufferPos);
//part 1.1 对于u,v通道,会缺失最后一个像u值或者v值,因此需要特殊处理,否则会crash
if(isVbuffer && i==times-1){
width = width -1;
}
buffer.get(byteArray, pos, width);
bufferPos+= rowStride;
pos = pos+width;
}
//nv21数组转成buffer并返回
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
private static byte[] YUV_420_888toNV21(Image image) {
int width = image.getWidth();
int height = image.getHeight();
ByteBuffer yBuffer = getBufferWithoutPadding(image.getPlanes()[0].getBuffer(), image.getWidth(), image.getPlanes()[0].getRowStride(),image.getHeight(),false);
ByteBuffer vBuffer;
//part1 获得真正的消除padding的ybuffer和ubuffer。需要对P格式和SP格式做不同的处理。如果是P格式的话只能逐像素去做,性能会降低。
if(image.getPlanes()[2].getPixelStride()==1){ //如果为true,说明是P格式。
vBuffer = getuvBufferWithoutPaddingP(image.getPlanes()[1].getBuffer(), image.getPlanes()[2].getBuffer(),
width,height,image.getPlanes()[1].getRowStride(),image.getPlanes()[1].getPixelStride());
}else{
vBuffer = getBufferWithoutPadding(image.getPlanes()[2].getBuffer(), image.getWidth(), image.getPlanes()[2].getRowStride(),image.getHeight()/2,true);
}
//part2 将y数据和uv的交替数据(除去最后一个v值)赋值给nv21
int ySize = yBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21;
int byteSize = width*height*3/2;
nv21 = new byte[byteSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
//part3 最后一个像素值的u值是缺失的,因此需要从u平面取一下。
ByteBuffer uPlane = image.getPlanes()[1].getBuffer();
byte lastValue = uPlane.get(uPlane.capacity() - 1);
nv21[byteSize - 1] = lastValue;
return nv21;
}
//悬浮窗口 移动 更新位置
private class FloatingOnTouchListener implements View.OnTouchListener {
private int x;
private int y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX - x;
int movedY = nowY - y;
x = nowX;
y = nowY;
layoutParams.x = layoutParams.x + movedX;
layoutParams.y = layoutParams.y + movedY;
windowManager.updateViewLayout(view, layoutParams);
break;
default:
break;
}
return true;
}
}
//
//权限
//
//
//
//硬件加速
// android:hardwareAccelerated="true"
//使用 useAndroidX
//android.useAndroidX=true
//增加依赖
//implementation 'androidx.core:core:1.2.0' 跟下面3句有关系,不增加,下面的找不到
// import androidx.annotation.NonNull;
//import androidx.core.app.ActivityCompat;
//import androidx.core.content.ContextCompat;
///
// unity 导出 Plugin Version(3.6.0) Gradle Version(6.1.1) 这里用
// compileSdkVersion 33
// buildToolsVersion '33.0.0'
// 不支持 所以需要提升下
// Android Gradle Plugin Version
//3.6.0 ----> 4.1.1
// Gradle Version
//6.1.1 ----> 6.9
//
//启动过程
//1: 申请权限,得到相机与屏幕 信息 多出相依处理
//applyForPermission() //放到 onCreate 里就OK了
//有权限 dosomething
//2:dosomething 里 初始化相机
//createFloatingWindowLayout() 初始化各种监听 等
//3: 点击事件 或其他地方调用
//addFloatingWindowView 触发
// TextureView.SurfaceTextureListener() 的
// public void onSurfaceTextureAvailable 从而 启动相机
//4: 启动相机
//openCamera 在 mCameraManager.openCamera 前 回调函数都设置好
//createCameraDeviceCallback() ; //相机状态监听回调
// createImageReader(); //图像解析
//触发 createCameraPreviewSession
//5 创建预览会话
// 预览 解析 加入会话申请中
}
开启悬浮窗口标志
private final boolean bOpenFloatingWindowFlag = true ; //是否开启悬浮窗口 //不开启只做数据分析
//是否开启悬浮窗口
private boolean isOpenFlatingWindow() {return bOpenFloatingWindowFlag;}
private HandlerThread mBackgroundThread; //用于运行不应阻塞UI的任务的附加线程
private Handler mBackgroundHandler; //用于在后台运行任务的{@link Handler}
private void createCameraPreviewSession() {
...............................
if(isOpenFlatingWindow()){ //是否开启悬浮窗口
previewRequestBuilder.addTarget(mPreviewSurface);
}
....................................
if(isOpenFlatingWindow()) { //是否开启悬浮窗口
surfaces.add(mPreviewSurface);
}
..............................
}
启动 背景线程处理 ,为了不影响 UI
//启动相机前 开启
private void startBackgroundThread() {
if (mBackgroundThread == null || mBackgroundHandler == null) {
Log.v(TAG, "startBackgroundThread");
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
//手动停止 或 onDestroy 停止
private void stopBackgroundThread() {
Log.v(TAG, "stopBackgroundThread");
if(mBackgroundThread != null){
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//释放 与切换
private void releaseCamera() {
if(mCaptureSession!=null){
mCaptureSession.close();
mCaptureSession = null ;
}
if(mCameraDevice !=null){
mCameraDevice.close();
mCameraDevice = null;
}
if(mImageReader !=null){
mImageReader.close();
mImageReader = null ;
}
stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()
}
private Handler getBackgroundHandler(){
return mBackgroundHandler ;
}
预览大小跟悬浮布布局大小有差异时
//更新预览框大小 当默认的窗口尺寸不存在时 重新设置个尺寸
private void updateFloatingWindowLayout(int w,int h){
if(layoutParams != null){
// layoutParams.width = getPreviewWidth(); // PREVIEW_WIDTH; //窗口宽度
//layoutParams.height = getPreviewHeight();//PREVIEW_HEIGHT; //窗口高度
layoutParams.width = w ; //窗口宽度
layoutParams.height = h ;//窗口高度
}
}
//初始化相机
private boolean initCameraInfo() {
..............................
if(bFound){
mPreviewSize = new Size(PREVIEW_WIDTH,PREVIEW_HEIGHT);
}else {
//这里取中间值不怎么好,最好得是找一个接近默认值比的宽高度,有旦继续优化
int average = (int)(outputSizes.length/2); //平均值 偶数个 取大的
mPreviewSize = outputSizes[average] ;//map.getOutputSizes(SurfaceTexture.class)[0]; //0 为最大尺寸, 感觉不合理,所以取平均值
updateFloatingWindowLayout(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //重新更新下
}
..............................
}
根据屏幕旋转角度
//预览图像旋转度根据屏幕旋(横竖)转度 //displayRotation 1 3 横屏 0 2 竖屏
private Matrix configureTransform(final int viewWidth, final int viewHeight,final int previewWidth,final int previewHeight,final int displayRotation) {
int rotation = displayRotation ;
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, previewHeight, previewWidth);
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
Matrix matrix = new Matrix();
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / previewHeight,
(float) viewWidth / previewWidth);
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
return matrix ;
}
return null ;
}
切换前后置相机
//切换相机 //直接点击事件下调用即可
private void exchangeCamera() {
if(windowManager == null) return;
//前后置相机切换
mCameraFacing = mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT?CameraCharacteristics.LENS_FACING_BACK:CameraCharacteristics.LENS_FACING_FRONT ;
releaseCamera();
initCameraInfo() ;
if(mTextureView != null){
/
//因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
Matrix matrix = configureTransform(w,h,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
if(matrix != null) mTextureView.setTransform(matrix);
/
}
openCamera();
}
2 运行结果(切换为后置)
临时 用button2 借用下 ,懒得再去unity 增加button3了
public void OnClickBtn2(){
exchangeCamera(); //切换相机
}
3 后续需要优化的部分
1 没找到匹配的窗口大小默认值,用中间值代替,不知道中间值宽高比是否跟默认值等效,
2 YUV_420_888toNV21 JAVA 效率不高,后面用c++ 重写个so 调用
4 觉得有用 ,点个赞,加个关注