简析android5.0 Camera使用

简析android5.0相机使用
android5.0提供了新的相机处理的API,我们想上一张图:我们先对整逻辑进行一下梳理,在来一步一步的对源码进行分析,不至于看源码的时候有一种懵逼的感觉:



Android5.0新提供的阿比中关键的几个类在上图中进行了描述:
CameraManager:管理手机上的所用摄像头设备,它的主要作用主要是获取摄像头列表和打开指定的摄像头
CameraDevice:具体的每一个摄像头,它有一系列参数(预览尺寸,拍照尺寸等)
CameraCaptureSession:相机捕获会话,用于处理拍照和预览工作
CaptureRequest:捕获请求,定义输出缓冲区以及显示界面(TextureView或者SurfaceView)


问题来了:
1.如何获取摄像头管理器,摄像头管理器是怎样管理到我们的应用程序中的
2.如何获取摄像头个数,选择要操作的摄像头
3.如何关联到我们选择的摄像头上
4.如何获取摄像同的预览和预览的每一帧的原始数据

1.如何获取摄像头管理器,摄像头管理器是怎样管理到我们的应用程序中的
如果对机器人系统底层源码有了解,机器人系统对硬件,软件,都是使用模块化的开发方式,WindowManagerService / ActivityManagerService / CameraManager等来对应不同的服务,这里我们要对相机进行处理,就对应要从CameraManager入手,这时系统提供给我们在应用程序开发是管理相机的入口接下来我们就从CameraManager下手:(android6.0源码)在活动启动时创建ActivityThread中通过:SystemServiceRegistry来统一管理系统启动的服务,使用静态代码块来进行初始化
/ **
 *管理{@link Context#getSystemService}可以返回的所有系统服务。
 *由{@link ContextImpl}使用。
 * /
最终类SystemServiceRegistry {
    private final static String TAG =“SystemServiceRegistry”;

    //服务注册表信息。
    //静态初始化完成后,该信息永远不会更改。
    private static final HashMap <Class <?>,String> SYSTEM_SERVICE_NAMES =
            新的HashMap <Class <?>,String>();
    private static final HashMap <String,ServiceFetcher <?>> SYSTEM_SERVICE_FETCHERS =
            新的HashMap <String,ServiceFetcher <?>>();
    private static int sServiceCacheSize;

    //不可实例化
    private SystemServiceRegistry(){}

    静态的 {
        registerService(Context.ACCESSIBILITY_SERVICE,AccessibilityManager.class,
                新的CachedServiceFetcher <AccessibilityManager>(){
            @覆盖
            public AccessibilityManager createService(ContextImpl ctx){
                返回AccessibilityManager.getInstance(ctx);
            }}); registerService(Context.CAMERA_SERVICE,CameraManager.class,
                新的CachedServiceFetcher <CameraManager>(){
            @覆盖
            public CameraManager createService(ContextImpl ctx){
                返回新的CameraManager(ctx);
            }});
     }
我们可以通过上下文中的getSystemService来获取相机管理器:
CameraManager manager =(CameraManager)activity.getSystemService(Context.CAMERA_SERVICE);

现在已经拿到了相机管理器,相机管理器管理手机上的前后摄像头的硬件的宏观信息:(有几个,是前置还是后置摄像头,是否开启)

2.如何获取摄像头个数,选择要操作的摄像头
手机上的每一个摄像头都是使用摄像机来进行标识的,现在需要来操作手机上摄像头对应的相机ID,下面几个步骤我们可以选择我们需要的摄像头的,设置摄像头的输出参数
   
    private void openCamera(int width,int height){
        if(ContextCompat.checkSelfPermission(getActivity(),Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED){
            requestCameraPermission();
            返回;
        }

        //设置相机的输出参数
        setUpCameraOutputs(width,height);
        configureTransform(width,height);
        活动活动= getActivity();
        CameraManager manager =(CameraManager)activity.getSystemService(Context.CAMERA_SERVICE);
        尝试{
            if(!mCameraOpenCloseLock.tryAcquire(2500,TimeUnit.MILLISECONDS)){
                抛出新的RuntimeException(“超时等待锁相机打开”);
            }
            manager.openCamera(mCameraId,mStateCallback,mBackgroundHandler);
        } catch(CameraAccessException e){
            e.printStackTrace();
        } catch(InterruptedException e){
            抛出新的RuntimeException(“尝试锁定相机打开时中断”,e);
        }
    }
现在看到setUpCameraOutputs(宽度,高度),这个函数对对摄像头进行遍历:看到函数很明显,这个是对相机的输出参数进行设置
1.遍历手机上的摄像头,查找我们需要打开的前置还是后置
2.设置获取每一帧时的数据回调
3.设置当手机传感器的坐标改变时的参数(实际上就是横竖屏时的处理)
   private void setUpCameraOutputs(int width,int height){
        活动活动= getActivity();
        CameraManager manager =(CameraManager)activity.getSystemService(Context.CAMERA_SERVICE);
        尝试{
	//遍历手机上的摄像头,查找我们需要打开的前置还是后置
            for(String cameraId:manager.getCameraIdList()){
                相机特点
                        = manager.getCameraCharacteristics(cameraId);

                //我们在此示例中不使用前置摄像头。
                整数face = characteristics.get(CameraCharacteristics.LENS_FACING);
                if(facing!= null && facing == CameraCharacteristics.LENS_FACING_FRONT){
                    继续;
                }

                StreamConfigurationMap map = characteristics.get(
                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if(map == null){
                    继续;
                }
		///////设置获取每一帧时的数据回调
                //对于静态图像捕获,我们使用最大的可用尺寸。
                大小最大= Collections.max(
                        Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                        CompareSizesByArea());
                mImageReader = ImageReader.newInstance(maximum.getWidth(),maximum.getHeight(),
                        ImageFormat.JPEG,/ * maxImages * / 2);
                mImageReader.setOnImageAvailableListener(
                        mOnImageAvailableListener,mBackgroundHandler);
		// 3。设置当手机传感器的坐标改变时的参数(实际上就是横竖屏时的处理)
                //了解我们是否需要交换维度以获取相对于传感器的预览大小
                //坐标。
                int displayRotation = activity.getWindowManager()。getDefaultDisplay()。getRotation();
                // noinspection ConstantConditions
                mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
                boolean swappedDimensions = false;
                switch(displayRotation){
                    case Surface.ROTATION_0:
                    case Surface.ROTATION_180:
                        if(mSensorOrientation == 90 || mSensorOrientation == 270){
                            swappedDimensions = true;
                        }
                        打破;
                    case Surface.ROTATION_90:
                    case Surface.ROTATION_270:
                        if(mSensorOrientation == 0 || mSensorOrientation == 180){
                            swappedDimensions = true;
                        }
                        打破;
                    默认:
                        Log.e(TAG,“显示旋转无效:”+ displayRotation);
                }

                点displaySize = new Point();
                。activity.getWindowManager()getDefaultDisplay()的getSize(显示尺寸)。
                int rotatePreviewWidth = width;
                int rotatePreviewHeight = height;
                int maxPreviewWidth = displaySize.x;
                int maxPreviewHeight = displaySize.y;

                if(swappedDimensions){
                    rotatePreviewWidth = height;
                    rotatePreviewHeight = width;
                    maxPreviewWidth = displaySize.y;
                    maxPreviewHeight = displaySize.x;
                }

                if(maxPreviewWidth> MAX_PREVIEW_WIDTH){
                    maxPreviewWidth = MAX_PREVIEW_WIDTH;
                }

                if(maxPreviewHeight> MAX_PREVIEW_HEIGHT){
                    maxPreviewHeight = MAX_PREVIEW_HEIGHT;
                }

                //危险,WR!尝试使用太大的预览大小可能会超出相机
                //总线“带宽限制,导致了华丽的预览,但存储
                //垃圾回收数据。
                mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                        rotatePreviewWidth,rotatePreviewHeight,maxPreviewWidth,
                        maxPreviewHeight,最大);

                //我们将TextureView的宽高比与我们选择的预览大小相匹配。
                int orientation = getResources()。getConfiguration()。orientation;
                if(orientation == Configuration.ORIENTATION_LANDSCAPE){
                    mTextureView.setAspectRatio(
                            mPreviewSize.getWidth(),mPreviewSize.getHeight());
                } else {
                    mTextureView.setAspectRatio(
                            mPreviewSize.getHeight(),mPreviewSize.getWidth());
                }

                //检查是否支持闪存。
                Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
                mFlashSupported =可用== null?假:可用;

                mCameraId = cameraId;
                返回;
            }
        } catch(CameraAccessException e){
            e.printStackTrace();
        } catch(NullPointerException e){
            //目前在使用Camera2API但不支持时会抛出NPE
            //设备此代码运行。
            ErrorDialog.newInstance(的getString(R.string.camera_error))
                    .show(getChildFragmentManager(),FRAGMENT_DIALOG);
        }
    }

使用矩阵来对TextSufaceView进行变换,下面不对矩阵变换来进行详解,后兴趣可以自己查找资料
    private void configureTransform(int viewWidth,int viewHeight){
        活动活动= getActivity();
        if(null == mTextureView || null == mPreviewSize || null == activity){
            返回;
        }
        int rotation = activity.getWindowManager()。getDefaultDisplay()。getRotation();
        矩阵矩阵= 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);
            浮标= Math.max(
                    (float)viewHeight / mPreviewSize.getHeight(),
                    (float)viewWidth / mPreviewSize.getWidth());
            matrix.postScale(scale,scale,centerX,centerY);
            matrix.postRotate(90 *(rotation-2),centerX,centerY);
        } else if(Surface.ROTATION_180 == rotation){
            matrix.postRotate(180,centerX,centerY);
        }
        mTextureView.setTransform(矩阵);
    }
我们在开启摄像同时会传入三个参数:
如图1所示,打开的相机
2.相机状态回调
3.处理线程
    / **
     *打开与给定ID相机的连接。
     * @param cameraId
     *要打开的相机设备的唯一标识符
     * @param回调
     *相机打开后调用的回调
     * @param处理程序
     *应该调用回调的处理程序,或
     * {@code null}使用当前线程的{@link android.os.Looper looper}。
     *
    
     * /
    @RequiresPermission(android.Manifest.permission.CAMERA)
    public void openCamera(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback,@Nullable Handler handler)
            抛出CameraAccessException {

        openCameraForUid(cameraId,回调,处理程序,USE_CALLING_UID);
    }
3.如何关联到我们选择的摄像头上
    私人最后CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback(){

        @覆盖
        public void onOpened(@NonNull CameraDevice cameraDevice){
            //打开相机时调用此方法。我们在这里开始相机预览。
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

        @覆盖
        public void onDisconnected(@NonNull CameraDevice cameraDevice){
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @覆盖
        public void onError(@NonNull CameraDevice cameraDevice,int error){
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            活动活动= getActivity();
            if(null!= activity){
                activity.finish();
            }
        }

    };
当摄像头打开成功之后,需要做的就是要建立以和打开的摄像机通信的会话(看到这个单词就大概知道是什么意思了),也就是所我们通过捕捉
1.通过我们打开的摄像头设备来创建一个摄像头请求
2.通过CameraDevice来创建一个CameraCaptureSession关联到摄像头
    / **
     *为相机预览创建新的{@link CameraCaptureSession}。
     * /
    private void createCameraPreviewSession(){
        尝试{
            SurfaceTexture纹理= mTextureView.getSurfaceTexture();
            assert texture!= null;

            //我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
            texture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());

            //这是我们需要开始预览的输出Surface。
            表面=新表面(纹理);

            //我们设置了一个具有输出Surface的CaptureRequest.Builder。
            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(表面);

            //这里,我们创建一个CameraCaptureSession来进行相机预览。
            mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()),
                    新的CameraCaptureSession.StateCallback(){

                        @覆盖
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession){
                            //相机已经关闭
                            if(null == mCameraDevice){
                                返回;
                            }

                            //会话准备就绪后,我们开始显示预览。
                            mCaptureSession = cameraCaptureSession;
                            尝试{
                                //自动对焦应连续进行相机预览。
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                //必要时自动启用Flash。
                                setAutoFlash(mPreviewRequestBuilder);

                                //最后,我们开始显示相机预览。
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback,mBackgroundHandler);
                            } catch(CameraAccessException e){
                                e.printStackTrace();
                            }
                        }

                        @覆盖
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession){
                            showToast( “失败”);
                        }
                    }, 空值
            );
        } catch(CameraAccessException e){
            e.printStackTrace();
        }
    }
我们通过CameraSession来对把CaptureRequest设置的参数传递到相机中:通过CameraDevice来创建一个会话绘画,并且将生成的会话返回,从而,我们这时候就可以
能够真正的与相机进行通信:
1.把要显示的视图传递到相机中便于后续的缓冲区关联,输出显示的界面
2.CameraCaptureSession的回调函数CameraCaptureSession.CaptureCallback
3.一个处理程序对象
mPreviewRequest = mPreviewRequestBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback,mBackgroundHandler);
我们通过CameraCaptureSession.CaptureCallback返回CameraResult每一张照片的图像数据内容:
之前我们还添加了一个ImageReader的,通过CaptureRequest构建,然后通过CameraCaptureSession出入数据,这个时候当点击拍照会把每一张图片的数据放回:
传入了两个参数:
OnImageAvailableListener侦听器,处理程序处理程序
 mImageReader = ImageReader.newInstance(maximum.getWidth(),maximum.getHeight(),
                        ImageFormat.JPEG,/ * maxImages * / 2);
                mImageReader.setOnImageAvailableListener(
                        mOnImageAvailableListener,mBackgroundHandler);
当有图片返回是就在OnImageAvailableListener中处理数据:
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener(){

        @覆盖
        public void onImageAvailable(ImageReader reader){
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(),mFile));
        }

    };
