当前位置:   article > 正文

Android之 Camera相机使用_android camera

android camera

一 简介

1.1 随着信息时代的发展,相机在我们生活中使用越来越频繁,也成为手机的基本配置之一。相机可以用来拍照,拍视频,人脸识别,视频聊天,扫码支付,监控等常见领域

不管什么场景,基本原理都差不多,都要先通过相机采集原始数据,也就是二进制字节数据,我们可以对原始数据做对应的操作,比如保存成图片,或者分析数据内容等等。

1.2 Android相机的API到目前发展了3个版本,如下面官方api文档所示

Camera
此类是用于控制设备相机的旧版 API,现已弃用,在Android5.0以下使用
Camera2
此软件包是用于控制设备相机的主要 API,Android5.0以上使用
CameraX
基于Camera 2 API封装,简化了开发流程,并增加生命周期控制

1.3 各版本优点

Camera

  • 检测设备摄像头,打开相机
  • 创建预览画面,显示实时预览画面
  • 设置相机参数,进行拍照监听
  • 监听中,保存图片资源或者直接操作原始数据
  • 释放相机资源

Camera2

  • 改进了新硬件的性能。Supported Hardware Level的概念,不同厂商对Camera2的支持程度不同,从低到高有LEGACY、LIMITED、FULL 和 LEVEL_3四个级别
  • 以更快的间隔拍摄图像
  • 显示来自多个摄像机的预览
  • 直接应用效果和滤镜

CameraX

  • CameraX 是一个 Jetpack 支持库,目的是简化Camera的开发工作,它是基于Camera2 API的基础,向后兼容至 Android 5.0(API 级别 21)。
  • 易用性,只需要几行代码就可以实现预览和拍照
  • 保持设备的一致性,在不同相机设备上,对宽高比、屏幕方向、旋转、预览大小和高分辨率图片大小,做到都可以正常使用
  • 相机特性的扩展,增加人像、HDR、夜间模式和美颜等功能

1.4 如果对相机要求不高,只是单纯的解析数据,那用Camera就可以了。虽然官方说已过时,但还是有非常多的场合使用。 

二 基于扫码支付的Camera的使用 

2.1 添加相机权限

  1. <uses-feature
  2. android:name="android.hardware.camera"
  3. android:required="true" />
  4. <uses-permission android:name="android.permission.CAMERA" />

2.2 检查相机权限

  1. //权限请求
  2. public final int REQUEST_CAMERA_PERMISSION = 1;
  3. private String cameraPermission = Manifest.permission.CAMERA;
  4. private void checkCameraPermission() {
  5. //检查是否有相机权限
  6. if (ContextCompat.checkSelfPermission(this, cameraPermission) != PackageManager.PERMISSION_GRANTED) {
  7. //没权限,请求权限
  8. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
  9. REQUEST_CAMERA_PERMISSION);
  10. } else {
  11. //有权限
  12. createSurfaceView();
  13. }
  14. }
  15. //权限请求回调
  16. @Override
  17. public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
  18. switch (requestCode) {
  19. case REQUEST_CAMERA_PERMISSION:
  20. if (grantResults != null && grantResults.length > 0
  21. && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  22. //用户同意权限
  23. createSurfaceView();
  24. } else {
  25. // 权限被用户拒绝了,可以提示用户,关闭界面等等。
  26. Toast.makeText(this, "拒绝权限,请去设置里面手动开启权限", Toast.LENGTH_SHORT).show();
  27. }
  28. break;
  29. }
  30. }

