精华内容
下载资源
问答
  • android 自定义相机Camera2
    万次阅读 多人点赞
    2017-05-03 16:27:17

    转载请标明来自:http://blog.csdn.net/qq_38416326/article/details/71124946

    上一篇文章我们已经运用Camera自定义了一个相机,今天我们就用Camera2自定义一个相机。Camera2是android5.0新增的api,Camera2与Camera差别比较大,采用了全新的模式,功能更加强大。今天这个例子就是Camera2拍照TextureView上面进行预览,把之前的SurfaceView代替了。

    一、打开摄像头

    1、TextureView设置监听

    //设置TextureView监听
    tv.setSurfaceTextureListener(surfaceTextureListener);
    2、在监听中,可用状态时打开摄像头

    /**TextureView的监听*/
    private TextureView.SurfaceTextureListener surfaceTextureListener= new TextureView.SurfaceTextureListener() {
        //可用
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            MainActivity.this.width=width;
            MainActivity.this.height=height;
            openCamera();
        }
    
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        }
    
        //释放
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            stopCamera();
            return true;
        }
    
        //更新
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    };
    3、打开摄像头(camera2采用的是CameraManager)

    /**打开摄像头*/
    private void openCamera() {
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        //设置摄像头特性
        setCameraCharacteristics(manager);
        try {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                //提示用户开户权限
                String[] perms = {"android.permission.CAMERA"};
                ActivityCompat.requestPermissions(MainActivity.this,perms, RESULT_CODE_CAMERA);
    
            }else {
                manager.openCamera(mCameraId, stateCallback, null);
            }
    
        } catch (CameraAccessException e){
            e.printStackTrace();
        }
    }
    
    4、设置摄像头的一些特性

    /**设置摄像头的参数*/
    private void setCameraCharacteristics(CameraManager manager)
    {
        try
        {
            // 获取指定摄像头的特性
            CameraCharacteristics characteristics
                    = manager.getCameraCharacteristics(mCameraId);
            // 获取摄像头支持的配置属性
            StreamConfigurationMap map = characteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 获取摄像头支持的最大尺寸
            Size largest = Collections.max(
                    Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),new CompareSizesByArea());
            // 创建一个ImageReader对象,用于获取摄像头的图像数据
            imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                    ImageFormat.JPEG, 2);
            //设置获取图片的监听
            imageReader.setOnImageAvailableListener(imageAvailableListener,null);
            // 获取最佳的预览尺寸
            previewSize = chooseOptimalSize(map.getOutputSizes(
                    SurfaceTexture.class), width, height, largest);
        }
        catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
        catch (NullPointerException e)
        {
        }
    }
    private static Size chooseOptimalSize(Size[] choices
            , int width, int height, Size aspectRatio)
    {
        // 收集摄像头支持的大过预览Surface的分辨率
        List<Size> bigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices)
        {
            if (option.getHeight() == option.getWidth() * h / w &&
                    option.getWidth() >= width && option.getHeight() >= height)
            {
                bigEnough.add(option);
            }
        }
        // 如果找到多个预览尺寸,获取其中面积最小的
        if (bigEnough.size() > 0)
        {
            return Collections.min(bigEnough, new CompareSizesByArea());
        }
        else
        {
           //没有合适的预览尺寸
            return choices[0];
        }
    }
    
    
    // 为Size定义一个比较器Comparator
    static class CompareSizesByArea implements Comparator<Size>
    {
        @Override
        public int compare(Size lhs, Size rhs)
        {
            // 强转为long保证不会发生溢出
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }
    }
    
    二、进行拍照预览

    1、监听摄像头

    manager.openCamera(mCameraId, stateCallback, null);
    2、在摄像头打开是进行画面的预览

    /**摄像头状态的监听*/
    private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
    {
        // 摄像头被打开时触发该方法
        @Override
        public void onOpened(CameraDevice cameraDevice){
           MainActivity.this.cameraDevice = cameraDevice;
            // 开始预览
            takePreview();
        }
    
        // 摄像头断开连接时触发该方法
        @Override
        public void onDisconnected(CameraDevice cameraDevice)
        {
            MainActivity.this.cameraDevice.close();
            MainActivity.this.cameraDevice = null;
    
        }
        // 打开摄像头出现错误时触发该方法
        @Override
        public void onError(CameraDevice cameraDevice, int error)
        {
            cameraDevice.close();
        }
    };
    
    3、进行预览的设置和处理

    /**
     * 开始预览
     */
    private void takePreview() {
        SurfaceTexture mSurfaceTexture = tv.getSurfaceTexture();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
        //获取Surface显示预览数据
        Surface mSurface = new Surface(mSurfaceTexture);
        try {
            //创建预览请求
            mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 设置自动对焦模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //设置Surface作为预览数据的显示界面
            mCaptureRequestBuilder.addTarget(mSurface);
            //创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
            cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    try {
                        //开始预览
                        mCaptureRequest = mCaptureRequestBuilder.build();
                        mPreviewSession = session;
                        //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
                        mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
    
                }
    
                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
    
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    
    }
    三、拍照

    1、进行拍照的处理和设置

    /**拍照*/
    private void takePicture()
    {
        try
        {
            if (cameraDevice == null)
            {
                return;
            }
            // 创建拍照请求
            captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 设置自动对焦模式
            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 将imageReader的surface设为目标
            captureRequestBuilder.addTarget(imageReader.getSurface());
            // 获取设备方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            // 根据设备方向计算设置照片的方向
            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION
                    , ORIENTATIONS.get(rotation));
            // 停止连续取景
            mPreviewSession.stopRepeating();
            //拍照
            CaptureRequest captureRequest = captureRequestBuilder.build();
            //设置拍照监听
            mPreviewSession.capture(captureRequest,captureCallback, null);
        }
        catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
    2、监听拍照结果(成功后恢复预览)

    /**监听拍照结果*/
    private CameraCaptureSession.CaptureCallback captureCallback= new CameraCaptureSession.CaptureCallback()
    {
        // 拍照成功
        @Override
        public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request,TotalCaptureResult result)
        {
            // 重设自动对焦模式
            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
            // 设置自动曝光模式
            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            try {
                //重新进行预览
                mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
    
        }
    
        @Override
        public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
        }
    };
    四、拍照的图片保存到本地相册(用的是ImageReader进行接收图片)

    /**监听拍照的图片*/
    private ImageReader.OnImageAvailableListener imageAvailableListener= new ImageReader.OnImageAvailableListener()
    {
        // 当照片数据可用时激发该方法
        @Override
        public void onImageAvailable(ImageReader reader) {
    
            //先验证手机是否有sdcard
            String status = Environment.getExternalStorageState();
            if (!status.equals(Environment.MEDIA_MOUNTED)) {
                Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show();
                return;
            }
            // 获取捕获的照片数据
            Image image = reader.acquireNextImage();
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
    
            //手机拍照都是存到这个路径
            String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            String picturePath = System.currentTimeMillis() + ".jpg";
            File file = new File(filePath, picturePath);
            try {
                //存到本地相册
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                fileOutputStream.write(data);
                fileOutputStream.close();
    
                //显示图片
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 2;
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
                iv.setImageBitmap(bitmap);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                image.close();
            }
        }
    
    
    };
    五、完整的代码

    1、activity代码

    package com.sunshanglei.camera.oneselfcamera;
    
    import android.Manifest;
    import android.content.Context;
    import android.content.pm.PackageManager;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.ImageFormat;
    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.CameraMetadata;
    import android.hardware.camera2.CaptureFailure;
    import android.hardware.camera2.CaptureRequest;
    import android.hardware.camera2.TotalCaptureResult;
    import android.hardware.camera2.params.StreamConfigurationMap;
    import android.media.Image;
    import android.media.ImageReader;
    import android.os.Environment;
    import android.support.v4.app.ActivityCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Size;
    import android.util.SparseIntArray;
    import android.view.Surface;
    import android.view.TextureView;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.Toast;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    /**
     * use 自定义相机Camera2
     * author 孙尚磊
     * create time 2017-4-25
     */
    public class MainActivity extends AppCompatActivity{
        private TextureView tv;
        private Button btn;
        private String mCameraId = "0";//摄像头id(通常0代表后置摄像头,1代表前置摄像头)
        private final int RESULT_CODE_CAMERA=1;//判断是否有拍照权限的标识码
        private CameraDevice cameraDevice;
        private CameraCaptureSession mPreviewSession;
        private CaptureRequest.Builder mCaptureRequestBuilder,captureRequestBuilder;
        private CaptureRequest mCaptureRequest;
        private ImageReader imageReader;
        private int height=0,width=0;
        private Size previewSize;
        private ImageView iv;
        private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
        static
        {
            ORIENTATIONS.append(Surface.ROTATION_0, 90);
            ORIENTATIONS.append(Surface.ROTATION_90, 0);
            ORIENTATIONS.append(Surface.ROTATION_180, 270);
            ORIENTATIONS.append(Surface.ROTATION_270, 180);
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv = (TextureView) findViewById(R.id.tv);
            btn = (Button) findViewById(R.id.btn);
            iv= (ImageView) findViewById(R.id.iv);
    
            //拍照
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    takePicture();
                }
            });
            //设置TextureView监听
            tv.setSurfaceTextureListener(surfaceTextureListener);
    
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            if(cameraDevice!=null) {
                stopCamera();
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            startCamera();
        }
    
        /**TextureView的监听*/
        private TextureView.SurfaceTextureListener surfaceTextureListener= new TextureView.SurfaceTextureListener() {
    
            //可用
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                MainActivity.this.width=width;
                MainActivity.this.height=height;
                openCamera();
            }
    
    
            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    
            }
    
            //释放
            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                stopCamera();
                return true;
            }
    
            //更新
            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    
            }
        };
    
    
        /**打开摄像头*/
        private void openCamera() {
            CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            //设置摄像头特性
            setCameraCharacteristics(manager);
            try {
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                    //提示用户开户权限
                    String[] perms = {"android.permission.CAMERA"};
                    ActivityCompat.requestPermissions(MainActivity.this,perms, RESULT_CODE_CAMERA);
    
                }else {
                    manager.openCamera(mCameraId, stateCallback, null);
                }
    
            } catch (CameraAccessException e){
                e.printStackTrace();
            }
        }
    
    
        /**设置摄像头的参数*/
        private void setCameraCharacteristics(CameraManager manager)
        {
            try
            {
                // 获取指定摄像头的特性
                CameraCharacteristics characteristics
                        = manager.getCameraCharacteristics(mCameraId);
                // 获取摄像头支持的配置属性
                StreamConfigurationMap map = characteristics.get(
                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                // 获取摄像头支持的最大尺寸
                Size largest = Collections.max(
                        Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),new CompareSizesByArea());
                // 创建一个ImageReader对象,用于获取摄像头的图像数据
                imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                        ImageFormat.JPEG, 2);
                //设置获取图片的监听
                imageReader.setOnImageAvailableListener(imageAvailableListener,null);
                // 获取最佳的预览尺寸
                previewSize = chooseOptimalSize(map.getOutputSizes(
                        SurfaceTexture.class), width, height, largest);
            }
            catch (CameraAccessException e)
            {
                e.printStackTrace();
            }
            catch (NullPointerException e)
            {
            }
        }
        private static Size chooseOptimalSize(Size[] choices
                , int width, int height, Size aspectRatio)
        {
            // 收集摄像头支持的大过预览Surface的分辨率
            List<Size> bigEnough = new ArrayList<>();
            int w = aspectRatio.getWidth();
            int h = aspectRatio.getHeight();
            for (Size option : choices)
            {
                if (option.getHeight() == option.getWidth() * h / w &&
                        option.getWidth() >= width && option.getHeight() >= height)
                {
                    bigEnough.add(option);
                }
            }
            // 如果找到多个预览尺寸,获取其中面积最小的
            if (bigEnough.size() > 0)
            {
                return Collections.min(bigEnough, new CompareSizesByArea());
            }
            else
            {
               //没有合适的预览尺寸
                return choices[0];
            }
        }
    
    
        // 为Size定义一个比较器Comparator
        static class CompareSizesByArea implements Comparator<Size>
        {
            @Override
            public int compare(Size lhs, Size rhs)
            {
                // 强转为long保证不会发生溢出
                return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                        (long) rhs.getWidth() * rhs.getHeight());
            }
        }
    
    
    
        /**摄像头状态的监听*/
        private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
        {
            // 摄像头被打开时触发该方法
            @Override
            public void onOpened(CameraDevice cameraDevice){
               MainActivity.this.cameraDevice = cameraDevice;
                // 开始预览
                takePreview();
            }
    
            // 摄像头断开连接时触发该方法
            @Override
            public void onDisconnected(CameraDevice cameraDevice)
            {
                MainActivity.this.cameraDevice.close();
                MainActivity.this.cameraDevice = null;
    
            }
            // 打开摄像头出现错误时触发该方法
            @Override
            public void onError(CameraDevice cameraDevice, int error)
            {
                cameraDevice.close();
            }
        };
    
        /**
         * 开始预览
         */
        private void takePreview() {
            SurfaceTexture mSurfaceTexture = tv.getSurfaceTexture();
            //设置TextureView的缓冲区大小
            mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
            //获取Surface显示预览数据
            Surface mSurface = new Surface(mSurfaceTexture);
            try {
                //创建预览请求
                mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                // 设置自动对焦模式
                mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                //设置Surface作为预览数据的显示界面
                mCaptureRequestBuilder.addTarget(mSurface);
                //创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
                cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        try {
                            //开始预览
                            mCaptureRequest = mCaptureRequestBuilder.build();
                            mPreviewSession = session;
                            //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
                            mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
    
                    }
    
                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
    
                    }
                }, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
    
        }
    
    
        /**拍照*/
        private void takePicture()
        {
            try
            {
                if (cameraDevice == null)
                {
                    return;
                }
                // 创建拍照请求
                captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                // 设置自动对焦模式
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                // 将imageReader的surface设为目标
                captureRequestBuilder.addTarget(imageReader.getSurface());
                // 获取设备方向
                int rotation = getWindowManager().getDefaultDisplay().getRotation();
                // 根据设备方向计算设置照片的方向
                captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION
                        , ORIENTATIONS.get(rotation));
                // 停止连续取景
                mPreviewSession.stopRepeating();
                //拍照
                CaptureRequest captureRequest = captureRequestBuilder.build();
                //设置拍照监听
                mPreviewSession.capture(captureRequest,captureCallback, null);
            }
            catch (CameraAccessException e)
            {
                e.printStackTrace();
            }
        }
    
        /**监听拍照结果*/
        private CameraCaptureSession.CaptureCallback captureCallback= new CameraCaptureSession.CaptureCallback()
        {
            // 拍照成功
            @Override
            public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request,TotalCaptureResult result)
            {
                // 重设自动对焦模式
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
                // 设置自动曝光模式
                captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                try {
                    //重新进行预览
                    mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
    
            }
    
            @Override
            public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
                super.onCaptureFailed(session, request, failure);
            }
        };
    
        /**监听拍照的图片*/
        private ImageReader.OnImageAvailableListener imageAvailableListener= new ImageReader.OnImageAvailableListener()
        {
            // 当照片数据可用时激发该方法
            @Override
            public void onImageAvailable(ImageReader reader) {
    
                //先验证手机是否有sdcard
                String status = Environment.getExternalStorageState();
                if (!status.equals(Environment.MEDIA_MOUNTED)) {
                    Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show();
                    return;
                }
                // 获取捕获的照片数据
                Image image = reader.acquireNextImage();
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
    
                //手机拍照都是存到这个路径
                String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
                String picturePath = System.currentTimeMillis() + ".jpg";
                File file = new File(filePath, picturePath);
                try {
                    //存到本地相册
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    fileOutputStream.write(data);
                    fileOutputStream.close();
    
                    //显示图片
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inSampleSize = 2;
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
                    iv.setImageBitmap(bitmap);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    image.close();
                }
            }
    
    
        };
    
        @Override
        public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
            switch(permsRequestCode){
                case RESULT_CODE_CAMERA:
                    boolean cameraAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
                    if(cameraAccepted){
                        //授权成功之后,调用系统相机进行拍照操作等
                        openCamera();
                    }else{
                        //用户授权拒绝之后,友情提示一下就可以了
                        Toast.makeText(MainActivity.this,"请开启应用拍照权限",Toast.LENGTH_SHORT).show();
                    }
                    break;
            }
        }
    
        /**启动拍照*/
        private void startCamera(){
            if (tv.isAvailable()) {
                if(cameraDevice==null) {
                    openCamera();
                }
            } else {
                tv.setSurfaceTextureListener(surfaceTextureListener);
            }
        }
    
        /**
         * 停止拍照释放资源*/
        private void stopCamera(){
            if(cameraDevice!=null){
                cameraDevice.close();
                cameraDevice=null;
            }
    
        }
    }
    

    2、xml代码

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextureView
            android:id="@+id/tv"
            android:layout_above="@+id/ll_bottom"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <RelativeLayout
            android:id="@+id/ll_bottom"
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:layout_alignParentBottom="true">
    
            <ImageView
                android:id="@+id/iv"
                android:layout_width="wrap_content"
                android:layout_height="70dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:src="@mipmap/ic_launcher"/>
            <Button
                android:id="@+id/btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:text="拍照"/>
        </RelativeLayout>
    </RelativeLayout>
    
    3.权限

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    六、相关资料

    1、效果图


    2、源码和apk下载

    apk下载:http://download.csdn.net/detail/qq_38416326/9833646

    源码下载:http://download.csdn.net/detail/qq_38416326/9833637

    https://github.com/sunshanglei/OneSelfCamera




    更多相关内容
  • Android 自定义相机Camera2
  • android 自定义相机apk 是的安装包,请结合我的博客:http://blog.csdn.net/qq_38416326/article/details/71124946
  • 主要介绍了Android自定义照相机Camera出现黑屏的解决方法,分析了黑屏出现的原因及参考解决方法,需要的朋友可以参考下
  • Android框架下采用android.hardware.camera的API去捕获图像和视频,预览视图采用SurfaceView
  • 笔者因为项目需要自定义相机,所以了解了一下 Android 关于 camera 这块的 API。Android SDK 21(LOLLIPOP) 开始已经弃用了之前的 Camera 类,提供了 camera2 相关 API,目前网上关于 camera2 API 介绍的资料比较少...
  • android自定义相机Camera

    2018-08-03 23:15:36
    Surfaceview 自定义相机,支持分辨率修改,开关闪光灯,选择前后摄像头,拍摄完成存为图片;
  • 我采用的是Camera来实现自定义相机的,如果你使用的是Camera2,那本文将不适用你。为了减少篇幅,本文主要讲解手动对焦的实现方式,前提是你已经能实现自定义相机的预览等基础功能。 目录 手动对焦的方法解析 ...
  • Android自定义Camera2相机 转载请标明出处:https://blog.csdn.net/ZhijunHong/article/details/115730693,谢谢~ 写在前面 Google从Android 5.0 L(API 21) 版本,开始引入Camera2(android.hardware.camera2)以取代...

    在这里插入图片描述
    转载请标明出处:https://blog.csdn.net/ZhijunHong/article/details/115730693,谢谢~

    写在前面

    Google从Android 5.0 L(API 21) 版本,开始引入Camera2(android.hardware.camera2)以取代Camera1(android.hardware.Camera)相机框架。

    Camera2相比于之前的Camera1架构完全不同,使用起来比较复杂,与此同时功能也变得非常强大。

    此篇博客,能够帮助你快速构建并理解自定义Camera2相机的关键步骤。

    完整代码,请移步:https://github.com/zhijunhong/custom_camera/tree/master/camera2

    使用Camera2的优点

    通过设计框架的改造和优化,Camera2具备了以下优点:

    1. 改进了新硬件的性能,使用更先进的API架构;
    2. 可以获取更多的帧(预览/拍照)信息以及手动控制每一帧的参数;
    3. 对Camera的控制更加完全(比如支持调整focus distance, 剪裁预览/拍照图片);
    4. 支持更多图片格式(yuv/raw)以及高速连拍;

    自定义Camera2相机

    一些概念

    1. Pipeline

    Camera2的API模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了Pipeline的工作流程,我们会通过一个简单的例子详细解释这张图。

    在这里插入图片描述

    Pipeline示意图

    为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:

    1. 创建一个用于从Pipeline获取图片的CaptureRequest;
    2. 修改CaptureRequest的闪光灯配置,让闪光灯在拍照过程中亮起来;
    3. 创建两个不同尺寸的Surface用于接收图片数据,并且将它们添加到CaptureRequest中;
    4. 发送配置好的CaptureRequest到Pipeline中等待它返回拍照结果。

    一个新的CaptureRequest会被放入一个被称作Pending Request Queue的队列中等待被执行,当In-Flight Capture Queue队列空闲的时候就会从Pending Request Queue获取若干个待处理的CaptureRequest,并且根据每一个CaptureRequest 的配置进行Capture操作。最后我们从不同尺寸的Surface中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的CaptureResult,流程结束。

    2. Supported Hardware Level

    相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以Camera2定义了一个叫做Supported Hardware Level的重要概念。其作用是将不同设备上的Camera2根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上Camera2的支持情况。截止到Android P为止,从低到高一共有LEGACY、LIMITED、FULL 和 LEVEL_3四个级别:

    1. LEGACY:向后兼容的级别,处于该级别的设备意味着它只支持Camera1的功能,不具备任何Camera2高级特性;
    2. LIMITED:除了支持Camera1的基础功能之外,还支持部分Camera2高级特性的级别;
    3. FULL:支持所有Camera2的高级特性;
    4. LEVEL_3:新增更多Camera2高级特性,例如YUV数据的后处理等。

    3. Capture

    相机的所有操作和参数配置最终都是服务于图像捕获,例如对焦是为了让某一个区域的图像更加清晰,调节曝光补偿是为了调节图像的亮度等。因此,在Camera2 里面所有的相机操作和参数配置都被抽象成Capture(捕获),所以不要简单的把Capture直接理解成是拍照,因为Capture操作可能仅仅是为了让预览画面更清晰而进行对焦而已。如果你熟悉Camera,那你可能会问 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告诉你,它们都是通过Capture 来实现的。

    Capture从执行方式上又被细分为【单次模式】、【多次模式】和【重复模式】三种,我们来一一解释下:

    • 单次模式(One-shot):指的是只执行一次的Capture操作,例如设置闪光灯模式、对焦模式和拍一张照片等。多个一次性模式的Capture会进入队列按顺序执行。
    • 多次模式(Burst):指的是连续多次执行指定的Capture操作,该模式和多次执行单次模式的最大区别是连续多次Capture期间不允许插入其他任何Capture 操作,例如连续拍摄100张照片,在拍摄这100张照片期间任何新的Capture请求都会排队等待,直到拍完100张照片。多组多次模式的Capture会进入队列按顺序执行。
    • 重复模式(Repeating):指的是不断重复执行指定的Capture操作,当有其他模式的Capture提交时会暂停该模式,转而执行其他被模式的Capture,当其他模式的 Capture 执行完毕后又会自动恢复继续执行该模式的Capture,例如显示预览画面就是不断 Capture 获取每一帧画面。该模式的 Capture 是全局唯一的,也就是新提交的重复模式Capture会覆盖旧的重复模式Capture。

    关键API

    CameraManagerCameraManager是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个CameraManager的关键功能:
    1.将相机信息封装到CameraCharacteristics中,并提获取CameraCharacteristics实例的方式;
    2.根据指定的相机ID连接相机设备;
    3.提供将闪光灯设置成手电筒模式的快捷方式。
    CameraCharacteristicsCameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等。如果你对Camera1比较熟悉,那么CameraCharacteristics有点像Camera1的 Camera.CameraInfo 或者 Camera.Parameters
    CameraDeviceCameraDevice 代表当前连接的相机设备,它的职责有以下四个:
    1.根据指定的参数创建 CameraCaptureSession;
    2.根据指定的模板创建 CaptureRequest;
    3.关闭相机设备;
    4.监听相机设备的状态,例如断开连接、开启成功和开启失败等。
    熟悉Camera1的人可能会说CameraDevice就是Camera1的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的CameraCaptureSession。
    SurfaceSurface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。
    CameraCaptureSessionCameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等。
    CaptureRequestCaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
    CaptureResultCaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
    ImageReader用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。

    开发流程

    在这里插入图片描述

    1.获取CameraManager

    private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }
    

    2.获取相机信息

    val cameraIdList = cameraManager.cameraIdList
    cameraIdList.forEach { cameraId ->
        val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
        if (cameraCharacteristics.isHardwareLevelSupported(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)) {
            if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
                frontCameraId = cameraId
                frontCameraCharacteristics = cameraCharacteristics
            } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) {
                backCameraId = cameraId
                backCameraCharacteristics = cameraCharacteristics
            }
        }
    }
    

    通过CameraManager获取到所有摄像头cameraId,通过循环判断是前摄像头(CameraCharacteristics.LENS_FACING_FRONT)还是后摄像头(CameraCharacteristics.LENS_FACING_BACK

    3.初始化ImageReader

    private var jpegImageReader: ImageReader? = null
    jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5)
    jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler)
    
    ......
    
    private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener {
    
      private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault())
      private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera"
    
      @WorkerThread
      override fun onImageAvailable(imageReader: ImageReader) {
        val image = imageReader.acquireNextImage()
        val captureResult = captureResults.take()
        if (image != null && captureResult != null) {
          image.use {
            val jpegByteBuffer = it.planes[0].buffer// Jpeg image data only occupy the planes[0].
            val jpegByteArray = ByteArray(jpegByteBuffer.remaining())
            jpegByteBuffer.get(jpegByteArray)
            val width = it.width
            val height = it.height
            saveImageExecutor.execute {
              val date = System.currentTimeMillis()
              val title = "IMG_${dateFormat.format(date)}"// e.g. IMG_20190211100833786
              val displayName = "$title.jpeg"// e.g. IMG_20190211100833786.jpeg
              val path = "$cameraDir/$displayName"// e.g. /sdcard/DCIM/Camera/IMG_20190211100833786.jpeg
              val orientation = captureResult[CaptureResult.JPEG_ORIENTATION]
              val location = captureResult[CaptureResult.JPEG_GPS_LOCATION]
              val longitude = location?.longitude ?: 0.0
              val latitude = location?.latitude ?: 0.0
    
              // Write the jpeg data into the specified file.
              File(path).writeBytes(jpegByteArray)
    
              // Insert the image information into the media store.
              val values = ContentValues()
              values.put(MediaStore.Images.ImageColumns.TITLE, title)
              values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName)
              values.put(MediaStore.Images.ImageColumns.DATA, path)
              values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date)
              values.put(MediaStore.Images.ImageColumns.WIDTH, width)
              values.put(MediaStore.Images.ImageColumns.HEIGHT, height)
              values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation)
              values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude)
              values.put(MediaStore.Images.ImageColumns.LATITUDE, latitude)
              contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    
              // Refresh the thumbnail of image.
              val thumbnail = getThumbnail(path)
              if (thumbnail != null) {
                runOnUiThread {
                  thumbnailView.setImageBitmap(thumbnail)
                  thumbnailView.scaleX = 0.8F
                  thumbnailView.scaleY = 0.8F
                  thumbnailView.animate().setDuration(50).scaleX(1.0F).scaleY(1.0F).start()
                }
              }
            }
          }
        }
      }
    }
    

    ImageReader是获取图像数据的重要途径,通过它可以获取到不同格式的图像数据,例如JPEG、YUV、RAW等。通过ImageReader.newInstance(int width, int height, int format, int maxImages)创建ImageReader对象,有4个参数:

    • width:图像数据的宽度
    • height:图像数据的高度
    • format:图像数据的格式,例如ImageFormat.JPEGImageFormat.YUV_420_888
    • maxImages:最大Image个数,Image对象池的大小,指定了能从ImageReader获取Image对象的最大值,过多获取缓冲区可能导致OOM,所以最好按照最少的需要去设置这个值

    ImageReader其他相关的方法和回调:

    • ImageReader.OnImageAvailableListener:有新图像数据的回调
    • acquireLatestImage():从ImageReader的队列里面,获取最新的Image,删除旧的,如果没有可用的Image,返回null
    • acquireNextImage():获取下一个最新的可用Image,没有则返回null
    • close():释放与此ImageReader关联的所有资源
    • getSurface():获取为当前ImageReader生成Image的Surface

    4.打开相机设备

    val cameraStateCallback = CameraStateCallback()
    cameraManager.openCamera(cameraId, cameraStateCallback, mainHandler)
    
    ......
    
     private inner class CameraStateCallback : CameraDevice.StateCallback() {
            @MainThread
            override fun onOpened(camera: CameraDevice) {
                cameraDeviceFuture!!.set(camera)
                cameraCharacteristicsFuture!!.set(getCameraCharacteristics(camera.id))
            }
    
            @MainThread
            override fun onClosed(camera: CameraDevice) {
    
            }
    
            @MainThread
            override fun onDisconnected(camera: CameraDevice) {
                cameraDeviceFuture!!.set(camera)
                closeCamera()
            }
    
            @MainThread
            override fun onError(camera: CameraDevice, error: Int) {
                cameraDeviceFuture!!.set(camera)
                closeCamera()
            }
        }
    

    cameraManager.openCamera(@NonNull String cameraId,@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)的三个参数:

    • cameraId:摄像头的唯一标识
    • callback:设备连接状态变化的回调
    • handler:回调执行的Handler对象,传入null则使用当前的主线程Handler

    其中CameraStateCallback回调:

    • onOpened:表示相机打开成功,可以真正开始使用相机,创建Capture会话
    • onDisconnected:当相机断开连接时回调该方法,需要进行释放相机的操作
    • onError:当相机打开失败时,需要进行释放相机的操作
    • onClosed:调用Camera.close()后的回调方法

    5.创建Capture Session

    val sessionStateCallback = SessionStateCallback()
    ......
    val cameraDevice = cameraDeviceFuture?.get()
    cameraDevice?.createCaptureSession(outputs, sessionStateCallback, mainHandler)
    
    ......
    
     private inner class SessionStateCallback : CameraCaptureSession.StateCallback() {
            @MainThread
            override fun onConfigureFailed(session: CameraCaptureSession) {
                captureSessionFuture!!.set(session)
            }
    
            @MainThread
            override fun onConfigured(session: CameraCaptureSession) {
                captureSessionFuture!!.set(session)
            }
    
            @MainThread
            override fun onClosed(session: CameraCaptureSession) {
    
            }
        }
    

    这段的代码核心方法是mCameraDevice.createCaptureSession()创建Capture会话,它接受了三个参数:

    • outputs:用于接受图像数据的surface集合,这里传入的是一个preview的surface
    • callback:用于监听 Session 状态的CameraCaptureSession.StateCallback对象
    • handler:用于执行CameraCaptureSession.StateCallback的Handler对象,传入null则使用当前的主线程Handler

    6.创建CaptureRequest

    CaptureRequest是向CameraCaptureSession提交Capture请求时的信息载体,其内部包括了本次Capture的参数配置和接收图像数据的Surface

    if (cameraDevice != null) {
      previewImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
      captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
    }
    
    ......
    
    val cameraDevice = cameraDeviceFuture?.get()
    val captureSession = captureSessionFuture?.get()
    val previewImageRequestBuilder = previewImageRequestBuilder!!
    val captureImageRequestBuilder = captureImageRequestBuilder!!
    if (cameraDevice != null && captureSession != null) {
      val previewSurface = previewSurface!!
      val previewDataSurface = previewDataSurface
      previewImageRequestBuilder.addTarget(previewSurface)
      // Avoid missing preview frame while capturing image.
      captureImageRequestBuilder.addTarget(previewSurface)
      if (previewDataSurface != null) {
        previewImageRequestBuilder.addTarget(previewDataSurface)
        // Avoid missing preview data while capturing image.
        captureImageRequestBuilder.addTarget(previewDataSurface)
      }
      val previewRequest = previewImageRequestBuilder.build()
      captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)
    }
    
    ......
    
    private inner class RepeatingCaptureStateCallback : CameraCaptureSession.CaptureCallback() {
      @MainThread
      override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
        super.onCaptureStarted(session, request, timestamp, frameNumber)
      }
    
      @MainThread
      override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
        super.onCaptureCompleted(session, request, result)
      }
    }
    

    除了模式的配置,CaptureRequest还可以配置很多其他信息,例如图像格式、图像分辨率、传感器控制、闪光灯控制、3A(自动对焦-AF、自动曝光-AE和自动白平衡-AWB)控制等。在createCaptureSession的回调中可以进行设置,最后通过build()方法生成CaptureRequest对象。

    7.预览

    Camera2中,通过连续重复的Capture实现预览功能,每次Capture会把预览画面显示到对应的Surface上。连续重复的Capture操作通过

    captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler)

    实现,该方法有三个参数:

    • request:CaptureRequest对象
    • listener:监听Capture 状态的回调
    • handler:用于执行CameraCaptureSession.CaptureCallback的Handler对象,传入null则使用当前的主线程Handler

    停止预览使用mCaptureSession.stopRepeating()方法。

    8.拍照

    设置上面的request,session后,就可以真正的开始拍照操作

    val captureImageRequest = captureImageRequestBuilder.build()
    captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler)
    
    ......
    
    private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() {
    
      @MainThread
      override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) {
        super.onCaptureStarted(session, request, timestamp, frameNumber)
        // Play the shutter click sound.
        cameraHandler?.post { mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) }
      }
    
      @MainThread
      override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
        super.onCaptureCompleted(session, request, result)
        captureResults.put(result)
      }
    }
    

    captureSession.capture()方法也有三个参数,和mCaptureSession.setRepeatingRequest一样:

    • request:CaptureRequest对象
    • listener:监听Capture 状态的回调
    • handler:用于执行CameraCaptureSession.CaptureCallback的Handler对象,传入null则使用当前的主线程Handler

    9.关闭相机

    和其他硬件资源的使用一样,当我们不再需要使用相机时记得调用 CameraDevice.close() 方法及时关闭相机回收资源。关闭相机的操作至关重要,因为如果你一直占用相机资源,其他基于相机开发的功能都会无法正常使用,严重情况下直接导致其他相机相关的 APP 无法正常使用,当相机被完全关闭的时候会通过 CameraStateCallback.onCllosed() 方法通知你相机已经被关闭。那么在什么时候关闭相机最合适呢?个人的建议是在 onPause() 的时候就一定要关闭相机,因为在这个时候相机页面已经不是用户关注的焦点,大部分情况下已经可以关闭相机了。

    cameraDevice?.close()
    previewDataImageReader?.close()
    jpegImageReader?.close()
    

    先后对CaptureSession,CameraDevice,ImageReader进行close操作,释放资源。

    从 Camera1迁移到Camera2的建议

    如果你的项目正在使用Camera1,并且打算从Camera1迁移到Camera2的话,希望以下几个建议可以对你有所帮助:

    1. Camera1严格区分了预览和拍照两个流程,而Camera2则把这两个流程都抽象成了Capture行为,所以建议你不要带着过多的Camera1思维使用Camera2,避免因为思维上的束缚而无法充分利用Camera2灵活的 API;
    2. 如同Camera1一样,Camera2的一些 API调用也会耗时,所以建议你使用独立的线程执行所有的相机操作,尽量避免直接在主线程调用Camera2的API,HandlerThread 是一个不错的选择;
    3. 可以认为Camera1是Camera2的一个子集,也就是说Camera1能做的事情Camera2一定能做,反过来则不一定行得通;
    4. 如果你的应用程序需要同时兼容Camera1 和Camera2,个人建议分开维护,因为Camera1蹩脚的API设计很可能让Camera2灵活的API无法得到充分的发挥,另外将两个设计上完全不兼容的东西搅和在一起带来的痛苦可能远大于其带来便利性,多写一些冗余的代码也许还更开心;
    5. 官方说Camera2的性能会更好,但在较早期的一些机器上运行Camera2的性能并没有比Camera1好多少;
    6. 当设备的 Supported Hardware Level 低于FULL的时候,建议还是使用Camera1,因为FULL级别以下的 Camera2 能提供的功能几乎和Camera1一样,所以倒不如选择更加稳定的Camera1。

    完整代码:https://github.com/zhijunhong/custom_camera/tree/master/camera2

    最后,如果此篇博文对你有所帮助,别忘了点个赞哟~

    参考:

    Android Camera-Camera2使用

    Android Camera2 教程

    展开全文
  • android 自定义相机,Camera和camera2.TextureView,相机遮罩层demo
  • Camera类在5.0以后不推荐使用了,取而代之的是android.hardware.camera2包下的类,本文使用Camera。 我们首先自定义一个View去继承SurfaceView: public class CameraSurfaceView extends SurfaceView implements ...
  • Android自定义camera2相机 系列(二)

    千次阅读 2019-03-08 16:14:48
    在我们前一篇文章中提到Camera,在开发中发现很多api 都已经不推荐使用,google给出的替代方案则就是我们今天的主角 Camera2 ,从5.0开始(API Level 21),可以完全控制Android设备相机的新api 。当然如果产品覆盖...

    该博客主要记录在开发过程中所运用到的Api 和部分技术调用的记录,阅读时间:15分钟+ ,该博客记录内容相对简单,仅以用于开发过程记录。

    <文章已收录于 Wan android 网站中>

    说明

    在我们前一篇文章中提到Camera,在开发中发现很多api 都已经不推荐使用,google给出的替代方案则就是我们今天的主角 Camera2 ,从5.0开始(API Level 21),可以完全控制Android设备相机的新api 。当然如果产品覆盖的还是有4.0版本的Android 用户的话,还是建议 使用Camera。但是在以前的Camera 中,对相机的手动控制都是通过更改系统才能实现,而且api也不友好。Camera2 这时针对这一点进行了管理的分离,Api会更加的友好,分工明确。

    文章对应 Demo 地址
    google demo 地址
    文章参考地址
    很感谢该篇文章作者的描述。

    效果图
    在这里插入图片描述

    1. camera2 概念

    camera2流程图
    相对于camera ,camera2 在Api上将拍照对象进行了独立,camera2采用pipeline的方式,将Camera 设备和 Android 设备连接起来,Android Device通过管道发送CaptureRequest拍照请求给Camera Device,Camera Device通过管道返回CameraMetadata数据给Android Device,这一切都发生在CameraCaptureSession的会话中。

    2. camera2 API简要说明

    camera2 主要API

    CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都通过该类的实例创建Session,控制预览的方法setRepeatingRequest();控制拍照的方法为capture()。

    CameraDevices:提供一组静态属性信息,描述硬件设备以及设备的可用设置和输出参数。通过getCameraCharacteristics获得。

    CameraManager:所有相机设备(CameraDevice)的管理者,要枚举,查询和打开可用的相机设备,用于打开和关闭系统摄像头,就获取CameraManager实例。

    CaptureRequest:定义了相机设备捕获单个映像所需的所有捕获参数。该请求还列出了哪些配置的输出表面应该用作此捕获的目标。

    CameraDevice:具有用于为给定用例创建请求构建器的工厂方法,针对应用程序正在运行的Android设备进行了优化,描述系统摄像头,类似于早期的Camera。

    CameraRequest CameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,程序对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

    CameraCharacteristics:描述摄像头的各种特性,我们可以通过CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法来获取。

    CaptureResult:描述拍照完成后的结果。

    ImageReader :通过添加 PreviewRequestBuilder.addTarget(mImageReader.getSurface()); 可以在 OnImageAvailableListener 接口中实时获取 yuv 数据。
    在这里插入图片描述

    3.Camera2接口使用的流程

    接口流程图
    1.调用openCamera方法后会回调CameraDevice.StateCallback这个方法,在该方法里重写onOpened函数。

    2.在onOpened方法中调用createCaptureSession,该方法又回调CameraCaptureSession.StateCallback方法。

    3.CameraCaptureSession.StateCallback中重写onConfigured方法,设置setRepeatingRequest方法(也就是开启预览)。

    4.setRepeatingRequest又会回调 CameraCaptureSession.CaptureCallback方法。

    5.重写CameraCaptureSession.CaptureCallback中的onCaptureCompleted方法,result就是未经过处理的帧数据了。

    4. 自定义Camera2

    Camera2与Camera一样也有cameraId的概念,我们通过mCameraManager.getCameraIdList()来获取cameraId列表,然后通过mCameraManager.getCameraCharacteristics(id) 获取每个id对应摄像头的参数。

    关于CameraCharacteristics里面的参数,主要用到的有以下几个:

    LENS_FACING:前置摄像头(LENS_FACING_FRONT)还是后置摄像头(LENS_FACING_BACK)。

    SENSOR_ORIENTATION:摄像头拍照方向。

    FLASH_INFO_AVAILABLE:是否支持闪光灯。

    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:获取当前设备支持的相机特性。

    注:事实上,在各个厂商的的Android设备上,Camera2的各种特性并不都是可用的,需要通过characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 来根据返回值来获取支持的级别,具体说来:

    INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,允许手动控制全高清的摄像、支持连拍模式以及其他新特性。

    INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,这个需要单独查询。

    INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有设备都会支持,也就是和过时的Camera API支持的特性是一致的。

    利用这个INFO_SUPPORTED_HARDWARE_LEVEL参数,我们可以来判断是使用Camera还是使用Camera

    通过上面的 原理的说明,大致流程可能还会有点模糊,我们直接对应上述逻辑开始代码。

    4.1 界面布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/black"
        tools:context="cn.tongue.tonguecamera.ui.CameraActivity">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    		// 自定义 TextureView
            <cn.tongue.tonguecamera.view.AutoFitTextureView
                android:id="@+id/textureView_g"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    
        </FrameLayout>
    
        <RelativeLayout
            android:id="@+id/homecamera_bottom_relative2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#00ffffff"
            android:layout_alignParentBottom="true">
    		// 返回按钮
            <ImageView
                android:id="@+id/iv_back_g"
                android:layout_width="40dp"
                android:layout_height="30dp"
                android:scaleType="centerInside"
                android:layout_marginBottom="20dp"
                android:layout_marginStart="20dp"
                android:layout_centerVertical="true"
                android:background="@drawable/icon_back" />
    		// 拍照按钮
            <ImageView
                android:id="@+id/img_camera_g"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:scaleType="centerInside"
                android:layout_marginBottom="20dp"
                android:layout_centerInParent="true"
                android:background="@drawable/camera" />
    
        </RelativeLayout>
    
    </RelativeLayout>
    
    

    通过上述布局我们会发现 camera2 中我们并没有继续选择 SurfaceView作为呈现图片的载体,这里选择的TextureView。

    	@RequiresApi(api = Build.VERSION_CODES.M)
        @Override
        public void onResume() {
            super.onResume();
            // 启动 HandlerThread ,后台维护一个 handler
            startBackgroundThread();
            // 存在关联则打开相机,没有则绑定事件
            if (mTextureView.isAvailable()) {
                openCamera(mTextureView.getWidth(), mTextureView.getHeight());
            } else {
            	// 绑定 载体监听事件
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
            }
        }
    

    如果是第一次进入 就需要配置 相机相关配置

    	/**
         * 打开相机
         *
         * @param width 宽度
         * @param height 长度
         */
        @RequiresApi(api = Build.VERSION_CODES.M)
        private void openCamera(int width, int height) {
        	// 判断相机权限 6.0 以上的动态权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                    != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
                return;
            }
            // 配置 相机的 预览尺寸
            setUpCameraOutputs(width, height);
            // Matrix 转换配置为 mTextureView
            configureTransform(width, height);
            CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            try {
                if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                    throw new RuntimeException("Time out waiting to lock camera opening.");
                }
                // 都配置完成后 打开相机 并绑定回调接口
                manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
            }
        }
    

    上述代码中,setUpCameraOutputs() 方法主要进行 相机参数的设置(前后摄像头,闪光灯的配置),设置mImageReader 的成像格式及数据流的回调监听事件 OnImageAvailableListener,并且根据硬件数据 查看是否需要交换尺寸以获得相对于传感器的预览尺寸。

    	/**
         * CameraDevice 改变状态时候 调用
         */
        private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
    
    		// 打开事件监听
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                //打开相机时会调用此方法。 我们在这里开始相机预览。
                mCameraOpenCloseLock.release();
                mCameraDevice = cameraDevice;
                createCameraPreviewSession();
            }
    
    		// 关闭监听
            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
            }
    
            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int error) {
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
                finish();
            }
    
        };
    

    从回调方法中,我们 通过 CameraCaptureSession 开始 预览图像,createCameraPreviewSession() 则是创建一个 相机预览。

    	/**
         * 创建一个新的相机预览
         */
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private void createCameraPreviewSession() {
            try {
                SurfaceTexture texture = mTextureView.getSurfaceTexture();
                //将默认缓冲区的大小配置为相机预览的大小。
                texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
                Surface surface = new Surface(texture);
                //  Camera2都是通过创建请求会话的方式进行调用的
                mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                // 使用Surface设置CaptureRequest.Builder
                mPreviewRequestBuilder.addTarget(surface);
                // 方法创建CaptureSession。
                mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                        new CameraCaptureSession.StateCallback() {
    
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                                if (null == mCameraDevice) {
                                    return;
                                }
                                mCaptureSession = cameraCaptureSession;
                                try {
                                    // 自动变焦是连续的
                                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                    setAutoFlash(mPreviewRequestBuilder);
                                    // 显示相机预览
                                    mPreviewRequest = mPreviewRequestBuilder.build();
                                    //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
                                    mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                            mCaptureCallback, mBackgroundHandler);
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }
    
                            @Override
                            public void onConfigureFailed(
                                    @NonNull CameraCaptureSession cameraCaptureSession) {
                            }
                        }, null
                );
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    

    createCaptureRequest()方法里参数templateType代表了请求类型,请求类型一共分为六种,分别为:
    TEMPLATE_PREVIEW:创建预览的请求
    TEMPLATE_STILL_CAPTURE:创建一个适合于静态图像捕获的请求,图像质量优先于帧速率。
    TEMPLATE_RECORD:创建视频录制的请求
    TEMPLATE_VIDEO_SNAPSHOT:创建视视频录制时截屏的请求
    TEMPLATE_ZERO_SHUTTER_LAG:创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量。
    TEMPLATE_MANUAL:创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。

    createCaptureSession()方法一共包含三个参数:
    1.List outputs:我们需要输出到的Surface列表。
    2.CameraCaptureSession.StateCallback callback:会话状态相关回调。
    3.Handler handler:callback可以有多个(来自不同线程),这个handler用来区别那个callback应该被回调,一般写当前线程的Handler即可。

    关于CameraCaptureSession.StateCallback里的回调方法
    1.onConfigured(@NonNull CameraCaptureSession session); 摄像头完成配置,可以处理Capture请求了。
    2.onConfigureFailed(@NonNull CameraCaptureSession session); 摄像头配置失败
    3.onReady(@NonNull CameraCaptureSession session); 摄像头处于就绪状态,当前没有请求需要处理。

    onActive(@NonNull CameraCaptureSession session); 摄像头正在处理请求。
    onClosed(@NonNull CameraCaptureSession session); 会话被关闭
    onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface准备就绪
    理解了这些东西,创建预览请求就十分简单了。

    设置预览界面尺寸信息,Surface就把它与CaptureRequestBuilder对象关联,然后就是设置会话开始捕获画面。最后的回调CameraCaptureSession.CaptureCallback就给我们设置预览完成的逻辑处理

    /**
         * ImageReader 的回调对象。 静止图像已准备好保存。
         */
        private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
                = new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Log.e(TAG, "onImageAvailable:-------------------");
                Image image = reader.acquireLatestImage();
                //我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                image.close();
    //            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
            }
    
        };
    

    最后这里的方法 就是同 camera 中的 实时帧数据,这里我们可以在这里获取 静止图片的 帧数据,进行一些 图层 水印的处理。google 源码中 一直维系了一个mBackgroundHandler ,我们可以在这里发送 子线程任务reade.acquireNextImage方法获取 静止图片信息,将图片保存到本地文件夹中,基本流程完成后,我们只需要看一下如何触发 拍照即可。

    	/**
         * 锁定焦点设置
         */
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private void lockFocus() {
            try {
                // 相机锁定的方法 (设置相机对焦)
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                        CameraMetadata.CONTROL_AF_TRIGGER_START);
                // mCaptureCallback 等待锁定  //修改状态
                mState = STATE_WAITING_LOCK;
                //发送对焦请求
                mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                        mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    

    通过点击事件,调用相机锁定 ,设置 mCaptureSession.capture,

    	/**
         * 处理与jpg文件捕捉的事件监听(预览)
         */
        private CameraCaptureSession.CaptureCallback mCaptureCallback
                = new CameraCaptureSession.CaptureCallback() {
    
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            private void process(CaptureResult result) {
                switch (mState) {
                    case STATE_PREVIEW: {
                        // 预览正常
                        break;
                    }
                      //等待对焦
                    case STATE_WAITING_LOCK: { 
                        Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                        if (afState == null) {
                        	// 对焦失败 直接拍照
                            captureStillPicture();
                        } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                                CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                            if (aeState == null ||
                                    aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                                mState = STATE_PICTURE_TAKEN;
                                //  对焦完成,进行拍照
                                captureStillPicture();
                            } else {
                                runPrecaptureSequence();
                            }
                        }
                        break;
                    }
                    case STATE_WAITING_PRECAPTURE: {
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null ||
                                aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                                aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                            mState = STATE_WAITING_NON_PRECAPTURE;
                        }
                        break;
                    }
                    case STATE_WAITING_NON_PRECAPTURE: {
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                            mState = STATE_PICTURE_TAKEN;
                            captureStillPicture();
                        }
                        break;
                    }
                    default:
                        break;
                }
            }
    
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                            @NonNull CaptureRequest request,
                                            @NonNull CaptureResult partialResult) {
                process(partialResult);
            }
    
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                process(result);
            }
    
        };
    
    	/**
         * 拍摄静止图片。 当我们得到响应时,应该调用此方法
         */
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private void captureStillPicture() {
            try {
                if (null == activity || null == mCameraDevice) {
                    return;
                }
                final CaptureRequest.Builder captureBuilder =
                        mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                captureBuilder.addTarget(mImageReader.getSurface());
    
                // 使用与预览相同的AE和AF模式。
                captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                setAutoFlash(captureBuilder);
    
                int rotation = getWindowManager().getDefaultDisplay().getRotation();
                // 设置 方向
                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
    			//创建会话
                CameraCaptureSession.CaptureCallback CaptureCallback
                        = new CameraCaptureSession.CaptureCallback() {
    
                    @Override
                    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                                   @NonNull CaptureRequest request,
                                                   @NonNull TotalCaptureResult result) {
                        Log.e(TAG, mFile.toString());
                        unlockFocus();
                    }
                };
    
                mCaptureSession.stopRepeating();
                mCaptureSession.abortCaptures();
                mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    

    此篇文章主要记录 camera2 自定义相机的简单流程,代码基本都来自于 Google 提供的代码,当然 如果有 Kotlin 的朋友,也可以在文章 开头进入Google github 地址查看Kotlin 版本。

    展开全文
  • 创建自定义相机界面调用Camera来实现拍照功能。如左图:上方是一个CAPTURE按钮,下方是用来实时显示摄像头预览画面的SurfaceView,通过点击CAPTURE按钮进行拍照,并将拍摄的图片显示到一个新的活动界面,新的活动...

    ebb9b1b54b5ea5d164ce0091e531a67c.png    

    5a193e2acb5864701424fcbdcf553fd0.png

    创建自定义相机界面调用Camera来实现拍照功能。如左图:上方是一个CAPTURE按钮,下方是用来实时显示摄像头预览画面的SurfaceView,通过点击CAPTURE按钮进行拍照,并将拍摄的图片显示到一个新的活动界面,新的活动界面如右图:上方是一个TextView,下方是一个ImageView显示拍摄好的照片。

    拍摄界面的活动代码如下:

    CustomCamera.javapublic class CustomCamera extends AppCompatActivity implements SurfaceHolder.Callback {

    private Camera mCamera;

    private SurfaceView mPreview;

    private SurfaceHolder mHolder;

    private Uri imageUri;

    private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {

    @Override//参数data保存了拍摄好的照片信息

    public void onPictureTaken(byte[] data, Camera camera) {

    File outputImage = new File(Environment.getExternalStorageDirectory(),

    "output_image.jpg");

    try {

    if (outputImage.exists()) {

    outputImage.delete();

    }

    outputImage.createNewFile();

    FileOutputStream fos = new FileOutputStream(outputImage);

    fos.write(data);

    fos.close();

    } catch (IOException e) {

    e.printStackTrace();

    }

    if (Build.VERSION.SDK_INT >= 24) {

    //通过FileProvider.getUriForFile获取URL,参数2应该与Provider在AndroidManifest.xml中定义的authorities标签一致

    imageUri = FileProvider.getUriForFile(CustomCamera.this, "com.example.cameratest.provider", outputImage);

    } else {

    imageUri = Uri.fromFile(outputImage);

    }

    Intent intent = new Intent(CustomCamera.this, ResultAty.class);

    intent.setData(imageUri);//传递存储照片地址的uri

    startActivity(intent);

    CustomCamera.this.finish();

    }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.custom);

    mPreview = findViewById(R.id.preview);

    mHolder = mPreview.getHolder();

    mHolder.addCallback(this);

    mPreview.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

    mCamera.autoFocus(null);

    }

    });

    }

    public void btnCapture(View view) {

    Camera.Parameters parameters = mCamera.getParameters();//获取各项参数

    parameters.setPictureFormat(ImageFormat.JPEG);//设置图片格式

    parameters.setPreviewSize(1920, 1080);//设置预览大小

    parameters.setPictureSize(1920, 1080);//设置保存的图片大小

    parameters.setJpegQuality(100);//设置照片质量

    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//设置自动对焦

    mCamera.setParameters(parameters);

    //获取最清晰的焦距后再进行操作

    mCamera.autoFocus(new Camera.AutoFocusCallback() {

    @Override

    public void onAutoFocus(boolean success, Camera camera) {

    //如果对焦已经完成

    if (success) {

    //相机拍照,回调参数3用来完成拍摄后所处理的操作

    mCamera.takePicture(null, null, mPictureCallback);

    }

    }

    });

    }

    @Override

    protected void onResume() {

    super.onResume();

    if (mCamera == null) {

    mCamera = getCamera();

    if (mHolder != null) {

    setStartPreview(mCamera, mHolder);

    }

    }

    }

    @Override

    protected void onPause() {

    super.onPause();

    releaseCamera();

    }

    /**

    * 获取系统的Camera对象

    *

    * @return

    */

    private Camera getCamera() {

    Camera camera;

    try {

    camera = Camera.open(0);//参数0和1,表示后和前摄像头

    } catch (Exception e) {

    camera = null;

    e.printStackTrace();

    }

    return camera;

    }

    /**

    * 开始预览相机内容

    */

    private void setStartPreview(Camera camera, SurfaceHolder holder) {

    try {

    camera.setPreviewDisplay(holder);//绑定Camera和SurfaceHolder

    camera.setDisplayOrientation(90);//将系统camera预览角度进行调整

    camera.startPreview();//开始在surfaceview中预览相机图像

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

    /**

    * 释放相机资源

    */

    private void releaseCamera() {

    if (mCamera != null) {

    mCamera.setPreviewCallback(null);

    mCamera.stopPreview();//停止相机预览

    mCamera.release();//释放相机资源

    mCamera = null;

    }

    }

    @Override

    public void surfaceCreated(SurfaceHolder holder) {

    setStartPreview(mCamera, mHolder);

    }

    @Override

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    mCamera.stopPreview();

    setStartPreview(mCamera, mHolder);

    }

    @Override

    public void surfaceDestroyed(SurfaceHolder holder) {

    releaseCamera();

    }

    }

    拍摄界面的布局代码如下:

    custom.xml

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:onClick="btnCapture"

    android:text="Capture" />

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    android:id="@+id/preview"

    android:layout_width="match_parent"

    android:layout_height="match_parent" />

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_alignParentBottom="true"

    android:layout_centerHorizontal="true"

    android:text="Welcome to my camera"

    android:textColor="@android:color/holo_red_light"

    android:textSize="20sp" />

    显示界面的活动代码如下:

    ResultAty.javapublic class ResultAty extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.result);

    ImageView imageview = findViewById(R.id.pic);

    //获取照片的uri

    Uri uri = getIntent().getData();

    //将获取到的照片旋转90度竖向显示

    Matrix matrix = new Matrix();

    matrix.setRotate(90);

    Bitmap bitmap;

    try {

    bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));

    } catch (FileNotFoundException e) {

    bitmap = null;

    e.printStackTrace();

    }

    bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

    imageview.setImageBitmap(bitmap);

    }

    }

    显示界面的布局代码如下:

    result.xml

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:gravity="center_horizontal"

    android:text="Capture result"

    android:textSize="30dp" />

    android:id="@+id/pic"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:scaleType="center" />

    本例用到了FileProvider来产生文件的Uri,具体设置参考笔记《Android调用系统相机》

    展开全文
  • Android 自定义相机Camera流程

    千次阅读 2021-04-10 20:33:43
    ## 1、如何利用 Camera 开发自定义相机? a、Camera.getNumberOfCameras() 获取 相机数目返回一个 可用相机集合(前/后置) int num=Camera.getNumberOfCameras(); b、遍历 相机 Camera.CameraInfo.CAMERA_...
  • Android自定义相机Camera

    2015-01-21 18:00:01
    比较完善的Android自定义相机Camera 解决了拉伸 拍照翻转等Bug 喜欢的朋友就下载吧 有异常联系我
  • 关于自定义相机预览界面拉伸问题 1、导致主要变形的原因是Camera预览界面旋转的角度和摄像头挂载的角度不同导致的 2、我们的Activity设置的方向是竖屏,这是手机的自然方向 所以宽比高短 3、角度:所谓屏幕和摄像头...
  • android自定义相机带取景框

    热门讨论 2016-11-19 19:54:18
    自定义相机,带取景框和照片预览
  • Android-自定义相机Camera

    千次阅读 2020-06-14 18:51:56
    由于最近一个项目需要自定义相机这块,踩了很多坑,在这里做个记录,以防忘记。 Android Camera 相关API可以说是Android 生态碎片化最严重的一块 目前有两套Camera Api 以android 5.0为分界线,5.0以下的是Camera ,...
  • 相机预览方向问题对于相机的预览方向我们可以通过如下API进行设置camera.setDisplayOrientation(0);但是,该API影响的是相机的预览方向,对于照片的保存方向并没有什么影响,最终照片保存的方向还是由Camera的图像...
  • Android使用Camera2打造自定义相机

    千次阅读 2017-05-27 23:29:36
    从5.0开始(API Level 21),可以完全控制Android设备相机的新api Camera2(android.hardware.Camera2)被引入了进来。在以前的Camera api(android.hardware.Camera)中,对相机的手动控制需要更改系统才能实现,而且...
  • 自定义相机camera2

    2020-12-17 13:46:07
    自定义camera2拍照功能,,通过TextureView设置自定义相机界面,实现拍照、保存照片,摄像头切换等功能
  • Android自定义相机,切换前后摄像头,照相机拍照
  • Android自定义相机Camera使用

    千次阅读 2021-03-22 16:17:12
    今天我们按步骤完成一个自定义相机的制作。 1.Camera相关的API 拍照我们主要用到的两个类,一个是SurfaceView,这个我们在上一次就介绍过了;还有一个就是Camera了。所以我们需要了解一下Camera相关API。 ...
  • android camera src 不调用系统相机,自定义相机拍照,自动与手动对焦,闪光灯等,含图片压缩,缩放尺寸功能。android studio项目,主流开发工具,直接打开使用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,245
精华内容 3,298
关键字:

android自定义相机camera2