视频采集:Android平台基于Camera2的实现

前言

上一篇介绍了Camera1如何使用,这篇对Camrea2进行分析使用,Camera2 API和旧的Camera1 API区别很大, 刚开始用可能会很不习惯, 但Camera2有很多优势, 提供了非常多的参数供我们控制, 后面API1可能会被移除, 所以可以尽早将项目用Camera2重写。
同样先按步骤看起,后面有完整的代码可以使用。

使用之前需要了解的知识

CameraManager:

摄像头管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用CameraManager的getCameraCharacteristics(String)方法即可获取指定摄像头的相关特性。在Camera打开之前主要操作CameraManager,打开后主要操作CameraCaptureSession。实例:getSystemService(Context.CAMERA_SERVICE);

CameraCharacteristics

摄像头属性,相当于原CameraInfo。通过CameraManager获取指定id的摄像头属性。 通过获取Camera属性信息,配置Camera输出,如FPS,大小,旋转等。实例获取:mCameraManager.getCameraCharacteristics(currentCameraId);

CameraDevice

代表系统摄像头,该类的功能类似于早期的Camera类。用于创建CameraCaptureSession和关闭摄像头。 通过CameraManager打开Camera,在StateCallback中会得到CameraDevice实例;

CameraCaptureSession

Camera打开后主要和CameraCaptureSession打交道,CameraCaptureSession建立了一个和Camera设备的通道,当这个通道建立完成后就可以向Camera发送请求获取图像。当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()。为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera v2 API为CameraCaptureSession提供了StateCallback、CaptureCallback等内部类;

CaptureRequest

当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CaptureRequest参数。CaptureRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CaptureRequest参数进行设置;

CaptureRequest.Builder

CaptureRequest.Builde中主要结构是一个Map,Builder构建后得到CaptureRequest;

采集预览步骤

通俗的说步骤就是:

1、权限的申请 2、给TextureView(我们的显示空间)设置监听setSurfaceTextureListener
3、重写onSurfaceTextureAvailable打开我们的摄像头 4、获取CameraManager
5、获取CameraCharacteristics摄像头属性,设置属性 6、创建一个采集线程在捕获回调中使用
7、如果需要获取采集的流,就获取ImageReader并设置监听setOnImageAvailableListener重写onImageAvailable获取
8、利用cameraManager.openCamera创建CameraDevice回调 9、重写onOpened创建会话设置预览参数
10、利用CameraDevice.createCaptureSession创建捕获回调
11、重写onConfigured创建CaptureSession.setRepeatingRequest回调
可以结合下面的代码进行理解

完整代码

JAVA
public class CameraTowCapture extends AppCompatActivity implements TextureView.SurfaceTextureListener, ImageReader.OnImageAvailableListener {

    private static Range<Integer>[] fpsRanges;
    private static Range<Integer> curFpsRanges;
    private TextureView textureView;
    private Size mPreviewSize;
    private ImageReader mImageReader;
    private CameraCaptureSession mCaptureSession;
    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;
    private CameraDevice mCameraDevice;
    private Context context;
    private CaptureRequest.Builder mPreviewRequestBuilder;
    private int mMinFps = 15;
    private int mMaxFps = 60;

