4-6.Andorid Camera 之拍照编码模板(结合 SurfaceView)

一、Camera

  1. Camera 用于捕获图像和视频

  2. 在 Android 开发的早期阶段,Android 提供 android.hardware.Camera API,开发者用它来访问和控制设备的摄像头硬件

  3. 然而,随着 Android 系统的发展,从 Android 5.0(API 级别 21)开始,Android 引入了一个新的 Camera2 API,以提供更强大和灵活的控制功能


二、Camera 拍照

1、Util
  • MyCameraTool.java
package com.my.camera.util;

import android.hardware.Camera;
import android.util.Log;

import com.my.camera.entity.CameraIdResult;

import java.util.List;

public class MyCameraTool {

    public static final String TAG = MyCameraTool.class.getSimpleName();

    /**
     * 得到摄像头 Id 对象
     *
     * @return 摄像头 Id 对象,它有前置摄像头 Id 和后置摄像头 Id
     */
    public static CameraIdResult getCameraIdResult() {
        CameraIdResult cameraIdResult = new CameraIdResult();

        int numberOfCameras = Camera.getNumberOfCameras();
        Log.i(TAG, "------------------------------ 摄像头个数:" + numberOfCameras);

        for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                Log.i(TAG, "------------------------------ 前置摄像头,cameraId 为:" + cameraId);
                cameraIdResult.setQzCameraId(cameraId);
            }
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                Log.i(TAG, "------------------------------ 后置摄像头,cameraId 为:" + cameraId);
                cameraIdResult.setHzCameraId(cameraId);
            } else {
                Log.i(TAG, "------------------------------ 其他摄像头,cameraId 为:" + cameraId);
            }
        }

        return cameraIdResult;
    }

    /**
     * 根据 SurfaceView 的尺寸和相机支持的预览尺寸来选择最优的预览尺寸
     *
     * @param sizes 相机支持的预览尺寸列表
     * @param w     SurfaceView 的宽度
     * @param h     SurfaceView 的高度
     * @return 最优的预览尺寸,如果没有合适的尺寸则返回 null
     */
    public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        if (sizes == null) return null;

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        int targetHeight = h;

        // 遍历所有支持的预览尺寸
        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;
    }

    /**
     * 打印相机参数信息
     *
     * @param parameters 相机参数对象
     */
    public static void showParameters(Camera.Parameters parameters) {
        Camera.Size previewSize = parameters.getPreviewSize();
        Log.i(TAG, "============================== getPreviewSize");
        Log.i(TAG, "---------- " + previewSize.width + " - " + previewSize.height);

        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        Log.i(TAG, "============================== supportedPreviewSizes");
        Log.i(TAG, "============================== supportedPreviewSizes size: " + supportedPreviewSizes.size());
        for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
            Log.i(TAG, "---------- " + supportedPreviewSize.width + " - " + supportedPreviewSize.height);
        }

        Camera.Size pictureSize = parameters.getPictureSize();
        Log.i(TAG, "============================== getPictureSize");
        Log.i(TAG, "---------- " + pictureSize.width + " - " + pictureSize.height);

        List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
        Log.i(TAG, "============================== getSupportedPictureSizes");
        Log.i(TAG, "============================== getSupportedPictureSizes: " + supportedPictureSizes.size());
        for (Camera.Size supportedPictureSize : supportedPictureSizes) {
            Log.i(TAG, "---------- " + supportedPictureSize.width + " - " + supportedPictureSize.height);
        }

        String focusMode = parameters.getFocusMode();
        Log.i(TAG, "============================== getFocusMode");
        Log.i(TAG,"---------- " + focusMode);

        String whiteBalance = parameters.getWhiteBalance();
        Log.i(TAG, "============================== getWhiteBalance");
        Log.i(TAG,"---------- " + whiteBalance);

        int exposureCompensation = parameters.getExposureCompensation();
        Log.i(TAG, "============================== getExposureCompensation");
        Log.i(TAG,"---------- " + exposureCompensation);

        String flashMode = parameters.getFlashMode();
        Log.i(TAG, "============================== getFlashMode");
        Log.i(TAG,"---------- " + flashMode);
    }
}
  1. MyIOTool.java
package com.my.camera.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * IO 工具类
 *
 */
public class MyIOTool {

    /**
     * 根据指定目录路径创建目录(支持多级目录)
     *
     * @param dirPath 目录路径,如 d:/logs
     */
    public static void createDirectory(String dirPath) {
        File fileDir = new File(dirPath);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
    }

    /**
     * 根据指定文件路径创建文件
     * 如果文件所在目录未创建,则会自动创建
     *
     * @param filePath 文件路径,如 d:/logs/myLogs.log
     */
    public static void createFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            createDirectory(file.getParent());
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据指定文件路径删除文件
     *
     * @param filePath 文件路径
     */
    public static void deleteFile(String filePath) {
        File file = new File(filePath);
        file.delete();
    }

    /**
     * 根据指定文件路径创建文件,若文件存在则覆盖
     *
     * @param filePath 文件路径
     */
    public static void overwriteFile(String filePath) {
        deleteFile(filePath);
        createFile(filePath);
    }

    /**
     * 写入文件
     *
     * @param filePath 文件路径
     * @param data     待写入的数据
     * @param isAppend 是否追加,不是就是覆盖
     */
    public static void writeFile(String filePath, byte[] data, boolean isAppend) {
        File file = new File(filePath);

        if (!file.exists()) {

            // 如果文件不存在,则创建文件
            createFile(filePath);
        } else {

            // 如果文件存在,且指定的是覆盖模式,则覆盖创建文件
            if (!isAppend) overwriteFile(filePath);
        }

        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file, isAppend);
            fileOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 覆盖写入文件
     *
     * @param filePath 文件路径
     * @param data     待写入的数据
     */
    public static void writeFile(String filePath, byte[] data) {
        writeFile(filePath, data, false);
    }
}
2、Activity Layout
  • activity_take_photo.java
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TakePhotoActivity"
    tools:ignore="MissingConstraints">

    <SurfaceView
        android:id="@+id/sv"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_take_photo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="拍照"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3、Activity Code
  • TakePhotoActivity.java
package com.my.camera;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.Button;

import com.my.camera.entity.CameraIdResult;
import com.my.camera.util.MyCameraTool;
import com.my.camera.util.MyIOTool;
import com.my.camera.util.MyImageTool;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

@SuppressWarnings("all")
public class TakePhotoActivity extends AppCompatActivity {

    public static final String TAG = TakePhotoActivity.class.getSimpleName();

    private SurfaceView sv;
    private Button btnTakePhoto;

    private SurfaceHolder surfaceHolder;
    private int hzCameraId;
    private Camera camera;

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

        // 保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        CameraIdResult cameraIdResult = MyCameraTool.getCameraIdResult();
        hzCameraId = cameraIdResult.getHzCameraId();

        sv = findViewById(R.id.sv);
        btnTakePhoto = findViewById(R.id.btn_take_photo);

        surfaceHolder = sv.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                Log.i(TAG, "------------------------------ surfaceCreated");
                openCamera();
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                Log.i(TAG, "------------------------------ surfaceChanged");
                cameraPreview(width, height);
            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                Log.i(TAG, "------------------------------ surfaceDestroyed");
                closeCamera();
            }
        });

        btnTakePhoto.setOnClickListener(v -> takePhoto());
    }

    private void openCamera() {
        try {
            if (camera != null) return;
            if (hzCameraId == -1) return;
            camera = Camera.open(hzCameraId);
            camera.setPreviewDisplay(surfaceHolder);
            camera.setDisplayOrientation(90);
        } catch (IOException e) {
            e.printStackTrace();
            closeCamera();
        }
    }

    private void cameraPreview(int width, int height) {
        if (camera == null) return;
        Camera.Parameters parameters = camera.getParameters();
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        Camera.Size optimalPreviewSize = MyCameraTool.getOptimalPreviewSize(supportedPreviewSizes, width, height);
        Log.i(TAG, "============================== optimalPreviewSize");
        Log.i(TAG, "---------- " + optimalPreviewSize.width + " - " + optimalPreviewSize.height);
        parameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
        parameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
        parameters.setRotation(90);
        camera.setParameters(parameters);
        camera.startPreview();
    }

    private void takePhoto() {
        if (camera == null) return;
        camera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                closeCamera();

                Log.i(TAG, "------------------------------ 得到照片数据 size 为 " + data.length);

                String photoPath = getFilesDir().getPath() + File.separatorChar + UUID.randomUUID().toString() + ".png";
                MyIOTool.writeFile(photoPath, data);
            }
        });
    }

    private void closeCamera() {
        if (camera == null) return;
        camera.stopPreview();
        camera.release();
        camera = null;
    }
}

三、Camera 拍照案例解析

1、添加权限
  • 在 AndroidManifest.xml 文件中添加相关权限,如果是 Android 6.0(API 级别 23)或之后,需要在运行时请求
<uses-permission android:name="android.permission.CAMERA" />
2、保持屏幕常亮
  • 保持屏幕常亮就是不会自动关闭屏幕,不仅是预览图像,它也非常适用于阅读、视频播放等
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
3、选择摄像头
  • 这里借助工具类,选择后置摄像头 ID,准备使用后置摄像头(手机背后的)
CameraIdResult cameraIdResult = MyCameraTool.getCameraIdResult();
hzCameraId = cameraIdResult.getHzCameraId();
4、准备 SurfaceView 作为图像预览容器
  • SurfaceView 控件可用于预览图像、视频播放等
<SurfaceView
    android:id="@+id/sv"
    android:layout_width="300dp"
    android:layout_height="300dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
  • 通过 SurfaceView 对象得到 SurfaceHolder 对象,它作为预览图像显示,它有三个方法
  1. surfaceCreated(@NonNull SurfaceHolder holder):当 Surface 被创建时调用,这里可以打开相机

  2. surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height):当 Surface 的尺寸或格式发生变化时调用,这里可以调整相机预览的尺寸并开始预览

  3. surfaceDestroyed(@NonNull SurfaceHolder holder):当 Surface 被销毁时调用,这里可以关闭相机

