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

前言

本章基于前章基础上功能补全和代码优化

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  创建预览会话
    // 预览 解析  加入会话申请中
}


2 说明
基于camera2 untiy悬浮窗摄像头预览分析(优化一)_第1张图片

开启悬浮窗口标志

 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 ;
    }

基于camera2 untiy悬浮窗摄像头预览分析(优化一)_第2张图片

切换前后置相机

 //切换相机 //直接点击事件下调用即可
    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(); //切换相机
}
基于camera2 untiy悬浮窗摄像头预览分析(优化一)_第3张图片

3 后续需要优化的部分
1 没找到匹配的窗口大小默认值,用中间值代替,不知道中间值宽高比是否跟默认值等效,
2 YUV_420_888toNV21 JAVA 效率不高,后面用c++ 重写个so 调用

4 觉得有用 ,点个赞,加个关注

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