    private final String[] PERMISSIONS = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_tow_capture);
        context = this;
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
        }
        textureView = findViewById(R.id.texture_preview);
        textureView.setSurfaceTextureListener(this);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        startCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    /**
     * 获取摄像头信息
     */
    private void startCamera() {
        //摄像头的管理类
        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        //这个摄像头的配置信息
        try {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics("0");
            //获取最适应帧率
            fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
            for (Range<Integer> fps : fpsRanges){
                Log.e("CameraTowCapture", "fpsRanges=: " + fps.toString());
                if (mMinFps >= fps.getLower() && mMaxFps <= fps.getUpper()){
                    curFpsRanges = new Range<>(mMinFps, mMaxFps);
                    break;
                }
                //计算权重,这里采用差值平方来做比较,也可以采用其他方式计算
                int weight = (fps.getLower() - mMinFps) * (fps.getLower() - mMinFps)
                        + (fps.getUpper() - mMaxFps) * (fps.getUpper() - mMaxFps);
                if (weight < Integer.MAX_VALUE) {
                    Log.e("CameraTowCapture", "fpsRanges=: " + Math.max(fps.getLower(), mMinFps) + "max" + Math.min(fps.getUpper(), mMaxFps));
                    curFpsRanges = new Range<>(Math.max(fps.getLower(), mMinFps), Math.min(fps.getUpper(), mMaxFps));
                }
            }
            Log.d("FPS", "SYNC_MAX_LATENCY_PER_FRAME_CONTROL: " + Arrays.toString(fpsRanges));
            //获取格式
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
            Log.e("CameraTowCapture", "mPreviewSizeWidth=: " + mPreviewSize.getWidth() + "mPreviewSizeHeight=: " + mPreviewSize.getHeight());
            mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),
                    mPreviewSize.getHeight(),
                    ImageFormat.YUV_420_888, 2
            );
            mBackgroundThread = new HandlerThread("CameraBackground");
            mBackgroundThread.start();
            mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
            mImageReader.setOnImageAvailableListener(this, mBackgroundHandler);
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            cameraManager.openCamera("0", mDeviceStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            //成功     前提       绝对
            mCameraDevice = cameraDevice;
            //建立绘画
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
            mCameraDevice = null;
        }
    };


    private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = textureView.getSurfaceTexture();
            //设置预览宽高
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            //创建有一个Surface   画面  ---》1
            Surface surface = new Surface(texture);
            //预览 还不够
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //设置帧率
            Log.e("CameraTowCapture", "curFpsRanges=: " + curFpsRanges.toString());
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, curFpsRanges);
            //预览的TexetureView
            mPreviewRequestBuilder.addTarget(surface);

            //必须设置  不然  文件   -----》
            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
            //            保存摄像头   数据  ---H264码流
            //  各种回调了
            //建立 链接     目的  几路 数据出口
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), mCaptureStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {

        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
//            系统的相机
            // The camera is already closed
            if (null == mCameraDevice) {
                return;
            }
            mCaptureSession = session;
            try {
                mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
                        new CameraCaptureSession.CaptureCallback() {
                        }, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
        }
    };

    @Override
    public void onImageAvailable(ImageReader reader) {
        //你可以认为他是刷新每一帧数据在画面上的作用
        Image image= reader.acquireNextImage();
        //合理的循环
        image.close();
    }

    private Size getBestSupportedSize(List<Size> sizes) {
        Point previewViewSize = null;
        Point maxPreviewSize = new Point(1920, 1080);
        Point minPreviewSize = new Point(1280, 720);
        Size defaultSize = sizes.get(0);
        Size[] tempSizes = sizes.toArray(new Size[0]);
        Arrays.sort(tempSizes, new Comparator<Size>() {
            @Override
            public int compare(Size o1, Size o2) {
                if (o1.getWidth() > o2.getWidth()) {
                    return -1;
                } else if (o1.getWidth() == o2.getWidth()) {
                    return o1.getHeight() > o2.getHeight() ? -1 : 1;
                } else {
                    return 1;
                }
            }
        });
        sizes = new ArrayList<>(Arrays.asList(tempSizes));
        for (int i = sizes.size() - 1; i >= 0; i--) {
            if (maxPreviewSize != null) {
                if (sizes.get(i).getWidth() > maxPreviewSize.x || sizes.get(i).getHeight() > maxPreviewSize.y) {
                    sizes.remove(i);
                    continue;
                }
            }
            if (minPreviewSize != null) {
                if (sizes.get(i).getWidth() < minPreviewSize.x || sizes.get(i).getHeight() < minPreviewSize.y) {
                    sizes.remove(i);
                }
            }
        }
        if (sizes.size() == 0) {
            return defaultSize;
        }
        Size bestSize = sizes.get(0);
        float previewViewRatio;
        if (previewViewSize != null) {
            previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
        } else {
            previewViewRatio = (float) bestSize.getWidth() / (float) bestSize.getHeight();
        }

        if (previewViewRatio > 1) {
            previewViewRatio = 1 / previewViewRatio;
        }

        for (Size s : sizes) {
            if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {
                bestSize = s;
            }
        }
        return bestSize;
    }
XML
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextureView
        android:id="@+id/texture_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

一些只有 Camera2 才支持的高级特性

在开启相机之前检查相机信息

出于某些原因,你可能需要先检查相机信息再决定是否开启相机,例如检查闪光灯是否可用。在 Caemra1 上,你无法在开机相机之前检查详细的相机信息,因为这些信息都是通过一个已经开启的相机实例提供的。在 Camera2 上,我们有了和相机实例完全剥离的 CameraCharacteristics 实例专门提供相机信息,所以我们可以在不开启相机的前提下检查几乎所有的相机信息。

在不开启预览的情况下拍照

在 Camera1 上,开启预览是一个很重要的环节,因为只有在开启预览之后才能进行拍照,因此即使显示预览画面与实际业务需求相违背的时候,你也不得不开启预览。而 Camera2 则不强制要求你必须先开启预览才能拍照。

一次拍摄多张不同格式和尺寸的图片

在 Camera1 上,一次只能拍摄一张图片,更不同谈多张不同格式和尺寸的图片了。而 Camera2 则支持一次拍摄多张图片,甚至是多张格式和尺寸都不同的图片。例如你可以同时拍摄一张 1440x1080 的 JPEG 图片和一张全尺寸的 RAW 图片。

控制曝光时间

在暗环境下拍照的时候,如果能够适当延长曝光时间,就可以让图像画面的亮度得到提高。在 Camera2 上,你可以在规定的曝光时长范围内配置拍照的曝光时间,从而实现拍摄长曝光图片,你甚至可以延长每一帧预览画面的曝光时间让整个预览画面在暗环境下也能保证一定的亮度。而在 Camera1 上你只能 YY 一下。

连拍

连拍 30 张图片这样的功能在 Camera2 出现之前恐怕只有系统相机才能做到了(通过 OpenGL 截取预览画面的做法除外),也可能是出于这个原因,市面上的第三方相机无一例外都不支持连拍。有了 Camera2,你完全可以让你的相机应用程序支持连拍功能,甚至是连续拍 30 张使用不同曝光时间的图片。

灵活的 3A 控制

3A(AF、AE、AWB)的控制在 Camera2 上得到了最大化的放权,应用层可以根据业务需求灵活配置 3A 流程并且实时获取 3A 状态,而 Camera1 在 3A 的控制和监控方面提供的接口则要少了很多。例如你可以在拍照前进行 AE 操作,并且监听本这次拍照是否点亮闪光灯

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值