当前位置:   article > 正文

使用Camera2实现预览功能_camera2 打开预览

camera2 打开预览

前言

首先需要知道的是该博客只是简单的将摄像头打开并进行预览的一个操作,对于横屏竖屏切换的一个判断处理并没有实现,后续会进行完善,但是不会在这个博客中进行说明。

其次在编写之前应该对整个预览过程用到的一些重要方法或重要的类有一个理解!

技术前瞻

1. CameraManager

摄像头管理器,用于打开和关闭系统摄像头

  • getCameraIdList()
    返回当前设备中可用的相机列表

  • getCameraCharacteristics(String cameraId)
    根据摄像头id返回该摄像头的相关信息

  • openCamera(String cameraId, final CameraDevice.StateCallback callback,Handler handler)
    打开指定cameraId的相机。参数callback为相机打开时的回调,参数handler为callback被调用时所在的线程

2. CameraDevice

描述系统摄像头,一般都是用于创建Capture请求以及创建CaptureSession会话

  • createCaptureRequest(int templateType)
    创建一个新的Capture请求。参数templateType代表了请求类型,请求类型一共分为六种,分别为:
  1. TEMPLATE_PREVIEW : 创建预览的请求
  2. TEMPLATE_STILL_CAPTURE: 创建一个适合于静态图像捕获的请求,图像质量优先于帧速率
  3. TEMPLATE_RECORD : 创建视频录制的请求
  4. TEMPLATE_VIDEO_SNAPSHOT : 创建视视频录制时截屏的请求
  5. TEMPLATE_ZERO_SHUTTER_LAG : 创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量
  6. TEMPLATE_MANUAL : 创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)
  • createCaptureSession(List<Surface> outputs,CameraCaptureSession.StateCallback callback,Handler handler)
    创建CaptureSession会话。第一个参数 outputs 是一个 List 数组,相机会把捕捉到的图片数据传递给该参数中的 Surface 。第二个参数 StateCallback 是创建会话的状态回调。第三个参数描述了 StateCallback 被调用时所在的线程。

3. CameraCharacteristics

描述摄像头的各种特性,类似于Camera1中的CamerInfo。通过CameraManager的getCameraCharacteristics(String cameraId)方法来获取

  • get(Key<T> key)
    通过制定的key获取相应的相机参数。

常用的key值有:

  1. CameraCharacteristics.LENS_FACING :
    获取摄像头方向。前置摄像头(LENS_FACING_FRONT)或 后置摄像头(LENS_FACING_BACK)
  2. CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:
    获取当前设备支持的相机特性
  3. CameraCharacteristics.SENSOR_ORIENTATION:
    获取摄像头方向
  4. CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP:
    获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
  5. CameraCharacteristics.FLASH_INFO_AVAILABLE:
    是否支持闪光灯
  6. CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT:
    同时检测到人脸的数量
  7. CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES:
    相机支持的人脸检测模式

4. CaptureRequest

描述了一次操作请求,拍照、预览等操作都需要先传入CaptureRequest参数,具体的参数控制也是通过CameraRequest的成员变量来设置

  • addTarget(Surface outputTarget)
    给此次请求添加一个Surface对象作为图像的输出目标

  • set(Key<T> key, T value)
    设置指定的参数值

// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
// 闪光灯
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
// 根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation)
// 人脸检测模式
captureRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE)

5. CameraCaptureSession

当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制(例如:拍照 capture())

  • setRepeatingRequest(CaptureRequest request,
    CaptureCallback listener, Handler handler)

    根据传入的 CaptureRequest 对象开始一个无限循环的捕捉图像的请求。第二个参数 listener 为捕捉图像的回调,在回调中可以拿到捕捉到的图像信息

  • capture( CaptureRequest request,
    CaptureCallback listener, Handler handler)

    拍照。第二个参数为拍照的结果回调

6. CaptureResult

描述拍照完成后的结果

7. ImageReader

用于接收拍照结果和访问拍摄照片的图像数据。
得到一个ImageReader对象的方法为newInstance(int width, int height, int format, int maxImages)。前两个参数是保存图片的宽高,第三个参数为保存图片的格式,第四个参数代表用户可以同时访问到的最大图片数量

注意:
这个参数应该根据具体需业务需求尽可能的小,因为它的数值越大意味着需要消耗的内存就越高

  • acquireNextImage()
    得到ImageReader图像队列中的下一张图片,返回值是一个Image对象

8. Image

一个完整的图片缓存

  • getPlanes()
    获取该图像的像素平面数组。这个数组的大小跟图片的格式有关,如 JPEG格式数组大小为1

9. Plane

图像数据的单色平面

  • getBuffer()
    获取包含帧数据的ByteBuffer。通过这个ByteBuffer我们就可以把图片保存下来

知识前瞻部分的内容,看了一篇帖子写的挺好,就搬运过来,这一部分的顺序很大程度的就是映射着开发中使用他们的顺序。

作者:Smashing丶
链接:https://www.jianshu.com/p/0ea5e201260f
来源:简书

代码实现

 本次demo实现的是使用viewpage2进行左右滑动切换拍照与录像功能,对于布局文件及布局代码在这里不是重点,我就把它给码下来不进行详细的讲解。

MainActivity布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <androidx.viewpager2.widget.ViewPager2
  9. android:id="@+id/camerapage"
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"/>
  12. </RelativeLayout>

