Android 循环录制最近一段时间的视频
在日常开发测试中,往往发生问题了再去想办法复现录屏、抓取日志
的工作,往往会出现偶现问题很难复现,导致问题很难定位。在这里给出一个能抓取历史操作视频的解决方案:
- 将录屏的视频帧数据一帧帧的缓存到一块固定大小的内存中(空间循环利用)
- 发现问题时,触发混合器(
MediaMuxer
)将指定时间范围的视频帧数据取出存储为指定的mp4文件
数据缓存
数据缓存用来解决历史数据保存,需要合理的分配内存大小,根据自己的实际情况(手机屏幕分辨率、多长时间的视频记录等等)选择合适的大小。
提供四个JNI函数:
/**
* 视频帧数据缓存工具类
*
* @author likunlun
* @since 2021/12/19
*/
object FrameDataCacheUtils {
/**
* 初始化缓存
*
* @param cacheSize 缓存空间大小,单位 M
* @param isDebug 是否debug模式
*/
external fun initCache(cacheSize: Int, isDebug: Boolean)
/**
* 添加新的一帧数据到缓存
*
* @param timestamp 时间戳 ms
* @param isKeyFrame 是否关键帧
* @param frameData 帧数据
* @param length 数据长度
*/
external fun addFrameData(
timestamp: Long,
isKeyFrame: Boolean,
frameData: ByteArray,
length: Int
)
/**
* 通过时间戳从缓存区中获取最近的一个关键帧数据
*
* @param timestamp 传入的时间戳 ms
* @param curTimestamp 查找到的关键帧的时间戳 ms
* @param frameData 帧数据
* @param length 数据长度
* @return 0成功,非0失败
*/
external fun getFirstFrameData(
timestamp: Long,
curTimestamp: LongArray,
frameData: ByteArray,
length: IntArray
): Int
/**
* 通过时间戳从缓存区中获取下一帧数据
*
* @param preTimestamp 前一帧的时间戳 ms
* @param curTimestamp 当前帧的时间戳 ms
* @param frameData 帧数据
* @param length 数据长度
* @param isKeyFrame 是否关键帧(I帧)true I帧
* @return 0成功,非0失败
*/
external fun getNextFrameData(
preTimestamp: Long,
curTimestamp: LongArray,
frameData: ByteArray,
length: IntArray,
isKeyFrame: BooleanArray
): Int
init {
System.loadLibrary("framedatacachejni")
}
}
缓存框架源码:https://download.csdn.net/download/lkl22/73404181
开启屏幕录屏
一、申请权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
二、创建service
<application>
<service
android:name=".service.ScreenCaptureService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaProjection" />
</application>
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
createNotificationChannel()
val resultCode = intent.getIntExtra(ScreenCapture.KEY_RESULT_CODE, -1)
val cacheSize = intent.getIntExtra(ScreenCapture.KEY_CACHE_SIZE, ScreenCapture.DEFAULT_CACHE_SIZE)
val resultData = intent.getParcelableExtra<Intent>(ScreenCapture.KEY_DATA)
resultData?.apply {
ScreenCaptureManager.instance.startRecord(resultCode, this, cacheSize)
LogUtils.e(TAG, "startRecord.")
}
return super.onStartCommand(intent, flags, startId)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
}
val builder =
NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) //获取一个Notification构造器
.setContentTitle("ScreenCapture") // 设置下拉列表里的标题
.setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
.setContentText("is running......") // 设置上下文内容
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setWhen(System.currentTimeMillis()) // 设置该通知发生的时间
LogUtils.d(TAG, "startForeground")
startForeground(NOTIFICATION_ID, builder.build())
}
三、开启录屏
1、创建MediaProjectionManager对象
private val mProjectionManager: MediaProjectionManager <