Android中自定义相机Camera使用

应用中我们时常会遇到要制作拍照上传功能,但我们一般的做法都是直接调起系统的拍照界面或者使用第三方提供的界面。如果用户有定制要求或者我们有自己的一些需求我们该怎么办呢?今天我们按步骤完成一个自定义相机的制作。

1.Camera相关的API

拍照我们主要用到的两个类,一个是SurfaceView,这个我们在上一次就介绍过了;还有一个就是Camera了。所以我们需要了解一下Camera相关API。

  • getNumberOfCameras:获取本设备的摄像头数目。
  • open:打开摄像头,默认打开后置摄像头。如果有多个摄像头,那么open(0)表示打开后置摄像头,open(1)表示打开前置摄像头。
  • getParameters:获取摄像头的拍照参数,返回Camera.Parameters对象。
  • setParameters:设置摄像头的拍照参数。具体的拍照参数通过调用Camera.Parameters的下列方法进行设置。

setPreviewSize

设置预览界面尺寸
setPictureSize设置保存图片的尺寸。
setPictureFormat设置图片格式。一般使用ImageFormat.JPEG表示JPG格式。
setFocusMode设置对焦模式。取值Camera.Parameters.FOCUS_MODE_AUTO只会对焦一次;取值FOCUS_MODE_CONTINUOUS_PICTURE则会连续对焦
  • setPreviewDisplay:设置预览界面的表面持有者,即SurfaceHolder对象。该方法必须在SurfaceHolder.Callback的surfaceCreated方法中调用。
  • startPreview:开始预览。该方法必须在setPreviewDisplay方法之后调用。
  • unlock:录像时需要对摄像头解锁,这样摄像头才能持续录像。该方法必须现在startPreview方法之后调用。
  • setDisplayOrientation:设置预览的角度。Android的0度在三点钟的水平位置,而手机屏幕是垂直位置,从水平位置到垂直位置需要旋转90度。
  • autoFocus:设置对焦事件。参数自动对焦接口AutoFocusCallback的onAutoFocus方法在对焦完成时触发,在此提示用户对焦完毕可以拍照了。
  • takePicture:开始拍照,并设置拍照相关事件。第一个参数为快门回调接口ShutterCallback,它的onShutter方法在按下快门时触发,通常可在此播放拍照声音,默认为“咔嚓”一声;第二个参数的PictureCallback表示原始图像的回调接口,通常无须处理直接传null;第三个参数的PictureCallback表示JPG图像的回调接口,压缩后的图像数据可在该接口中的onPictureTaken方法中获得。
  • setZoomChangeListener:设置缩放比例变化事件。缩放变化监听器OnZoomChangeListener的onZoomChange方法在缩放比例发生变化时触发。
  • setPreviewCallback:设置预览回调事件,通常在连拍时调用。预览回调接口PreviewCallback的onPreviewFrame方法在预览图像发生变化时触发。
  • stopPreview:停止预览。
  • lock:录像完毕对摄像头加锁。该方法在stopPreview方法之后调用。
  • release:释放摄像头。因为摄像头不能重复打开,所以每次退出拍照时都要释放摄像头。

2.代码设置表面视图SurfaceView

接着我们一步一步来实现我们的功能。首先我们要把表面视图做好。

首先我们的布局是这样的。

<?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">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:id="@+id/bt_take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="拍照"/>
</RelativeLayout>

里面一共有两个元素,一个是我们的表面视图SurfaceView,另一个是我们的拍照按钮。

下面是我们Activity的代码

