Android 5.0 version above screen recording implementation code of complete code

  • 2021-08-12 03:28:01
  • OfStack

The way I record the screen is to record audio and video separately, and finally merge them into mp4 format, which is troublesome. Because there are few complete tutorials on the Internet, I plan to write a full version. After writing according to my code, at least it can realize the function, instead of simply introducing the usage.

Since we are recording video, we should have a button to control the start and end.

Before recording, it is necessary to judge whether the version of Android system under 1 is greater than 5.0, and dynamically apply for permission under 1 (read and write, record, camera). This step can be executed when clicking the Start button


    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);// The normal situation is to execute here , The function is to apply to capture the screen 
 } else {
  ShowUtil.showToast(context, "Android The version is too low to use this feature ");
 }

3 Define MediaProjection and MediaProjectionManager and other necessary variables


    boolean isrun = false;// Used to mark the status of the recording screen private MediaProjectionManager mediaProjectionManager;
  private MediaProjection mediaProjection;// Tools for recording videos private int width, height, dpi;// Sum of screen width and height dpi , which will be used later 
  private ScreenRecorder screenRecorder;// This is a tool class written by myself to record videos, and the complete code will be put below 
  Thread thread;// Recording video should be put in a thread to execute 

Write instantiation in onCreat


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 We handle the returned event in the onActivityResult callback method


@Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (requestCode == 102) {
  Toast.makeText(context, " Missing read and write permissions ", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode == 103) {
  Toast.makeText(context, " Missing recording permission ", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode == 104) {
  Toast.makeText(context, " Missing camera permissions ", Toast.LENGTH_SHORT).show();
  return;
 }
 if (requestCode != 101) {
  Log.e("HandDrawActivity", "error requestCode =" + requestCode);
 }
 if (resultCode != RESULT_OK) {
  Toast.makeText(context, " Capture screen is prohibited ", 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();// Follow ScreenRecorder Let's talk about it later. In short, the meaning of this sentence is to start recording the screen 
  }
 };
 thread.start();
 binding.startPlayer.setText(" Stop ");// Start and stop the same thing I use 1 Buttons, so change the button text after starting to record the screen 1 Under 
 isrun = true;// The recording screen status is changed to true 
 }

5 first put on ScreenRecorder code, only want the result of friends, directly paste the class away, the place to report errors change 1 change (in my own project but do not report errors), on the realization of the recording screen function, still want to see, you can look down


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", " Recording has started ");
 } catch (IOException e) {
  e.printStackTrace();
 }
 }
 private void prepareRecorder() {
 mBufferInfo = new MediaCodec.BufferInfo(); // Metadata, description bytebuffer Data, size, offset 
 // To create a formatted object  MIMI_TYPE  Incoming  video/avc  Yes H264 Coding format 
 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", " Screen recording has started ");
  }
  }
  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", " Screen recording has ended ");
 }
 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", " Preparing for synthesis ");
 boolean result = false;
 if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) {
  throw new IllegalArgumentException();
 }
 if (filePaths.size() == 1) { //  Only 1 Video clips without merging 
  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));
  }
  }
  //  Take out the audio track and video separately 
  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);
   }
  }
  }
  //  Merge into the final video file 
  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);
  //  Output the file 
  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", " Completion of synthesis ");
  //  Delete the original fragment file after the synthesis is completed 
  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 Starting from startRecorder method


public void startRecorder() {
 prepareRecorder();// Preparation before recording video 
 startLuYin();// Direct recording frequency ( No need to prepare )
 startRecording();// Record video 
 }

Method of recording


private void startLuYin() {
 File file = new File(path, "APlanyinpin.amr");
 mediaRecorder = new MediaRecorder();
 mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// Sound source, Mike 
 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);// Audio format, default, is actually defined above amr Yes, besides that, there are mp4
 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);// Coding format, the problem is that I don't know what the coding format has an impact on, is the sound quality or file size or parsing speed, wait for me to have time to specialize in research 1 Under 
 mediaRecorder.setOutputFile(file.getAbsolutePath());
 try {
  mediaRecorder.prepare();
  mediaRecorder.start();
  Log.e("HandDrawActivity", " Recording has started ");
 } catch (IOException e) {
  e.printStackTrace();
 }
 }

// Preparation before recording video 
private void prepareRecorder() {
 mBufferInfo = new MediaCodec.BufferInfo(); // Metadata, description bytebuffer Data, size, offset 
 // To create a formatted object  MIMI_TYPE  Incoming  video/avc  Yes H264 Coding format 
 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);// The setting of encoder, what is the specific setting, I am not very clear, but check it online 1 That's what Cha wrote! ! ! 
 try {
  mEncorder = MediaCodec.createEncoderByType("video/avc");
  mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  mInputSurface = mEncorder.createInputSurface();
  mEncorder.start();// Let the encoder run first 
 } catch (IOException e) {
  e.printStackTrace();
  releaseEncorders(0);
 }
 }

This is also the preparatory work


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);// Baidu 1 Under MediaMuxer, It's very detailed 
  mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
   mInputSurface, null, null);
  drainEncoder();
  }
 } catch (Exception e) {
  e.printStackTrace();
 }
 }

This is to start writing video files


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", " Screen recording has started ");
  }
  }
  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", " Screen recording has ended ");
 }

This is the method of combining recorded audio and video into mp4, which is also used when clicking Stop Recording Screen


    boolean isrun = false;// Used to mark the status of the recording screen private MediaProjectionManager mediaProjectionManager;
  private MediaProjection mediaProjection;// Tools for recording videos private int width, height, dpi;// Sum of screen width and height dpi , which will be used later 
  private ScreenRecorder screenRecorder;// This is a tool class written by myself to record videos, and the complete code will be put below 
  Thread thread;// Recording video should be put in a thread to execute 
0

This is the end of the time, the emptying emptying, the logout of the logout, i is used to judge whether recorded, it is possible that just entered this page have not recorded, directly returned to other pages, it is possible that the null pointer is abnormal, because some variables are not initialized, so use i to judge 1, you can also write other methods to judge the end


    boolean isrun = false;// Used to mark the status of the recording screen private MediaProjectionManager mediaProjectionManager;
  private MediaProjection mediaProjection;// Tools for recording videos private int width, height, dpi;// Sum of screen width and height dpi , which will be used later 
  private ScreenRecorder screenRecorder;// This is a tool class written by myself to record videos, and the complete code will be put below 
  Thread thread;// Recording video should be put in a thread to execute 
1

7 part of the code is also my online steak, but the online code has not seen a relatively complete version, I write above is absolutely no problem after my own test and the code is not missing, if I find any missing code, I will make up for it later.


Related articles: