热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android实现Camera2预览和拍照效果

这篇文章主要为大家详细介绍了Android开发之一个类实现Camera2预览和拍照效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

简介

网上对于 Camera2 的介绍有很多,在 Github 上也有很多关于 Camera2 的封装库,但是对于那些库,封装性太强,有时候我们仅仅是需要个简简单单的拍照功能而已,因此,自定义一个 Camera 使之变得轻量级那是非常重要的了。(本文并非重复造轮子, 而是在于学习 Camera2API 的基本功能, 笔记之。)

学习要点:

使用 Android Camera2 API 的基本功能。
迭代连接到设备的所有相机的特征。
显示相机预览和拍摄照片。

Camera2 API 为连接到 Android 设备的各个相机设备提供了一个界面。 它替代了已弃用的 Camera 类。

  • 使用 getCameraIdList 获取所有可用摄像机的列表。 然后,您可以使用 getCameraCharacteristics,并找到适合您需要的最佳相机(前 / 后面,分辨率等)。
  • 创建一个 CameraDevice.StateCallback 的实例并打开相机。 当相机打开时,准备开始相机预览。
  • 使用 TextureView 显示相机预览。 创建一个 CameraCaptureSession 并设置一个重复的 CaptureRequest。
  • 静像拍摄需要几个步骤。 首先,需要通过更新相机预览的 CaptureRequest 来锁定相机的焦点。
  • 然后,以类似的方式,需要运行一个预捕获序列。之后,它准备拍摄一张照片。 创建一个新的 CaptureRequest 并调用 [capture] 。

完成后,别忘了解锁焦点。

实现效果

环境

SDK>21

Camera2 类图

代码实现

CameraPreview.java

/**
 * Created by shenhua on 2017-10-20-0020.
 * Email shenhuanet@126.com
 */
public class CameraPreview extends TextureView {

  private static final String TAG = "CameraPreview";
  private static final SparseIntArray ORIENTATIOnS= new SparseIntArray();//从屏幕旋转转换为JPEG方向
  private static final int MAX_PREVIEW_WIDTH = 1920;//Camera2 API 保证的最大预览宽高
  private static final int MAX_PREVIEW_HEIGHT = 1080;
  private static final int STATE_PREVIEW = 0;//显示相机预览
  private static final int STATE_WAITING_LOCK = 1;//焦点锁定中
  private static final int STATE_WAITING_PRE_CAPTURE = 2;//拍照中
  private static final int STATE_WAITING_NON_PRE_CAPTURE = 3;//其它状态
  private static final int STATE_PICTURE_TAKEN = 4;//拍照完毕
  private int mState = STATE_PREVIEW;
  private int mRatioWidth = 0, mRatioHeight = 0;
  private int mSensorOrientation;
  private boolean mFlashSupported;

