广告位联系
返回顶部
分享到

Android录屏的三种方法!

Android 来源:互联网搜集 作者:秩名 发布时间:2020-03-23 19:09:57 人浏览
摘要

本文总结三种用于安卓录屏的解决方案: adb shell命令screenrecord MediaRecorder, MediaProjection MediaProjection , MediaCodec和MediaMuxer screenrecord命令 screenrecord是一个shell命令,支持Android4.4(API level 19)以上,录制的视频格式为mp4 ,存

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

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中获取结果

?
1
2
3
4
5
6
7
8
9
10
11
12
@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进程

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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获取录屏权限,在回调中进行屏幕数据处理。

这里创建另一个进程:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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似乎只有在屏幕发生变化时才传输,因此录屏推流的画面显得不够流畅


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://www.jianshu.com/p/8b313692ac85
相关文章
  • Kotlin的Collection与Sequence操作异同点介绍

    Kotlin的Collection与Sequence操作异同点介绍
    在Android开发中,集合是我们必备的容器,Kotlin的标准库中提供了很多处理集合的方法,而且还提供了两种基于容器的工作方式:Collection 和
  • 实现一个Kotlin函数类型方法

    实现一个Kotlin函数类型方法
    接口与函数类型 业务开发中,经常会有实现一个函数式接口(即接口只有一个方法需要实现)的场景,大家应该都会不假思索的写出如下代
  • Android10 App启动Activity源码分析
    ActivityThread的main方法 让我们把目光聚焦到ActivityThread的main方法上。 ActivityThread的源码路径为/frameworks/base/core/java/android/app/ActivityThread。 1 2
  • Android10客户端事务管理ClientLifecycleManager源码解析

    Android10客户端事务管理ClientLifecycleManager源码解析
    在Android 10 App启动分析之Activity启动篇(二)一文中,简单地介绍了Activity的生命周期管理器是如何调度Activity进入onCreate生命周期的流程。这
  • Kotlin对象的懒加载方式by lazy与lateinit异同介绍

    Kotlin对象的懒加载方式by lazy与lateinit异同介绍
    属性或对象的延时加载是我们相当常用的,一般我们都是使用 lateinit 和 by lazy 来实现。 他们两者都是延时初始化,那么在使用时那么他们两
  • Android类加载流程分析

    Android类加载流程分析
    本文分析的代码基于Android8.1.0源码。 流程分析 从loadClass开始,我们来看下Android中类加载的流程 /libcore/ojluni/src/main/java/java/lang/ClassLoader.ja
  • Android实现读写USB串口数据的代码

    Android实现读写USB串口数据的代码
    最近在研究USB方面的内容;先后做了关于Android读写HID、串口设备的DEMO。本文比较简单,主要介绍的是Android实现读取串口数据的功能 废话不
  • Epoxy - 在RecyclerView中构建复杂界面
    Diffing 对于复杂数据结构支持的多个视图类型展示在屏幕上, Epoxy此时是尤其有用的. 在这些场景中, 数据可能会被网络请求, 异步 Observable, 用
  • Android性能优化的详细介绍

    Android性能优化的详细介绍
    性能优化是一个app很重要的一部分,一个性能优良的app从被下载到启动到使用都能给用户到来很好的体验。自然我们做性能优化也是从被下
  • Android进阶宝典-插件化2(Hook启动插件中四大组件

    Android进阶宝典-插件化2(Hook启动插件中四大组件
    在上一节,我们主要介绍了如果通过反射来加载插件中的类,调用类中的方法;既然插件是一个apk,其实最重要的是启动插件中的Activity、
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计