Android Camera基本用法一

1 Camera 简介

讲解编解码之前先对Camera进行简单的介绍,本篇介绍完之后只能保证小白会用Camera预览画面,其他的Camera知识会后续讲解。
在这里插入图片描述
考虑兼容性依然介绍Camera,目录为android.hardware.Camera,可以看到从api21开始这个类已经被标记为过时,谷歌大大推荐使用android.hardware.Camera2,但是Camera2要从api21才支持,但现在大部分开发还必须以4.+为基础进行开发,所以也只能不听google的坚持使用Camera了。

借助Camera可以利用设备的相机来预览画面,拍照和拍视频。要使用Camera需要在Manifest文件中添加Manifest.permission.CAMERA 权限同时如果要进行自动对焦,还需要特性声明。
完整地权限和特性声明设置为:

 <uses-permission android:name="android.permission.CAMERA" />
 <uses-feature android:name="android.hardware.camera" />
 <uses-feature android:name="android.hardware.camera.autofocus" />
//存储权限也需要
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />

注意:如果你只是想简单的实现拍照和拍照视频功能,可以利用Intent打开系统提供的功能。MediaStore.ACTION_IMAGE_CAPTURE 拍摄照片;MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频;

利用Camera想要拍照,需要如下使用步骤:

  • 1 利用open(int)获取Camera实例
  • 2 利用getParameters()获取默认设置,如果需要利用setParameters(Camera.Parameters)进行参数设置
  • 3 利用setDisplayOrientation(int)函数设置正确的预览方向
  • 4 想要预览,需要配合SurfaceView,利用setPreviewDisplay(SurfaceHolder)设置SurfaceView的SurfaceHolder用于预览。
  • 5 调用startPreview()开始预览,拍照之前必须已经开始预览
  • 6 takePicture 拍摄照片
  • 7 调用takePickture后预览会停止,想要继续预览需要调用startPreview()函数
  • 8 调用stopPreview()停止预览
  • 9 调用release()释放资源,为了节省资源在Activity.onPause是调用停止预览,在onResume是开始预览。

2 打开相机

检查是否有相机
/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}

获取相机数目:
Camera.getNumberOfCameras()

open或open(int)打开相机
open开启默认相机(后置相机),open(int)开启特定相机,打开相机是可能失败的,所以一定要检查相机是否打开成功,判断Camera是否为null, mCamera = Camera.open(cameraID);
后置相机和前置相机id常量:CameraInfo.CAMERA_FACING_BACK, CameraInfo.CAMERA_FACING_FRONT
打开特定相机Camera.open(cameraid)。

Camera.getCameraInfo() 可以获取CameraInfo,可以知道相机是位于前面还是后面。

public static class CameraInfo {
        public static final int CAMERA_FACING_BACK = 0;

        public static final int CAMERA_FACING_FRONT = 1;

        /**
         * 这个值就是标明相机是前置还是后置
         * CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
         */
        public int facing;

        public int orientation;
    };

3 相机预览方向设置

相机的方向(0,90,180,270)有四种,预览需要设置正确的方向和尺寸,预览的图片才不会变形,可以利用Camera.setDisplayOrientaion(int)设置相机的预览方向。可设置的参数有0,90,180,270,默认为0,是指手机的左侧为摄像头顶部画面,所以相机默认为横屏,如果要竖屏预览,就需要设置90度。
如果想让相机跟随设备方向变化,改变预览的方向,需要结合相机已经旋转的角度和屏幕旋转的角度以及相机的前后(前置相机和后置相机预览界面是不同的,前置有镜面效果),最好固定Activity的方向。
特别注意:
设置预览角度,setDisplayOrientation本身只能改变预览的角度previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的,所以拍摄最后得到的照片需要自行处理(旋转)。
在布局发生改变时要重新设置相机预览方向。

一般设置相机方向的通用方法:

public static int calculateCameraPreviewOrientation(Activity activity) {
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(mCameraID, info);
    int rotation = activity.getWindowManager().getDefaultDisplay()
            .getRotation();
    int degrees = 0;
    switch (rotation) {
        case Surface.ROTATION_0:
            degrees = 0;
            break;
        case Surface.ROTATION_90:
            degrees = 90;
            break;
        case Surface.ROTATION_180:
            degrees = 180;
            break;
        case Surface.ROTATION_270:
            degrees = 270;
            break;
    }

    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        result = (info.orientation + degrees) % 360;
        result = (360 - result) % 360;
    } else {
        result = (info.orientation - degrees + 360) % 360;
    }
    return result;
}

最终算出来的值result是0,90,180,270中的一个,要获取info.orientation的目的是不知道相机默认角度是多少。

4 设置预览大小

相机的宽度和高度跟屏幕坐标不一样,相机的宽高和手机屏幕的宽高是反过来的。例如如果设置展示预览图的SurfaceView的宽高比3:4,选取Camera预览图的尺寸时应该是4:3(因为预览默认对应横向,如果要竖向显示时,就是反过来的。)。

设备上的相机支持的预览大小是确定的,当然也不是只有一种,会有很多种,我们只能从它支持的大小的列表中选取一个最接近我们需要比例的宽高。

为了保证相机预览画面不变形,预览界面大小的设置必须和选取的相机支持的尺寸的宽高的比例相同。
举个例子,获取手机上支持的所有预览尺寸:

Camera.Parameters parameters = camera.getParameters();
parameters.getSupportedPreviewSizes()

结果:
==支持的预览尺寸=宽= 176 144
==支持的预览尺寸=宽= 320 240
==支持的预览尺寸=宽= 352 288
==支持的预览尺寸=宽= 640 480
==支持的预览尺寸=宽= 1280 720
==支持的预览尺寸=宽= 1280 960
==支持的预览尺寸=宽= 176 144
==支持的预览尺寸=宽= 320 240
==支持的预览尺寸=宽= 352 288
==支持的预览尺寸=宽= 640 480
==支持的预览尺寸=宽= 1280 720
==支持的预览尺寸=宽= 1280 960

再次强调:可以看到getSupportedPreviewSizes获取了相机支持的所有预览尺寸,注意一点,屏幕的宽高是按照竖屏获取的,而getSupportedPreviewSizes获得支持的尺寸是按照横屏来说的,也就是说我们上面获取的宽高实际上是反过来的,高是宽,宽是高,不知道大家理解没。

例如选取上面获取到的640:480,其实对应到竖向屏幕上是480:640。,如果我们选取640x480的尺寸,那么预览Camera的SurfaceView的宽高比也必须为3:4,这样预览的画面才不会变形,否则可能导致变形。
expectWidth和expectHeight 分别对应期望的宽和高,是对应相机的宽高,所以如果希望在view中看到的是640*480的预览,则期望宽高应该写入3:4的宽高,也就是把希望的对应view的宽高反过来。

查找最合适尺寸的规则:
找出最合适的尺寸,规则如下:
1.将尺寸按比例分组,找出比例最接近屏幕比例的尺寸组
2.在比例最接近的尺寸组中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
3.如果没有找到,则忽略2中第二个条件再找一遍,应该是最合适的尺寸了

/**
 * 找出最合适的尺寸,规则如下:
 * 1.将尺寸按比例分组,找出比例最接近屏幕比例的尺寸组
 * 2.在比例最接近的尺寸组中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
 * 3.如果没有找到,则忽略2中第二个条件再找一遍,应该是最合适的尺寸了
 */
private static Camera.Size findProperSize(Point surfaceSize, List<Camera.Size> sizeList) {
    if (surfaceSize.x <= 0 || surfaceSize.y <= 0 || sizeList == null) {
        return null;
    }

    int surfaceWidth = surfaceSize.x;
    int surfaceHeight = surfaceSize.y;

    List<List<Camera.Size>> ratioListList = new ArrayList<>();
    for (Camera.Size size : sizeList) {
        addRatioList(ratioListList, size);
    }

    final float surfaceRatio = (float) surfaceWidth / surfaceHeight;
    List<Camera.Size> bestRatioList = null;
    float ratioDiff = Float.MAX_VALUE;
    for (List<Camera.Size> ratioList : ratioListList) {
        float ratio = (float) ratioList.get(0).width / ratioList.get(0).height;
        float newRatioDiff = Math.abs(ratio - surfaceRatio);
        if (newRatioDiff < ratioDiff) {
            bestRatioList = ratioList;
            ratioDiff = newRatioDiff;
        }
    }

    Camera.Size bestSize = null;
    int diff = Integer.MAX_VALUE;
    assert bestRatioList != null;
    for (Camera.Size size : bestRatioList) {
        int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
        if (size.height >= surfaceHeight && newDiff < diff) {
            bestSize = size;
            diff = newDiff;
        }
    }

    if (bestSize != null) {
        return bestSize;
    }

    diff = Integer.MAX_VALUE;
    for (Camera.Size size : bestRatioList) {
        int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
        if (newDiff < diff) {
            bestSize = size;
            diff = newDiff;
        }
    }

    return bestSize;
}

private static void addRatioList(List<List<Camera.Size>> ratioListList, Camera.Size size) {
    float ratio = (float) size.width / size.height;
    for (List<Camera.Size> ratioList : ratioListList) {
        float mine = (float) ratioList.get(0).width / ratioList.get(0).height;
        if (ratio == mine) {
            ratioList.add(size);
            return;
        }
    }

    List<Camera.Size> ratioList = new ArrayList<>();
    ratioList.add(size);
    ratioListList.add(ratioList);
}

设置SurfaceView的宽高为3:4的图像。
在这里插入图片描述
设置SurfaceView的宽高为4:3的图像,明显变形了。
在这里插入图片描述

5 设置拍摄图片大小

调用camera的takePicture方法后,获得拍照的图像数据,如何设置保存图片的大小,类似设置预览大小,利用parameters.getSupportedPictureSizes()可以获取支持的保存图片的大小,图片尺寸同样只能从支持的列表中选取一个设置。

picturesize和previewsize的宽高比也要保证一致,否则获取的图片会将preview时的图像裁剪成picturesize的比例。
previewsize的分辨率,只会影响预览时的分辨率,不会影响获取图片的分辨率,所以preview只是确定了图像的取景最大范围(所谓的取景范围就是展示多大的画面),最终图片的分辨率是由picturesize来决定。

6 Camera设置帧率

 /**
     * 选择合适的FPS
     * @param parameters
     * @param expectedThoudandFps 期望的FPS
     * @return
     */
    public static int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) {
        List<int[]> supportedFps = parameters.getSupportedPreviewFpsRange();
        for (int[] entry : supportedFps) {
            if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) {
                parameters.setPreviewFpsRange(entry[0], entry[1]);
                return entry[0];
            }
        }
        int[] temp = new int[2];
        int guess;
        parameters.getPreviewFpsRange(temp);
        if (temp[0] == temp[1]) {
            guess = temp[0];
        } else {
            guess = temp[1] / 2;
        }
        return guess;
    }

getSupportedPreviewFpsRange函数可以获取设备支持的帧率,fps不是越高越好,FPS不宜过高,一般30fps足够了。

7 如何读取Camera的NV21数据和YUV数据

给camera对象设置一个 Camera.PreviewCallback,在这个回调中实现一个方法onPreviewFrame(byte[] data, Camera camera),就可以去Camera预览图片时的数据。

当然如果设置了camera.setPreviewCallback(callback),onPreviewFrame这个方法会被一直调用,可以在摄像头对焦成功后设置camera.setOneShotPreviewCallback(previewCallback),这样设置onPreviewFrame这个方法就会被调用一次,处理data数据,bitmap来做相应的处理就行了。这两个方法都是系统自动配置缓冲区。
setPreviewFormat 函数可以设置预览是onPreviewFrame返回数据的格式。
最常见的获取的数据格式为NV21,如果不设置默认返回数据也是NV21编码的数据。

Camera.Parameters parameters = mCamera.getParameters();
parameters.setRecordingHint(true);
{
    //设置获取数据的格式
    parameters.setPreviewFormat(ImageFormat.NV21);
    //parameters.setPreviewFormat(ImageFormat.YV12);

    //通过setPreviewCallback方法监听预览的回调:
    byte[] imageByte;
    Bitmap bitmap;
    mCamera.setPreviewCallback(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] bytes, Camera camera) {
            //这里面的Bytes的数据就是NV21格式的数据,或者YV12的数据
            Camera.Size previewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;
        YuvImage yuvimage = new YuvImage(
                data,
                ImageFormat.NV21,
                previewSize.width,
                previewSize.height,
                null);
        baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);// 80--JPG图片的质量[0-100],100最高
        imageByte = baos.toByteArray();
        //将imageByte转换成bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        bitmap = BitmapFactory.decodeByteArray(imageByte, 0, imageByte.length, options);
        }
    });

}
mCamera.setParameters(parameters);