sv = findViewById(R.id.sv);

surfaceHolder = sv.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        Log.i(TAG, "------------------------------ surfaceCreated");
        openCamera();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "------------------------------ surfaceChanged");
        cameraPreview(width, height);
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.i(TAG, "------------------------------ surfaceDestroyed");
        closeCamera();
    }
});
5、打开相机
  1. camera = Camera.open(hzCameraId);:通过打开摄像头

  2. camera.setPreviewDisplay(surfaceHolder);:设置预览图像显示

  3. camera.setDisplayOrientation(90);:设置预览方向(否则图像不是正向的)

private void openCamera() {
    try {
        if (camera != null) return;
        if (hzCameraId == -1) return;
        camera = Camera.open(hzCameraId);
        camera.setPreviewDisplay(surfaceHolder);
        camera.setDisplayOrientation(90);
    } catch (IOException e) {
        e.printStackTrace();
        closeCamera();
    }
}
6、调整相机预览的尺寸并开始预览
  1. Camera.Parameters parameters = camera.getParameters();:得到相机参数对象

  2. List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();:得到支持的预览尺寸列表

  3. Camera.Size optimalPreviewSize = MyCameraTool.getOptimalPreviewSize(supportedPreviewSizes, width, height);:借助工具类选择最优的预览尺寸

  4. parameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);:设置图像预览的尺寸

  5. parameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);:设置拍照照片的尺寸

  6. parameters.setRotation(90);:设置照片方向(否则照片不是正向的)

  7. camera.startPreview();:开始预览图像

private void cameraPreview(int width, int height) {
    if (camera == null) return;
    Camera.Parameters parameters = camera.getParameters();
    List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
    Camera.Size optimalPreviewSize = MyCameraTool.getOptimalPreviewSize(supportedPreviewSizes, width, height);
    Log.i(TAG, "============================== optimalPreviewSize");
    Log.i(TAG, "---------- " + optimalPreviewSize.width + " - " + optimalPreviewSize.height);
    parameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
    parameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
    parameters.setRotation(90);
    camera.setParameters(parameters);
    camera.startPreview();
}
7、拍照
  1. 通过调用 takePicture 方法来拍照

  2. 该方法接收 3 个参数,其中 2 个被设置为 null,即不需要关注拍照过程中的快门声和 JPEG 回调

  3. 第 3 个参数是一个 Camera.PictureCallback 实例对象,用于处理拍照完成后获得的照片数据,这里借助工具类将照片存储到私有空间的 files 目录下

private void takePhoto() {
    if (camera == null) return;
    camera.takePicture(null, null, new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            closeCamera();

            Log.i(TAG, "------------------------------ 得到照片数据 size 为 " + data.length);

            String photoPath = getFilesDir().getPath() + File.separatorChar + UUID.randomUUID().toString() + ".png";
            MyIOTool.writeFile(photoPath, data);
        }
    });
}
8、关闭相机
  1. camera.stopPreview();:停止预览图像

  2. camera.release();:释放摄像头资源

private void closeCamera() {
    if (camera == null) return;
    camera.stopPreview();
    camera.release();
    camera = null;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android的USBCamera库是一组用于在Android设备上与USB相机进行交互的API。这个库是通过Android的USB设备框架来实现的。使用这个库,开发人员可以获取USB相机的图像或视频数据,并将其用于实时预览或保存到设备存储器中。 使用USB相机的优势之一是它们可以在各种不同设备之间共享,而不需要额外的软件或驱动程序。与使用内置相机的设备相比,它们通常具有更高的分辨率和更大的存储容量。 USBCamera库提供了一系列API和类,使开发人员能够实现各种与USB相机相关的功能,例如打开相机、设置分辨率和帧率、设置曝光和对焦等等。 另外,USBCamera库也支持与其他Android库和框架联合使用,例如OpenGL ES、OpenCV、TensorFlow等等。通过将数据流式传输到这些库中的算法,可以实现更高级别的图像处理,例如目标检测、人脸识别等。 总的来说,Android的USBCamera库使开发人员能够以更简单的方式与USB相机交互,从而使他们能够更轻松地实现高级别的图像处理应用。 ### 回答2: Android USB Camera库是Android操作系统提供的一种机制,它允许开发人员与USB摄像头进行通信,并且内置了一些API来管理它们。它使得Android设备可以成为一个普通的USB摄像头,可以通过USB端口与计算机连接。最近的Android设备允许使用USB Type-C端口连接USB摄像头,这使得它们可以用作高质量的视频摄像头。 Android USB Camera库的主要功能包括打开USB摄像头,捕获视频和静态图像,在捕获的过程中设置摄像头参数(例如曝光时间、帧速率等),以及对图像进行处理和渲染。它还提供了一个API来访问USB设备列表,并可以选择所需的摄像头。 通过使用Android USB Camera库,开发人员可以创建基于USB摄像头的高质量视频应用程序,例如视频交流、监视和安防应用程序等。这使得Android设备成为了一个能够替代传统摄像头的全功能摄像机,为用户提供更具创意性和拍摄控制权的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值