前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android5.0以上版本录屏实现代码(完整代码)

Android5.0以上版本录屏实现代码(完整代码)

作者头像
砸漏
发布2020-11-04 13:16:35
1.3K0
发布2020-11-04 13:16:35
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

我录屏的方式是分别录制音频和视频,最后合并成mp4格式,比较麻烦,因为网上完整的教程比较少,所以我打算写一个完整版的,照着我的代码写完之后,至少是能够实现功能的,而不是简单的介绍下用法。

1既然是录制视频,我们应该有一个按钮控制开始和结束。

2在录制之前,需要先判断一下Android系统的版本是否大于5.0,并且动态申请一下权限(读写,录音,照相机),这一步可以在点开始按钮的时候执行  

代码语言:javascript
复制
  if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
  != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 102);
 }
 if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
  != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 103);
 }
 if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
  != PackageManager.PERMISSION_GRANTED) {
  ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 104);
 }
 Intent intent = null;
 if (android.os.Build.VERSION.SDK_INT  = android.os.Build.VERSION_CODES.LOLLIPOP) {
  intent = mediaProjectionManager.createScreenCaptureIntent();
  startActivityForResult(intent, 101);//正常情况是要执行到这里的,作用是申请捕捉屏幕
 } else {
  ShowUtil.showToast(context, "Android版本太低,无法使用该功能");
 }

3定义MediaProjection和MediaProjectionManager等一些其他必要的变量

代码语言:javascript
复制
  boolean isrun = false;//用来标记录屏的状态private MediaProjectionManager mediaProjectionManager;
  private MediaProjection mediaProjection;//录制视频的工具private int width, height, dpi;//屏幕宽高和dpi,后面会用到
  private ScreenRecorder screenRecorder;//这个是自己写的录视频的工具类,下文会放完整的代码
  Thread thread;//录视频要放在线程里去执行

在onCreat里写好实例化

代码语言:javascript
复制
mediaProjectionManager = (MediaProjectionManager) context.getSystemService(MEDIA_PROJECTION_SERVICE);
WindowManager manager = this.getWindowManager();
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
width = outMetrics.widthPixels;
height = outMetrics.heightPixels;
dpi = outMetrics.densityDpi;

4我们在onActivityResult回调方法中,来处理返回的事件