2.3 用SurfaceView来加载相机画面

  1. /**
  2. * 创建预览画面
  3. */
  4. private SurfaceView surfaceView;
  5. private SurfaceHolder mHolder;
  6. public void createSurfaceView() {
  7. //创建预览
  8. surfaceView = new SurfaceView(this);
  9. flContent.removeAllViews();
  10. flContent.addView(surfaceView);
  11. //获取预览的管理器
  12. mHolder = surfaceView.getHolder();
  13. //监听预览状态
  14. mHolder.addCallback(new SurfaceHolder.Callback() {
  15. @Override
  16. public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
  17. //预览控件创建成功的时候,打开相机并预览
  18. openCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
  19. }
  20. @Override
  21. public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
  22. }
  23. @Override
  24. public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
  25. //预览控件销毁的时候,释放相机资源
  26. releaseCamera();
  27. }
  28. });
  29. }

2.4 打开相机

  1. private Camera mCamera;
  2. private Camera.CameraInfo cameraInfo;
  3. /**
  4. * 打开相机
  5. * Camera.CameraInfo.CAMERA_FACING_FRONT前置
  6. * Camera.CameraInfo.CAMERA_FACING_BACK后置
  7. *
  8. * @param cameraIndex 摄像头的方位
  9. */
  10. public void openCamera(int cameraIndex) {
  11. try {
  12. //先释放相机资源
  13. releaseCamera();
  14. //获取相机信息
  15. if (cameraInfo == null) {
  16. cameraInfo = new Camera.CameraInfo();
  17. }
  18. //获取相机个数
  19. int cameraCount = Camera.getNumberOfCameras();
  20. //由于不知道第几个是前置摄像头,遍历获取前置摄像头
  21. for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
  22. Camera.getCameraInfo(camIdx, cameraInfo);
  23. if (cameraInfo.facing == cameraIndex) {
  24. mCamera = Camera.open(camIdx);
  25. break;
  26. }
  27. }
  28. //开启预览
  29. startPreview();
  30. } catch (Exception e) {
  31. //获取相机异常
  32. mCamera = null;
  33. }
  34. }

2.5 开始预览

  1. /**
  2. * 开始预览
  3. */
  4. public void startPreview() {
  5. try {
  6. //获取屏幕宽高,预览尺寸默认为屏幕的屏幕
  7. WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  8. Display display = manager.getDefaultDisplay();
  9. int shortSize = display.getWidth();
  10. int longSize = display.getHeight();
  11. //设置相机参数
  12. initPreviewParams(shortSize, longSize);
  13. //设置相机方向
  14. adjustCameraOrientation();
  15. //预览方式一,没缓冲区,会频繁GC
  16. //mCamera.setPreviewCallback(previewCallback);
  17. //绑定预览视图
  18. mCamera.setPreviewDisplay(mHolder);
  19. //设置缓冲区
  20. mCamera.addCallbackBuffer(new byte[shortSize * longSize * 3 / 2]);
  21. mCamera.setPreviewCallbackWithBuffer(previewCallback);
  22. //开始预览
  23. mCamera.startPreview();
  24. } catch (IOException e) {
  25. }
  26. }

2.6 配置相机参数和预览大小

  1. /**
  2. * 设置相机参数
  3. */
  4. private void initPreviewParams(int shortSize, int longSize) {
  5. if (mCamera != null) {
  6. Camera.Parameters parameters = mCamera.getParameters();
  7. //获取手机支持的尺寸
  8. List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
  9. //获取合适的预览尺寸,保证不变形
  10. Camera.Size bestSize = getBestSize(shortSize, longSize, sizes);
  11. //设置预览大小
  12. parameters.setPreviewSize(bestSize.width, bestSize.height);
  13. //设置图片大小,拍照
  14. parameters.setPictureSize(bestSize.width, bestSize.height);
  15. //设置格式,所有的相机都支持 NV21格式
  16. parameters.setPreviewFormat(ImageFormat.NV21);
  17. //设置聚焦,如果拍照就设置持续对焦FOCUS_MODE_CONTINUOUS_PICTURE,其它可以自动对焦FOCUS_MODE_AUTO
  18. parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
  19. mCamera.setParameters(parameters);
  20. }
  21. }
  22. /**
  23. * 获取预览最佳尺寸
  24. */
  25. private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
  26. Camera.Size bestSize = null;
  27. float uiRatio = (float) longSize / shortSize;
  28. float minRatio = uiRatio;
  29. for (Camera.Size previewSize : sizes) {
  30. float cameraRatio = (float) previewSize.width / previewSize.height;
  31. //如果找不到比例相同的,找一个最近的,防止预览变形
  32. float offset = Math.abs(cameraRatio - minRatio);
  33. if (offset < minRatio) {
  34. minRatio = offset;
  35. bestSize = previewSize;
  36. }
  37. //比例相同
  38. if (uiRatio == cameraRatio) {
  39. bestSize = previewSize;
  40. break;
  41. }
  42. }
  43. return bestSize;
  44. }