  private Semaphore mCameraOpenCloseLock = new Semaphore(1);//使用信号量 Semaphore 进行多线程任务调度
  private Activity activity;
  private File mFile;
  private HandlerThread mBackgroundThread;
  private Handler mBackgroundHandler;
  private Size mPreviewSize;
  private String mCameraId;
  private CameraDevice mCameraDevice;
  private CaptureRequest.Builder mPreviewRequestBuilder;
  private CaptureRequest mPreviewRequest;
  private CameraCaptureSession mCaptureSession;
  private ImageReader mImageReader;

  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);
  }

  public CameraPreview(Context context) {
    this(context, null);
  }

  public CameraPreview(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mFile = new File(getContext().getExternalFilesDir(null), "pic.jpg");
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    if (0 == mRatioWidth || 0 == mRatioHeight) {
      setMeasuredDimension(width, height);
    } else {
      if (width  MAX_PREVIEW_WIDTH) {
          maxPreviewWidth = MAX_PREVIEW_WIDTH;
        }

        if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
          maxPreviewHeight = MAX_PREVIEW_HEIGHT;
        }

        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
            rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
            maxPreviewHeight, largest);

        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
          setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        } else {
          setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
        }
        Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
        mFlashSupported = available == null ? false : available;

        mCameraId = cameraId;
        return;
      }
    } catch (CameraAccessException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      Log.e(TAG, "设备不支持Camera2");
    }
  }

  /**
   * 获取一个合适的相机预览尺寸
   *
   * @param choices      支持的预览尺寸列表
   * @param textureViewWidth 相对宽度
   * @param textureViewHeight 相对高度
   * @param maxWidth     可以选择的最大宽度
   * @param maxHeight     可以选择的最大高度
   * @param aspectRatio    宽高比
   * @return 最佳预览尺寸
   */
  private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight,
                     int maxWidth, int maxHeight, Size aspectRatio) {
    List bigEnough = new ArrayList<>();
    List notBigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
      if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
          option.getHeight() == option.getWidth() * h / w) {
        if (option.getWidth() >= textureViewWidth &&
            option.getHeight() >= textureViewHeight) {
          bigEnough.add(option);
        } else {
          notBigEnough.add(option);
        }
      }
    }
    if (bigEnough.size() > 0) {
      return Collections.min(bigEnough, new CompareSizesByArea());
    } else if (notBigEnough.size() > 0) {
      return Collections.max(notBigEnough, new CompareSizesByArea());
    } else {
      Log.e(TAG, "Couldn't find any suitable preview size");
      return choices[0];
    }
  }

  /**
   * 为相机预览创建新的CameraCaptureSession
   */
  private void createCameraPreviewSession() {
    try {
      SurfaceTexture texture = this.getSurfaceTexture();
      assert texture != null;
      // 将默认缓冲区的大小配置为想要的相机预览的大小
      texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
      Surface surface = new Surface(texture);
      mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      mPreviewRequestBuilder.addTarget(surface);
      // 我们创建一个 CameraCaptureSession 来进行相机预览
      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();
    }
  }

  /**
   * 从指定的屏幕旋转中检索照片方向
   *
   * @param rotation 屏幕方向
   * @return 照片方向(0,90,270,360)
   */
  private int getOrientation(int rotation) {
    return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
  }

  /**
   * 锁定焦点
   */
  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();
    }
  }

  /**
   * 解锁焦点
   */
  private void unlockFocus() {
    try {
      mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
          CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
      setAutoFlash(mPreviewRequestBuilder);
      mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
          mBackgroundHandler);
      mState = STATE_PREVIEW;
      mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
          mBackgroundHandler);
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
  }

  /**
   * 拍摄静态图片
   */
  private void captureStillPicture() {
    try {
      if (null == activity || null == mCameraDevice) {
        return;
      }
      final CaptureRequest.Builder captureBuilder =
          mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
      captureBuilder.addTarget(mImageReader.getSurface());
      captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
          CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
      setAutoFlash(captureBuilder);
      // 方向
      int rotation = activity.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) {
          Toast.makeText(getContext(), "Saved: " + mFile, Toast.LENGTH_SHORT).show();
          Log.d(TAG, mFile.toString());
          unlockFocus();
        }
      };
      mCaptureSession.stopRepeating();
      mCaptureSession.abortCaptures();
      mCaptureSession.capture(captureBuilder.build(), captureCallback, null);
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
  }

  /**
   * 运行preCapture序列来捕获静止图像
   */
  private void runPreCaptureSequence() {
    try {
      // 设置拍照参数请求
      mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
          CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
      mState = STATE_WAITING_PRE_CAPTURE;
      mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
      e.printStackTrace();
    }
  }

  /**
   * 比较两者大小
   */
  private static class CompareSizesByArea implements Comparator {

    @Override
    public int compare(Size lhs, Size rhs) {
      return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
          (long) rhs.getWidth() * rhs.getHeight());
    }
  }

  /**
   * ImageReader的回调对象
   */
  private final ImageReader.OnImageAvailableListener mOnImageAvailableListener= new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(ImageReader reader) {
      mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
    }
  };

  /**
   * 将捕获到的图像保存到指定的文件中
   */
  private static class ImageSaver implements Runnable {

    private final Image mImage;
    private final File mFile;

    ImageSaver(Image image, File file) {
      mImage = image;
      mFile = file;
    }

    @Override
    public void run() {
      ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
      byte[] bytes = new byte[buffer.remaining()];
      buffer.get(bytes);
      FileOutputStream output = null;
      try {
        output = new FileOutputStream(mFile);
        output.write(bytes);
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        mImage.close();
        if (null != output) {
          try {
            output.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

}

MainActivity.java

public class MainActivity extends AppCompatActivity {

  CameraPreview cameraView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    cameraView = (CameraPreview) findViewById(R.id.cameraView);
  }

  @Override
  protected void onResume() {
    super.onResume();
    cameraView.onResume(this);
  }

  @Override
  protected void onPause() {
    cameraView.onPause();
    super.onPause();
  }

  public void takePic(View view) {
    cameraView.takePicture();
  }
}

activity_main.xml

<&#63;xml version="1.0" encoding="utf-8"&#63;>


  

  

资源文件 ic_capture_200px.xml


  
  

其它

Manifest 权限:




Android6.0 运行时权限未贴出。(注意:为了方便读者手机端阅读,本文代码部分的成员变量使用了行尾注释,在正常编程习惯中,请使用 /* / 注释。)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细介绍了如何使用PHP检测AJAX请求,通过分析预定义服务器变量来判断请求是否来自XMLHttpRequest。此方法简单实用,适用于各种Web开发场景。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文探讨了 RESTful API 和传统接口之间的关键差异,解释了为什么 RESTful API 在设计和实现上具有独特的优势。 ... [详细]
  • 本文详细探讨了在Android 8.0设备上使用ChinaCock的TCCBarcodeScanner进行扫码时出现的应用闪退问题,并提供了解决方案。通过调整配置文件,可以有效避免这一问题。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 本文介绍如何在应用程序中使用文本输入框创建密码输入框,并通过设置掩码来隐藏用户输入的内容。我们将详细解释代码实现,并提供专业的补充说明。 ... [详细]
  • 本文介绍如何通过SQL查询从JDE(JD Edwards)系统中提取所有字典数据,涵盖关键表的关联和字段选择。具体包括F0004和F0005系列表的数据提取方法。 ... [详细]
  • 本文介绍如何使用 NSTimer 实现倒计时功能,详细讲解了初始化方法、参数配置以及具体实现步骤。通过示例代码展示如何创建和管理定时器,确保在指定时间间隔内执行特定任务。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
author-avatar
小豇豆和花椰菜
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有