代码语言:javascript
复制
@Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (requestCode == 102) {
  Toast.makeText(context, "缺少读写权限", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode == 103) {
  Toast.makeText(context, "缺少录音权限", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode == 104) {
  Toast.makeText(context, "缺少相机权限", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode != 101) {
  Log.e("HandDrawActivity", "error requestCode =" + requestCode);
 }
 if (resultCode != RESULT_OK) {
  Toast.makeText(context, "捕捉屏幕被禁止", Toast.LENGTH_SHORT).show();
  return;
 }
 mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
 if (mediaProjection != null) {
  screenRecorder = new ScreenRecorder(width, height, mediaProjection, dpi);
 }
 thread = new Thread() {
  @Override
  public void run() {
  screenRecorder.startRecorder();//跟ScreenRecorder有关的下文再说,总之这句话的意思就是开始录屏的意思
  }
 };
 thread.start();
 binding.startPlayer.setText("停止");//开始和停止我用的同一个按钮,所以开始录屏之后把按钮文字改一下
 isrun = true;//录屏状态改成真
 }

5先放上ScreenRecorder代码,只想要结果的朋友呢,直接把类粘贴走,把报错的地方改一改(在我自己的项目里可是不报错的),就实现了录制屏幕的功能了,还想看看的,可以往下看看

代码语言:javascript
复制
import android.hardware.display.DisplayManager;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;
import com.coremedia.iso.boxes.Container;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ScreenRecorder {
private int mWidth, mHeight, mDensty;
private MediaProjection mediaProjection;
private MediaCodec.BufferInfo mBufferInfo;
private MediaCodec mEncorder;
private Surface mInputSurface;
private MediaMuxer mMuxer;
private boolean isQuit = false;
private boolean mMuxerStarted = false;
private int mTrackIndex;
private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cache";
private MediaRecorder mediaRecorder;
public ScreenRecorder(int mWidth, int mHeight, MediaProjection mediaProjection, int mDensty) {
this.mWidth = mWidth;
this.mHeight = mHeight;
this.mediaProjection = mediaProjection;
this.mDensty = mDensty;
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
}
public void startRecorder() {
prepareRecorder();
startLuYin();
startRecording();
}
public void stop() {
isQuit = true;
releaseEncorders(1);
List<String  filePath = new ArrayList< ();
filePath.add(path + "/APlanyinpin.amr");
filePath.add(path + "/APlanshipin.mp4");
joinVideo(filePath, path);
}
public void destory() {
releaseEncorders(0);
}
private void startLuYin() {
File file = new File(path, "APlanyinpin.amr");
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
mediaRecorder.setOutputFile(file.getAbsolutePath());
try {
mediaRecorder.prepare();
mediaRecorder.start();
Log.e("HandDrawActivity", "已经开始录音");
} catch (IOException e) {
e.printStackTrace();
}
}
private void prepareRecorder() {
mBufferInfo = new MediaCodec.BufferInfo(); //元数据,描述bytebuffer的数据,尺寸,偏移
//创建格式化对象 MIMI_TYPE 传入的 video/avc 是H264编码格式
MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
int frameRate = 45;
format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);
try {
mEncorder = MediaCodec.createEncoderByType("video/avc");
mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mEncorder.createInputSurface();
mEncorder.start();
} catch (IOException e) {
e.printStackTrace();
releaseEncorders(0);
}
}
private void startRecording() {
File saveFile = new File(path, "APlanshipin.mp4");
try {
if (Build.VERSION.SDK_INT  = Build.VERSION_CODES.LOLLIPOP) {
mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mInputSurface, null, null);
drainEncoder();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void drainEncoder() {
while (!isQuit) {
Log.e("TAG", "drain.....");
int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0);
if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
if (!mMuxerStarted && mTrackIndex  = 0) {
mMuxer.start();
mMuxerStarted = true;
Log.e("HandDrawActivity", "已经开始录屏");
}
}
if (bufferIndex  = 0) {
Log.e("TAG", "drain...write..");
ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {
if (mMuxerStarted) {
bufferData.position(mBufferInfo.offset);
bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
}
}
mEncorder.releaseOutputBuffer(bufferIndex, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
Log.e("HandDrawActivity", "已经结束录屏");
}
private void releaseEncorders(int i) {
if (mediaProjection != null) {
mediaProjection.stop();
}
mBufferInfo = null;
if (mEncorder != null) {
mEncorder.stop();
}
mInputSurface = null;
if (mMuxer != null && i == 1) {
mMuxer.stop();
}
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
}
}
private boolean joinVideo(List<String  filePaths, String resultPath) {
Log.e("HandDrawActivity", "准备合成中");
boolean result = false;
if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) {
throw new IllegalArgumentException();
}
if (filePaths.size() == 1) { // 只有一个视频片段,不需要合并
return true;
}
try {
Movie[] inMovies = new Movie[filePaths.size()];
for (int i = 0; i < filePaths.size(); i++) {
Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i));
File f = new File(filePaths.get(i));
if (f.exists()) {
inMovies[i] = MovieCreator.build(filePaths.get(i));
}
}
// 分别取出音轨和视频
List<Track  videoTracks = new LinkedList< ();
List<Track  audioTracks = new LinkedList< ();
for (Movie m : inMovies) {
for (Track t : m.getTracks()) {
if (t.getHandler().equals("soun")) {
audioTracks.add(t);
}
if (t.getHandler().equals("vide")) {
videoTracks.add(t);
}
}
}
// 合并到最终的视频文件
Movie outMovie = new Movie();
if (audioTracks.size()   0) {
outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}
if (videoTracks.size()   0) {
outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
}
Container mp4file = new DefaultMp4Builder().build(outMovie);
// 将文件输出
File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4");
if (resultFile.exists() && resultFile.isFile()) {
resultFile.delete();
}
FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel();
mp4file.writeContainer(fc);
fc.close();
Log.e("HandDrawActivity", "合成完毕");
// 合成完成后把原片段文件删除
for (String filePath : filePaths) {
File file = new File(filePath);
file.delete();
}
result = true;
HandDrawActivity.sendVideo();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}

6从startRecorder方法说起

代码语言:javascript
复制
public void startRecorder() {
prepareRecorder();//录视频前的准备
startLuYin();//直接录音频(不用准备)
startRecording();//录视频
}

录音的方法

代码语言:javascript
复制
private void startLuYin() {
File file = new File(path, "APlanyinpin.amr");
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//声音来源,麦克
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//音频格式,默认,其实就是上面定义好的amr了,除此之外还有mp4
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//编码格式,问题是我不知道编码格式对什么有影响,是音质高低还是文件大小还是解析快慢,等我有时间去专门研究一下
mediaRecorder.setOutputFile(file.getAbsolutePath());
try {
mediaRecorder.prepare();
mediaRecorder.start();
Log.e("HandDrawActivity", "已经开始录音");
} catch (IOException e) {
e.printStackTrace();
}
}
代码语言:javascript
复制
//录视频前的准备工作
private void prepareRecorder() {
mBufferInfo = new MediaCodec.BufferInfo(); //元数据,描述bytebuffer的数据,尺寸,偏移
//创建格式化对象 MIMI_TYPE 传入的 video/avc 是H264编码格式
MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
int frameRate = 45;
format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);//编码器的设置,具体是设置的啥我也不太清楚,但是网上查一查都是这么写的!!!
try {
mEncorder = MediaCodec.createEncoderByType("video/avc");
mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mEncorder.createInputSurface();
mEncorder.start();//让编码器先跑起来
} catch (IOException e) {
e.printStackTrace();
releaseEncorders(0);
}
}

这里也是准备工作

代码语言:javascript
复制
private void startRecording() {
File saveFile = new File(path, "APlanshipin.mp4");
try {
if (Build.VERSION.SDK_INT  = Build.VERSION_CODES.LOLLIPOP) {
mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);//百度一下MediaMuxer,讲的很详细的
mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mInputSurface, null, null);
drainEncoder();
}
} catch (Exception e) {
e.printStackTrace();
}
}

这个就是开始写视频文件了

代码语言:javascript
复制
private void drainEncoder() {
while (!isQuit) {
Log.e("TAG", "drain.....");
int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0);
if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
if (!mMuxerStarted && mTrackIndex  = 0) {
mMuxer.start();
mMuxerStarted = true;
Log.e("HandDrawActivity", "已经开始录屏");
}
}
if (bufferIndex  = 0) {
Log.e("TAG", "drain...write..");
ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {
if (mMuxerStarted) {
bufferData.position(mBufferInfo.offset);
bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
}
}
mEncorder.releaseOutputBuffer(bufferIndex, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
Log.e("HandDrawActivity", "已经结束录屏");
}

这个就是把录好的音频和视频合并成mp4的方法了,也是点击停止录屏的时候用到的

代码语言:javascript
复制
private boolean joinVideo(List<String  filePaths, String resultPath) {
Log.e("HandDrawActivity", "准备合成中");
boolean result = false;
if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) {
throw new IllegalArgumentException();
}
if (filePaths.size() == 1) { // 只有一个视频片段,不需要合并
return true;
}
try {
Movie[] inMovies = new Movie[filePaths.size()];
for (int i = 0; i < filePaths.size(); i++) {
Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i));
File f = new File(filePaths.get(i));
if (f.exists()) {
inMovies[i] = MovieCreator.build(filePaths.get(i));
}
}
// 分别取出音轨和视频
List<Track  videoTracks = new LinkedList< ();
List<Track  audioTracks = new LinkedList< ();
for (Movie m : inMovies) {
for (Track t : m.getTracks()) {
if (t.getHandler().equals("soun")) {
audioTracks.add(t);
}
if (t.getHandler().equals("vide")) {
videoTracks.add(t);
}
}
}
// 合并到最终的视频文件
Movie outMovie = new Movie();
if (audioTracks.size()   0) {
outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}
if (videoTracks.size()   0) {
outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
}
Container mp4file = new DefaultMp4Builder().build(outMovie);
// 将文件输出
File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4");
if (resultFile.exists() && resultFile.isFile()) {
resultFile.delete();
}
FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel();
mp4file.writeContainer(fc);
fc.close();
Log.e("HandDrawActivity", "合成完毕");
// 合成完成后把原片段文件删除
for (String filePath : filePaths) {
File file = new File(filePath);
file.delete();
}
result = true;
HandDrawActivity.sendVideo();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

这个就是结束的时候了,该清空的清空,该注销的注销, i是用来判断录没录的,有可能刚进入这个页面都没录过,直接就返回到别的页面了,那就有可能空指针异常,因为有些变量都没初始化,所以用i判断一下,也可以自己写别的方法判端

代码语言:javascript
复制
private void releaseEncorders(int i) {
if (mediaProjection != null) {
mediaProjection.stop();
}
mBufferInfo = null;
if (mEncorder != null) {
mEncorder.stop();
}
mInputSurface = null;
if (mMuxer != null && i == 1) {
mMuxer.stop();
}
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
}
}

7部分代码也是我从网上扒的,但是网上的代码就没怎么见过比较完整的版本的,我上面写的都是经过我自己测试绝对没问题的而且代码也没什么遗漏的,要是发现有遗漏的代码我后续再补上。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档