2.7 配置预览方向

  1. /**
  2. * 调整预览方向
  3. * 由于手机的图片数据都来自摄像头硬件传感器,这个传感器默认的方向横向的,所以要根据前后摄像头调整方向
  4. */
  5. private void adjustCameraOrientation() {
  6. //判断当前的横竖屏
  7. int rotation = getWindowManager().getDefaultDisplay().getRotation();
  8. int degress = 0;
  9. //获取手机的方向
  10. switch (rotation) {
  11. case Surface.ROTATION_0:
  12. degress = 0;
  13. break;
  14. case Surface.ROTATION_90:
  15. degress = 90;
  16. break;
  17. case Surface.ROTATION_180:
  18. degress = 180;
  19. break;
  20. case Surface.ROTATION_270:
  21. degress = 270;
  22. break;
  23. }
  24. int result = 0;
  25. if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
  26. //后置摄像头
  27. result = (cameraInfo.orientation - degress + 360) % 360;
  28. } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
  29. //前置摄像头,多一步镜像
  30. result = (cameraInfo.orientation + degress) % 360;
  31. result = (360 - result) % 360;
  32. }
  33. mCamera.setDisplayOrientation(result);
  34. }

 2.8 监听预览数据

  1. /**
  2. * 预览数据监听
  3. */
  4. Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
  5. public void onPreviewFrame(byte[] data, Camera camera) {
  6. if (data != null) {
  7. //获取预览分辨率
  8. Camera.Parameters parameters = camera.getParameters();
  9. Camera.Size size = parameters.getPreviewSize();
  10. //拿到字节数组,可以生成图片,也可以解析数据(比如二维码扫描,人脸识别)
  11. //................................
  12. // //创建解码图像,并转换为原始灰度数据,注意图片是被旋转了90度的
  13. // Image source = new Image(size.width, size.height, "Y800");
  14. // //图片旋转了90度,将扫描框的TOP作为left裁剪
  15. // source.setData(data);//填充数据
  16. // //解码,返回值为0代表失败,>0表示成功
  17. // int dataResult = mImageScanner.scanImage(source);
  18. }
  19. //不管有没有数据,重新设置缓冲区,避免频繁GC
  20. camera.addCallbackBuffer(data);
  21. }
  22. };