MainActivity的Java文件

  1. package com.yjs.cameraapplication;
  2. import androidx.appcompat.app.ActionBar;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import androidx.fragment.app.Fragment;
  5. import androidx.lifecycle.Lifecycle;
  6. import androidx.viewpager.widget.ViewPager;
  7. import androidx.viewpager2.widget.ViewPager2;
  8. import android.Manifest;
  9. import android.graphics.Color;
  10. import android.os.Build;
  11. import android.os.Bundle;
  12. import android.view.TextureView;
  13. import android.view.View;
  14. import android.widget.Adapter;
  15. import com.yjs.cameraapplication.FragmentPackage.RecorderVideoFragment;
  16. import com.yjs.cameraapplication.FragmentPackage.TakePhotoFragment;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. public class MainActivity extends AppCompatActivity {
  20. private String[] permissions={Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO,Manifest.permission.CAMERA};
  21. private ViewPager2 viewPager;
  22. private List<Fragment> fragments;
  23. @Override
  24. protected void onCreate(Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. setContentView(R.layout.activity_main);
  27. /*修改各种状态栏状态*/
  28. changeWindowStatus();
  29. /*初始化各种view*/
  30. initView();
  31. }
  32. private void initView() {
  33. fragments=new ArrayList<Fragment>();
  34. viewPager = findViewById(R.id.camerapage);
  35. TakePhotoFragment takePhotoFragment = new TakePhotoFragment();
  36. fragments.add(takePhotoFragment);
  37. RecorderVideoFragment recorderVideoFragment = new RecorderVideoFragment();
  38. fragments.add(recorderVideoFragment);
  39. MyViewAdapter myViewAdapter = new MyViewAdapter(getSupportFragmentManager(), getLifecycle(), fragments);
  40. viewPager.setAdapter(myViewAdapter);
  41. }
  42. private void changeWindowStatus() {
  43. //隐藏通知栏状态栏
  44. if (Build.VERSION.SDK_INT >= 21) {
  45. View decorView=getWindow().getDecorView();//获取当前界面的decorView
  46. int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  47. |View.SYSTEM_UI_FLAG_FULLSCREEN//隐藏状态栏
  48. |View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整个View的稳定,使其不会随着SystemUI的变化而变化;
  49. |View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//让导航栏悬浮在Activity上
  50. |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隐藏导航栏
  51. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且状态栏和导航栏出现片刻后会自动隐藏
  52. decorView.setSystemUiVisibility(option);
  53. getWindow().setStatusBarColor(Color.TRANSPARENT);//设置透明颜色
  54. getWindow().setNavigationBarColor(Color.TRANSPARENT);
  55. }
  56. ActionBar actionBar=getSupportActionBar();
  57. actionBar.hide();
  58. }
  59. }

使用PageView需要使用AdaptView对每一页进行一个管理 

MyViewAdapter文件

  1. package com.yjs.cameraapplication;
  2. import androidx.annotation.NonNull;
  3. import androidx.fragment.app.Fragment;
  4. import androidx.fragment.app.FragmentManager;
  5. import androidx.fragment.app.FragmentPagerAdapter;
  6. import androidx.lifecycle.Lifecycle;
  7. import androidx.viewpager2.adapter.FragmentStateAdapter;
  8. import java.util.List;
  9. public class MyViewAdapter extends FragmentStateAdapter {
  10. private List<Fragment> fragments;
  11. public MyViewAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List<Fragment> fragmentList) {
  12. super(fragmentManager, lifecycle);
  13. this.fragments=fragmentList;
  14. }
  15. @NonNull
  16. @Override
  17. public Fragment createFragment(int position) {
  18. return fragments.get(position);
  19. }
  20. @Override
  21. public int getItemCount() {
  22. return fragments.size();
  23. }
  24. }

其实对于上面都是基本操作没有什么好说的,但是有一个点可以注意一下,就是对于手机状态栏、功能栏的一些设置。 我在上面的注释中也是表明的比较明白。接下来就是我们核心功能实现代码。

拍照界面Fragment

我们将着重看向拍照界面的预览。首先一个相机预览的流程大概就是初始化TextureView,然后在TextureView的回调中进行设置相机特性,设置完相机特性后将设备相机设备进行打开操作。具体的实现细节将结合代码进行理解。

首先还是最简单的Fragment的布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:context=".FragmentPackage.TakePhotoFragment">
  7. <!-- TODO: Update blank fragment layout -->
  8. <TextureView
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:id="@+id/textureView"/>
  12. <ImageButton
  13. android:id="@+id/takePicture"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_alignParentBottom="true"
  17. android:layout_centerHorizontal="true"
  18. android:layout_marginBottom="50dp"
  19. android:background="@drawable/shape_white_ring"
  20. android:scaleX="0.4"
  21. android:scaleY="0.4"
  22. android:src="@drawable/cam" />
  23. <ImageView
  24. android:id="@+id/image_show"
  25. android:layout_width="50dp"
  26. android:layout_height="50dp"
  27. android:layout_alignParentBottom="true"
  28. android:layout_marginLeft="50dp"
  29. android:layout_marginBottom="50dp" />
  30. <ImageButton
  31. android:id="@+id/change"
  32. android:layout_width="wrap_content"
  33. android:layout_height="wrap_content"
  34. android:src="@drawable/changecam"
  35. android:scaleY="0.2"
  36. android:scaleX="0.2"
  37. android:background="@drawable/shape_white_ring"
  38. android:layout_alignParentBottom="true"
  39. android:layout_marginRight="50dp"
  40. android:layout_marginBottom="50dp"
  41. android:layout_alignParentRight="true"
  42. />
  43. </RelativeLayout>

 实现预览主要还是对TextureView控件的一个操作。我们首先需要在fragment创建View的回调函数中进行控件的绑定,动态授权的操作,以及初始化TextureView

  1. @Override
  2. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  3. Bundle savedInstanceState) {
  4. // Inflate the layout for this fragment
  5. View view = inflater.inflate(R.layout.fragment_take_photo, container, false);
  6. //控件绑定
  7. initView(view);
  8. //初始化TextureView
  9. textureView.setSurfaceTextureListener(textureListener);
  10. //动态授权
  11. getPermission();
  12. return view;
  13. }

控件的绑定、动态授权我这边就不在具体的去讲。下面我将动态授权的代码码出来。

  1. private void getPermission() {
  2. Log.d(TAG, "getPermission: success");
  3. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  4. for (String permission : permissions) {
  5. if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
  6. permissionList.add(permission);
  7. }
  8. }
  9. if (!permissionList.isEmpty()) {
  10. //进行授权
  11. requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
  12. } else {
  13. //表示全部已经授权
  14. //这时候回调一个预览view的回调函数
  15. textureView.setSurfaceTextureListener(textureListener);
  16. }
  17. }
  18. }

