Android Camera系列(二):TextureView+Camera

news2024/12/24 9:38:40

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

本系列主要讲述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,https://gitee.com/xiaozhi003/android-camera

参考:

  1. https://github.com/afei-cn/CameraDemo
  2. https://github.com/saki4510t/UVCCamera
  3. https://github.com/google/grafika

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024霸王餐小程序cps,h5公众号小程序开源版系统搭建开发,外卖霸王餐小程序系统源码

目录 前言&#xff1a; 一、霸王餐小程序的操作是怎么样的&#xff1f; 二、霸王餐系统后台 三、怎么搭建部署? 前言&#xff1a; 霸王餐项目基于美团和饿了么平台开发的小程序。 一、霸王餐小程序的操作是怎么样的&#xff1f; 1、进入小程序后选择自己要下单的店铺&am…

MongoDB 向 PostgreSQL 宣战

上周 MongoDB 发布了一份亮眼的季度财报&#xff0c;盘后股价涨幅超过 18%。 值得一提的是&#xff0c;MongoDB 的 CEO Dev Ittycheria 特别提到 MongoDB 正在借助自己的数据库服务 Atlas 从 PostgreSQL 那里挖角。原话是举了一个博彩网站的例子&#xff1a; “Initially, th…

Hvv结束了,裁员提上日程

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect Hvv陆陆续续结…

反常识!科研巨头扎堆夕阳行业?A股研发之王是它?

这是邢不行第 119 期量化小讲堂的分享 作者 | 邢不行 2023年华为研发费用再创新高&#xff0c;高达1600亿。 多年高研发投入让华为在一众领域遥遥领先。 研发费用占全年收入23% 遍观全球&#xff0c;各行各业巨头也极为重视研发。 2022年全球研发投入排名 细数它们的成功史…

严管下快速通道何在?

首先我们要清楚什么是快速通道&#xff1f; 其实就是一句话&#xff0c;是券商为高净值客户提供的一种特殊交易通道。可以提高你的交易速度&#xff0c;但是这里面又细分了很多。 但是VIP通道也就是快速交易通道其实里面还细分了很多种种类的&#xff0c;我们简单区分下&#x…

JavaEE:多线程进阶(CAS)

文章目录 CAS什么是 CASCAS 伪代码 CAS有哪些应用CAS的ABA问题什么是ABA问题ABA问题带来的BUG解决方案 CAS 什么是 CAS CAS: 全称Compare and swap&#xff0c;字面意思:”比较并交换“&#xff0c;一个 CAS 涉及到以下操作: 我们假设内存中的原数据V&#xff0c;旧的预期值A…

【Python报错已解决】`Provisional headers are shown Learn more`

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述&#xff1a;1.1 报错示例&#xff1a;1.2 报错分析&#xff1a;1.3 解决思路&#xff…

软件测试面试如何正确谈薪

又是一波离职高峰&#xff0c;很多小伙伴已经开始投身跳槽的准备中了。大家选择跳槽无非是想增加自己的工资收入&#xff0c;所以面试过程中的谈薪环节就显得尤为重要&#xff0c;谈的好与不好&#xff0c;未来整个的薪资水平都可能受影响。 那面试中&#xff0c;当问到“你的…

SprinBoot+Vue二手回收微信小程序的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue3.6 uniapp代码 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平…

【C++】vector的简单模拟实现

目录 一、vector的基本实现机制&#xff1a; 二、vector的部分接口模拟实现&#xff1a; 1、构造与析构&#xff1a; 1、普通构造&#xff1a; 2、拷贝构造&#xff1a; 3、析构函数&#xff1a; 2、关于扩容&#xff1a; 1、reserve&#xff1a; 2、resize 3、增删查…

SpringCloud开发实战(六):Feign的最佳实践

目录 SpringCloud开发实战&#xff08;一&#xff09;&#xff1a;搭建SpringCloud框架 SpringCloud开发实战&#xff08;二&#xff09;&#xff1a;通过RestTemplate实现远程调用 SpringCloud开发实战&#xff08;三&#xff09;&#xff1a;集成Eureka注册中心 SpringCloud开…

基于SpringBoot的高校BBS在线互动论坛系统

&#x1f4a5;&#x1f4a5;源码和论文下载&#x1f4a5;&#x1f4a5;&#xff1a;基于SpringBoot的高校BBS在线互动论坛系统-源码论文报告数据库.rar 1. 系统介绍 本论文设计并实现了一个基于Spring Boot和Vue的校园论坛系统&#xff0c;该系统分为用户和管理员两个角色。用户…

9/4 链表-力扣 234、19

234.回文链表 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表&#xff1b;如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true 思考&#xff1a;链表遍历只能从前往后&a…

Android studio 更换下载的gradle

首先我们下载gradle 打个比方如果我们下载了一个github上的项目&#xff0c;而它使用的是gradle-6.5-bin.zip https://services.gradle.org/distributions/gradle-6.5-bin.zip 用浏览器去下载&#xff0c;可能需要翻墙 解压到电脑里 找到setting里的这一项&#xff0c;设置…

plc1200 weiluntong001

快接口 快代码 main代码 电脑IP地址 编译&#xff0c;启动仿真&#xff0c;下载到仿真PLCsim 必要时候可以设备离线。 打开并监视块。 打开netto plcsim 添加 本机IP&#xff0c;选择&#xff0c;双击。 PLC启动仿真之后&#xff0c;出现这个IP地址&#xff0…

88、k8s之pv+pvc

一、pv和pvc pv pv&#xff1a;Persistent volume 是k8s虚拟化的存储资源&#xff0c;实际上就是存储&#xff0c;例如本地的硬盘&#xff0c;网络文件系统&#xff08;nfs&#xff09; lvm RAID oss&#xff08;ceph&#xff09; 云存储。 pvc pvc&#xff1a;Persisten…

关于SPI通信失败的一种情况(CRC校验不匹配的问题)

问题 该项目中&#xff0c;使用外置的ADC芯片采集电压电流&#xff0c;主控MCU通过SPI与ADC芯片通信。调试时&#xff0c;SPI通信一直失败&#xff0c;与之前成功的项目对比&#xff0c;发现是SPI配置的问题。 void MX_SPI2_Init(void) {/* USER CODE BEGIN SPI2_Init 0 *//*…

2024.9计算机视觉设计开发工程师专项培训通知

为进一步贯彻落实中共中央印发《关于深化人才发展体制机制改革的意见》和国务院印发《关于“十四五”数字经济发展规划》等有关工作的部署要求&#xff0c;深入实施人才强国战略和创新驱动发展战略&#xff0c;加强全国数字化人才队伍建设&#xff0c;持续推进人工智能从业人员…

fastadmin 文件上传腾讯云

1-安装腾讯云SDK composer require qcloud/cos-sdk-v5 2-腾讯云配置 <?phpnamespace app\common\controller;use Qcloud\Cos\Client; use think\Controller; use think\Db;class Tencent extends Controller {/*** 上传文件* param $config* param $key* return array*/p…

微信公众号《GIS 数据工程:开始您的 ETL 之旅 》 文章删除及原因

微信公众号多次限制付费文章发布&#xff0c;不太明确其原因。我猜可能是得罪了某位大神&#xff0c;这倒是也不是不可能。我这说话口无遮拦&#xff0c;得罪几个人偶尔搞我一下也是应该的 。当然也可能是部分喜欢白嫖的网友一看我收费就不太高兴&#xff0c;偶尔做点小动作也是…