Android Camera系列(二):TextureView+Camera

两岸猿声啼不住,轻舟已过万重山—李白

本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会

Alt

本章我们来讲解TextureView进行Camera预览,基于上一篇Android Camera系列(一):SurfaceView+Camera的成果,我们已经对Camera进行了封装,CameraManager拿来直接使用就好

一.TextureView使用

优点:
支持复杂的视图变换‌:与SurfaceView不同,TextureView支持包括缩放、旋转在内的各种变换操作,这些操作在视图层次中进行,使得TextureView更加灵活和适应复杂的用户界面需求。
缺点:
性能不如SurfaceView:在低端设备或高GPU负荷情况下,可能会出现掉帧或卡顿现象,低性能可以给一个参考指标大概就是10年前的设备,或者是给欠发达国家提供的低性能的海外设备

TextureView作为Camera的预览视图与SurfaceView不同,TextureView要获取SurfaceTexture,并传递给Camera作为预览容器

CameraManager针对SurfaceTexture的预览接口

    /**
     * 使用TextureView预览Camera
     *
     * @param surface
     */
    @Override
    public synchronized void startPreview(SurfaceTexture surface) {
        Logs.i(TAG, "startPreview...");
        if (isPreviewing) {
            return;
        }
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(surface);
                if (!mPreviewBufferCallbacks.isEmpty()) {
                    mCamera.addCallbackBuffer(new byte[mPreviewWidth * mPreviewHeight * 3 / 2]);
                    mCamera.setPreviewCallbackWithBuffer(mPreviewCallback);
                }
                mCamera.startPreview();
                onPreview(mPreviewWidth, mPreviewHeight);
            } catch (Exception e) {
                onPreviewError(CAMERA_ERROR_PREVIEW, e.getMessage());
            }
        }
    }
  1. 自定义CameraTextureView继承TextureView
  2. 实现TextureView.SurfaceTextureListener接口,并在CameraTextureView初始化时设置回调
  3. 实现自定义CameraCallback接口,监听Camera状态
  4. 一定要实现onResumeonPause接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
/**
 * 摄像头预览TextureView
 *
 * @author xiaozhi
 * @since 2024/8/22
 */
public abstract class BaseTextureView extends TextureView implements TextureView.SurfaceTextureListener, CameraCallback, BaseCameraView {
    private static final String TAG = BaseTextureView.class.getSimpleName();

    private Context mContext;
    private SurfaceTexture mSurfaceTexture;
    private boolean isMirror;
    private boolean hasSurface; // 是否存在摄像头显示层
    private ICameraManager mCameraManager;

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    private int mTextureWidth;
    private int mTextureHeight;

    public BaseTextureView(Context context) {
        super(context);
        init(context);
    }

    public BaseTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public BaseTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mCameraManager = createCameraManager(context);
        mCameraManager.setCameraCallback(this);
        setSurfaceTextureListener(this);
    }

    public abstract ICameraManager createCameraManager(Context context);

    /**
     * 获取摄像头工具类
     *
     * @return
     */
    public ICameraManager getCameraManager() {
        return mCameraManager;
    }

    /**
     * 是否镜像
     *
     * @return
     */
    public boolean isMirror() {
        return isMirror;
    }

    /**
     * 设置是否镜像
     *
     * @param mirror
     */
    public void setMirror(boolean mirror) {
        isMirror = mirror;
        requestLayout();
    }

    private void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @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, width * 4 / 3);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }

        if (isMirror) {
            Matrix transform = new Matrix();
            transform.setScale(-1, 1, getMeasuredWidth() / 2, 0);
            setTransform(transform);
        } else {
            setTransform(null);
        }
    }

    /**
     * 获取SurfaceTexture
     *
     * @return
     */
    @Override
    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
        Logs.i(TAG, "onSurfaceTextureAvailable.");
        mTextureWidth = width;
        mTextureHeight = height;
        mSurfaceTexture = surfaceTexture;
        hasSurface = true;
        openCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
        Logs.i(TAG, "onSurfaceTextureSizeChanged.");
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        Logs.v(TAG, "onSurfaceTextureDestroyed.");
        closeCamera();
        hasSurface = false;
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    }

    /**
     * 打开摄像头并预览
     */
    @Override
    public void onResume() {
        if (hasSurface) {
            // 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()
            // 并不会调用,需要在此处初始化摄像头
            openCamera();
        }
    }

    /**
     * 停止预览并关闭摄像头
     */
    @Override
    public void onPause() {
        closeCamera();
    }

    @Override
    public void onDestroy() {
    }

    /**
     * 初始化摄像头,较为关键的内容
     */
    private void openCamera() {
        if (mSurfaceTexture == null) {
            Logs.e(TAG, "mSurfaceTexture is null.");
            return;
        }
        if (mCameraManager.isOpen()) {
            Logs.w(TAG, "Camera is opened!");
            return;
        }
        mCameraManager.openCamera();
    }

    private void closeCamera() {
        mCameraManager.releaseCamera();
    }

    @Override
    public void onOpen() {
        mCameraManager.startPreview(mSurfaceTexture);
    }

    @Override
    public void onOpenError(int error, String msg) {

    }

    @Override
    public void onPreview(int previewWidth, int previewHeight) {
        if (mTextureWidth > mTextureHeight) {
            setAspectRatio(previewWidth, previewHeight);
        } else {
            setAspectRatio(previewHeight, previewWidth);
        }
    }

    @Override
    public void onPreviewError(int error, String msg) {

    }

    @Override
    public void onClose() {

    }
}

1.Camera操作时机

  • onSurfaceTextureAvailable回调中打开Camera,在onSurfaceTextureDestroyed中关闭摄像头
  • 一定要记得在onResume中也打开摄像头,onPause中关闭摄像头。

再次强调onResumeonPuase中一定也要对Camera进行打开关闭操作。SurfaceTexture的回调和SurfaceHolder不同,页面不显示时SurfaceHolder会destroy,而SurfaceTexture并不会回调onSurfaceTextureDestroyed。如果我们按Home键回到桌面打开系统相机,然后再次进入我们的应用你会发现预览黑屏,这就是没有正确在生命周期中关闭Camera导致。这也是很多别的开源项目正常用没问题,随便退出再进来总有Bug的源头。

2.TextureView计算大小

基本上和CameraSurfaceView一样,我们在onPreview回调中设置TextureView的大小和比例即可

二.最后

本文介绍了Camera+TextureView的基本操作及关键代码。本章内容不是很多,这得益于我们上一章定义好了CameraManager的功劳。下一章介绍GLSurfaceView同样也是用第一章的CameraManager,嘻嘻嘻。

lib-camera库包结构如下:

说明
cameracamera相关操作功能包,包括Camera和Camera2。以及各种预览视图
encoderMediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制
glesopengles操作相关
permission权限相关
util工具类

每个包都可独立使用做到最低的耦合,方便白嫖

github地址:https://github.com/xiaozhi003/AndroidCamera

参考:

  1. https://github.com/afei-cn/CameraDemo
  2. https://github.com/saki4510t/UVCCamera
  3. https://github.com/google/grafika
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值