记录一次Android推流、录像踩坑过程
背景:
按照需求,需要支持APP在手机息屏时进行推流、录像。
技术要点:
1、手机在息屏时能够打开camera获取预览数据
2、获取预览数据时进行编码以及合成视频
一、息屏时获取camera预览数据:
①Camera.setPreviewDisplay(SurfaceHolder holder):
一般常规的打开camera后(Camera.open(int cameraId)),给相机设置预览setPreviewDisplay(SurfaceHolder holder),holder通过surfaceview获取。但是者在surfaceDestroyed(xxxxxx)后无法获取预览数据,所以setPreviewDisplay(SurfaceHolder holder)此方法无法满足息屏的需求。
②Camera.setPreviewTexture(SurfaceTexture surfaceTexture):
此方法通过创建一个new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)传入就可以实现息屏获取相机的预览数据。这样就可以避免直接使用TextureView带来的onSurfaceTextureDestroyed(xxxx)导致息屏后无法获取预览数据。
二、预览camera预览数据:
①Camera.setPreviewTexture(SurfaceTexture surfaceTexture):
获取到yuv数据进行转换成bitmap,然后用Imageview或者Surfaceview直接显示。
此方法带来的弊端:
1、每一帧数据都要生成bitmap,短时间频繁的创建对象会导致STW,从而导致ANR
2、预览数据不流畅,是用Imageview或者Surfaceview手动方式展示的
②Camera.setPreviewDisplay(SurfaceHolder holder):
此方法是Android自带的,没有上述的弊端:ANR、画面卡顿,但是在息屏时无法获取预览数据
③Camera.setPreviewTexture(SurfaceTexture surfaceTexture)+Camera.setPreviewDisplay(SurfaceHolder holder):
此方法既解决了预览问题也解决了息屏获取预览数据问题,但是此方法在MediaMuxer两种模式转换合成音视频时无法合成连续的音视频,只能亮屏时合成一段,息屏时合成一段。不过也尝试在转换模式时,MediaMuxer继续写入数据,虽然视频可以播放但是会导致写入失败,视频画面卡顿在转换的那一帧画面。因为在转换模式时,编码的数据出问题了,大小比之前的要小很多,此问题待研究。
三、解决方案:
采用上述的第三种方法:
Camera.setPreviewTexture(SurfaceTexture surfaceTexture)+Camera.setPreviewDisplay(SurfaceHolder holder);
息屏、切换前后置摄像头时先释放相机releaseCamera(),代码如下:
override fun releaseCamera() {try {stopBackgroundThread()mCamera?.stopPreview()mCamera?.setPreviewCallbackWithBuffer(null)mCamera?.release()mCamera = null} catch (runError: RuntimeException) {KLog.e(TAG, "releaseCamera happened error: " + runError.message)} catch (e: Exception) {KLog.e(TAG, "releaseCamera error: $e")}}
然后再重新打开相机openCamera,代码如下:
override fun openCamera(cameraId: Int,imageFormat: Int,holder: SurfaceHolder?) {mCameraId = cameraIdthis.previewFormat = imageFormatsurfaceHolder = holdermSurfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)openCamera(surfaceHolder, mSurfaceTexture!!, cameraId)}private fun openCamera(surfaceHolder: SurfaceHolder?,surfaceTexture: SurfaceTexture,cameraId: Int) {if (cameraId < 0 /*|| cameraId > Camera.getNumberOfCameras() - 1*/) {Log.w(TAG,"openCamera failed, cameraId=" + cameraId + ", Camera.getNumberOfCameras()=" + Camera.getNumberOfCameras())return}startBackgroundThread()try {
// Log.i(TAG,"surfaceCreated open camera cameraId=$cameraId start")mCamera = Camera.open(cameraId)mCamera?.setDisplayOrientation(90)if (surfaceHolder == null) {mCamera?.setPreviewTexture(surfaceTexture)} else {mCamera?.setPreviewDisplay(surfaceHolder)}// set preview format @{this.previewFormat = setCameraPreviewFormat(mCamera!!, this.previewFormat)// @}// 设置fps@{val minFps: Int = 30000val maxFps: Int = 30000setCameraPreviewFpsRange(mCamera!!, minFps, maxFps)// @}// 设置预览尺寸 @{val hasSetPreviewSize = setCameraPreviewSize(mCamera!!)if (hasSetPreviewSize.size > 1) {/* previewWidth = hasSetPreviewSize[0]previewHeight = hasSetPreviewSize[1]GBApp.getInstance().previewWidth = hasSetPreviewSize[0]GBApp.getInstance().previewHeight = hasSetPreviewSize[1]*/previewWidth = 640previewHeight = 480GBApp.instance!!.previewWidth = 640GBApp.instance!!.previewHeight = 480}// @}// 设置照片尺寸 @{setCameraPictureSize(mCamera!!)// @}// 设置预览回调函数@{mCamera?.setPreviewCallbackWithBuffer(mCameraCallbacks)Log.i(TAG,"ImageFormat: $previewFormat bits per pixel=" + ImageFormat.getBitsPerPixel(previewFormat))// 初始化数组for (index in 0 until previewDataSize) {val previewData = if (previewFormat != ImageFormat.YV12) {ByteArray(previewWidth * previewHeight * ImageFormat.getBitsPerPixel(previewFormat) / 8)} else {val size = ImageUtils.getYV12ImagePixelSize(previewWidth, previewHeight)ByteArray(size)}previewDataArray.add(previewData)}//addAllPreviewCallbackData()mCamera?.addCallbackBuffer(ByteArray(previewWidth * previewHeight * 3 / 2))// @}//autoRatioTextureView()mCamera?.startPreview()} catch (localIOException: IOException) {Log.e(TAG,"surfaceCreated open camera localIOException cameraId=" + cameraId + ", error=" + localIOException.message,localIOException)} catch (run: RuntimeException) {Log.e(TAG,"open camera RuntimeException error=" + run.message)} catch (e: Exception) {Log.e(TAG,"surfaceCreated open camera cameraId=" + cameraId + ", error=" + e.message,e)}}
此情况依旧会导致在切换相机时,出现录制的视频卡在某一帧,解决方案如下:
依旧使用SurfaceView预览相机
1、相机停止写入数据pauseRecord()
// 根据 status 状态是否写入数据
public void pauseRecord() {if (status == Status.RECORDING) {pauseMoment = System.nanoTime() / 1000;status = Status.PAUSED;if (listener != null) listener.onStatusChange(status);}}
2、释放相机
fun releaseCamera() {try {stopBackgroundThread()mCamera?.stopPreview()mCamera?.setPreviewCallbackWithBuffer(null)mCamera?.release()mCamera = null} catch (runError: RuntimeException) {KLog.e(TAG, "releaseCamera happened error: " + runError.message)} catch (e: Exception) {KLog.e(TAG, "releaseCamera error: $e")}}
3、继续录制视频
fun doResumeRecord(eventData: ResumeRecordEvent) {// 打开相机GBApp.instance?.service?.doOpenCamera(OpenCameraEvent(eventData.holder,VideoTaskUtil.instance.mCameraId,ImageFormat.NV21,eventData.eventType))// 请求关键帧camera2Base?.videoEncoder?.requestKeyframe()// 继续写入音视频数据camera2Base?.resumeRecord()}public void resumeRecord() {if (status == Status.PAUSED) {pauseTime += System.nanoTime() / 1000 - pauseMoment;status = Status.RESUMED;if (listener != null) listener.onStatusChange(status);}}
如果合成的视频在后续还会卡在某一帧,可以把之前的视频数据队列清空,这样避免因为切换相机之前的垃圾数据导致问题,然后执行上面的步骤
相关文章:
记录一次Android推流、录像踩坑过程
背景: 按照需求,需要支持APP在手机息屏时进行推流、录像。 技术要点: 1、手机在息屏时能够打开camera获取预览数据 2、获取预览数据时进行编码以及合成视频 一、息屏时获取camera预览数据: ①Camera.setPreviewDisplay(SurfaceH…...
VsCode 与远程服务器 ssh免密登录
首先配置信息 加入下列信息 Host qb-zn HostName 8.1xxx.2xx.3xx User root ForwardAgent yes Port 22 IdentityFile ~/.ssh/id_rsa 找到自己的公钥,不带pub是私钥,打死都不能给别人。复制公钥 拿到公钥后,来到远程服务器 vim ~/.ss…...
7/13 - 7/15
vo.setId(rs.getLong("id"))什么意思? vo.setId(rs.getLong("id")); 这行代码是在Java中使用ResultSet对象(通常用于从数据库中检索数据)获取一个名为"id"的列,并将其作为long类型设置为一个对象…...
烟雾监测与太阳能源:实验装置在其中的作用
太阳光在烟雾中的散射效应研究实验装置是一款模拟阳光透过烟雾环境的设备。此装置能帮助探究阳光在烟雾中的传播特性、散射特性及其对阳光的影响。 该装置主要包括光源单元、烟雾发生装置、光学组件、以及系统。光源单元负责产生类似于太阳光的光线,通常选用高亮度的…...
QT下,如何获取控制台输入
最近工作中为了测试某个模块,需要把原先输入模块部分,改成控制台输入来方便测试。在QT中,我们可以使用 QTextStream 类来读取用户的输入来达到目的。下面是一个简单的例子: #include <QCoreApplication> #include <QTex…...
mybatis动态传入参数 pgsql 日期 Interval ,day,minute
mybatis动态传入参数 pgsql 日期 Interval 在navicat中,标准写法 SELECT * FROM test WHERE time > (NOW() - INTERVAL 5 day)在mybatis中,错误写法 SELECT * FROM test WHERE time > (NOW() - INTERVAL#{numbers,jdbcTypeINTEGER} day)报错内…...
常见CSS属性
常见CSS属性。 1. display: 定义:display 属性控制元素如何渲染在文档流中,影响了元素是否占用空间、位置及盒子模型的行为。 使用说明:它可以设置为如block, inline, inline-block, flex, grid, none等值,用于决定元素显示模式…...
WSL-Ubuntu20.04训练环境配置
1.YOLOv8训练环境配置 训练环境配置的话就仍然以YOLOv8为例,来说明如何配置深度学习训练环境。这部分内容比较简单,主要是安装miniAnaconda以及安装torch和torchvision. 首先是miniAnaconda的安装(参考官网的教程Miniconda — Anaconda ),执行…...
运维检查:mysql表自增id是否快要用完
数据库表中最大自增ID用完会报错。判断是否接近或达到自增ID类型的最大值: 对于MySQL中的自增ID,如果使用的是int类型,其无符号(unsigned)的最大值可以达到2^32 - 1,即4294967295。如果使用的…...
深入理解FFmpeg--libavformat接口使用(一)
libavformat(lavf)是一个用于处理各种媒体容器格式的库。它的主要两个目的是去复用(即将媒体文件拆分为组件流)和复用的反向过程(以指定的容器格式写入提供的数据)。它还有一个I/O模块,支持多种…...
坚持日更的意义何在?
概述 日更,就是每天更新一次或一篇文章。 坚持日更,就是坚持每天更新一次或一篇文章。 这里用了坚持,实际上不是恰当的表述,正确的感觉应该是让日更当作习惯,然后,让自己习惯每天去更新一篇文章。 日更…...
内容长度不同的div如何自动对齐展示
平时我们经常会遇到页面内容div结构相同页,这时为了美观我们会希望div会对齐展示,但当div里的文字长度不一时又不想写固定高度,就会出现div长度长长短短,此时实现样式可以这样写: .e-commerce-Wrap {display: flex;fle…...
Qt中https的使用,报错TLS initialization failed和不能打开ssl.lib问题解决
前言 在现代应用程序中,安全地传输数据变得越来越重要。Qt提供了一套完整的网络API来支持HTTP和HTTPS通信。然而,在实际开发过程中,开发者可能会遇到SSL相关的错误,例如“TLS initialization failed”,cantt open ssl…...
P2p网络性能测度及监测系统模型
P2p网络性能测度及监测系统模型 网络IP性能参数 IP包传输时延时延变化误差率丢失率虚假率吞吐量可用性连接性测度单向延迟测度单向分组丢失测度往返延迟测度 OSI中的位置-> 网络层 用途 面相业务的网络分布式计算网络游戏IP软件电话流媒体分发多媒体通信 业务质量 通过…...
zookeeper相关总结
1. ZooKeeper 的架构 ZooKeeper 采用主从架构(Leader-Follower 模型),包括以下组件: Leader:负责处理所有写请求和协调事务一致性。Follower:处理读请求并转发写请求给 Leader。参与 Leader 选举和事务提…...
【openwrt】Openwrt系统新增普通用户指南
文章目录 1 如何新增普通用户2 如何以普通用户权限运行服务3 普通用户如何访问root账户的ubus服务4 其他权限控制5 参考 Openwrt系统在默认情况下只提供一个 root账户,所有的服务都是以 root权限运行的,包括 WebUI也是通过root账户访问的,…...
【GD32】从零开始学GD32单片机 | WDGT看门狗定时器+独立看门狗和窗口看门狗例程(GD32F470ZGT6)
1. 简介 看门狗从本质上来说也是一个定时器,它是用来监测硬件或软件的故障的;它的工作原理大概就是开启后内部定时器会按照设置的频率更新,在程序运行过程中我们需不断地重装载看门狗,以使它不溢出;如果硬件或软件发生…...
详解曼达拉升级:如何用网络拓扑结构扩容BSV区块链
发表时间:2024年5月24日 BSV曼达拉升级是对BSV基础设施的战略性重塑,意在显著增强其性能,运行效率和可扩容。该概念于2018年提出,其战略落地将使BSV区块链顺利过渡,从现有的基于单一集成功能组件的网络拓扑结构&am…...
编译打包自己的云手机(redroid)镜像
前言 香橙派上跑云手机可以看之前的文章: 香橙派5plus上跑云手机方案一 redroid(带硬件加速)香橙派5plus上跑云手机方案二 waydroid 还有一个cuttlefish方案没说,后面再研究,cuttlefish的优势在于可以自定义内核且selinux是开启的…...
自动驾驶的规划控制简介
自动驾驶的规划控制是自动驾驶系统中的核心组成部分,它负责生成安全、合理且高效的行驶轨迹,并控制车辆按照这个轨迹行驶。规划控制分为几个层次,通常包括行为决策(Behavior Planning)、轨迹规划(Trajector…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