接下来我们来看看TextureView的初始化代码运行。

 //初始化TextureView
 textureView.setSurfaceTextureListener(textureListener);

 初始化其实就是设置一个SurfaceTexture的监听者,当其可用的时候进行回调函数

  1. /*SurfaceView状态回调*/
  2. TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
  3. @Override
  4. public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
  5. Log.d(TAG, "onSurfaceTextureAvailable: success");
  6. //首先就需要设置相机,然后再打开相机
  7. setupCamera(width,height);
  8. openCamera();
  9. }
  10. //下面的方法可以先不看,我们先实现相机预览
  11. @Override
  12. public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
  13. }
  14. @Override
  15. public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
  16. return false;
  17. }
  18. @Override
  19. public void onSurfaceTextureUpdated(SurfaceTexture surface) {
  20. }
  21. };

可以看到回调函数中其实就做了两件事, 在SufaceTexture可用的时候,设置相机的特性,设置完之后便打开相机相关操作,首先我们先看一下setupCamera方法进行设置相机

  1. private void setupCamera(int width,int height) {
  2. Log.d(TAG, "setupCamera: success");
  3. CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
  4. try {
  5. String[] cameraIdList = cameraManager.getCameraIdList();
  6. for (String cameraId : cameraIdList) {
  7. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
  8. if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
  9. continue;
  10. }
  11. StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  12. //相机支持的所有分辨率,下一步就是获取最合适的分辨率
  13. Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
  14. Size size = getOptimalSize(outputSizes, width, height);
  15. previewSize = size;
  16. mCameraId = cameraId;
  17. break;
  18. }
  19. } catch (CameraAccessException e) {
  20. e.printStackTrace();
  21. }
  22. }
  1. //选择sizeMap中大于并且接近width和height的size
  2. private Size getOptimalSize(Size[] outputSizes, int width, int height) {
  3. Size tempSize=new Size(width,height);
  4. List<Size> sizes = new ArrayList<>();
  5. for (Size outputSize : outputSizes) {
  6. if (width > height) {
  7. //横屏的时候
  8. if (outputSize.getHeight() > height && outputSize.getWidth() > width) {
  9. sizes.add(outputSize);
  10. }
  11. } else {
  12. //竖屏的时候
  13. if (outputSize.getWidth() > height && outputSize.getHeight() > width) {
  14. sizes.add(outputSize);
  15. }
  16. }
  17. }
  18. if (sizes.size() > 0) {
  19. //如果有多个符合条件找到一个差距最小的,最接近预览分辨率的
  20. tempSize= sizes.get(0);
  21. int minnum = 999999;
  22. for (Size size : sizes) {
  23. int num = size.getHeight() * size.getHeight() - width * height;
  24. if (num < minnum) {
  25. minnum = num;
  26. tempSize = size;
  27. }
  28. }
  29. }
  30. return tempSize;
  31. /*if (sizes.size() > 0) {
  32. return Collections.min(sizes, new Comparator<Size>() {
  33. @Override
  34. public int compare(Size size, Size t1) {
  35. return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
  36. }
  37. });
  38. }
  39. return outputSizes[0];*/
  40. }

看着上面的设置相机部分代码,我们可以知道, 我们通过getActivity().getSystemService(Context.CAMERA_SERVICE)去获得CameraManager去对相机特性进行设置。其中cameraCharacteristics是获取相机的所有特性,例如分辨率、相机前后摄像头等特性的设置。设置相机代码主要设置了后摄的最佳分辨率的一个选择初始化。

 设置好相机之后,我们便可以进行打开相机的操作,在打开相机操作的过程中再初始化一下ImageReader为后面拍照保存图片做好准备。

  1. //打开摄像头
  2. private void openCamera() {
  3. Log.d(TAG, "openCamera: success");
  4. CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
  5. try {
  6. if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  7. // TODO: Consider calling
  8. // ActivityCompat#requestPermissions
  9. // here to request the missing permissions, and then overriding
  10. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  11. // int[] grantResults)
  12. // to handle the case where the user grants the permission. See the documentation
  13. // for ActivityCompat#requestPermissions for more details.
  14. return;
  15. }
  16. cameraManager.openCamera(mCameraId, stateCallback, null);
  17. } catch (CameraAccessException e) {
  18. e.printStackTrace();
  19. }
  20. }

其实打开相机函数,就是通过CameraManager进行openCamera方法,这个方法有三个参数去进行完善,首先第一个参数是相机的Id,因为一个设备上可能有多个相机设备,刚刚的设置相机中已经提及,第二个参数则是监控相机设备状态的回调,第三个参数是设置该方法在哪个线程进行操作,如果是null则代表是在当前线程下进行opencamera。接下来我们来看一下第二个参数监控相机设备的回调函数。

  1. //摄像头状态回调
  2. private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  3. @Override
  4. public void onOpened(@NonNull CameraDevice camera) {
  5. Log.d(TAG, "onOpened: success");
  6. mCameraDevice = camera;
  7. //开启预览
  8. startPreview();
  9. }
  10. @Override
  11. public void onDisconnected(@NonNull CameraDevice camera) {
  12. Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
  13. }
  14. @Override
  15. public void onError(@NonNull CameraDevice camera, int error) {
  16. Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
  17. }
  18. };

