基于camera2 untiy悬浮窗摄像头预览分析(优化二)

前言

本章基于前章基础上功能 继续优化,
主要优化点 1 优化取中间值 预览画面的尺寸与帧数
2 YUV_420_888toNV21 用c++ 重写,提升效率

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.DisplayMetrics;
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 (isOpenFloatingWindow()) {
            addFloatingWindowView(); //增加悬浮窗后
        } else {
            openCamera();
        }
    }

    //供unity 调用 接口
    public void OnClickBtn2() {
        Log.d(TAG, "android OnClickBtn2");
        if (isOpenFloatingWindow()) {
            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 = "1";  //相机ID 可以自行设定  // 0 后置  1  前置  //默认使用前置摄像头
    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;//   //预览的宽度test 680;
    public static final int PREVIEW_HEIGHT = 480;    //预览的高度
    public static final int LAYOUT_WIDTH = 640;       //预览的宽度
    public static final int LAYOUT_HEIGHT = 480;      //预览的高度
    public static final int DEFAULT_SCREEN_X = 300;   //默认开始位置
    public static final int DEFAULT_SCREEN_Y = 300;    //默认开始位置
    public static final int DEFAULT_CAMERA_FPS = 30;   //默认摄像头帧数   // 20-40
    private Size mPreviewSize = null;     //预览的尺寸 默认 PREVIEW_WIDTH  PREVIEW_HEIGHT
    private Size mLayoutSize = null;      //布局尺寸 view尺寸跟布局 match_parent 所以一样大
    private Size mScreenSize = null;      //屏幕尺寸
    private View mPreviewdisplayView = null;  //预览窗口
    private Surface mPreviewSurface;                 //预览窗口surface
    //  LayoutInflater floatingWindowlayoutInflater = null ;  //悬浮窗口布局填充器  //放这里也可以,用临时变量也可以的
    private final boolean bOpenFloatingWindowFlag = true;  //是否开启悬浮窗口 //不开启只做数据分析
    private String[] mAllCameraIds = null;             //所有摄像头
    //
    //权限 相关
    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 isOpenFloatingWindow() {
        return bOpenFloatingWindowFlag;
    }

//    private  boolean getWindowManger(){
//
//        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);  //在哪都能有, 下面只有在Activity及继续类里有
//        // windowManager = getWindowManager() ;  //上面1句  跟这句 是一样的  Activity里 自带
//        return  windowManager
//    }
    //创建悬浮窗布局
    void createFloatingWindowLayout(){
        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 =  getLayoutWidth(); //   //窗口宽度
        layoutParams.height = getLayoutHeight();// //窗口高度
        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);
        final int w = getPreviewWidth() ;
        final int h = getPreviewHeight() ;
        final int wl = getLayoutWidth() ;
        final int hl = getLayoutHeight();
         Matrix  matrix =  configureTransform(wl,hl,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(getDefaultCameraID())){
            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 ;
        swapDefaultCameraID(); //切换相机
        releaseCamera();
        if(initCameraInfo(getDefaultCameraID()) ){
            if(mTextureView != null){
                /
                //因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
                final int w = getPreviewWidth() ;
                final int h = getPreviewHeight() ;
                final int wl = getLayoutWidth() ;
                final int hl = getLayoutHeight();
                configureTransform(wl,hl,w ,h,windowManager.getDefaultDisplay().getRotation()) ;  //旋转角度根据屏幕
                /
            }
            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;
//    }

   //得到屏幕信息
   private  boolean getScreenDisplayInfo(){
       windowManager = (WindowManager)getSystemService(UnityPlayerActivity.WINDOW_SERVICE);
       if(windowManager == null) return  false ;
       DisplayMetrics outMetrics = new DisplayMetrics();
       windowManager.getDefaultDisplay().getRealMetrics(outMetrics);
       if(outMetrics.widthPixels < 1  || outMetrics.heightPixels < 1){
           return  false ;
       }
       mScreenSize = new Size(outMetrics.widthPixels,outMetrics.heightPixels);
       outMetrics = null ;
       return  true;
   }


    //预览图像旋转度根据屏幕旋(横竖)转度  //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  int getLayoutWidth(){
        return mLayoutSize!=null?mLayoutSize.getWidth():LAYOUT_WIDTH ;
    }

    private  int getLayoutHeight(){
        return mLayoutSize!=null?mLayoutSize.getHeight():LAYOUT_HEIGHT;
    }

    private  int getScreenWidth(){
        return  mScreenSize!=null?mScreenSize.getWidth():PREVIEW_WIDTH ;
    }

    private  int getScreenHeight(){
        return mScreenSize!=null?mScreenSize.getHeight():PREVIEW_HEIGHT;
    }

    //是否支持闪光灯模式
    private  boolean isFlashSupported(){
        return  mFlashSupported ;
    }

    //得到所有摄像头ID
    private  String [] getAllCameraIds(){
        if(mCameraManager == null)   mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
        try {
            String []  carmeraids = mCameraManager.getCameraIdList() ;
            return  carmeraids ;
        }catch (CameraAccessException e){
            Log.e(TAG,e.getMessage());
        }
        return null ;
    }

    //检测相机ID是否存在
    private  boolean checkCameraExistByCameraID(String strCameraID){
        if(mAllCameraIds == null){
            mAllCameraIds = getAllCameraIds();
        }
        if(mAllCameraIds != null && mAllCameraIds.length > 0){
            for (String cameraid : mAllCameraIds){
                if(cameraid.equals(strCameraID)){
                    return  true;
                }
            }
        }
        return  false ;
    }

    //通过ID 得到 该摄像头特征属性
    private  CameraCharacteristics getCameraCharacteristicsByCameraID(String strCameraId){
        if(mCameraManager == null) return null ;
        try{
            return  mCameraManager.getCameraCharacteristics(strCameraId);
        } catch (CameraAccessException e){
            Log.e(TAG,e.getMessage());
        }
        return  null ;
    }

    private  Size   getSuitableLayoutSize(Size previewSize,int ratio,int screenW,int screenH ){
        final Size s4_3 = new Size(320,240);  //80
        final Size s3_2 = new Size(320,160);
        final Size s16_10 = new Size(320,200);
        final Size s16_9 = new Size(320,180);
        final int ratio4_3 = 13 ; //1.333*10
        final int ratio3_2 = 15 ;//1.5*10
        final int ratio16_10 = 16 ; //1.6*10
        final int ratio16_9 = 17 ;//1.777*10

        final int screenratio = screenW/320 ;
        if(screenratio >= 4){
            switch (ratio){
                case ratio4_3:{
                    return  new Size(s4_3.getWidth()*2,s4_3.getHeight()*2) ;
                }
                case ratio3_2:{
                    return  new Size(s3_2.getWidth()*2,s3_2.getHeight()*2) ;
                }
                case ratio16_10:{
                    return  new Size(s16_10.getWidth()*2,s16_10.getHeight()*2) ;
                }
                case ratio16_9:{
                    return  new Size(s16_9.getWidth()*2,s16_9.getHeight()*2) ;
                }
            }
        }else {
            switch (ratio){
                case ratio4_3:{
                    return  new Size(s4_3.getWidth(),s4_3.getHeight()) ;
                }

                case ratio3_2:{
                    return  new Size(s3_2.getWidth(),s3_2.getHeight()) ;
                }

                case ratio16_10:{
                    return  new Size(s16_10.getWidth(),s16_10.getHeight()) ;
                }

                case ratio16_9:{
                    return  new Size(s16_9.getWidth(),s16_9.getHeight()) ;
                }
            }
        }
        //这个会拉伸
        return  new Size(LAYOUT_WIDTH,LAYOUT_HEIGHT) ;
    }

    //得到合适的预览尺寸 //暂时只针对横屏  //0 为最大尺寸
    private  boolean getSuitablePreviewSize(Size [] outputSizes){
        if(null == outputSizes) return  false ;
        if(1 > outputSizes.length) return  false ;
        //4:3 3:2 16:10 16:9 //4种
        final Size s4_3 = new Size(320,240);  //80
        final Size s3_2 = new Size(320,160);
        final Size s16_10 = new Size(320,200);
        final Size s16_9 = new Size(320,180);
        final int ratio4_3 = 13 ; //1.333*10
        final int ratio3_2 = 15 ;//1.5*10
        final int ratio16_10 = 16 ; //1.6*10
        final int ratio16_9 = 17 ;//1.777*10
      //  final Size[] screenSizes = new Size []{s4_3,s3_2,s16_10,s16_9};
        final int screenW = getScreenWidth() ;
        final int screenH = getScreenHeight() ;
        final int screenTotalPixel = screenW*screenH ;

        final float rangemin = 0.1f ; // 1/10  to  1/6
        final float rangeamx = 0.17f ;

        ArrayList<Size>  sizelistSuitable = new  ArrayList<Size>();  //
        int  widthSuitable =  0;
        int  heightSuitable = 0;
        int  whRatioSuitable =  0;


        ArrayList<Size>  sizelistNoSuitable = new  ArrayList<Size>();  //
        ArrayList<Integer>  sizelistNoSuitableRatio = new  ArrayList<Integer>();  //

        ArrayList<Size>  sizelistNoSuitable_1 = new  ArrayList<Size>();  //

        for(Size s : outputSizes) {
            float sceenRatio = (float)(s.getWidth() * s.getHeight())/ screenTotalPixel ;
            if (sceenRatio >= rangemin && sceenRatio <= rangeamx) {  //合适的大小
                int aspect_ratio =  s.getWidth() *10 /s.getHeight() ;
                switch (aspect_ratio){
                    case ratio4_3:
                    case ratio3_2:
                    case ratio16_10:
                    case ratio16_9:
                        widthSuitable = s.getWidth(); heightSuitable = s.getHeight();  whRatioSuitable = aspect_ratio ;
                        break;
                    default:
                        sizelistSuitable.add(s);
                        break;
                }

                if (widthSuitable > 0) {
                    break;
                }
            }else{
                int aspect_ratio =  s.getWidth() *10 /s.getHeight() ;
                switch (aspect_ratio){
                    case ratio4_3:
                    case ratio3_2:
                    case ratio16_10:
                    case ratio16_9:
                        sizelistNoSuitable.add(s);
                        sizelistNoSuitableRatio.add(aspect_ratio);
                        break;
                    default:
                        sizelistNoSuitable_1.add(s);
                        break;
                }
            }
        }


        //最高优先级
        if(widthSuitable > 0){ //有合适的大小 宽高比 已知
             mPreviewSize =  new Size(widthSuitable,heightSuitable);
            mLayoutSize = getSuitableLayoutSize(mPreviewSize,whRatioSuitable,screenW,screenH);
             updateFloatingWindowLayout(mLayoutSize.getWidth(),mLayoutSize.getHeight());
        }else if(sizelistNoSuitable.size() > 0){//
            int average = (int)(sizelistNoSuitable.size()/2); //平均值  偶数个 取大的
            Size  tSize = sizelistNoSuitable.get(average);
            int  tRatio = sizelistNoSuitableRatio.get(average);
            mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());

            mLayoutSize = getSuitableLayoutSize(mPreviewSize,tRatio,screenW,screenH);
            updateFloatingWindowLayout(mLayoutSize.getWidth(),mLayoutSize.getHeight());
        }else if(sizelistSuitable.size()  > 0){ //没找到匹配的比例,图像拉伸吧
            int average = (int)(sizelistSuitable.size()/2); //平均值  偶数个 取大的
            Size  tSize = sizelistSuitable.get(average);
            mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());
        }else if(sizelistNoSuitable_1.size() > 0){ //没找到匹配的比例,图像拉伸吧
            int average = (int)(sizelistNoSuitable_1.size()/2); //平均值  偶数个 取大的
            Size  tSize = sizelistNoSuitable_1.get(average);
            mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());
        }

        //不可能走到这里的 只要 outputSizes 有数据
        return  false ;

    }

    //得到合适的FPS
    private  android.util.Range<Integer> getSuitableFpsByDefaultFps(android.util.Range<Integer>[] ranges){
         final int default_fps_diff = DEFAULT_CAMERA_FPS/4; //1/4 范围
         final int default_fps_min = Math.max(DEFAULT_CAMERA_FPS - default_fps_diff,20);
         final int default_fps_max = Math.min(DEFAULT_CAMERA_FPS + default_fps_diff,40);
        ArrayList<android.util.Range<Integer>>  sizelist1 = new   ArrayList<android.util.Range<Integer>>();
        ArrayList<android.util.Range<Integer>>  sizelist2 = new   ArrayList<android.util.Range<Integer>>(); //恒定帧

         for(android.util.Range<Integer> r : ranges){
             if(r.getLower()>= default_fps_min && r.getUpper() <= default_fps_max){
                 sizelist1.add(r);
                 if(r.getLower() == r.getUpper()){
                     sizelist2.add(r);
                     if(r.getLower() == DEFAULT_CAMERA_FPS){ //有默认帧,直接返回
                         return  r ;
                     }
                 }
             }
         }
         //从低往高  0 帧率最低
        final int fps2 = sizelist2.size() ;
         if(fps2 > 0){
             return  sizelist2.get(fps2-1); //取高帧
         }else if(sizelist1.size() >0){
             int avarge = sizelist1.size()/2;
             return  sizelist1.get(avarge);
         }
        都找不到就取个中间值
        int avarge = ranges.length/2;
        return  ranges[avarge];
    }

    //检测 设置 相关属性
    private  boolean setAndCheckLinkProp(CameraCharacteristics cameraCharacteristics){

        int  supportLevel = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
        if(!isHardwareLevelSupported(supportLevel,CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)){ //希望支持
            Toast.makeText(this,"当前相机硬件不支持",Toast.LENGTH_SHORT).show();
            return  false ;
        }
        //判断是否支持闪光灯
        Boolean available = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
        mFlashSupported = available == null ? false : available;

        StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        Size [] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
        //  Size largest = Collections.max(Arrays.asList(outputSizes), new CompareSizeByArea()); //得到最大尺寸
        // 获取预览画面的尺寸 //[0] 最大 从高到底
        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 为最大尺寸, 感觉不合理,所以取平均值
          //  mPreviewSize = getSuitablePreviewSize(outputSizes);
          //  if(mPreviewSize == null) return  false ;
            getSuitablePreviewSize(outputSizes);
           // updateFloatingWindowLayout(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //重新更新下
        }

        //帧率
        //Key[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
        android.util.Range<Integer>[] ranges =  cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
        if(ranges != null  && ranges.length > 0){
            //取中间值 从低往高  0 帧率最低
          //  int average = (int)(ranges.length/2); //平均值  偶数个 取大的
          //  mAverageFPS = ranges[average];  //中间值
            mAverageFPS = getSuitableFpsByDefaultFps(ranges);
        }
        return  true;
    }


    //得到默认选择的相机ID(String 类型)
    private  String  getDefaultCameraID(){
        return  cameraIdFront ;//
    }
    //切换相机
    private void swapDefaultCameraID(){
        if(cameraIdFront.equals("1")){  //前置
            cameraIdFront = "0" ;  //后置相机
        }else{
            cameraIdFront = "1" ; 前置相机
        }
    }
    //初始化相机
    private boolean initCameraInfo(String strCameraID) {
        if(null == strCameraID) return  false ;
        if(strCameraID.isEmpty()) return  false ;
        if(!getScreenDisplayInfo()) return  false ; //得到屏幕信息
        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
        //if(!checkCameraExistByCameraID(strCameraID)) return  false ; //检测相机是否存在 //没必要检测,都指定ID了
        cameraCharacteristicsFront = getCameraCharacteristicsByCameraID(strCameraID); //
        if (cameraCharacteristicsFront == null) return false;
        return setAndCheckLinkProp(cameraCharacteristicsFront);
    }