我们在线程中处理返回的图片字节数据:
ByteBuffer buffer = mImage.getPlanes()[0] .getBuffer();
            byte [] bytes = new byte [buffer.remaining()];
            buffer.get(字节);
            FileOutputStream output = null;
            尝试{
                output = new FileOutputStream(mFile);
                output.write(字节);
            } catch(IOException e){
                e.printStackTrace();
            } finally {
                mImage.close();
                if(null!= output){
                    尝试{
                        output.close();
                    } catch(IOException e){
                        e.printStackTrace();
                    }
                }
            }
通过上面的处理,我们已经分析完整个的调用流程

接下来,我们总结一下相机的基本使用:
处理相机拍照时,我们处理的数据有两个步骤
1.预览处理
2.捕获相片数据
如果不需要预览的话那么就可以直接使用捕捉就能够来处理数据

1.通过getSystemService获取相机管理器
通过CameraManager的manager.getCameraIdList()来获取摄像头
3.通过createCaptureRequest来构建一个相机的请求参数(TextSurfaceView,ImageReader)
4.通过CameraCaptureSession 把配置的参数关联到相机中
调用捕获(mPreviewRequestBuilder.build(),mCaptureCallback,mBackgroundHandler)拍照
6.在OnImageAvailableListener中处理传出来的每一帧的数据
7.保存字节码数据到文件中

代码仓库: https://github.com/cangck/TestCamera


猜你喜欢

转载自blog.csdn.net/cangck_x/article/details/76665300