这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
两个类实现Android录制屏幕功能
随着5G的到来,屏幕共享、音视频成为火热的话题,音视频对于普通开发者来说还是算比较复杂,这篇文章主要讲解在Android中如何进行屏幕录制。
一、原理
在Android5.0之前,需要获取Root权限才能支持屏幕共享。
在Android5.0之后,Android API 开放了视频录制的接口,MediaProjection 和 MediaProjectionManager 在 API 21上增加。
该篇文章是基于该方法进行的录制。
在Windows平台,我们可以使用RDP进行屏幕共享。
在Android 平台,没有官方的屏幕共享技术,所以只能通过录屏之后进行编码传输,接收方进行解码并显示。
二、操作步骤
2.1 申请权限
先申请存储权限
- AndroidManifest配置
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- Android6.0后的动态权限
public static void checkPermission(AppCompatActivity activity) {
if (Build.VERSION.SDK_INT >= 23) {
int checkPermission =
ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_STATE)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
if (checkPermission != PackageManager.PERMISSION_GRANTED) {
//动态申请
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 123);
}
}
}
2.2 允许录制屏幕
分析一下流程
- 检查权限
checkPermission(this);
- 初始化获取
MediaProjectionManager
通过调用getSystemService方法获取MediaProjectionManager的实例
mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
- 申请录制屏幕
由于安卓权限的收紧,高级别的权限都需要显式的提醒用户,用户给到权限之后才可以进行屏幕的录制。因为录屏产生的危险系数很高,可以看到屏幕上的所有内容,所以会显式的提醒用户
- 在
onActivityResult拿到权限是否允许的结果
在onActivityResult 中获取requestCode == REQUEST_CODE 和 resultCode == Activity.RESULT_OK 同时满足时,即用户允许了权限。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
try {
Toast.makeText(this, "允许录屏", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "拒绝录屏", Toast.LENGTH_SHORT).show();
}
}
}
- 开启
Service进行录制屏幕
由于应用在后台的时候仍然需要屏幕录制。
所以我们需要在Service 中进行操作。
在拿到确认录屏的权限之后,进行开启Service
综上所诉,该类完整代码如下
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.screen.recorder.demo.service.ScreenRecordService;
/**
* @author by talon, Date on 19/6/23.
* note:
*/
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 1;
private MediaProjectionManager mMediaProjectionManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission(this); //检查权限
mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
}
public void StartRecorder(View view) {
createScreenCapture();
}
public void StopRecorder(View view){
Intent service = new Intent(this, ScreenRecordService.class);
stopService(service);
}
public static void checkPermission(AppCompatActivity activity) {
if (Build.VERSION.SDK_INT >= 23) {
int checkPermission =
ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_PHONE_STATE)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
if (checkPermission != PackageManager.PERMISSION_GRANTED) {
//动态申请
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 123);
}
}
}
private void createScreenCapture() {
Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
try {
Toast.makeText(this, "允许录屏", Toast.LENGTH_SHORT).show();
Intent service = new Intent(this, ScreenRecordService.class);
service.putExtra("resultCode", resultCode);
service.putExtra("data", data);
startService(service);
} catch (Exception e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "拒绝录屏", Toast.LENGTH_SHORT).show();
}
}
}
对应的视图代码如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始录制"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="StartRecorder"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止录制"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="StopRecorder"/>
</LinearLayout>
2.3 在 Service 中进录制屏幕
- 拿到传递由
onActivityResult回调产生的resultCode与data
mResultData = intent.getParcelableExtra("data");
- 获取手机屏幕的宽高与
dpi - 创建
MediaProjectionMediaRecorderVirtualDisplay
MediaRecorder 支持录屏时录音,主要是设置录屏过程中的参数。
VirtualDisplay 相当于一个虚拟显示器,调用DisplayManager 的createVirtualDisplay 方法后,此时虚拟显示器的内容会渲染在Surface上。
该主要是用来操作显示器,例如获取显示器,设置/得到surface,
- 在
createMediaRecorder方法中配置保存视频的信息,文件的路径、文件名、清晰度等
比如设置录屏结束的文件名
mediaRecorder.setOutputFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/" + videoQuality + curTime + ".mp4");
该类代码如下
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import com.screen.recorder.demo.utils.ScreenUtils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author by talon, Date on 19/6/23.
* note:
*/
public class ScreenRecordService extends Service {
private final String TAG = "ScreenRecordService";
/**
* 是否为标清视频
*/
private boolean isVideoSd = false;
private int mScreenWidth;
private int mScreenHeight;
private int mScreenDensity;
private int mResultCode;
private Intent mResultData;
private MediaProjection mMediaProjection;
private MediaRecorder mMediaRecorder;
private VirtualDisplay mVirtualDisplay;
public ScreenRecordService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mResultCode = intent.getIntExtra("resultCode", 1);
mResultData = intent.getParcelableExtra("data");
getScreenBaseInfo();
mMediaProjection = createMediaProjection();
mMediaRecorder = createMediaRecorder();
mVirtualDisplay = createVirtualDisplay(); // 必须在mediaRecorder.prepare() 之后调用,否则报错"fail to get surface"
mMediaRecorder.start();
return Service.START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
if(mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if(mMediaRecorder != null) {
mMediaRecorder.setOnErrorListener(null);
mMediaProjection.stop();
mMediaRecorder.reset();
}
if(mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
}
/**
* 获取屏幕相关数据
*/
private void getScreenBaseInfo() {
mScreenWidth = ScreenUtils.getScreenWidth(this);
mScreenHeight = ScreenUtils.getScreenHeight(this);
mScreenDensity = ScreenUtils.getScreenDensityDpi(this);
}
private MediaProjection createMediaProjection() {
Log.i(TAG, "Create MediaProjection");
return ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)).getMediaProjection(mResultCode, mResultData);
}
private MediaRecorder createMediaRecorder() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Date curDate = new Date(System.currentTimeMillis());
String curTime = formatter.format(curDate).replace(" ", "");
String videoQuality = "HD";
if (isVideoSd) videoQuality = "SD";
Log.i(TAG, "Create MediaRecorder");
MediaRecorder mediaRecorder = new MediaRecorder();
// if(isAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/" + videoQuality + curTime + ".mp4");
mediaRecorder.setVideoSize(mScreenWidth, mScreenHeight); //after setVideoSource(), setOutFormat()
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); //after setOutputFormat()
// if(isAudio) mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); //after setOutputFormat()
int bitRate;
if (isVideoSd) {
mediaRecorder.setVideoEncodingBitRate(mScreenWidth * mScreenHeight);
mediaRecorder.setVideoFrameRate(30);
bitRate = mScreenWidth * mScreenHeight / 1000;
} else {
mediaRecorder.setVideoEncodingBitRate(5 * mScreenWidth * mScreenHeight);
mediaRecorder.setVideoFrameRate(60); //after setVideoSource(), setOutFormat()
bitRate = 5 * mScreenWidth * mScreenHeight / 1000;
}
try {
mediaRecorder.prepare();
} catch (IllegalStateException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return mediaRecorder;
}
private VirtualDisplay createVirtualDisplay() {
Log.i(TAG, "Create VirtualDisplay");
return mMediaProjection.createVirtualDisplay(TAG, mScreenWidth, mScreenHeight, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), null, null);
}
}