//        对于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(getDefaultCameraID(), 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(isOpenFloatingWindow()){ //是否开启悬浮窗口
                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(isOpenFloatingWindow()) { //是否开启悬浮窗口
                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  创建预览会话
    // 预览 解析  加入会话申请中
}

调用部分
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第1张图片
函数说明
getSuitablePreviewSize
找到合适的宽高比 尺寸
当前 4种(4:3 3:2 16:10 16:9) ,预览显示框TextureView 会根据选定相机输出比 重置自己的尺寸
找不到预定的比,用默认的图像会拉伸
这里 希望找到 屏幕 的 1/9 到 1/6 相机输出图像 ,如果屏幕像素比320*240还小会出问题,
屏幕不会这么小的吧
宽高比 这里放到10倍了
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第2张图片

基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第3张图片
一个前置相机支持的尺寸
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第4张图片

2 图像格式 调用c++ so
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第5张图片

java 接口代码

package com.unity3d.player;

public class JNIUtils {

    static {
        System.loadLibrary("ImageFormatConversion");
    }

    public native static boolean yuv420888ToNv21(int width, int height, int yRowStride, int uRowStride, int vRowStride, int pixelStride, byte[] yPlane, byte[] uPlane, byte[] vPlane, byte[] nv21);
}

com_unity3d_player_JNIUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_unity3d_player_JNIUtils */

