Android录屏
参考
Android录屏的三种方案
使用MediaCodec和RTMP做直播推流
android.media.projection
概念
通过
MediaProjection
创建一个投影,可以将这个投影显示到自己的 SurfaceView 上,也可以通过 MediaRecorder 编码存储到本地实现录屏效果,也可以通过 MediaCodec 编码后获取实时数据推送直播
相关权限
权限 | 说明 | 是否动态申请 |
---|---|---|
android.permission.RECORD_AUDIO | 录音权限 | 是 |
android.permission.FOREGROUND_SERVICE | 前台服务 | 否 |
相关类
类 | 说明 |
---|---|
MediaProjectionManager | MediaProjection管理 |
MediaProjection | 授予捕获屏幕或记录系统音频的功能 |
VirtualDisplay | 类似投影仪?捕获屏幕后将数据输出到投影仪 投影仪可以获取视频的信息,指定输出的位置等 |
MediaRecorder | 用于将音视频编码输出 |
MediaMuxer | 将音视频混合生成多媒体文件 |
MediaCodec | 进行音视频压缩编解码 |
流程
1.申请录屏
通过MediaProjectionManager.createScreenCaptureIntent()
获取一个Intent
调用startActivityForResult()
发起录屏请求
在onActivityResult()
中获取请求结果并开始录屏
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent screenCaptureIntent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(screenCaptureIntent,10012);
2.启用前台服务
Android 10之后使用录屏等功能要求在前台Service中进行
AndroidManifest.xml
中要为该Service设置android:foregroundServiceType="mediaProjection"
属性
且需要声明
启动Service时,需要调用startForegroundService()
作为前台服务启动
在Service中需要先调用startForeground()
启动一个Notification
后才能调用录屏
流程:
AndroidManifest.xml
MediaRecordActivity
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 获取申请录屏结果
if (requestCode == 10012 && resultCode == RESULT_OK){
Intent intent = new Intent(this, MediaRecordService.class);
intent.putExtra("data",data);
intent.putExtra("resultCode",resultCode);
intent.putExtra("width",WindowUtils.getWindowWidth(this)); // 屏幕的宽
intent.putExtra("height",WindowUtils.getWindowHeight(this)); // 屏幕的高
intent.putExtra("surface",surface); // Surface 用于显示录屏的数据
startForegroundService(intent); // 启动前台服务
}
}
MediaRecordService
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 创建通知栏
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(this, "123123")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("录屏")
.setContentText(getString(R.string.app_name) + "录屏中")
.build();
if(Build.VERSION.SDK_INT>=26) {
// 推送通道
NotificationChannel channel = new NotificationChannel("123123", "通道说明", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
// 展示前台服务
startForeground(123123, notification);
int resultCode = intent.getIntExtra("resultCode", -1);
width = intent.getIntExtra("width", -1);
height = intent.getIntExtra("height", -1);
Intent data = intent.getParcelableExtra("data");
final Surface surface = intent.getParcelableExtra("surface");
// 获取 MediaProjectionManager
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// 获取 MediaProjection
final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection != null) {
/**
* 创建投影
* name 本次虚拟显示的名称
* width 录制后视频的宽
* height 录制后视频的高
* dpi 显示屏像素
* flags VIRTUAL_DISPLAY_FLAG_PUBLIC 通用显示屏
* Surface 输出的Surface
*/
VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("record-video", 200, 200, 6000000,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
}
return super.onStartCommand(intent, flags, startId);
}
使用 MediaRecorder 录制保存到本地
- 初始化 MediaRecorder
private void initMediaRecorder() {
mediaRecorder = new MediaRecorder();
// 设置音频来源 需要动态申请 android.permission.RECORD_AUDIO 权限
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置视频来源
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
// 设置输出格式
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// 设置输出文件
String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();
mediaRecorder.setOutputFile(absolutePath);
// 设置视频宽高
mediaRecorder.setVideoSize(width,height);
// 设置视频帧率
mediaRecorder.setVideoFrameRate(60);
// 设置视频编码比特率
mediaRecorder.setVideoEncodingBitRate(6000000);
// 设置音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 设置视频编码
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
- 创建投影时,将 MediaRecorder 的 Surface 设为输出位置
// mediaRecorder.getSurface() 获取要记录的 Surface
virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null);
- 开始
mediaRecorder.start()
使用 MediaCodec 编码
编码后数据未验证是否可以直接进行推流,按 使用MediaCodec和RTMP做直播推流 对数据进行RTMP编码后应该是可以推流的
- 初始化 MediaCodec
private void initMediaCodec() {
String MIME_TYPE = "video/avc"; // H.264 类型
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
// 颜色格式
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
// 比特率
format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
// 帧速率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
// I帧的帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
try {
// 创建指定类型的编码器
videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
// 设置编码器属性
videoEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
// 创建作为输入的 Surface
inputSurface = videoEncoder.createInputSurface();
videoEncoder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
- 创建投影时,将 MediaCodec的 Surface 设为输出位置
virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, inputSurface, null, null);
- 读取解码后数据
new Thread(new Runnable() {
@Override
public void run() {
while (isRecord){
// 获取已经解码的缓冲区索引
int index = videoEncoder.dequeueOutputBuffer(bufferInfo, 10000);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
// 输出格式已改变
resetOutputFormat();
}else if (index == MediaCodec.INFO_TRY_AGAIN_LATER){
// 超时
}else if (index >= 0){
ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(index);
MediaFormat outputFormat = videoEncoder.getOutputFormat();
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
bufferInfo.size = 0;
}
if (bufferInfo.size == 0){
outputBuffer = null;
}else {
if (outputBuffer != null){
// 将 ByteBuffer 转换为 byte[]
// 得到编码后数据(需要验证)
byte[] bytes = bytebuffer2ByteArray(outputBuffer);
}
}
videoEncoder.releaseOutputBuffer(index, false);
}
}
}
}).start();
- 使用 MediaMuxer 将编码后数据写入到本地
// 创建 MediaMuxer
mediaMuxer = new MediaMuxer(absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// 写入
mediaMuxer.writeSampleData(videoTrackIndex,outputBuffer,bufferInfo);
MediaRecordService
public class MediaRecordService extends Service {
private MediaRecorder mediaRecorder;
private File recordFile;
private int width;
private int height;
private Surface surface;
private VirtualDisplay virtualDisplay;
private MediaCodec videoEncoder;
private Surface inputSurface;
private MediaMuxer mediaMuxer;
private int videoTrackIndex;
private MediaCodec.BufferInfo bufferInfo;
private boolean isRecord = false;
private NotificationManager notificationManager;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
public void paused(){
// 置为null时,表示暂停
virtualDisplay.setSurface(null);
}
public void stop(){
isRecord = false;
virtualDisplay.setSurface(null);
virtualDisplay.release();
videoEncoder.stop();
videoEncoder.release();
mediaMuxer.stop();
mediaMuxer.release();
notificationManager.cancel(123123);
}
public void resume(){
virtualDisplay.setSurface(surface);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(this, "123123")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("录屏")
.setContentText(getString(R.string.app_name) + "录屏中")
.build();
if(Build.VERSION.SDK_INT>=26) {
// 推送通道
NotificationChannel channel = new NotificationChannel("123123", "通道说明", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
// 展示前台服务
startForeground(123123, notification);
int resultCode = intent.getIntExtra("resultCode", -1);
width = intent.getIntExtra("width", -1);
height = intent.getIntExtra("height", -1);
Intent data = intent.getParcelableExtra("data");
surface = intent.getParcelableExtra("surface");
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection != null) {
// 获取存储的位置
recordFile = getExternalFilesDir("RecordFile");
boolean mkdirs = recordFile.mkdirs();
// initMediaRecorder();
initMediaCodec();
String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();
try {
final FileOutputStream fos = new FileOutputStream(absolutePath);
mediaMuxer = new MediaMuxer(absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
/**
* 创建投影
* name 本次虚拟显示的名称
* width 录制后视频的宽
* height 录制后视频的高
* dpi 显示屏像素
* flags VIRTUAL_DISPLAY_FLAG_PUBLIC 通用显示屏
* Surface 输出位置
*/
virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, inputSurface, null, null);
isRecord = true;
bufferInfo = new MediaCodec.BufferInfo();
readEncoderData();
} catch (IOException e) {
e.printStackTrace();
}
// mediaRecorder.start();
}
return super.onStartCommand(intent, flags, startId);
}
private void readEncoderData() {
new Thread(new Runnable() {
@Override
public void run() {
while (isRecord){
// 获取已经解码的缓冲区索引
int index = videoEncoder.dequeueOutputBuffer(bufferInfo, 10000);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
// 输出格式已改变
resetOutputFormat();
}else if (index == MediaCodec.INFO_TRY_AGAIN_LATER){
// 超时
}else if (index >= 0){
// 获取数据
ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(index);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
bufferInfo.size = 0;
}
if (bufferInfo.size == 0){
outputBuffer = null;
}else {
if (outputBuffer != null){
// 将 ByteBuffer 转换为 byte[]
// byte[] bytes = bytebuffer2ByteArray(outputBuffer);
mediaMuxer.writeSampleData(videoTrackIndex,outputBuffer,bufferInfo);
}
}
videoEncoder.releaseOutputBuffer(index, false);
}
}
}
}).start();
}
/**
* byteBuffer 转 byte数组
* @param buffer
* @return
*/
public static byte[] bytebuffer2ByteArray(ByteBuffer buffer) {
//获取buffer中有效大小
int len = buffer.limit() - buffer.position();
byte[] bytes = new byte[len];
buffer.get(bytes);
return bytes;
}
private void initMediaCodec() {
String MIME_TYPE = "video/avc"; // H.264 类型
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
// 颜色格式
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
// 比特率
format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
// 帧速率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
// I帧的帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
try {
// 创建指定类型的编码器
videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
// 设置编码器属性
videoEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
// 创建作为输入的 Surface
inputSurface = videoEncoder.createInputSurface();
videoEncoder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private void resetOutputFormat() {
MediaFormat newFormat = videoEncoder.getOutputFormat();
videoTrackIndex = mediaMuxer.addTrack(newFormat);
mediaMuxer.start();
}
private void initMediaRecorder() {
mediaRecorder = new MediaRecorder();
// 设置音频来源
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置视频来源
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
// 设置输出格式
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// 设置输出文件
String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();
mediaRecorder.setOutputFile(absolutePath);
// 设置视频宽高
mediaRecorder.setVideoSize(width,height);
// 设置视频帧率
mediaRecorder.setVideoFrameRate(60);
// 设置视频编码比特率
mediaRecorder.setVideoEncodingBitRate(6000000);
// 设置音频编码
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 设置视频编码
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
}
源码
MediaRecordActivity.java
MediaRecordService.java