Android录屏的三种解决方案

作者:charles0427 时间:2021-09-28 14:07:59 

本文总结三种用于安卓录屏的解决方案:

adb shell命令screenrecord
MediaRecorder, MediaProjection
MediaProjection , MediaCodec和MediaMuxer

screenrecord命令

screenrecord是一个shell命令,支持Android4.4(API level 19)以上,录制的视频格式为mp4 ,存放到手机sd卡里,默认录制时间为180s

adb shell screenrecord --size 1280*720 --bit-rate 6000000 --time-limit 30 /sdcard/demo.mp4

 --size 指定视频分辨率;

 --bit-rate 指定视频比特率,默认为4M,该值越小,保存的视频文件越小;

 --time-limit 指定录制时长,若设定大于180,命令不会被执行;

MediaRecorder

MediaProjection是Android5.0后才开放的屏幕采集接口,通过系统级服务MediaProjectionManager进行管理。

录屏过程可以分成两个部分,即通过MediaProjectionManage申请录屏权限,用户允许后开始录制屏幕;然后通过MediaRecorder对音视频数据进行处理。

获取MediaProjectionManager实例

MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService("media_projection");

申请权限

Intent captureIntent = mProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, LOCAL_REQUEST_CODE);

createScreenCaptureIntent()这个方法会返回一个intent,你可以通过startActivityForResult方法来传递这个intent,为了能开始屏幕捕捉,activity会提示用户是否允许屏幕捕捉(为了防止开发者做一个木马,来捕获用户私人信息),你可以通过getMediaProjection来获取屏幕捕捉的结果。

在onActivityResult中获取结果


@Override
 public void onActivityResult(int requestCode, int resultCode, Intent data) {
   MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
   if (mediaProjection == null) {
   Log.e(TAG, "media projection is null");
   return;
 }
   File file = new File("xx.mp4"); //录屏生成文件
   mediaRecord = new MediaRecordService(displayWidth, displayHeight, 6000000, 1,
     mediaProjection, file.getAbsolutePath());
   mediaRecord.start();
}

创建MediaRecorder进程


package com.unionpay.service;
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");
 }
}

MediaCodec与MediaMuxer

MediaCodec提供对音视频压缩编码和解码功能,MediaMuxer可以将音视频混合生成多媒体文件,生成MP4文件。
与MediaRecorder类似,都需要先通过MediaProjectionManager获取录屏权限,在回调中进行屏幕数据处理。

这里创建另一个进程:


package com.unionpay.service;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.projection.MediaProjection;
import android.util.Log;
import android.view.Surface;
public class ScreenRecordService extends Thread{
   private static final String TAG = "ScreenRecordService";
 private int mWidth;
 private int mHeight;
 private int mBitRate;
 private int mDpi;
 private String mDstPath;
 private MediaProjection mMediaProjection;
 // parameters for the encoder
 private static final String MIME_TYPE = "video/avc"; // H.264 Advanced
                // Video Coding
 private static final int FRAME_RATE = 30; // 30 fps
 private static final int IFRAME_INTERVAL = 10; // 10 seconds between
               // I-frames
 private static final int TIMEOUT_US = 10000;
 private MediaCodec mEncoder;
 private Surface mSurface;
 private MediaMuxer mMuxer;
 private boolean mMuxerStarted = false;
 private int mVideoTrackIndex = -1;
 private AtomicBoolean mQuit = new AtomicBoolean(false);
 private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
 private VirtualDisplay mVirtualDisplay;
 public ScreenRecordService(int width, int height, int bitrate, int dpi, MediaProjection mp, String dstPath) {
   super(TAG);
   mWidth = width;
   mHeight = height;
   mBitRate = bitrate;
   mDpi = dpi;
   mMediaProjection = mp;
   mDstPath = dstPath;
 }
 /**
  * stop task
  */
 public final void quit() {
   mQuit.set(true);
 }
 @Override
 public void run() {
   try {
   try {
     prepareEncoder();
     mMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
   } catch (IOException e) {
     throw new RuntimeException(e);
   }
   mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display", mWidth, mHeight, mDpi,
     DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mSurface, null, null);
   Log.d(TAG, "created virtual display: " + mVirtualDisplay);
   recordVirtualDisplay();
   } finally {
   release();
   }
 }
 private void recordVirtualDisplay() {
   while (!mQuit.get()) {
   int index = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
//   Log.i(TAG, "dequeue output buffer index=" + index);
   if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     // 后续输出格式变化
     resetOutputFormat();
   } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
     // 请求超时
//     Log.d(TAG, "retrieving buffers time out!");
     try {
     // wait 10ms
     Thread.sleep(10);
     } catch (InterruptedException e) {
     }
   } else if (index >= 0) {
     // 有效输出
     if (!mMuxerStarted) {
     throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
     }
     encodeToVideoTrack(index);
     mEncoder.releaseOutputBuffer(index, false);
   }
   }
 }
 /**
  * 硬解码获取实时帧数据并写入mp4文件
  *
  * @param index
  */
 private void encodeToVideoTrack(int index) {
   // 获取到的实时帧视频数据
   ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
   if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
   // The codec config data was pulled out and fed to the muxer
   // when we got
   // the INFO_OUTPUT_FORMAT_CHANGED status.
   // Ignore it.
   Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
   mBufferInfo.size = 0;
   }
   if (mBufferInfo.size == 0) {
   Log.d(TAG, "info.size == 0, drop it.");
   encodedData = null;
   } else {
//   Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size + ", presentationTimeUs="
//     + mBufferInfo.presentationTimeUs + ", offset=" + mBufferInfo.offset);
   }
   if (encodedData != null) {
   mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);
   }
 }
 private void resetOutputFormat() {
   // should happen before receiving buffers, and should only happen
   // once
   if (mMuxerStarted) {
   throw new IllegalStateException("output format already changed!");
   }
   MediaFormat newFormat = mEncoder.getOutputFormat();
   mVideoTrackIndex = mMuxer.addTrack(newFormat);
   mMuxer.start();
   mMuxerStarted = true;
   Log.i(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex);
 }
 private void prepareEncoder() throws IOException {
   MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
   format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
   format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
   format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
   format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
   Log.d(TAG, "created video format: " + format);
   mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
   mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
   mSurface = mEncoder.createInputSurface();
   Log.d(TAG, "created input surface: " + mSurface);
   mEncoder.start();
 }
 private void release() {
   if (mEncoder != null) {
   mEncoder.stop();
   mEncoder.release();
   mEncoder = null;
   }
   if (mVirtualDisplay != null) {
   mVirtualDisplay.release();
   }
   if (mMediaProjection != null) {
   mMediaProjection.stop();
   }
   if (mMuxer != null) {
   mMuxer.stop();
   mMuxer.release();
   mMuxer = null;
   }
 }
}

该进程只实现了视频录制,调用该进程只需修改主进程中的onActivityResult方法。

总结

MediaProjection似乎只有在屏幕发生变化时才传输,因此录屏推流的画面显得不够流畅

来源:https://www.jianshu.com/p/8b313692ac85

标签:android,录屏
0
投稿

猜你喜欢

  • 解决MySQL忘记密码问题的方法

    2022-04-05 07:55:43
  • Java数据封装树形结构代码实例

    2022-04-12 08:11:44
  • Mybatis中的mapper模糊查询语句LIKE

    2022-02-17 16:21:15
  • Java基于JDK 1.8的LinkedList源码详析

    2021-07-15 03:29:26
  • Android View背景选择器编写技巧

    2023-09-16 21:37:43
  • Android Studio 2022.1.1创建项目的Gradle配置问题

    2022-05-05 21:45:36
  • Spring Boot 集成Dubbo框架实例

    2022-02-03 21:23:27
  • 简单的java socket客户端和服务端示例

    2023-06-27 18:13:27
  • Android实现Window弹窗效果

    2022-12-07 21:48:16
  • java实现短信通信的完整教程

    2023-03-23 23:03:40
  • MyBatis深入解读懒加载的实现

    2021-10-02 01:09:44
  • C#实现DataTable映射成Model的方法(附源码)

    2023-03-12 06:10:56
  • C#在运行时动态创建类型的实现方法

    2023-08-26 21:51:17
  • Android编程实现wifi扫描及连接的方法

    2022-11-21 21:11:36
  • Android同步屏障机制sync barrier实例应用详解

    2023-04-24 05:27:04
  • java模拟http的Get/Post请求,并设置ip与port代理的方法

    2021-11-25 11:30:57
  • Android开发组件化架构设计原理到实战

    2023-06-14 12:51:13
  • Kotlin 基础教程之数组容器

    2021-09-09 17:22:40
  • Android实现左侧滑动菜单

    2022-10-10 14:58:41
  • 谈一谈Android内存泄漏问题

    2023-01-29 23:23:54
  • asp之家 软件编程 m.aspxhome.com