public class MainActivity extends WaterPermissionActivity<MainModel>
        implements MainCallback, View.OnClickListener {

    private Button bt_take_photo;
    private SurfaceView surfaceView;

    @Override
    protected MainModel getModelImp() {
        return new MainModel(this,this);
    }

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        bt_take_photo = findViewById(R.id.bt_take_photo);
        surfaceView = findViewById(R.id.surfaceView);
        bt_take_photo.setOnClickListener(this);
        //获取表面视图的表面持有者
        SurfaceHolder holder = surfaceView.getHolder();
        //给表面持有者添加表面变更监听器
        holder.addCallback(mSurfaceCallback);
        //去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
        holder.setFormat(PixelFormat.TRANSPARENT);
        requestPermission(CAMERA);
    }

    @Override
    protected void doCamera() {
        requestPermission(READ_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDRead() {
        requestPermission(WRITE_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDWrite() {
        
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_take_photo:
                //点击拍照

                break;
        }
    }

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

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

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };
}

MainActivity,MainCallback,MainModel是我的MVC框架结构,不用管他。如果想了解的同学可以看我之前的博客,或者留言问我。代码中还包含了我们请求动态权限的内容,老规矩,大家可以用自己的。

核心代码有两部分,第一部分

//获取表面视图的表面持有者
SurfaceHolder holder = surfaceView.getHolder();
//给表面持有者添加表面变更监听器
holder.addCallback(mSurfaceCallback);
//去除黑色背景,TRANSLUCENT半透明,TRANSPARENT透明
holder.setFormat(PixelFormat.TRANSPARENT);

第二部分就是第一部分中的mSurfaceCallback的定义

private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {

        }

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

        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

        }
    };

清单文件配置的权限也不要忘记

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

到这里,我们的表面视图就设置好了。

3.显示摄像头摄制的画面到SurfaceView上

首先我么引入一个相机操作的工具类,一会儿我们会用到

public class CameraUtil {

    private static final Pattern COMMA_PATTERN = Pattern.compile(",");

    public static Point getSize(Context ctx) {
        WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        Point size = new Point();
        size.x = dm.widthPixels;
        size.y = dm.heightPixels;
        return size;
    }

    public static Point getCameraSize(Camera.Parameters params, Point screenSize) {
        String previewSizeValueString = params.get("preview-size-values");
        if (previewSizeValueString == null) {
            previewSizeValueString = params.get("preview-size-value");
        }
        Point cameraSize = null;
        if (previewSizeValueString != null) {
            cameraSize = findBestPreviewSizeValue(previewSizeValueString, screenSize);
        }
        if (cameraSize == null) {
            cameraSize = new Point((screenSize.x >> 3) << 3, (screenSize.y >> 3) << 3);
        }
        return cameraSize;
    }

    private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenSize) {
        int bestX = 0;
        int bestY = 0;
        int diff = Integer.MAX_VALUE;
        for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
            previewSize = previewSize.trim();
            int dimPosition = previewSize.indexOf('x');
            if (dimPosition < 0) {
                continue;
            }

            int newX;
            int newY;
            try {
                newX = Integer.parseInt(previewSize.substring(0, dimPosition));
                newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
            } catch (NumberFormatException nfe) {
                continue;
            }

            int newDiff = Math.abs((newX - screenSize.x) + (newY - screenSize.y));
            if (newDiff == 0) {
                bestX = newX;
                bestY = newY;
                break;
            } else if (newDiff < diff) {
                bestX = newX;
                bestY = newY;
                diff = newDiff;
            }
        }

        if (bestX > 0 && bestY > 0) {
            return new Point(bestX, bestY);
        }
        return null;
    }
}

接着我们在刚才的MainActivity中,加入如下成员变量

private Camera mCamera;//声明一个相机对象
private boolean isPreviewing;//是否正在预览
private Point mCameraSize;//相机画面的尺寸
private int mCameraType = 0;//设置前置还是后置 0:后置  1:前置
public static int CAMERA_BEHIND = 0; // 后置摄像头
public static int CAMERA_FRONT = 1; // 前置摄像头