2.9  拍照并保存照片

  1. /**
  2. * 拍照
  3. */
  4. public void takePicture() {
  5. if (mCamera == null) {
  6. Toast.makeText(this, "请打开相机", Toast.LENGTH_SHORT).show();
  7. return;
  8. }
  9. mCamera.takePicture(new Camera.ShutterCallback() {
  10. @Override
  11. public void onShutter() {
  12. }
  13. }, null, new Camera.PictureCallback() {
  14. @Override
  15. public void onPictureTaken(byte[] data, Camera camera) {
  16. new SavePicAsyncTask(CameraActivity.this, cameraInfo.facing, data).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  17. }
  18. });
  19. }
  20. /**
  21. * 保存图片
  22. */
  23. class SavePicAsyncTask extends AsyncTask<Void, Void, File> {
  24. Context context;
  25. int facing;
  26. byte[] data;
  27. public SavePicAsyncTask(Context context, int facing, byte[] data) {
  28. this.context = context;
  29. this.facing = facing;
  30. this.data = data;
  31. }
  32. @Override
  33. protected File doInBackground(Void... voids) {
  34. //保存的文件
  35. File picFile = null;
  36. try {
  37. Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
  38. if (bitmap == null) {
  39. return null;
  40. }
  41. //保存之前先调整方向
  42. if (facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
  43. Matrix matrix = new Matrix();
  44. matrix.postRotate(90);
  45. bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
  46. } else {
  47. Matrix matrix = new Matrix();
  48. matrix.postRotate(270);
  49. bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
  50. }
  51. // SD卡根目录
  52. File dir = context.getExternalFilesDir("print");
  53. if (!dir.exists()) {
  54. dir.mkdirs();
  55. }
  56. picFile = new File(dir, System.currentTimeMillis() + ".jpg");
  57. FileOutputStream fos = new FileOutputStream(picFile);
  58. bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
  59. fos.close();
  60. bitmap.recycle();
  61. return picFile;
  62. } catch (Exception e) {
  63. e.printStackTrace();
  64. }
  65. return picFile;
  66. }
  67. @Override
  68. protected void onPostExecute(File file) {
  69. super.onPostExecute(file);
  70. if (file != null) {
  71. Toast.makeText(context, "图片保存成功", Toast.LENGTH_SHORT).show();
  72. } else {
  73. Toast.makeText(context, "图片保存失败", Toast.LENGTH_SHORT).show();
  74. }
  75. }
  76. }

2.10 释放相机资源

  1. /**
  2. * 释放相机资源
  3. */
  4. private void releaseCamera() {
  5. if (mCamera != null) {
  6. mCamera.stopPreview();
  7. mCamera.stopFaceDetection();
  8. mCamera.setPreviewCallback(null);
  9. mCamera.release();
  10. mCamera = null;
  11. }
  12. }

三 重点注意

3.1 对焦模式setFocusMode

FOCUS_MODE_AUTO:自动对焦
FOCUS_MODE_INFINITY:无穷远
FOCUS_MODE_MACRO:微距
FOCUS_MODE_FIXED:固定焦距
FOCUS_MODE_EDOF:景深扩展
FOCUS_MODE_CONTINUOUS_PICTURE:持续对焦(针对照片)
FOCUS_MODE_CONTINUOUS_VIDEO:(针对视频)

3.2  预览格式setPreviewFormat,默认返回NV21的数据

ImageFormat.NV16
ImageFormat.NV21
ImageFormat.YUY2
ImageFormat.YV12
ImgaeFormat.RGB_565
ImageFormat.JPEG

3.3  设置预览尺寸,setPreviewSize。根据屏幕尺寸设备最佳预览尺寸

/**
 * 获取预览最佳尺寸
 */
private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
    Camera.Size bestSize = null;
    float uiRatio = (float) longSize / shortSize;
    float minRatio = uiRatio;
    for (Camera.Size previewSize : sizes) {
        float cameraRatio = (float) previewSize.width / previewSize.height;

        //如果找不到比例相同的,找一个最近的,防止预览变形
        float offset = Math.abs(cameraRatio - minRatio);
        if (offset < minRatio) {
            minRatio = offset;
            bestSize = previewSize;
        }
        //比例相同
        if (uiRatio == cameraRatio) {
            bestSize = previewSize;
            break;
        }

    }
    return bestSize;
}

3.4  预览方向,上面说了,传感器方向默认横向的,所以预览成像要调整方向。

下图是传感器方向和需要调整的角度图示

 3.5 拍照图片方向,由于拍照也是存储的传感器方向,所以也需要做旋转

保存之前先调整方向
if (facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
     bitmap = BitmapUtils.rotate(bitmap, 90);
} else {
     bitmap = BitmapUtils.rotate(bitmap, 270);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/46142
推荐阅读
相关标签
  

闽ICP备14008679号