Android录屏 MediaRecorder介绍
Android录屏的三种方案
1、adb shell命令screenrecord
2、MediaRecorder, MediaProjection
3、MediaCodec和MediaMuxer, MediaProjection ,
一、screenrecord命令
screenrecord是一个shell命令,支持Android4.4(API level 19)以上,
录制的视频格式为mp4 ,存放到手机sd卡里,默认录制时间为180s
adb shell screenrecord --size 1280x720 --bit-rate 6000000 --time-limit 30 /sdcard/demo.mp4
--size 指定视频分辨率,根据手机情况决定
--bit-rate 指定视频比特率,默认为4M,该值越小,保存的视频文件越小;
--time-limit 指定录制时长,若设定大于180,命令不会被执行;
并不是所以手机都执行screenrecord命令,部分手机不识别。
我在几款华为手机就没执行成功报错:
/system/bin/sh: screenrecord: inaccessible or not found
二、 MediaRecorder
MediaProjection是Android5.0后才开放的屏幕采集接口,通过系统级服务MediaProjectionManager进行管理。
这里先整体说一下屏幕录制的流程,不然看起来费劲。
1、通过startActivityForResult(Intent intent)判断是否录屏授权的Activity
其中intent对象就需要MediaProjectionManager.createScreenCaptureIntent();获取
2、在onActivityResult回调方法中做具体录屏工作
比如:创建MediaRecorder,设置MP4文件路径
创建VirtualDisplay,设置屏幕相关参数
如果不在onActivityResult回调中执行会有问题。
3、开始录屏
MediaRecorder.start()
4、停止录屏
MediaRecorder.reset();
MediaRecorder.release();
录屏过程用到录音权限和数据读写权限。
三、MediaCodec和MediaMuxer
MediaCodec提供对音视频压缩编码和解码功能,MediaMuxer可以将音视频混合生成多媒体文件,生成MP4文件。
这个录屏的方式和MediaRecorder是类似的,只是流程第二部有点不同,这里不做介绍。
详情可以参考:https://www.jianshu.com/p/8b313692ac85
四、MediaRecorder项目示例的主要代码
这里只做了录制和停止录制,没有做相关适配,比如横竖屏切换后尺寸变化。
简单效果:
生成的MP4文件会在sdcard目录下,并且以录屏时间为文件名。
1、MainActivity
package com.liwenzhi.screen;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
String TAG = "MainActivity";
MediaProjectionManager mProjectionManager;
MediaRecordService mediaRecord;
int displayWidth = 1280;
int displayHeight = 1920;
Button btn_start_record;
Button btn_stop_record;
TextView tv_time;
int REQUEST_CODE_PERMISSIONS = 99;
int REQUEST_CODE_SCREEN = 100;
//录音权限和数据读写权限
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
boolean mPassPermissions = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initEvent();
}
private void initView() {
btn_start_record = findViewById(R.id.btn_start_record);
btn_stop_record = findViewById(R.id.btn_stop_record);
tv_time = findViewById(R.id.tv_time);
}
private void initData() {
//权限申请
//逐个判断你要的权限是否已经通过
judgePermissions();
if (!mPassPermissions) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_PERMISSIONS);
}
}
private void initEvent() {
btn_start_record.setOnClickListener(this);
btn_stop_record.setOnClickListener(this);
}
// 申请权限后的回调
// 1、录音和读写
// 2、录屏startActivityForResult后的回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//录音和读写权限
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (resultCode != Activity.RESULT_OK) {
mPassPermissions = false;
}
} else
// 录屏权限
if (requestCode == REQUEST_CODE_SCREEN) {
if (resultCode == Activity.RESULT_OK) {
try {
// mediaProjection 如果不在权限申请中回调,获取到的对象为空
MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection == null) {
Log.e(TAG, "media projection is null");
return;
}
File file = new File("/sdcard/" + MyTimeUtils.getTimeString("yyyy-MM-dd_HH:mm:ss") + ".mp4"); //录屏生成文件
if (!file.exists()) {
file.createNewFile();
}
mediaRecord = new MediaRecordService(displayWidth, displayHeight, 6000000, 1,
mediaProjection, file.getAbsolutePath());
mediaRecord.start();
second = 0;
mHandler.sendEmptyMessageDelayed(10, 1000);
} catch (Exception e) {
e.printStackTrace();
}
} else {
Toast.makeText(MainActivity.this, "录屏失败", Toast.LENGTH_LONG).show();
}
}
}
@Override
public void onBackPressed() {
//super.onBackPressed();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_record:
startRecord();
break;
case R.id.btn_stop_record:
stopRecord();
break;
}
}
// 开始录屏
private void startRecord() {
judgePermissions();
if (!mPassPermissions) {
Toast.makeText(this, "基础权限没通过!", Toast.LENGTH_LONG).show();
return;
}
mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (mProjectionManager != null) {
Intent intent = mProjectionManager.createScreenCaptureIntent();
PackageManager packageManager = getPackageManager();
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
//存在录屏授权的Activity
startActivityForResult(intent, REQUEST_CODE_SCREEN);
} else {
Toast.makeText(this, "没有录屏权限!", Toast.LENGTH_LONG).show();
}
}
}
// 停止录屏
private void stopRecord() {
mediaRecord.release();
mHandler.removeMessages(10);
}
int second;
// 计算时间
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 10:
second++;
tv_time.setText(second + "");
mHandler.sendEmptyMessageDelayed(10, 1000);
break;
}
}
};
//判断每个权限是否通过
private void judgePermissions() {
boolean permission = true;
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
// 未授予的权限
permission = false;
}
}
mPassPermissions = permission;
}
}
2、MediaRecordService
package com.liwenzhi.screen;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.util.Log;
public class MediaRecordService extends Thread {
private static final String TAG = "MediaRecordService";
private int mWidth;
private int mHeight;
private int mBitRate;
private int mDpi;
private String mDstPath;
private MediaRecorder mMediaRecorder;
private MediaProjection mMediaProjection;
private static final int FRAME_RATE = 60; // 60 fps
private VirtualDisplay mVirtualDisplay;
public MediaRecordService(int width, int height, int bitrate, int dpi, MediaProjection mp, String dstPath) {
mWidth = width;
mHeight = height;
mBitRate = bitrate;
mDpi = dpi;
mMediaProjection = mp;
mDstPath = dstPath;
}
@Override
public void run() {
try {
initMediaRecorder();
//在mediarecorder.prepare()方法后调用
mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display", mWidth, mHeight, mDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mMediaRecorder.getSurface(), null, null);
Log.i(TAG, "created virtual display: " + mVirtualDisplay);
mMediaRecorder.start();
Log.i(TAG, "mediarecorder start");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 初始化MediaRecorder
*
* @return
*/
public void initMediaRecorder() {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setOutputFile(mDstPath);
mMediaRecorder.setVideoSize(mWidth, mHeight);
mMediaRecorder.setVideoFrameRate(FRAME_RATE);
mMediaRecorder.setVideoEncodingBitRate(mBitRate);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
try {
mMediaRecorder.prepare();
} catch (Exception e) {
e.printStackTrace();
}
Log.i(TAG, "media recorder" + mBitRate + "kps");
}
public void release() {
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if (mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null);
mMediaProjection.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
}
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
Log.i(TAG, "release");
}
}
3、MyTimeUtils
package com.liwenzhi.screen;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* a Utils for time
* <p>
* identify the following letter: :"yyyy-MM-dd DD HH:mm:ss SSS"
* * method1: long getTimeLong()
* * method2: int getTimeInt(String filter)
* * method3: String getTimeString()
* * method4: String getTimeString(long time)
* * method5: String getTimeString(long time, String filter)
* * method6: String getTimeString( String filter)
*/
public class MyTimeUtils {
public static long getTimeLong() {
return System.currentTimeMillis();
}
public static int getTimeInt(String filter) {
SimpleDateFormat format = new SimpleDateFormat(filter);
String time = format.format(new Date());
return Integer.parseInt(time);
}
public static final String getTimeString() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date(getTimeLong()));
}
public static final String getTimeString(long time) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(new Date(time));
}
public static final String getTimeString(long time, String filter) {
SimpleDateFormat format = new SimpleDateFormat(filter);
return format.format(new Date(time));
}
public static final String getTimeString(String filter) {
SimpleDateFormat format = new SimpleDateFormat(filter);
return format.format(new Date(getTimeLong()));
}
public static Long getTimeLong(String filter, String date) {
try {
SimpleDateFormat format = new SimpleDateFormat(filter);
Date dateTime = format.parse(date);
return dateTime.getTime();
} catch (Exception e) {
e.printStackTrace();
}
return 0L;
}
}
测试apk和项目源码下载:
这个项目只是简单录屏,如果要做得好,
最好是正在通知栏/悬浮框里面控制停止,
并且可以按退出键回到主界面,
要用到服务来控制录屏屏幕的开始和停止。
共勉:时间总会过去,多做一下有意义的事情。