当检测到相机设备打开时回调onOpened函数,首先在这时我们可以获得对应的cameradevice对象,再其后开启预览界面startPreview()。

  1. //预览功能
  2. private void startPreview() {
  3. Log.d(TAG, "startPreview: success");
  4. //设置图片阅读器
  5. setImageReader();
  6. //注意这里:sufacetexture跟surfaceview是两个东西,需要注意!
  7. //sufacetexture是textureview的重要属性
  8. SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
  9. //设置textureview的缓存区大小
  10. surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
  11. //设置surface进行预览图像数据
  12. mPreviewSurface = new Surface(surfaceTexture);
  13. //创建CaptureRequest
  14. setCaptureRequest();
  15. //创建capturesession
  16. /*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。
  17. 对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。
  18. 对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/
  19. // previewSurface 用于预览, mImageReader.getSurface() 用于拍照
  20. try {
  21. mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
  22. @Override
  23. public void onConfigured(@NonNull CameraCaptureSession session) {
  24. //当回调创建成功就会调用这个回调
  25. mCaptureSession = session;
  26. setRepeatCapture();
  27. }
  28. @Override
  29. public void onConfigureFailed(@NonNull CameraCaptureSession session) {
  30. }
  31. }, null);
  32. } catch (CameraAccessException e) {
  33. e.printStackTrace();
  34. }
  35. }

在打开预览界面之前,我们先对ImageReader进行一个初始化,为后续拍照做准备。ImageReader主要时实现对图片的保存,以及通知广播对图库数据库进行一个更新。过程还是比较直白,就直接贴一下代码

  1. //设置图片阅读器
  2. private void setImageReader() {
  3. Log.d(TAG, "setImageReader: success");
  4. //创建ImageReader实例,接下来应该是设置一些属性参数
  5. mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);
  6. //果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流
  7. mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
  8. @Override
  9. public void onImageAvailable(ImageReader reader) {
  10. Image image = reader.acquireLatestImage();
  11. //进行保存图片,开一个线程进行保存
  12. ImageSaver imageSaver = new ImageSaver(getContext(), image);
  13. new Thread(imageSaver).start();
  14. Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();
  15. }
  16. }, null);
  17. }
  1. //创建一个图片保存类
  2. public class ImageSaver implements Runnable {
  3. private Context context;
  4. private Image image;
  5. public ImageSaver(Context context, Image image) {
  6. Log.d(TAG, "ImageSaver: success");
  7. this.context = context;
  8. this.image = image;
  9. }
  10. @Override
  11. public void run() {
  12. Image.Plane[] planes = image.getPlanes();
  13. ByteBuffer buffer = planes[0].getBuffer();
  14. byte[] bytes = new byte[buffer.remaining()];
  15. buffer.get(bytes);
  16. System.out.println(planes);
  17. String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/" + System.currentTimeMillis() + ".jpg";
  18. File file = new File(filname);
  19. FileOutputStream fileOutputStream=null;
  20. try {
  21. //保存图片
  22. fileOutputStream = new FileOutputStream(file);
  23. fileOutputStream.write(bytes,0,bytes.length);
  24. } catch (FileNotFoundException e) {
  25. e.printStackTrace();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. finally {
  30. if (fileOutputStream!=null){
  31. try {
  32. fileOutputStream.close();
  33. } catch (IOException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. //最后还要广播通知相册更新数据库
  38. notiBroadcast();
  39. //保存操作结束后,需要用handle进行主线程数据的更新
  40. Message message = new Message();
  41. message.what=0;
  42. Bundle bundle = new Bundle();
  43. bundle.putString("path",filname);
  44. message.setData(bundle);
  45. handler.sendMessage(message);
  46. //image也算是个流也需要进行关闭,否则可能拍照的时候会报错
  47. image.close();
  48. }
  49. }
  50. }
  1. private void notiBroadcast(){
  2. String path = Environment.getExternalStorageDirectory() + "/DCIM/";
  3. Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
  4. Uri uri = Uri.fromFile(new File(path));
  5. intent.setData(uri);
  6. getContext().sendBroadcast(intent);
  7. }
  8. private void setLastImagePath(String path){
  9. Log.d(TAG, "setLastImagePath: success");
  10. //拿到最后一张拍照照片
  11. Bitmap bitmap = BitmapFactory.decodeFile(path);
  12. mImageView.setImageBitmap(bitmap);
  13. }
  1. private Handler handler=new Handler(Looper.myLooper()){
  2. @Override
  3. public void handleMessage(Message msg) {
  4. super.handleMessage(msg);
  5. if (msg.what==0){
  6. Bundle pathdata = msg.getData();
  7. String path = (String) pathdata.get("path");
  8. imageList.add(path);
  9. //设置拍照界面显示的一个图片(左下角的图片预览)
  10. setLastImagePath(path);
  11. }
  12. }
  13. };

 设置好ImageReader之后,以及对Surface进行设置尺寸大小之后,便需要setCaptureRequest();还没讲解setCaptureRequest()之前,可能会疑问Surface是拿来干什么的,接下来,我把setCaptureRequest()码出来就好理解很多。

  1. //无论是预览还是拍照都需要设置capturerequest
  2. private void setCaptureRequest(){
  3. try {
  4. mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  5. mPreviewRequestBuilder.addTarget(mPreviewSurface);
  6. //手动对焦,还不会
  7. /*
  8. TODO
  9. */
  10. // 自动对焦
  11. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
  12. // 闪光灯
  13. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
  14. // 人脸检测模式
  15. mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);
  16. } catch (CameraAccessException e) {
  17. e.printStackTrace();
  18. }
  19. }

