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
投稿

猜你喜欢

  • C#中Array与ArrayList用法及转换的方法

    2021-07-18 13:13:23
  • Spring Boot缓存实战 Caffeine示例

    2021-11-15 17:39:34
  • C#实现XML文件操作详解

    2023-07-16 12:36:52
  • 如何使用正则表达式判断邮箱(以C#为例)

    2022-12-09 21:38:30
  • C#异步编程由浅入深(二)之Async/Await的使用

    2022-10-14 16:45:20
  • 在.NET中读取嵌入和使用资源文件的方法

    2022-03-28 21:46:14
  • Android WebView的使用方法总结

    2022-08-23 22:14:02
  • Android实现ListView异步加载的方法(改进版)

    2023-11-24 12:08:49
  • java异常处理详细介绍及实例

    2023-11-28 05:07:02
  • 用C#实现启动另一程序的方法实例

    2023-06-20 12:05:43
  • Java C++ 算法题解leetcode669修剪二叉搜索树示例

    2022-09-22 04:56:13
  • c# AJAX实践VS2005 + RSSToolKit 开发你自己的RSS在线阅读器

    2023-11-30 18:22:11
  • 详解SpringBoot项目整合Vue做一个完整的用户注册功能

    2022-02-13 21:46:35
  • Android利用CountDownTimer实现点击获取验证码倒计时效果

    2023-09-09 21:09:54
  • JAVA进程突然消失问题解决方案

    2023-12-24 04:38:43
  • Android实现可拖动层叠卡片布局

    2023-03-25 03:28:06
  • C#使用Aforge调用摄像头拍照的方法

    2022-02-10 02:34:36
  • ArrayList和LinkedList的区别、扩容机制以及底层的实现方式

    2023-11-27 01:26:57
  • java使用RandomAccessFile类基于指针读写文件实例代码

    2021-07-29 17:45:45
  • 【IntelliJ IDEA】Maven构建自己的第一个Java后台的方法

    2023-09-24 20:11:16
  • asp之家 软件编程 m.aspxhome.com