在SurfaceHolder.Callback中我们加入如下代码

    /**
     * 表面变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            //打开摄像头
            mCamera = Camera.open(mCameraType);
            try {
                //设置相机的预览页面
                mCamera.setPreviewDisplay(holder);
                //获得相机画面的尺寸
                mCameraSize = CameraUtil.getCameraSize(mCamera.getParameters()
                        , CameraUtil.getSize(MainActivity.this));
                //获取相机的参数信息
                Camera.Parameters parameters = mCamera.getParameters();
                //设置预览界面的尺寸
                parameters.setPreviewSize(1920, 1080);
                //设置图片的分辨率
                parameters.setPictureSize(1920, 1080);
                parameters.setJpegQuality(100); // 设置照片质量
                //设置图片的格式
                parameters.setPictureFormat(ImageFormat.JPEG);
                //设置对焦模式为自动对焦。前置摄像头似乎无法自动对焦
                if (mCameraType == CAMERA_BEHIND) {
                    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                }
                //设置相机的参数信息
                mCamera.setParameters(parameters);
            } catch (IOException e) {
                e.printStackTrace();
                mCamera.release();//遇到异常要释放相机资源
                mCamera = null;
            }
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            //设置相机的展示角度
            mCamera.setDisplayOrientation(90);
            //开始预览画面
            mCamera.startPreview();
            isPreviewing = true;
            //开始自动对焦
            mCamera.autoFocus(null);
            //设置相机的预览监听器。注意这里的setPreviewCallback给连拍功能用
//            mCamera.setPreviewCallback(mPr);
        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            //将预览监听器置空
            mCamera.setPreviewCallback(null);
            //停止预览画面
            mCamera.stopPreview();
            //释放相机资源
            mCamera.release();
            mCamera = null;
        }
    };

这样我们运行起来就会发现SurfaceView显示出了我们的图像。

4.拍照

接下来我们就要进入我们的关键功能拍照了。

首先就是我们拍照按钮的点击事件,我们这里让它调用一个方法

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.bt_take_photo:
            //点击拍照
            doTakePicture();
            break;
    }
}

拍照方法的实现如下

private void doTakePicture(){
    if (isPreviewing && mCamera!=null){
        //命令相机拍摄一张照片
        mCamera.takePicture(mShutterCallback,null,mPictureCallback);
    }
}

这里用到了两个对象,mShutterCallback是我们实现的按快门监听对象,mPictureCallback是我们拍照结果的回调。

mShutterCallback的实现

private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
    @Override
    public void onShutter() {
            
    }
};

这个是我们按下快门后的一个监听,我们可以让程序在这里播放一个咔嚓声,或进行其他的一些操作。

mPictureCallback的实现

private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap raw = null;
            if (null != data) {
                //原始图像数据data是字节数组,需要将其解析成位图
                raw = BitmapFactory.decodeByteArray(data, 0, data.length);
                //停止预览画面
                mCamera.stopPreview();
                isPreviewing = false;
            }
            //旋转位图
            Bitmap bitmap = getRotateBitmap(raw
                    , (mCameraType == CAMERA_BEHIND) ? 90 : -90);
            //获取本次拍摄的照片保存路径
            List<String> listPath = new ArrayList<>();
            listPath.add("myCamera");
            listPath.add("photos");
            mPhotoPath = PathGetUtil.getLongwayPath(MainActivity.this, listPath);
            File fileDir = new File(mPhotoPath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File filePic = new File(mPhotoPath, "ww" + System.currentTimeMillis() + ".jpg");
            if (!filePic.exists()) {
                try {
                    filePic.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            saveImage(filePic.getPath(), bitmap);
            //保存文件需要时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //再次进入预览画面
            mCamera.startPreview();
            isPreviewing = true;
        }
    };

其中我们使用了一个旋转位图的工具方法,代码如下

// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) {
    // 创建操作图片用的矩阵对象
    Matrix matrix = new Matrix();
    // 执行图片的旋转动作
    matrix.postRotate(rotateDegree);
    // 创建并返回旋转后的位图对象
    return Bitmap.createBitmap(b, 0, 0, b.getWidth(),b.getHeight(), matrix, false);
}

成员变量中我们加入一个照片的存储路径

private String mPhotoPath;//照片的保存路径

操作Bitmap相关方法

    public static void saveImage(String path, Bitmap bitmap){
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
            bitmap.compress(Bitmap.CompressFormat.JPEG,80,bos);
            bos.flush();
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这样我们自定义的一个相机功能就完成了。

ps:对于现在的手机,Camera已经可以说过时了。我使用Android 9.0的手机测试,拍摄效果已经不尽如人意。而我们设置的预览尺寸和照片尺寸已经是最大可以支持的1920*1080,所以明显已经不是很适合现在的手机使用。不过作为开发者我们需要对这个技术有一个了解,并且明白基本的一个实现方式。这样对我们之后Camera2和Jetpack中提供的CameraX的学习可以有一个很好的帮助。

### 回答1: Android自定义相机Camera2是一种基于Android Camera2 API的相机应用程序开发技术。相比于旧版Camera API,Camera2 API提供了更多的控制权和更高的性能。使用Camera2 API,开发者可以自定义相机的各种参数,如曝光时间、ISO、焦距等,从而实现更加精细的相机控制。同时,Camera2 API还支持RAW格式的图像输出,使得开发者可以更加灵活地处理相机输出的图像数据。 ### 回答2: Android自定义相机Camera2是基于Android 5.0之后提供的新一代相机API,相比较Camera1,它提供了更加丰富和灵活的功能。在使用Camera2 API时,需要使用一些异步的回调接口,通过监听器来处理相机的各种状态和数据。 Camera2 API 的使用分为三个阶段:预览、拍照和保存图片。其,预览是最基本的功能,也是所有相机功能的基础。可以通过创建CameraDevice,设置相机的参数和预览界面,再通过ImageReader获取相机捕获的数据,最后进行图像显示。 在拍照功能,需要使用相机的Capture请求和CaptureSession会话来进行捕获相片的操作。首先需要创建相机的CaptureRequest.Builder对象,设置拍照相关的参数。然后,需要使用CaptureRequest.Builder构建捕获请求CaptureRequest,再将捕获请求加入到CaptureSession,通过光圈控制、曝光时间、ISO值、白平衡等参数控制相机的拍摄效果。 最后,在保存图片时,需要使用ImageReader对象从相机捕获的数据获取图片数据,然后将其保存到文件或者显示到界面上。 总之,使用Camera2 API自定义相机可以在相机的预览、拍照和保存等各个环节上实现更加灵活的控制,能够充分发挥相机的功能,达到更好的拍照效果。 ### 回答3: Android自定义相机成为了越来越多开发者关注和学习的领域,其相机API2(Camera2)是我们不可忽视的一部分。Android Lollipop时代,Google引入了Camera2 API,它是原来的相机API(Camera)的替代品,提供了很多强大的功能和灵活的控制权,包括更高的FPS、更低的延迟和更好的控制,让我们可以更精确地控制相机硬件,实现更好的相机+应用程序的体验。 使用Camera2 API, 我们可以: 1. 使用更高质量的图像传感器. 2. 以流的方式更容易访问图像预览数据. 3. 支持高帧率的视频录制,甚至支持高达120fps. 4. 可以在更广泛的控制选项(如焦距,曝光等)进行定制,以实现更具创意的摄影模式。 5. 显着提高了拍摄速度和唤醒速度。 然而,相较于Camera API, Camera2 API一个更复杂和庞大的API,需要更多的配置和使用,刚开始可能会让些开发者望而却步,然而掌握Camera2 API后,它将为您带来更多的好处。开始使用Camera2 API不仅需要对Android摄像头架构有很好的理解,还需要一定的Java编程经验和计算机图形知识。 总之,Android自定义相机Camera2是非常复杂和庞大的API,需要开发人员掌握许多技能。但是,一旦掌握,它将为开发人员带来更高的图像质量,更精确的控制和更多的相机配置选项,这将为开发人员提高用户体验的基础,满足客户的各种摄影需求。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值