android 实现屏幕录制功能,Android实现屏幕录制功能
本文实例为大家分享了Android实现屏幕录制功能的具体代码,供大家参考,具体内容如下
1.效果图:
2.添加依赖
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.core:core-ktx:1.0.2"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
testImplementation "junit:junit:4.12"
androidTestImplementation "androidx.test.ext:junit:1.1.1"
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0"
api "com.blankj:utilcode:1.24.4"
}
repositories {
mavenCentral()
}
3.注册权限:
4.主界面,
test.aac是录屏的时候配的音乐,可以随便找另外一个放到assets文件夹里面进行替换
package com.ufi.pdioms.ztkotlin
import android.content.Intent
import android.content.res.AssetFileDescriptor
import android.media.MediaPlayer
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.blankj.utilcode.util.PathUtils
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
// https://github.com/fanqilongmoli/AndroidScreenRecord
private var screenRecordHelper: ScreenRecordHelper? = null
private val afdd:AssetFileDescriptor by lazy { assets.openFd("test.aac") }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (screenRecordHelper == null) {
screenRecordHelper = ScreenRecordHelper(this, object : ScreenRecordHelper.OnVideoRecordListener {
override fun onBeforeRecord() {
}
override fun onStartRecord() {
play()
}
override fun onCancelRecord() {
releasePlayer()
}
override fun onEndRecord() {
releasePlayer()
}
}, PathUtils.getExternalStoragePath() + "/fanqilong")
}
screenRecordHelper?.apply {
if (!isRecording) {
// 如果你想录制音频(一定会有环境音量),你可以打开下面这个限制,并且使用不带参数的 stopRecord()
// recordAudio = true
startRecord()
}
}
} else {
Toast.makeText(this@MainActivity.applicationContext, "sorry,your phone does not support recording screen", Toast.LENGTH_LONG).show()
}
}
btnStop.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
screenRecordHelper?.apply {
if (isRecording) {
if (mediaPlayer != null) {
// 如果选择带参数的 stop 方法,则录制音频无效
stopRecord(mediaPlayer!!.duration.toLong(), 15 * 1000, afdd)
} else {
stopRecord()
}
}
}
}
}
}
private fun play() {
mediaPlayer = MediaPlayer()
try {
mediaPlayer?.apply {
this.reset()
this.setDataSource(afdd.fileDescriptor, afdd.startOffset, afdd.length)
this.isLooping = true
this.prepare()
this.start()
}
} catch (e: Exception) {
Log.d("fanqilong", "播放音乐失败")
} finally {
}
}
// 音频播放
private var mediaPlayer: MediaPlayer? = null
private fun releasePlayer() {
mediaPlayer?.apply {
stop()
release()
}
mediaPlayer = null
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && data != null) {
screenRecordHelper?.onActivityResult(requestCode, resultCode, data)
}
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
screenRecordHelper?.clearAll()
}
afdd.close()
super.onDestroy()
}
}
5.录屏代码
package com.ufi.pdioms.ztkotlin
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.*
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import com.blankj.utilcode.constant.PermissionConstants
import com.blankj.utilcode.util.PermissionUtils
import java.io.File
import java.lang.Exception
import java.nio.ByteBuffer
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class ScreenRecordHelper @JvmOverloads constructor(
private var activity: Activity,
private val listener: OnVideoRecordListener?,
private var savePath: String = Environment.getExternalStorageDirectory().absolutePath + File.separator
+ "DCIM" + File.separator + "Camera",
private val saveName: String = "record_${System.currentTimeMillis()}"
) {
private val mediaProjectionManager by lazy { activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as? MediaProjectionManager }
private var mediaRecorder: MediaRecorder? = null
private var mediaProjection: MediaProjection? = null
private var virtualDisplay: VirtualDisplay? = null
private val displayMetrics by lazy { DisplayMetrics() }
private var saveFile: File? = null
var isRecording = false
var recordAudio = false
init {
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
}
companion object {
private const val VIDEO_FRAME_RATE = 30
private const val REQUEST_CODE = 1024
private const val TAG = "ScreenRecordHelper"
}
fun startRecord() {
if (mediaProjectionManager == null) {
Log.d(TAG, "mediaProjectionManager == null,当前手机暂不支持录屏")
showToast(R.string.phone_not_support_screen_record)
return
}
PermissionUtils.permission(PermissionConstants.STORAGE, PermissionConstants.MICROPHONE)
.callback(object : PermissionUtils.SimpleCallback {
override fun onGranted() {
mediaProjectionManager?.apply {
listener?.onBeforeRecord()
val intent = this.createScreenCaptureIntent()
if (activity.packageManager.resolveActivity(
intent,
PackageManager.MATCH_DEFAULT_ONLY
) != null
) {
activity.startActivityForResult(intent, REQUEST_CODE)
} else {
showToast(R.string.phone_not_support_screen_record)
}
}
}
override fun onDenied() {
showToast(R.string.permission_denied)
}
}).request()
}
@RequiresApi(Build.VERSION_CODES.N)
fun resume() {
mediaRecorder?.resume()
}
@RequiresApi(Build.VERSION_CODES.N)
fun pause() {
mediaRecorder?.pause()
}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
mediaProjection = mediaProjectionManager!!.getMediaProjection(resultCode, data)
// 部分手机录制视频的时候 会出现弹框
Handler().postDelayed({
if (initRecorder()) {
isRecording = true
mediaRecorder?.start()
listener?.onStartRecord()
} else {
showToast(R.string.phone_not_support_screen_record)
}
}, 150)
} else {
showToast(R.string.phone_not_support_screen_record)
}
}
}
fun cancelRecord(){
stopRecord()
saveFile?.delete()
saveFile = null
listener?.onCancelRecord()
}
fun stopRecord(videoDuration: Long = 0, audioDuration: Long = 0, afdd: AssetFileDescriptor? = null){
stop()
if (audioDuration != 0L && afdd != null) {
syntheticAudio(videoDuration, audioDuration, afdd)
} else {
// saveFile
if (saveFile != null) {
val newFile = File(savePath, "$saveName.mp4")
// 录制结束后修改后缀为 mp4
saveFile!!.renameTo(newFile)
refreshVideo(newFile)
}
saveFile = null
}
}
private fun refreshVideo(newFile: File) {
Log.d(TAG, "screen record end,file length:${newFile.length()}.")
if (newFile.length() > 5000) {
val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
intent.data = Uri.fromFile(newFile)
activity.sendBroadcast(intent)
Log.e("TAG","refreshVideo: "+savePath)
showToast(R.string.save_to_album_success)
} else {
newFile.delete()
showToast(R.string.phone_not_support_screen_record)
Log.d(TAG, activity.getString(R.string.record_faild))
}
}
private fun stop() {
if (isRecording) {
isRecording = false
try {
mediaRecorder?.apply {
setOnErrorListener(null)
setOnInfoListener(null)
setPreviewDisplay(null)
stop()
Log.d(TAG, "stop success")
}
} catch (e: Exception) {
Log.e(TAG, "stopRecorder() error!${e.message}")
} finally {
mediaRecorder?.reset()
virtualDisplay?.release()
mediaProjection?.stop()
listener?.onEndRecord()
}
}
}
private fun initRecorder(): Boolean {
var result = true
val f = File(savePath)
if (!f.exists()) {
f.mkdir()
}
saveFile = File(savePath, "$saveName.tmp")
saveFile?.apply {
if (exists()) {
delete()
}
}
mediaRecorder = MediaRecorder()
val width = Math.min(displayMetrics.widthPixels, 1080)
val height = Math.min(displayMetrics.heightPixels, 1920)
mediaRecorder?.apply {
if (recordAudio) {
setAudioSource(MediaRecorder.AudioSource.MIC)
}
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
if (recordAudio) {
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
}
setOutputFile(saveFile!!.absolutePath)
setVideoSize(width, height)
setVideoEncodingBitRate(8388608)
setVideoFrameRate(VIDEO_FRAME_RATE)
try {
prepare()
virtualDisplay = mediaProjection?.createVirtualDisplay(
"MainScreen", width, height, displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null
)
Log.d(TAG, "initRecorder 成功")
} catch (e: Exception) {
Log.e(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")
e.printStackTrace()
result = false
}
}
return result
}
private fun showToast(resId: Int) {
Toast.makeText(activity.applicationContext, activity.applicationContext.getString(resId), Toast.LENGTH_SHORT)
.show()
}
fun clearAll() {
mediaRecorder?.release()
mediaRecorder = null
virtualDisplay?.release()
virtualDisplay = null
mediaProjection?.stop()
mediaProjection = null
}
/**
* https://stackoverflow.com/questions/31572067/android-how-to-mux-audio-file-and-video-file
*/
private fun syntheticAudio(audioDuration: Long, videoDuration: Long, afdd: AssetFileDescriptor) {
Log.d(TAG, "start syntheticAudio")
val newFile = File(savePath, "$saveName.mp4")
if (newFile.exists()) {
newFile.delete()
}
try {
newFile.createNewFile()
val videoExtractor = MediaExtractor()
videoExtractor.setDataSource(saveFile!!.absolutePath)
val audioExtractor = MediaExtractor()
afdd.apply {
audioExtractor.setDataSource(fileDescriptor, startOffset, length * videoDuration / audioDuration)
}
val muxer = MediaMuxer(newFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
videoExtractor.selectTrack(0)
val videoFormat = videoExtractor.getTrackFormat(0)
val videoTrack = muxer.addTrack(videoFormat)
audioExtractor.selectTrack(0)
val audioFormat = audioExtractor.getTrackFormat(0)
val audioTrack = muxer.addTrack(audioFormat)
var sawEOS = false
var frameCount = 0
val offset = 100
val sampleSize = 1000 * 1024
val videoBuf = ByteBuffer.allocate(sampleSize)
val audioBuf = ByteBuffer.allocate(sampleSize)
val videoBufferInfo = MediaCodec.BufferInfo()
val audioBufferInfo = MediaCodec.BufferInfo()
videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
muxer.start()
// 每秒多少帧
// 实测 OPPO R9em 垃圾手机,拿出来的没有 MediaFormat.KEY_FRAME_RATE
val frameRate = if (videoFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
videoFormat.getInteger(MediaFormat.KEY_FRAME_RATE)
} else {
31
}
// 得出平均每一帧间隔多少微妙
val videoSampleTime = 1000 * 1000 / frameRate
while (!sawEOS) {
videoBufferInfo.offset = offset
videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset)
if (videoBufferInfo.size < 0) {
sawEOS = true
videoBufferInfo.size = 0
} else {
videoBufferInfo.presentationTimeUs += videoSampleTime
videoBufferInfo.flags = videoExtractor.sampleFlags
muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo)
videoExtractor.advance()
frameCount++
}
}
var sawEOS2 = false
var frameCount2 = 0
while (!sawEOS2) {
frameCount2++
audioBufferInfo.offset = offset
audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset)
if (audioBufferInfo.size < 0) {
sawEOS2 = true
audioBufferInfo.size = 0
} else {
audioBufferInfo.presentationTimeUs = audioExtractor.sampleTime
audioBufferInfo.flags = audioExtractor.sampleFlags
muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo)
audioExtractor.advance()
}
}
muxer.stop()
muxer.release()
videoExtractor.release()
audioExtractor.release()
// 删除无声视频文件
saveFile?.delete()
} catch (e: Exception) {
Log.e(TAG, "Mixer Error:${e.message}")
// 视频添加音频合成失败,直接保存视频
saveFile?.renameTo(newFile)
} finally {
afdd.close()
Handler().post {
refreshVideo(newFile)
saveFile = null
}
}
}
interface OnVideoRecordListener {
/**
* 录制开始时隐藏不必要的UI
*/
fun onBeforeRecord()
/**
* 开始录制
*/
fun onStartRecord()
/**
* 取消录制
*/
fun onCancelRecord()
/**
* 结束录制
*/
fun onEndRecord()
}
}
6.布局
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="start"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="stop"/>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持云海天教程。
android 实现屏幕录制功能,Android实现屏幕录制功能相关推荐
- android屏幕录制功能,Android利用ADB进行屏幕录制
前言 在写博客时,为了方便大家理解,我们经常需要把一些操作或动画录制成Gif,一般需要下载一个屏幕录制App将手机屏幕录制成视频(可能需要Root权限),然后导出到电脑,再转为Gif.今天就来教大家一 ...
- Android11灵敏度,Android 11即将上线!推出原生屏幕录制、触摸灵敏度等功能
对于喜欢国产手机的用户而言,最期待的莫过于Android 11上线了.大家通过外媒已经了解到Android 11的消息了,Android 11被传的神乎其神,Android 11真的有那么厉害吗? A ...
- android 屏幕录制方案,Android录制屏幕的实现方法
原文:Paul Kinlan 翻译:Agora.io 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已 ...
- Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能
Android opengles 实现触碰屏幕,根据运动轨迹画直线的功能 目录 引言 第一步,先自己学会绘制一条固定坐标的直线 第二步,动态的绘制一条直线 第三步,坐标转换 第四步,绘制多条直线 代码 ...
- android屏幕 录制检测,Android 录制屏幕的实现方法
Android 录制屏幕的实现方法,长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.C ...
- android手机录屏多少fps,如何在Android上以90fps或120fps的屏幕录制?
[5G资讯网]Android智能手机每天都在开拓新的领域,应用程序也在以相同的速度发展.最近增加的90赫兹显示屏为智能手机上的更好游戏铺平了道路.在OnePlus 7T推出之前,90Hz显示屏仅限于游 ...
- android 屏幕录制代码,Android 录制屏幕的实现方法
长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.Chrome团队正在添加一种功能,可以 ...
- android 屏幕录制方案,Android录屏的三种解决方案
本文总结三种用于安卓录屏的解决方案: adb shell命令screenrecord MediaRecorder, MediaProjection MediaProjection , MediaCod ...
- android 屏幕录制方案,Android录屏的三种方案
本文总结三种用于安卓录屏的解决方案: adb shell命令screenrecord MediaRecorder, MediaProjection MediaProjection , MediaCod ...
最新文章
- shell编程系列7--shell中常用的工具find、locate、which、whereis
- 从零入门 FreeRTOS 操作系统之任务的概念
- jQuery 表格自动增加
- openjdk替换java_ubuntu中将java环境由安装版的openjdk替换为Oracle的jdk
- css 输入框 按钮 对齐,CSS让input button元素对齐的代码收集
- python install_[Python] Linux下python install
- 捷联惯导系统学习7.2(捷联惯导精对准 )
- java压缩图片thumbnails_Java压缩图片、减小图片文件体积大小,Thumbnails使用教程...
- VMware清理vmdk文件
- Katalon Studio:一款静候你使用的免费自动化测试工具
- 【mssql】SQL Server2012编程入门经典(第四版)(上) 读书笔记
- 秦汉考场科目三路线图_秦汉科目三考场考试攻略,附考场路线图
- 发票查验API,批量查验发票真伪
- 退出计算机控制如何恢复,在您退出大势至U盘禁用软件、电脑U口屏蔽软件之后如何取消U盘写保护功能、恢复向U盘复制文件的功能?...
- 433芯片的基本原理和对应优缺点理解
- 嵌入式新闻早班车-第16期
- 数据库mysql命令
- NSIS + QT 制作自定义界面安装包
- 立创EDA专业版,修改自带库中的元件
- 电控系统开发工作内容梳理
热门文章
- 电脑开机自检过程都有什么?
- 我彻底抛弃Windows,入坑MacBook
- [OpenCV4] 湖南大学数字图像处理实验1
- 怎么样给小孩取名字好听又准确?有它就可以搞定了
- php+中文分词scws+sphinx+mysql打造千万级数据全文搜索
- 【Android】自定义progressBar样式
- Windows 10下安装Elementary OS双系统
- 【云杂谈】之二《AT&T发布基于云存储的物联网产品》
- 4.0 ipu_soc,ipu_channel_t ,ipu_channel_params_t结构体详解
- 【Nexus】安装配置与使用