可以看到我们设计的相机怎么知道预览界面的,便是通过 mPreviewRequestBuilder.addTarget(mPreviewSurface)进行一个设置,然后builder还可以对自动对焦、闪光灯等进行一个设置,当builder都设置好之后,便可以调用build进行captureRequire的生成。创建好captureRequire之后我们便需要capturesession去调用预览方法,预览方法中需要captureRequire,其实整个过程都是迭迭相加的。

  1. private void setRepeatCapture(){
  2. Log.d(TAG, "setRepeatCapture: success");
  3. mPreviewRequestBuilder.setTag(TAG);
  4. //首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequire
  5. mPreviewRequest=mPreviewRequestBuilder.build();
  6. //session中需要用到capturerequire
  7. try {
  8. mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
  9. @Override
  10. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  11. super.onCaptureCompleted(session, request, result);
  12. }
  13. },null);
  14. } catch (CameraAccessException e) {
  15. e.printStackTrace();
  16. }
  17. }

 致辞预览就简单实现了,但是我个人文字逻辑并不清晰,估计这个就我自己能看懂,我就把拍照的Fragement的所有代码进行码。大家一步一步慢慢看,总能看懂。

  1. package com.yjs.cameraapplication.FragmentPackage;
  2. import android.Manifest;
  3. import android.app.AlertDialog;
  4. import android.content.Context;
  5. import android.content.DialogInterface;
  6. import android.content.Intent;
  7. import android.content.pm.PackageManager;
  8. import android.graphics.Bitmap;
  9. import android.graphics.BitmapFactory;
  10. import android.graphics.Canvas;
  11. import android.graphics.ColorFilter;
  12. import android.graphics.ImageFormat;
  13. import android.graphics.Matrix;
  14. import android.graphics.Paint;
  15. import android.graphics.Path;
  16. import android.graphics.PixelFormat;
  17. import android.graphics.SurfaceTexture;
  18. import android.graphics.drawable.Drawable;
  19. import android.hardware.camera2.CameraAccessException;
  20. import android.hardware.camera2.CameraCaptureSession;
  21. import android.hardware.camera2.CameraCharacteristics;
  22. import android.hardware.camera2.CameraDevice;
  23. import android.hardware.camera2.CameraManager;
  24. import android.hardware.camera2.CaptureRequest;
  25. import android.hardware.camera2.TotalCaptureResult;
  26. import android.hardware.camera2.params.StreamConfigurationMap;
  27. import android.media.Image;
  28. import android.media.ImageReader;
  29. import android.net.Uri;
  30. import android.os.Build;
  31. import android.os.Bundle;
  32. import androidx.annotation.NonNull;
  33. import androidx.annotation.Nullable;
  34. import androidx.core.app.ActivityCompat;
  35. import androidx.core.content.ContextCompat;
  36. import androidx.core.graphics.drawable.RoundedBitmapDrawable;
  37. import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
  38. import androidx.fragment.app.DialogFragment;
  39. import androidx.fragment.app.Fragment;
  40. import android.os.Environment;
  41. import android.os.Handler;
  42. import android.os.Looper;
  43. import android.os.Message;
  44. import android.util.Log;
  45. import android.util.Size;
  46. import android.view.LayoutInflater;
  47. import android.view.Surface;
  48. import android.view.TextureView;
  49. import android.view.View;
  50. import android.view.ViewGroup;
  51. import android.widget.ImageButton;
  52. import android.widget.ImageView;
  53. import android.widget.Toast;
  54. import com.yjs.cameraapplication.MainActivity;
  55. import com.yjs.cameraapplication.R;
  56. import com.yjs.cameraapplication.SelfView.MyImageView;
  57. import java.io.File;
  58. import java.io.FileNotFoundException;
  59. import java.io.FileOutputStream;
  60. import java.io.FilenameFilter;
  61. import java.io.IOException;
  62. import java.nio.ByteBuffer;
  63. import java.sql.Time;
  64. import java.util.ArrayList;
  65. import java.util.Arrays;
  66. import java.util.Collections;
  67. import java.util.Comparator;
  68. import java.util.List;
  69. import java.util.concurrent.TimeUnit;
  70. /**
  71. * A simple {@link Fragment} subclass.
  72. * Use the {@link TakePhotoFragment#newInstance} factory method to
  73. * create an instance of this fragment.
  74. */
  75. public class TakePhotoFragment extends Fragment implements View.OnClickListener {
  76. private static final String TAG = "TakePhotoFragment";
  77. private static final String ARG_PARAM1 = "param1";
  78. private static final String ARG_PARAM2 = "param2";
  79. private String mParam1;
  80. private String mParam2;
  81. /*fragment空间声明*/
  82. private TextureView textureView;
  83. private ImageButton takePhotoBtn;
  84. private ImageView mImageView;
  85. /*private MyImageView mImageView;*/
  86. private ImageButton changeCamBtn;
  87. /*除此之外,还需要一些参数*/
  88. private String mCameraId; //摄像头ID
  89. private Size previewSize; //预览分辨率
  90. private ImageReader mImageReader; //图片阅读器
  91. private static CameraDevice mCameraDevice; //摄像头设备
  92. private static CameraCaptureSession mCaptureSession; //获取会话
  93. private CaptureRequest mPreviewRequest; //获取预览请求
  94. private CaptureRequest.Builder mPreviewRequestBuilder; //获取到预览请求的Builder通过它创建预览请求
  95. private Surface mPreviewSurface; //预览显示图
  96. //新建一个权限链
  97. private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
  98. Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
  99. private List<String> permissionList = new ArrayList();
  100. //添加一个图片集合
  101. List<String> imageList = new ArrayList<>();
  102. private Boolean isCreated=false;
  103. private Boolean isLeave=false;
  104. public TakePhotoFragment() {
  105. // Required empty public constructor
  106. }
  107. /**
  108. * Use this factory method to create a new instance of
  109. * this fragment using the provided parameters.
  110. *
  111. * @param param1 Parameter 1.
  112. * @param param2 Parameter 2.
  113. * @return A new instance of fragment TakePhotoFragment.
  114. */
  115. // TODO: Rename and change types and number of parameters
  116. public static TakePhotoFragment newInstance(String param1, String param2) {
  117. TakePhotoFragment fragment = new TakePhotoFragment();
  118. Bundle args = new Bundle();
  119. args.putString(ARG_PARAM1, param1);
  120. args.putString(ARG_PARAM2, param2);
  121. fragment.setArguments(args);
  122. return fragment;
  123. }
  124. @Override
  125. public void onCreate(Bundle savedInstanceState) {
  126. super.onCreate(savedInstanceState);
  127. if (getArguments() != null) {
  128. mParam1 = getArguments().getString(ARG_PARAM1);
  129. mParam2 = getArguments().getString(ARG_PARAM2);
  130. }
  131. }
  132. @Override
  133. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  134. Bundle savedInstanceState) {
  135. Log.d(TAG, "onCreateView: success");
  136. // Inflate the layout for this fragment
  137. View view = inflater.inflate(R.layout.fragment_take_photo, container, false);
  138. initView(view);
  139. textureView.setSurfaceTextureListener(textureListener);
  140. //动态授权
  141. getPermission();
  142. isCreated=true;
  143. return view;
  144. }
  145. //看看viewpage对fragment的生命周期的影响
  146. @Override
  147. public void onPause() {
  148. super.onPause();
  149. Log.d(TAG, "onPause: success");
  150. closeCamera();
  151. System.out.println(textureView.isAvailable());
  152. if (textureView.isAvailable()){
  153. //如果可用,就是除了进行切换pageview之外的所有操作
  154. isLeave=true;
  155. }
  156. else {
  157. //不可用说明就是直接切换了pageview
  158. closeCamera();
  159. }
  160. }
  161. @Override
  162. public void onStop() {
  163. super.onStop();
  164. Log.d(TAG, "onStop: success");
  165. }
  166. @Override
  167. public void onDestroyView() {
  168. super.onDestroyView();
  169. Log.d(TAG, "onDestroyView: success");
  170. }
  171. @Override
  172. public void onDestroy() {
  173. super.onDestroy();
  174. Log.d(TAG, "onDestroy: success");
  175. }
  176. @Override
  177. public void onResume() {
  178. super.onResume();
  179. Log.d(TAG, "onResume: success");
  180. System.out.println("++++++++++");
  181. System.out.println(textureView.isAvailable());
  182. if (textureView.isAvailable()){
  183. if (isLeave)
  184. {
  185. //息屏等操作会自动关闭camera,所以就得手动再打开一次
  186. openCamera();
  187. isLeave=false;
  188. }
  189. }
  190. }
  191. @Override
  192. public void onStart() {
  193. super.onStart();
  194. Log.d(TAG, "onStart: success");
  195. /*if (textureView.isAvailable()){
  196. openCamera();
  197. }*/
  198. }
  199. @Override
  200. public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  201. super.onViewCreated(view, savedInstanceState);
  202. Log.d(TAG, "onViewCreated: success");
  203. }
  204. private void closeCamera(){
  205. Log.d(TAG, "closeCamera: success");
  206. //首先要关闭session
  207. if (mCaptureSession!=null){
  208. mCaptureSession.close();
  209. }
  210. if (mCameraDevice!=null){
  211. mCameraDevice.close();
  212. }
  213. }
  214. //绑定控件
  215. private void initView(View view) {
  216. textureView = view.findViewById(R.id.textureView);
  217. takePhotoBtn = view.findViewById(R.id.takePicture);
  218. mImageView = view.findViewById(R.id.image_show);
  219. changeCamBtn = view.findViewById(R.id.change);
  220. changeCamBtn.setOnClickListener(this);
  221. }
  222. private void getPermission() {
  223. Log.d(TAG, "getPermission: success");
  224. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  225. for (String permission : permissions) {
  226. if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
  227. permissionList.add(permission);
  228. }
  229. }
  230. if (!permissionList.isEmpty()) {
  231. //进行授权
  232. ActivityCompat.requestPermissions(getActivity(),permissionList.toArray(new String[permissionList.size()]),1);
  233. } else {
  234. //表示全部已经授权
  235. //这时候回调一个预览view的回调函数
  236. textureView.setSurfaceTextureListener(textureListener);
  237. }
  238. }
  239. }
  240. //只能写在Activity中,下次把授权写到activity中,减少麻烦
  241. /*@Override
  242. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  243. Log.d(TAG, "onRequestPermissionsResult: success");
  244. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  245. if (requestCode==1){
  246. if (grantResults.length!=0){
  247. //表示有权限没有授权
  248. getPermission();
  249. }
  250. else {
  251. //表示都授权
  252. openCamera();
  253. }
  254. }
  255. }*/
  256. /*SurfaceView状态回调*/
  257. TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
  258. @Override
  259. public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
  260. Log.d(TAG, "onSurfaceTextureAvailable: success");
  261. //首先就需要设置相机,然后再打开相机
  262. setLastImagePath();
  263. setupCamera(width,height);
  264. openCamera();
  265. }
  266. @Override
  267. public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
  268. }
  269. @Override
  270. public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
  271. return false;
  272. }
  273. @Override
  274. public void onSurfaceTextureUpdated(SurfaceTexture surface) {
  275. }
  276. };
  277. private void setupCamera(int width,int height) {
  278. Log.d(TAG, "setupCamera: success");
  279. CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
  280. try {
  281. String[] cameraIdList = cameraManager.getCameraIdList();
  282. for (String cameraId : cameraIdList) {
  283. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
  284. if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
  285. continue;
  286. }
  287. StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  288. //相机支持的所有分辨率,下一步就是获取最合适的分辨率
  289. Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
  290. Size size = getOptimalSize(outputSizes, width, height);
  291. previewSize = size;
  292. mCameraId = cameraId;
  293. break;
  294. }
  295. } catch (CameraAccessException e) {
  296. e.printStackTrace();
  297. }
  298. }
  299. //打开摄像头
  300. private void openCamera() {
  301. Log.d(TAG, "openCamera: success");
  302. CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
  303. try {
  304. if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  305. // TODO: Consider calling
  306. // ActivityCompat#requestPermissions
  307. // here to request the missing permissions, and then overriding
  308. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  309. // int[] grantResults)
  310. // to handle the case where the user grants the permission. See the documentation
  311. // for ActivityCompat#requestPermissions for more details.
  312. AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
  313. builder.setMessage("该应用需要相机授权,点击授权按钮跳转到设置进行设置");
  314. builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
  315. @Override
  316. public void onClick(DialogInterface dialog, int which) {
  317. getActivity().finish();
  318. }
  319. });
  320. builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
  321. @Override
  322. public void onClick(DialogInterface dialog, int which) {
  323. Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
  324. intent.setData(Uri.fromParts("package", getActivity().getPackageName(), null));
  325. startActivity(intent);
  326. }
  327. }).create().show();
  328. return;
  329. }
  330. cameraManager.openCamera(mCameraId, stateCallback, null);
  331. } catch (CameraAccessException e) {
  332. e.printStackTrace();
  333. }
  334. }
  335. //摄像头状态回调
  336. private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  337. @Override
  338. public void onOpened(@NonNull CameraDevice camera) {
  339. Log.d(TAG, "onOpened: success");
  340. mCameraDevice = camera;
  341. //开启预览
  342. startPreview();
  343. }
  344. @Override
  345. public void onDisconnected(@NonNull CameraDevice camera) {
  346. Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
  347. }
  348. @Override
  349. public void onError(@NonNull CameraDevice camera, int error) {
  350. Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
  351. }
  352. };
  353. //预览功能
  354. private void startPreview() {
  355. Log.d(TAG, "startPreview: success");
  356. //设置图片阅读器
  357. setImageReader();
  358. //注意这里:sufacetexture跟surfaceview是两个东西,需要注意!
  359. //sufacetexture是textureview的重要属性
  360. SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
  361. //设置textureview的缓存区大小
  362. surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
  363. //设置surface进行预览图像数据
  364. mPreviewSurface = new Surface(surfaceTexture);
  365. //创建CaptureRequest
  366. setCaptureRequest();
  367. //创建capturesession
  368. /*Surface表示有多个输出流,我们有几个显示载体,就需要几个输出流。
  369. 对于拍照而言,有两个输出流:一个用于预览、一个用于拍照。
  370. 对于录制视频而言,有两个输出流:一个用于预览、一个用于录制视频。*/
  371. // previewSurface 用于预览, mImageReader.getSurface() 用于拍照
  372. try {
  373. mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
  374. @Override
  375. public void onConfigured(@NonNull CameraCaptureSession session) {
  376. //当回调创建成功就会调用这个回调
  377. mCaptureSession = session;
  378. setRepeatCapture();
  379. }
  380. @Override
  381. public void onConfigureFailed(@NonNull CameraCaptureSession session) {
  382. }
  383. }, null);
  384. } catch (CameraAccessException e) {
  385. e.printStackTrace();
  386. }
  387. }
  388. //设置图片阅读器
  389. private void setImageReader() {
  390. Log.d(TAG, "setImageReader: success");
  391. //创建ImageReader实例,接下来应该是设置一些属性参数
  392. mImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.JPEG, 1);
  393. //果然跟我想的一样,接下来是设置监听当图片流可用的时候的监听器,即为拍照之后产生照片流
  394. mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
  395. @Override
  396. public void onImageAvailable(ImageReader reader) {
  397. Image image = reader.acquireLatestImage();
  398. //进行保存图片,开一个线程进行保存
  399. ImageSaver imageSaver = new ImageSaver(getContext(), image);
  400. new Thread(imageSaver).start();
  401. Toast.makeText(getContext(), "保存图片成功", Toast.LENGTH_SHORT).show();
  402. }
  403. }, null);
  404. }
  405. //选择sizeMap中大于并且接近width和height的size
  406. private Size getOptimalSize(Size[] outputSizes, int width, int height) {
  407. Size tempSize=new Size(width,height);
  408. List<Size> sizes = new ArrayList<>();
  409. for (Size outputSize : outputSizes) {
  410. if (width > height) {
  411. //横屏的时候
  412. if (outputSize.getHeight() > height && outputSize.getWidth() > width) {
  413. sizes.add(outputSize);
  414. }
  415. } else {
  416. //竖屏的时候
  417. if (outputSize.getWidth() > height && outputSize.getHeight() > width) {
  418. sizes.add(outputSize);
  419. }
  420. }
  421. }
  422. if (sizes.size() > 0) {
  423. //如果有多个符合条件找到一个差距最小的,最接近预览分辨率的
  424. tempSize= sizes.get(0);
  425. int minnum = 999999;
  426. for (Size size : sizes) {
  427. int num = size.getHeight() * size.getHeight() - width * height;
  428. if (num < minnum) {
  429. minnum = num;
  430. tempSize = size;
  431. }
  432. }
  433. }
  434. return tempSize;
  435. /*if (sizes.size() > 0) {
  436. return Collections.min(sizes, new Comparator<Size>() {
  437. @Override
  438. public int compare(Size size, Size t1) {
  439. return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
  440. }
  441. });
  442. }
  443. return outputSizes[0];*/
  444. }
  445. /*Fragment控件点击事件*/
  446. @Override
  447. public void onClick(View v) {
  448. switch (v.getId()){
  449. case R.id.change:
  450. changeLens();
  451. break;
  452. }
  453. }
  454. private void changeLens(){
  455. Log.d(TAG, "changeLens: success");
  456. if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))){
  457. mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
  458. }
  459. else {
  460. if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT))){
  461. mCameraId=String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
  462. }
  463. }
  464. CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
  465. try {
  466. CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(mCameraId);
  467. StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  468. Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
  469. Size optimalSize = getOptimalSize(outputSizes, textureView.getWidth(), textureView.getHeight());
  470. previewSize=optimalSize;
  471. } catch (CameraAccessException e) {
  472. e.printStackTrace();
  473. }
  474. /*这里不能直接使用关闭camera这个方法*/
  475. /*closeCamera();*/
  476. closeCamera();
  477. openCamera();
  478. }
  479. //创建一个图片保存类
  480. public class ImageSaver implements Runnable {
  481. private Context context;
  482. private Image image;
  483. public ImageSaver(Context context, Image image) {
  484. Log.d(TAG, "ImageSaver: success");
  485. this.context = context;
  486. this.image = image;
  487. }
  488. @Override
  489. public void run() {
  490. Image.Plane[] planes = image.getPlanes();
  491. ByteBuffer buffer = planes[0].getBuffer();
  492. byte[] bytes = new byte[buffer.remaining()];
  493. buffer.get(bytes);
  494. System.out.println(planes);
  495. String filname = Environment.getExternalStorageDirectory() + "/DCIM/Camera/" + System.currentTimeMillis() + ".jpg";
  496. File file = new File(filname);
  497. FileOutputStream fileOutputStream=null;
  498. try {
  499. //保存图片
  500. fileOutputStream = new FileOutputStream(file);
  501. fileOutputStream.write(bytes,0,bytes.length);
  502. } catch (FileNotFoundException e) {
  503. e.printStackTrace();
  504. } catch (IOException e) {
  505. e.printStackTrace();
  506. }
  507. finally {
  508. if (fileOutputStream!=null){
  509. try {
  510. fileOutputStream.close();
  511. } catch (IOException e) {
  512. e.printStackTrace();
  513. }
  514. }
  515. //最后还要广播通知相册更新数据库
  516. notiBroadcast();
  517. //保存操作结束后,需要用handle进行主线程数据的更新
  518. Message message = new Message();
  519. message.what=0;
  520. Bundle bundle = new Bundle();
  521. bundle.putString("path",filname);
  522. message.setData(bundle);
  523. handler.sendMessage(message);
  524. //image也算是个流也需要进行关闭,否则可能下一次拍照的时候会报错
  525. image.close();
  526. }
  527. }
  528. }
  529. private Handler handler=new Handler(Looper.myLooper()){
  530. @Override
  531. public void handleMessage(Message msg) {
  532. super.handleMessage(msg);
  533. if (msg.what==0){
  534. Bundle pathdata = msg.getData();
  535. String path = (String) pathdata.get("path");
  536. imageList.add(path);
  537. //设置拍照界面显示的一个图片(左下角的图片预览)
  538. setLastImagePath();
  539. }
  540. }
  541. };
  542. private void notiBroadcast(){
  543. String path = Environment.getExternalStorageDirectory() + "/DCIM/";
  544. Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
  545. Uri uri = Uri.fromFile(new File(path));
  546. intent.setData(uri);
  547. getContext().sendBroadcast(intent);
  548. }
  549. private void setLastImagePath(){
  550. Log.d(TAG, "setLastImagePath: success");
  551. //先判断一下手机有没有权限
  552. if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
  553. return;
  554. }
  555. //拿到最后一张拍照照片,遍历所有相册照片
  556. String DCIMPath=Environment.getExternalStorageDirectory()+"/DCIM/Camera";
  557. File file = new File(DCIMPath);
  558. //对该文件夹进行遍历
  559. String[] jpgs = file.list(new FilenameFilter() {
  560. @Override
  561. public boolean accept(File dir, String name) {
  562. if (name.contains("jpg")) {
  563. return true;
  564. } else {
  565. return false;
  566. }
  567. }
  568. });
  569. String finalImagePath=Environment.getExternalStorageDirectory()+"/DCIM/Camera/"+jpgs[jpgs.length-1];
  570. Bitmap bitmap = BitmapFactory.decodeFile(finalImagePath);
  571. /*Canvas canvas = new Canvas();
  572. canvas.drawBitmap(bitmap,0,0,new Paint());
  573. canvas.drawCircle(bitmap.getWidth()/2,bitmap.getHeight()/2,bitmap.getWidth()/2,new Paint());
  574. mImageView.draw(canvas);*/
  575. RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), bitmap);
  576. roundedBitmapDrawable.setCircular(true);
  577. /*mImageView.setImageBitmap(bitmap);*/
  578. mImageView.setImageDrawable(roundedBitmapDrawable);
  579. }
  580. //无论是预览还是拍照都需要设置capturerequest
  581. private void setCaptureRequest(){
  582. try {
  583. mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  584. mPreviewRequestBuilder.addTarget(mPreviewSurface);
  585. //手动对焦,还不会
  586. /*
  587. TODO
  588. */
  589. // 自动对焦
  590. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
  591. // 闪光灯
  592. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
  593. // 人脸检测模式
  594. mPreviewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);
  595. } catch (CameraAccessException e) {
  596. e.printStackTrace();
  597. }
  598. }
  599. private void setRepeatCapture(){
  600. Log.d(TAG, "setRepeatCapture: success");
  601. mPreviewRequestBuilder.setTag(TAG);
  602. //首先要知道整个调用顺序 devices创建出capturebuilder,当builder设置好各种参数之后,就可以build出capturerequire
  603. mPreviewRequest=mPreviewRequestBuilder.build();
  604. //session中需要用到capturerequire
  605. try {
  606. mCaptureSession.setRepeatingRequest(mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
  607. @Override
  608. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  609. super.onCaptureCompleted(session, request, result);
  610. }
  611. },null);
  612. } catch (CameraAccessException e) {
  613. e.printStackTrace();
  614. }
  615. }
  616. }