注意,onPreviewFrame()方法跟Camera.open()是运行于同一个线程,所以为了防止onPreviewFrame()会阻塞UI线程,将Camera.open()放置在子线程中运行。

为什么要用到这个函数,因为如果调用takePicture别管怎么设置界面都会卡顿,如果通过onPreviewFrame的回调处理函数,不会导致界面卡顿。

setPreviewCallbackWithBuffer 类似setPreiewCallback,一般配合setPreviewCallback使用
setPreviewCallbackWithBuffer (Camera.PreviewCallback cb) 要求指定一个字节数组作为缓冲区,用于预览帧数据,这样能够更好的管理预览帧数据时使用的内存。

setPreviewCallbackWithBuffer需要在startPreview()之前调用,因为setPreviewCallbackWithBuffer使用时需要指定一个字节数组作为缓冲区,用于预览帧数据,所以我们需要在setPreviewCallbackWithBuffer之前调用addCallbackBuffer,这样onPreviewFrame的data才有值。然后需要在onPreviewFrame中调用,如果在onPreviewFrame中不调用,那么预览帧数据就不会回调给onPreviewFrame。

代码示例:

//通过setPreviewCallback方法监听预览的回调:
                mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(byte[] bytes, Camera camera) {
                        //这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据
                    }
                });

                mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(byte[] data, Camera camera) {

                    }
                });
            }

            mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    
                }
            });

8 其他

camera的切换(前后摄像头)

要先释放前一个Camera,然后打开新相机,打开预览。

takePicture获取图片

调用takePicture后预览会停止,需用重新调用startPreview才能再次开始预览。预览开始后,就可以通过Camera.takePicture()方法拍摄一张照片,返回的照片数据通过Callback接口获取。
takePicture()接口可以获取三个类型的照片:
第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效;
第二个,PictureCallback接口,返回未经压缩的RAW类型照片;
第三个,PictureCallback接口,返回经过压缩的JPEG类型照片;

是否支持自动对焦

List modes = Parameters.getSupportedFocusModes();
modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
getSupportedFocusModes函数获取所有的焦点模式,FOCUS_MODE_AUTO标识自动对焦,对焦方式还有FOCUS_MODE_CONTINUOUS_VIDEO使用视频录制,FOCUS_MODE_CONTINUOUS_PICTURE 用于拍照。

9 简单实例:

public class Main23Activity extends AppCompatActivity implements SurfaceHolder.Callback ,SensorEventListener{
    private static int mOrientation = 0;
    private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;
    private CustomSurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Camera mCamera;
    private boolean havePermission = false;
    private Button btnFocus;
    private Button btnTakePic;
    private Button btnRestar;
    private Button btnChange;
    private int useWidth;
    private int useHeight;
    private SensorManager mSensorManager;
    private Sensor mSensor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main23);

        btnFocus = findViewById(R.id.focus);
        btnTakePic = findViewById(R.id.takepic);
        btnRestar = findViewById(R.id.restar);
        btnChange = findViewById(R.id.change);

        btnChange.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switchCamera();
            }
        });

        btnRestar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mCamera != null){
                    mCamera.startPreview();
                }
            }
        });
        btnFocus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mCamera != null && mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){
                    mCamera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            if(success){
                                Toast.makeText(Main23Activity.this,"对焦成功",Toast.LENGTH_SHORT).show();
                            }else{

                            }
                        }
                    });
                }
            }
        });

        btnTakePic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mCamera!= null){
                    mCamera.takePicture(null, null, new Camera.PictureCallback() {
                        @Override
                        public void onPictureTaken(byte[] data, Camera camera) {
                            // 获取Jpeg图片,并保存在sd卡上
                            String path = Environment.getExternalStorageDirectory()
                                    .getPath()  +"/focus/";
                            File pathDir = new File(path);
                            if (!pathDir.exists()){
                                pathDir.mkdir();
                            }
                            File pictureFile = new File(path+ "focusdemo.jpg");
                            if (pictureFile.exists()){
                                pictureFile.delete();
                            }
                            try {
                                    FileOutputStream fos = new FileOutputStream(pictureFile);
                                    fos.write(data);
                                    fos.close();
                                } catch (Exception e) {

                                }
                        }
                    });
                }
            }
        });

        mSensorManager = (SensorManager) Main23Activity.this.getSystemService(Activity.SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 加速度

        // Android 6.0相机动态权限检查,省略了
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {
            havePermission = true;
            init();
        } else {
            havePermission = false;
            ActivityCompat.requestPermissions(this,
                    new String[]{
                            Manifest.permission.CAMERA,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE
                    }, 100);

        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        mSensorManager.unregisterListener(this,mSensor);
    }

    public void releaseCamera(){
        if (mCamera != null) {
            mSurfaceHolder.removeCallback(this);
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;

        }
    }

    public void switchCamera(){
        if(mCameraID ==  Camera.CameraInfo.CAMERA_FACING_BACK){
            mCameraID =  Camera.CameraInfo.CAMERA_FACING_FRONT;
        }else{

          mCameraID =  Camera.CameraInfo.CAMERA_FACING_BACK;

        }
        try {
            initCamera();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void init(){
        if(mSurfaceView == null){
            mSurfaceView = findViewById(R.id.surfaceview);
            mSurfaceView.setCustomEvent(new CustomSurfaceView.ONTouchEvent() {
                @Override
                public void onTouchEvent(MotionEvent event) {
                    handleFocus(event, mCamera);
                }
            });
            mSurfaceHolder = mSurfaceView.getHolder();
            mSurfaceHolder.addCallback(this);
            WindowManager wm = (WindowManager) Main23Activity.this.getSystemService(Context.WINDOW_SERVICE);
            int width = wm.getDefaultDisplay().getWidth();
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mSurfaceView.getLayoutParams();
            layoutParams.width = width;
            layoutParams.height = width*4/3;
            useWidth = width;
            useHeight = width*4/3;
            mSurfaceView.setLayoutParams(layoutParams);
        }

    }

    private void initCamera() {
        if (mCamera != null){
            releaseCamera();
            System.out.println("===================releaseCamera=============");
        }
        mCamera = Camera.open(mCameraID);
        System.out.println("===================openCamera=============");
        if (mCamera != null){
            try {
                mCamera.setPreviewDisplay(mSurfaceHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setRecordingHint(true);
            {
                //设置获取数据
                parameters.setPreviewFormat(ImageFormat.NV21);
                //parameters.setPreviewFormat(ImageFormat.YUV_420_888);

                //通过setPreviewCallback方法监听预览的回调:
                mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                    @Override
                    public void onPreviewFrame(byte[] bytes, Camera camera) {
                        //这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据


                    }
                });
            }

            if(mCameraID == Camera.CameraInfo.CAMERA_FACING_BACK){
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }

            mCamera.setParameters(parameters);

            calculateCameraPreviewOrientation(this);
            Camera.Size tempSize = setPreviewSize(mCamera, useHeight,useWidth);
            {
                //此处可以处理,获取到tempSize,如果tempSize和设置的SurfaceView的宽高冲突,重新设置SurfaceView的宽高
            }

            setPictureSize(mCamera,  useHeight,useWidth);
            mCamera.setDisplayOrientation(mOrientation);
            int degree = calculateCameraPreviewOrientation(Main23Activity.this);
            mCamera.setDisplayOrientation(degree);
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //当SurfaceView变化时也需要做相应操作,这里未做相应操作
        if (havePermission){
            initCamera();
        }

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
    }

    private void setPictureSize(Camera camera ,int expectWidth,int expectHeight){
        Camera.Parameters parameters = camera.getParameters();
        Point point = new Point(expectWidth, expectHeight);
        Camera.Size size = findProperSize(point,parameters.getSupportedPreviewSizes());
        parameters.setPictureSize(size.width, size.height);
        camera.setParameters(parameters);
    }

    private Camera.Size setPreviewSize(Camera camera, int expectWidth, int expectHeight) {
        Camera.Parameters parameters = camera.getParameters();
        Point point = new Point(expectWidth, expectHeight);
        Camera.Size size = findProperSize(point,parameters.getSupportedPictureSizes());
        parameters.setPictureSize(size.width, size.height);
        camera.setParameters(parameters);
        return size;
    }

    /**
     * 找出最合适的尺寸,规则如下:
     * 1.将尺寸按比例分组,找出比例最接近屏幕比例的尺寸组
     * 2.在比例最接近的尺寸组中找出最接近屏幕尺寸且大于屏幕尺寸的尺寸
     * 3.如果没有找到,则忽略2中第二个条件再找一遍,应该是最合适的尺寸了
     */
    private static Camera.Size findProperSize(Point surfaceSize, List<Camera.Size> sizeList) {
        if (surfaceSize.x <= 0 || surfaceSize.y <= 0 || sizeList == null) {
            return null;
        }

        int surfaceWidth = surfaceSize.x;
        int surfaceHeight = surfaceSize.y;

        List<List<Camera.Size>> ratioListList = new ArrayList<>();
        for (Camera.Size size : sizeList) {
            addRatioList(ratioListList, size);
        }

        final float surfaceRatio = (float) surfaceWidth / surfaceHeight;
        List<Camera.Size> bestRatioList = null;
        float ratioDiff = Float.MAX_VALUE;
        for (List<Camera.Size> ratioList : ratioListList) {
            float ratio = (float) ratioList.get(0).width / ratioList.get(0).height;
            float newRatioDiff = Math.abs(ratio - surfaceRatio);
            if (newRatioDiff < ratioDiff) {
                bestRatioList = ratioList;
                ratioDiff = newRatioDiff;
            }
        }

        Camera.Size bestSize = null;
        int diff = Integer.MAX_VALUE;
        assert bestRatioList != null;
        for (Camera.Size size : bestRatioList) {
            int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
            if (size.height >= surfaceHeight && newDiff < diff) {
                bestSize = size;
                diff = newDiff;
            }
        }

        if (bestSize != null) {
            return bestSize;
        }

        diff = Integer.MAX_VALUE;
        for (Camera.Size size : bestRatioList) {
            int newDiff = Math.abs(size.width - surfaceWidth) + Math.abs(size.height - surfaceHeight);
            if (newDiff < diff) {
                bestSize = size;
                diff = newDiff;
            }
        }

        return bestSize;
    }

    private static void addRatioList(List<List<Camera.Size>> ratioListList, Camera.Size size) {
        float ratio = (float) size.width / size.height;
        for (List<Camera.Size> ratioList : ratioListList) {
            float mine = (float) ratioList.get(0).width / ratioList.get(0).height;
            if (ratio == mine) {
                ratioList.add(size);
                return;
            }
        }

        List<Camera.Size> ratioList = new ArrayList<>();
        ratioList.add(size);
        ratioListList.add(ratioList);
    }
    /**
     * 排序
     * @param list
     */
    private static void sortList(List<Camera.Size> list) {
        Collections.sort(list, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size pre, Camera.Size after) {
                if (pre.width > after.width) {
                    return 1;
                } else if (pre.width < after.width) {
                    return -1;
                }
                return 0;
            }
        });
    }

    /**
     * 设置预览角度,setDisplayOrientation本身只能改变预览的角度
     * previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的
     * 拍摄的照片需要自行处理
     * 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。
     * @param activity
     */
    public static int calculateCameraPreviewOrientation(Activity activity) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraID, info);
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        mOrientation = result;
        System.out.println("=========orienttaion============="+result);
        return result;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mSensor,
                SensorManager.SENSOR_DELAY_NORMAL);
        if (havePermission && mCamera != null)
       mCamera.startPreview();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (havePermission && mCamera != null)
      mCamera.stopPreview();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            // 相机权限
            case 100:
                havePermission = true;
                init();
                break;
        }
    }

    /**
     * 转换对焦区域
     * 范围(-1000, -1000, 1000, 1000)
     */
    private  Rect calculateTapArea(float x, float y,  int width, int height, float coefficient) {
        float focusAreaSize = 200;
        int areaSize = (int) (focusAreaSize * coefficient);
        int surfaceWidth = width;
        int surfaceHeight = height;
        int centerX = (int) (x / surfaceHeight * 2000 - 1000);
        int centerY = (int) (y / surfaceWidth * 2000 - 1000);
        int left = clamp(centerX - (areaSize / 2), -1000, 1000);
        int top = clamp(centerY - (areaSize / 2), -1000, 1000);
        int right = clamp(left + areaSize, -1000, 1000);
        int bottom = clamp(top + areaSize, -1000, 1000);
        return new Rect(left, top, right, bottom);
    }

    //不大于最大值,不小于最小值
    private  int clamp(int x, int min, int max) {
        if (x > max) {
            return max;
        }
        if (x < min) {
            return min;
        }
        return x;
    }

    private  void handleFocus(MotionEvent event, Camera camera) {
        int viewWidth = useWidth;
        int viewHeight = useHeight;
        Rect focusRect = calculateTapArea(event.getX(), event.getY(),  viewWidth, viewHeight,1.0f);

        //一定要首先取消
        camera.cancelAutoFocus();
        Camera.Parameters params = camera.getParameters();
        if (params.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusAreas = new ArrayList<>();
            focusAreas.add(new Camera.Area(focusRect, 800));
            params.setFocusAreas(focusAreas);
        } else {
            //focus areas not supported
        }
        //首先保存原来的对焦模式,然后设置为macro,对焦回调后设置为保存的对焦模式
        final String currentFocusMode = params.getFocusMode();
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
        camera.setParameters(params);

        camera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                //回调后 还原模式
                Camera.Parameters params = camera.getParameters();
                params.setFocusMode(currentFocusMode);
                camera.setParameters(params);
                if(success){
                    Toast.makeText(Main23Activity.this,"对焦区域对焦成功",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        //手机移动一段时间后静止,然后静止一段时间后进行对焦
        // 读取加速度传感器数值,values数组0,1,2分别对应x,y,z轴的加速度
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            int x = (int) event.values[0];
            int y = (int) event.values[1];
            int z = (int) event.values[2];

        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
}

在这里插入图片描述
上面的实例代码支持,触摸对焦,自动对焦,拍照,摄像头切换,其他更多高级内容后续会接着讲。

抱歉源码已经丢失,CustomSurfaceView只是内部添加了一个点击回调函数(点击之后展示一个方框View)为实现点击聚焦,可以直接使用原生surfaceview,添加一个点击事件(替换mSurfaceView.setCustomEvent)内部调用handleFocus。

  • 22
    点赞
  • 135
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
以下是一个简单的 Android Camera 使用示例: 1. 首先,在 AndroidManifest.xml 文件中添加相机的权限: ``` <uses-permission android:name="android.permission.CAMERA" /> ``` 2. 在布局文件中添加一个 SurfaceView 作为相机的预览视图: ``` <SurfaceView android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 在 Activity 中获取相机实例并设置预览视图: ``` private Camera mCamera; private SurfaceView mPreview; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPreview = (SurfaceView) findViewById(R.id.camera_preview); // 获取相机实例 mCamera = getCameraInstance(); // 设置预览视图 CameraPreview cameraPreview = new CameraPreview(this, mCamera); mPreview.addView(cameraPreview); } // 获取相机实例的方法 private Camera getCameraInstance() { Camera camera = null; try { camera = Camera.open(); } catch (Exception e) { // 相机不可用或无法访问 } return camera; } ``` 4. 创建一个 CameraPreview 类,继承 SurfaceView 并实现 SurfaceHolder.Callback 接口: ``` public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; public CameraPreview(Context context, Camera camera) { super(context); mCamera = camera; // 初始化 SurfaceHolder,并添加回调 mHolder = getHolder(); mHolder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { // 当 Surface 创建时,启动相机预览 try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { // 相机预览失败 } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 当 Surface 改变时,调整相机预览大小 if (mHolder.getSurface() == null || mCamera == null) { return; } try { mCamera.stopPreview(); } catch (Exception e) { // 忽略异常 } try { Camera.Parameters parameters = mCamera.getParameters(); List<Camera.Size> sizes = parameters.getSupportedPreviewSizes(); Camera.Size optimalSize = getOptimalPreviewSize(sizes, width, height); parameters.setPreviewSize(optimalSize.width, optimalSize.height); mCamera.setParameters(parameters); mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); } catch (Exception e) { // 相机预览失败 } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 当 Surface 销毁时,释放相机资源 if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } // 获取最佳的相机预览尺寸 private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) width / height; if (sizes == null) { return null; } Camera.Size optimalSize = null; double minDiff = Double.MAX_VALUE; int targetHeight = height; for (Camera.Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) { continue; } if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (Camera.Size size : sizes) { if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } return optimalSize; } } ``` 5. 最后,在 Activity 的 onDestroy() 方法中释放相机资源: ``` @Override protected void onDestroy() { super.onDestroy(); if (mCamera != null) { mCamera.release(); mCamera = null; } } ``` 以上是一个简单的 Android Camera 使用示例,可以实现基本的相机预览功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值