#ifndef _Included_com_unity3d_player_JNIUtils
#define _Included_com_unity3d_player_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_unity3d_player_JNIUtils
 * Method:    yuv420888ToNv21
 * Signature: (IIIIII[B[B[B[B)Z
 */
JNIEXPORT jboolean JNICALL Java_com_unity3d_player_JNIUtils_yuv420888ToNv21
  (JNIEnv *, jclass, jint, jint, jint, jint, jint, jint, jbyteArray, jbyteArray, jbyteArray, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif


cpp

```cpp
#include 
#include 
#include 
#include 
using namespace std ;
#include "com_unity3d_player_JNIUtils.h"

#include 
#include


#define TAG "ImageFormatConversion"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, TAG, FORMAT, ##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, TAG, FORMAT, ##__VA_ARGS__);

void* aligned_malloc(size_t required_bytes, size_t alignment);
void aligned_free(void *p2);

//NEON  指令
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jboolean

JNICALL Java_com_unity3d_player_JNIUtils_yuv420888ToNv21
        (JNIEnv *env, jclass jc, jint w, jint h, jint yRowStride, jint uRowStride, jint vRowStride,
         jint pixelStride, jbyteArray jy, jbyteArray ju, jbyteArray jv, jbyteArray jnv) {
    jbyte *ySrcPtr =  (jbyte*)env->GetByteArrayElements(jy, 0);
    jbyte *uSrcPtr =  (jbyte*)env->GetByteArrayElements(ju, 0);
    jbyte *vSrcPtr =  (jbyte*)env->GetByteArrayElements(jv, 0);
    jsize  oldsize = env->GetArrayLength(jy);
//    jbyte *uSrcPtr = (jbyte *) ju;
//    jbyte *vSrcPtr = (jbyte *) jv;
    const int width = w;
    const int height = h;
    //首先nv21图像数组的长度一定是长×宽×3/2,其中y数据的大小是长×宽(因为每个像素都有一个y值),接着所有像素的u,v值总共有(长×宽×1/2)个
   // unsigned char *buv = (unsigned char *) malloc(width * height * 3 / 2);
     const  int len = width * height * 3 / 2;
  //  jbyteArray array = env->NewByteArray(len);
  // const int len =  env->GetArrayLength(jnv);
    jbyteArray array = jnv ;//env->NewByteArray(len); //jnv ;
    //Y
    int row = 0;
    for(row = 0; row < height; row++){ //307200  //arry  len  460800
        env->SetByteArrayRegion(array, row * width, width, ySrcPtr + row * yRowStride);
    }
    const  int curpos = row * width ;
    constexpr int kStride = 8;
    const size_t nGroups = width / kStride;
  //  LOGE("#666666##")
    //uv
    if (pixelStride == 1) { //YUV_P
  //      LOGE("#77777##")
        int8_t *line = (int8_t*)aligned_malloc(width, 64);
        for(row = 0; row < height / 2; row ++) {
            size_t vrowOff = row * vRowStride;
            size_t urowOff = row * uRowStride;
            for(int g = 0; g < nGroups / 2; g++){
                size_t colOff = g * kStride;
                int8x8_t v0 = vld1_s8(vSrcPtr + vrowOff + colOff);
                int8x8_t v1 = vld1_s8(uSrcPtr + urowOff + colOff);
                int8x8x2_t vz = vzip_s8(v0, v1);
                vst1_s8(line + colOff, vz.val[0]);
                vst1_s8(line + colOff + kStride, vz.val[1]);
            }
            env->SetByteArrayRegion(array, curpos+row * width, width, line);
        }
      //  LOGE("#99999##")
        aligned_free(line);
    } else {  //YUV_SP  交替的
       // LOGE("#88888##")
        int8_t *line = (int8_t*)aligned_malloc(width, 64);
        int8_t *mask = (int8_t*)aligned_malloc(kStride, 64);
        memset(mask, 0, kStride);
        for(int i=0; i < kStride / 2; i++) {
            mask[i * 2] = -1;
        }
        int8x8_t vm = vld1_s8(mask);
        for(int row = 0; row < height / 2; row ++){
            size_t vrowOff = row * vRowStride;
            size_t urowOff = row * uRowStride;
            for(int g = 0; g < nGroups; g++) {
                size_t colOff = g * kStride;
                int8x8_t v0  = vld1_s8(vSrcPtr + vrowOff + colOff);
                int8x8_t v1  = vld1_s8(uSrcPtr + urowOff + colOff);
                int8x8_t a0  = vand_s8(v0, vm);
                int16x4_t b1 = vreinterpret_s16_s8(vand_s8(v1, vm));
                int8x8_t a1  = vreinterpret_s8_s16(vshl_n_s16(b1, 8));
                int8x8_t r = vorr_s8(a0, a1);
                vst1_s8(line + colOff, r);
            }
            env->SetByteArrayRegion(array,curpos+ row * width, width, line);
        }
       // LOGE("#99999##")
        aligned_free(mask);
        aligned_free(line);
    }
  //  LOGE("#101010##")
   // jbyte *tcopy=  (jbyte*)env->GetByteArrayElements(array, 0);
  //  env->SetByteArrayRegion(jnv,0, len, tcopy);
 //   env->DeleteLocalRef(array);  //释放
  //  LOGE("#AAAAAAAA##")
    return true;
}

#ifdef __cplusplus
}
#endif

void* aligned_malloc(size_t required_bytes, size_t alignment)
{

    int offset = alignment - 1 + sizeof(void*);

    void* p1 = (void*)malloc(required_bytes + offset);

    if (p1 == NULL)

        return NULL;

    void** p2 = (void**)( ( (size_t)p1 + offset ) & ~(alignment - 1) );

    p2[-1] = p1;

    return p2;

}

void aligned_free(void *p2)
{

    void* p1 = ((void**)p2)[-1];

    free(p1);

}

buid.gradle
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第6张图片

// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN

apply plugin: 'com.android.library'


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    //by eh  add
    implementation 'androidx.core:core:1.2.0'
    1.1.0-beta01
    //下面2句没加一样跑,不知道在上面那里是否已经包含了
    def camera2_version = "1.2.0"
    implementation "androidx.camera:camera-core:${camera2_version}"
    implementation "androidx.camera:camera-camera2:${camera2_version}"
}

android {
    compileSdkVersion 33
    buildToolsVersion '33.0.0'

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        minSdkVersion 23
        targetSdkVersion 30
        ndk {
            abiFilters 'arm64-v8a'
        }
        versionCode 1
        versionName '1.0'
        consumerProguardFiles 'proguard-unity.txt'

        //cmake build//
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

        ndk {
            abiFilters 'arm64-v8a'
        }
        
    }

    lintOptions {
        abortOnError false
    }

    aaptOptions {
        noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ')
        ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
    }

    packagingOptions {
        doNotStrip '*/arm64-v8a/*.so'
    }
    //cmake build//
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    /
}

def getSdkDir() {
    Properties local = new Properties()
    local.load(new FileInputStream("${rootDir}/local.properties"))
    return local.getProperty('sdk.dir')
}

def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) {
    exec {
        commandLine(workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/netcoreapp3.1/il2cpp.exe",
            "--compile-cpp",
            "--libil2cpp-static",
            "--platform=Android",
            "--architecture=" + architecture,
            "--configuration=" + configuration,
            "--outputpath=" + workingDir + targetDirectory + abi + "/libil2cpp.so",
            "--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache",
            "--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/external/bdwgc/include",
            "--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/include",
            "--tool-chain-path=" + android.ndkDirectory,
            "--map-file-parser=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/MapFileParser/MapFileParser.exe",
            "--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput",
            "--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi,
            "--dotnetprofile=unityaot")
        environment "ANDROID_SDK_ROOT", getSdkDir()
    }
    delete workingDir + targetDirectory + abi + "/libil2cpp.sym.so"
    ant.move(file: workingDir + targetDirectory + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
}

android {
    task BuildIl2CppTask {
        doLast {
              BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARM64', 'arm64-v8a', 'Release');
        }
    }
    afterEvaluate {
        if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders'))
            project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
        if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders'))
            project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
    }
    sourceSets {
        main {
            jni.srcDirs = ["src/main/Il2CppOutputProject"]
        }
    }
}

CMakelist.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)
add_library(ImageFormatConversion
        SHARED
        src/main/jni/com_unity3d_player_JNIUtils.cpp
        )

find_library(log-lib log)
find_library(dl-lib dl)
find_library(android-lib android)

target_link_libraries(ImageFormatConversion ${log-lib} ${dl-lib}  ${android-lib})

3 测试代码及结果
测试代码

 static  int  tcount =  0;
    static long  tesms = 0;
   // .............................................  
  long starttime = System.currentTimeMillis() ;
                    {/*  c++ so 测试
                        int w = image.getWidth();
                        int h = image.getHeight();
                        Image.Plane y = image.getPlanes()[0];
                        Image.Plane u = image.getPlanes()[1];
                        Image.Plane v = image.getPlanes()[2];

                        byte[] buf = new byte[w * h * 3 / 2];
                        int pixelStride = v.getPixelStride(); // image.getPlanes()[2].getPixelStride()
                        int ylen = y.getBuffer().remaining(); //可用长度
                        byte[] by = new byte[ylen];
                        y.getBuffer().get(by);
                        int ulen = u.getBuffer().remaining();
                        byte[] bu = new byte[ulen];
                        u.getBuffer().get(bu);
                        int vlen = v.getBuffer().remaining();
                        byte[] bv = new byte[vlen];
                        v.getBuffer().get(bv);

                        if (!JNIUtils.yuv420888ToNv21(w, h, y.getRowStride(), u.getRowStride(), v.getRowStride(), pixelStride, by, bu, bv, buf)) {
                            Log.e(TAG, "FAIL");
                        }
                        */
                        {  //java 测试
                            byte [] date  = YUV_420_888toNV21(image);
                        }

                    }
                    long end = System.currentTimeMillis() ;
                    tesms += end-starttime ;
                    tcount++ ;
                    if(tcount < 100){

                    }else{
                        Log.e(TAG,String.valueOf(tcount)+":"+String.valueOf(tesms));
                        tcount = 0;
                        tesms = 0;
                    }

结果 出乎意料,不知道是不是so为DEBUG版本 c++ 版这么差
基于camera2 untiy悬浮窗摄像头预览分析(优化二)_第7张图片
4 demo工程
下载地址
觉得有用 ,点个赞,加个关注

你可能感兴趣的:(android,unity,android,android,studio,unity)