后续补充

这是写完这篇文章的一个补充,上面的所有代码示例,我也更新到完善Bug之后的代码。首先说一下这次预览主要遇到的问题:因为我设计的这个demo是搭载在Fragment上,所以出现的问题都是在当Fragment不可见的时候出现的问题,例如当切换ViewPage时,切换回相机预览页面,CameraDevice的回调会调disconnnect方法表示连接出错;以及当息屏时,亮屏预览只显示息屏前一帧;以及当在该Activity跳转到其他Activity时,返回的时候,预览也停留在离开该Activity的前一帧。

对于这几个bug的解决之路也是非常艰辛,一开始的时候就发现了第一个情况的bug,没当回事直接找到onPause调用closeCamera方法,然后再resume的时候再重新打开,但是随着调试的不断进行,这个方法只能将第一种情况的bug解决。后续又尝试了getUserVisibleHint回调方法,但是发现程序并不会回调这个方法。

后来没办法,只能从fragment的生命周期出发,发现viewpage切换的时候,textureview会自动变为不可用,滑动回预览界面的时候,textureview在加载完之后会回调自己的监听函数进行重新打开相机的操作,也就是说我们在onpause的时候记得将camera关闭就行,而对于锁屏或是跳到其他Activity返回的时候,可以发现textureview时一直可用的,也就是说上面两个情况重新回到页面,是不会自动调用textureview的加载完的回调函数,所以我们这时候就可以设置一个标识符,然后在resume函数中进行判断,如果符合这个标识符就进行重新打开相机。(onpause的时候也要关闭camera,否则后续会报错)。具体看代码实现

  1. //回调这个函数的时候,无论什么情况都要关闭相机
  2. //若是已经离开该活动的相关操作,只需要将isLeave置为true就可
  3. @Override
  4. public void onPause() {
  5. super.onPause();
  6. Log.d(TAG, "onPause: success");
  7. closeCamera();
  8. System.out.println(textureView.isAvailable());
  9. if (textureView.isAvailable()){
  10. //如果可用,就是除了进行切换pageview之外的所有操作
  11. isLeave=true;
  12. }
  13. /*else {
  14. //不可用说明就是直接切换了pageview
  15. closeCamera();
  16. }*/
  17. }
  18. //这个方法是无论什么操作,回到预览界面都会被回调的,这时候只要稍加区别
  19. //就可以很好的解决预览的各种极端情况下的问题
  20. @Override
  21. public void onResume() {
  22. super.onResume();
  23. Log.d(TAG, "onResume: success");
  24. System.out.println("++++++++++");
  25. System.out.println(textureView.isAvailable());
  26. if (textureView.isAvailable()){
  27. if (isLeave)
  28. {
  29. //息屏等操作会自动关闭camera,所以就得手动再打开一次
  30. openCamera();
  31. isLeave=false;
  32. }
  33. }

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

闽ICP备14008679号