Android录屏

参考

概念

通过MediaProjection创建一个投影,可以将这个投影显示到自己的 SurfaceView 上,也可以通过 MediaRecorder 编码存储到本地实现录屏效果,也可以通过 MediaCodec 编码后获取实时数据推送直播

相关权限

权限

说明

是否动态申请

android.permission.RECORD_AUDIO

录音权限

android.permission.FOREGROUND_SERVICE

前台服务

相关类

说明

MediaProjectionManager

MediaProjection管理

MediaProjection

授予捕获屏幕或记录系统音频的功能

VirtualDisplay

类似投影仪?捕获屏幕后将数据输出到投影仪 投影仪可以获取视频的信息,指定输出的位置等

MediaRecorder

用于将音视频编码输出

MediaMuxer

将音视频混合生成多媒体文件

MediaCodec

进行音视频压缩编解码

流程

1.申请录屏

通过MediaProjectionManager.createScreenCaptureIntent()获取一个Intent

调用startActivityForResult()发起录屏请求

在onActivityResult()中获取请求结果并开始录屏

MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

Intent screenCaptureIntent = mediaProjectionManager.createScreenCaptureIntent();

startActivityForResult(screenCaptureIntent,10012);

2.启用前台服务

Android 10之后使用录屏等功能要求在前台Service中进行

AndroidManifest.xml中要为该Service设置android:foregroundServiceType="mediaProjection"属性

且需要声明

启动Service时,需要调用startForegroundService()作为前台服务启动

在Service中需要先调用startForeground()启动一个Notification后才能调用录屏

流程:

AndroidManifest.xml

android:foregroundServiceType="mediaProjection">

MediaRecordActivity

@RequiresApi(api = Build.VERSION_CODES.O)

@Override

protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

super.onActivityResult(requestCode, resultCode, data);

// 获取申请录屏结果

if (requestCode == 10012 && resultCode == RESULT_OK){

Intent intent = new Intent(this, MediaRecordService.class);

intent.putExtra("data",data);

intent.putExtra("resultCode",resultCode);

intent.putExtra("width",WindowUtils.getWindowWidth(this)); // 屏幕的宽

intent.putExtra("height",WindowUtils.getWindowHeight(this)); // 屏幕的高

intent.putExtra("surface",surface); // Surface 用于显示录屏的数据

startForegroundService(intent); // 启动前台服务

}

}

MediaRecordService

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

// 创建通知栏

NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

Notification notification = new NotificationCompat.Builder(this, "123123")

.setSmallIcon(R.mipmap.ic_launcher)

.setContentTitle("录屏")

.setContentText(getString(R.string.app_name) + "录屏中")

.build();

if(Build.VERSION.SDK_INT>=26) {

// 推送通道

NotificationChannel channel = new NotificationChannel("123123", "通道说明", NotificationManager.IMPORTANCE_DEFAULT);

notificationManager.createNotificationChannel(channel);

}

// 展示前台服务

startForeground(123123, notification);

int resultCode = intent.getIntExtra("resultCode", -1);

width = intent.getIntExtra("width", -1);

height = intent.getIntExtra("height", -1);

Intent data = intent.getParcelableExtra("data");

final Surface surface = intent.getParcelableExtra("surface");

// 获取 MediaProjectionManager

MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

// 获取 MediaProjection

final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);

if (mediaProjection != null) {

/**

* 创建投影

* name 本次虚拟显示的名称

* width 录制后视频的宽

* height 录制后视频的高

* dpi 显示屏像素

* flags VIRTUAL_DISPLAY_FLAG_PUBLIC 通用显示屏

* Surface 输出的Surface

*/

VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("record-video", 200, 200, 6000000,

DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);

}

return super.onStartCommand(intent, flags, startId);

}

使用 MediaRecorder 录制保存到本地

初始化 MediaRecorder

private void initMediaRecorder() {

mediaRecorder = new MediaRecorder();

// 设置音频来源 需要动态申请 android.permission.RECORD_AUDIO 权限

mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

// 设置视频来源

mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

// 设置输出格式

mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

// 设置输出文件

String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();

mediaRecorder.setOutputFile(absolutePath);

// 设置视频宽高

mediaRecorder.setVideoSize(width,height);

// 设置视频帧率

mediaRecorder.setVideoFrameRate(60);

// 设置视频编码比特率

mediaRecorder.setVideoEncodingBitRate(6000000);

// 设置音频编码

mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

// 设置视频编码

mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

try {

mediaRecorder.prepare();

} catch (IOException e) {

e.printStackTrace();

}

}

创建投影时,将 MediaRecorder 的 Surface 设为输出位置

// mediaRecorder.getSurface() 获取要记录的 Surface

virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000,

DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null);

开始

mediaRecorder.start()

使用 MediaCodec 编码

编码后数据未验证是否可以直接进行推流,按 使用MediaCodec和RTMP做直播推流 对数据进行RTMP编码后应该是可以推流的

初始化 MediaCodec

private void initMediaCodec() {

String MIME_TYPE = "video/avc"; // H.264 类型

MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);

// 颜色格式

format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

// 比特率

format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);

// 帧速率

format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);

// I帧的帧率

format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);

try {

// 创建指定类型的编码器

videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);

// 设置编码器属性

videoEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

// 创建作为输入的 Surface

inputSurface = videoEncoder.createInputSurface();

videoEncoder.start();

} catch (IOException e) {

e.printStackTrace();

}

}

创建投影时,将 MediaCodec的 Surface 设为输出位置

virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, inputSurface, null, null);

读取解码后数据

new Thread(new Runnable() {

@Override

public void run() {

while (isRecord){

// 获取已经解码的缓冲区索引

int index = videoEncoder.dequeueOutputBuffer(bufferInfo, 10000);

if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){

// 输出格式已改变

resetOutputFormat();

}else if (index == MediaCodec.INFO_TRY_AGAIN_LATER){

// 超时

}else if (index >= 0){

ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(index);

MediaFormat outputFormat = videoEncoder.getOutputFormat();

if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {

bufferInfo.size = 0;

}

if (bufferInfo.size == 0){

outputBuffer = null;

}else {

if (outputBuffer != null){

// 将 ByteBuffer 转换为 byte[]

// 得到编码后数据(需要验证)

byte[] bytes = bytebuffer2ByteArray(outputBuffer);

}

}

videoEncoder.releaseOutputBuffer(index, false);

}

}

}

}).start();

使用 MediaMuxer 将编码后数据写入到本地

// 创建 MediaMuxer

mediaMuxer = new MediaMuxer(absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

// 写入

mediaMuxer.writeSampleData(videoTrackIndex,outputBuffer,bufferInfo);

MediaRecordService

public class MediaRecordService extends Service {

private MediaRecorder mediaRecorder;

private File recordFile;

private int width;

private int height;

private Surface surface;

private VirtualDisplay virtualDisplay;

private MediaCodec videoEncoder;

private Surface inputSurface;

private MediaMuxer mediaMuxer;

private int videoTrackIndex;

private MediaCodec.BufferInfo bufferInfo;

private boolean isRecord = false;

private NotificationManager notificationManager;

@Nullable

@Override

public IBinder onBind(Intent intent) {

return new MyBinder();

}

public class MyBinder extends Binder {

public void paused(){

// 置为null时,表示暂停

virtualDisplay.setSurface(null);

}

public void stop(){

isRecord = false;

virtualDisplay.setSurface(null);

virtualDisplay.release();

videoEncoder.stop();

videoEncoder.release();

mediaMuxer.stop();

mediaMuxer.release();

notificationManager.cancel(123123);

}

public void resume(){

virtualDisplay.setSurface(surface);

}

}

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

Notification notification = new NotificationCompat.Builder(this, "123123")

.setSmallIcon(R.mipmap.ic_launcher)

.setContentTitle("录屏")

.setContentText(getString(R.string.app_name) + "录屏中")

.build();

if(Build.VERSION.SDK_INT>=26) {

// 推送通道

NotificationChannel channel = new NotificationChannel("123123", "通道说明", NotificationManager.IMPORTANCE_DEFAULT);

notificationManager.createNotificationChannel(channel);

}

// 展示前台服务

startForeground(123123, notification);

int resultCode = intent.getIntExtra("resultCode", -1);

width = intent.getIntExtra("width", -1);

height = intent.getIntExtra("height", -1);

Intent data = intent.getParcelableExtra("data");

surface = intent.getParcelableExtra("surface");

MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);

if (mediaProjection != null) {

// 获取存储的位置

recordFile = getExternalFilesDir("RecordFile");

boolean mkdirs = recordFile.mkdirs();

// initMediaRecorder();

initMediaCodec();

String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();

try {

final FileOutputStream fos = new FileOutputStream(absolutePath);

mediaMuxer = new MediaMuxer(absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

/**

* 创建投影

* name 本次虚拟显示的名称

* width 录制后视频的宽

* height 录制后视频的高

* dpi 显示屏像素

* flags VIRTUAL_DISPLAY_FLAG_PUBLIC 通用显示屏

* Surface 输出位置

*/

virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000,

DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, inputSurface, null, null);

isRecord = true;

bufferInfo = new MediaCodec.BufferInfo();

readEncoderData();

} catch (IOException e) {

e.printStackTrace();

}

// mediaRecorder.start();

}

return super.onStartCommand(intent, flags, startId);

}

private void readEncoderData() {

new Thread(new Runnable() {

@Override

public void run() {

while (isRecord){

// 获取已经解码的缓冲区索引

int index = videoEncoder.dequeueOutputBuffer(bufferInfo, 10000);

if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){

// 输出格式已改变

resetOutputFormat();

}else if (index == MediaCodec.INFO_TRY_AGAIN_LATER){

// 超时

}else if (index >= 0){

// 获取数据

ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(index);

if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {

bufferInfo.size = 0;

}

if (bufferInfo.size == 0){

outputBuffer = null;

}else {

if (outputBuffer != null){

// 将 ByteBuffer 转换为 byte[]

// byte[] bytes = bytebuffer2ByteArray(outputBuffer);

mediaMuxer.writeSampleData(videoTrackIndex,outputBuffer,bufferInfo);

}

}

videoEncoder.releaseOutputBuffer(index, false);

}

}

}

}).start();

}

/**

* byteBuffer 转 byte数组

* @param buffer

* @return

*/

public static byte[] bytebuffer2ByteArray(ByteBuffer buffer) {

//获取buffer中有效大小

int len = buffer.limit() - buffer.position();

byte[] bytes = new byte[len];

buffer.get(bytes);

return bytes;

}

private void initMediaCodec() {

String MIME_TYPE = "video/avc"; // H.264 类型

MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);

// 颜色格式

format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

// 比特率

format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);

// 帧速率

format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);

// I帧的帧率

format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);

try {

// 创建指定类型的编码器

videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);

// 设置编码器属性

videoEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

// 创建作为输入的 Surface

inputSurface = videoEncoder.createInputSurface();

videoEncoder.start();

} catch (IOException e) {

e.printStackTrace();

}

}

private void resetOutputFormat() {

MediaFormat newFormat = videoEncoder.getOutputFormat();

videoTrackIndex = mediaMuxer.addTrack(newFormat);

mediaMuxer.start();

}

private void initMediaRecorder() {

mediaRecorder = new MediaRecorder();

// 设置音频来源

mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);

// 设置视频来源

mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

// 设置输出格式

mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

// 设置输出文件

String absolutePath = new File(recordFile + "/record.mp4").getAbsolutePath();

mediaRecorder.setOutputFile(absolutePath);

// 设置视频宽高

mediaRecorder.setVideoSize(width,height);

// 设置视频帧率

mediaRecorder.setVideoFrameRate(60);

// 设置视频编码比特率

mediaRecorder.setVideoEncodingBitRate(6000000);

// 设置音频编码

mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

// 设置视频编码

mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

try {

mediaRecorder.prepare();

} catch (IOException e) {

e.printStackTrace();

}

}

}

android 录屏广播,Android 录屏相关推荐

  1. android 程序退出广播,android 利用广播实现程序的强制退出

    利用广播机制,实现程序的强制退出. 前面有篇博文,写到了一键退出,这里利用到了那里的ActivityCollector的集合和BaseActivity 功能:打开程序后,先登录,登录成功后,进入主页面 ...

  2. Android usb 权限广播,[Android]USB开发

    第一:请求权限和请求权限回调(通过广播回调) 注册一个广播接收器用于接收USB权限被同意或拒绝后发出的广播 //注册USB设备权限管理广播 IntentFilter filter = new Inte ...

  3. android动态静态广播,Android BroadcastReceiver实现自定义静态广播和动态广播(黏性广播)...

    思维导图: 静态广播: 当广播发出去的时候 如果接收广播的应用程序已经退出,也能接收广播: 发送者代码: package com.example.g160628_android_21_sender; ...

  4. android系统广播 定向广播,Android之定向广播

    Android中当多个应用都接收同一个广播时,会导致很多系统进程同时开启,这会导致系统卡顿.有了ssp我们可以定向的接收或发送某一特定广播达到优化系统的效果. 接收或发送定向广播需要用到android ...

  5. android 电池电量广播,Android查看电池电量的方法(基于BroadcastReceiver)

    本文实例讲述了Android查看电池电量的方法.分享给大家供大家参考,具体如下: 程序如下: import android.app.Activity; import android.app.Dialo ...

  6. android系统关机广播,Android开机广播和关机广播

    android开机广播:代码如下: import android.content.BroadcastReceiver; import android.content.Context; import a ...

  7. android 接收闹钟广播,android 设置闹钟及通知示例

    简单说一下这次demo内容,首先做一个设置一次性闹钟,先得到alarmManager,打开一个时间对话框,在里面设置闹钟的时间,时间一到发送广播,然后广播接受者接到跳转到新的activity播放音乐. ...

  8. android常驻型广播,Android 常驻广播和非常驻广播

    一.知识准备 ①常驻广播接受者:使用AndroidManifest.xml注册,接受者不随Activity的销毁而销毁,也就是拥有独立的生命周期. ②非常驻广播接受者:使用registerReceiv ...

  9. android充电状态广播,Android 获取电池是否充电状态、充电器类型

    0. demo 下载 https://github.com/sufadi/BatteryInfo 1. 电池广播获取充电状态 // 电池状态 import static android.os.Batt ...

  10. android 蓝牙相关广播,Android 蓝牙相关的广播

    Android 蓝牙相关的广播 监听蓝牙相关的广播并获得相关的信息,蓝牙相关的广播主要集中在BluetoothAdapter和BluetoothDevice类中, 可以通过在AndroidManife ...

最新文章

  1. OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算
  2. 第三十三课.一些经典的优化策略与神经网络变种
  3. GTDB:基因组分类数据库,物种注释和进化树构建工具GTDB-tk
  4. 开始测试鸿蒙系统,华为打起反击战!正式测试“鸿蒙系统”,谷歌认怂,恢复华为资格...
  5. AJPFX学习笔记JavaAPI之String类
  6. 学习笔记:linuxsocket通信基础
  7. 首发:徐亦达团队新论文推荐(ECCV2020):端到端多人多视角3d动态匹配网络
  8. MySQL Replace()函数
  9. Java Throwable initCause()方法与示例
  10. 如何给mac地址赋值_交换机工作基础——MAC地址表的构成与安全
  11. 04-windows上安装Kibana
  12. 36 Unicode和字节字符串
  13. java 中文乱码转换_java中文乱码怎么转换
  14. Python 找完美数
  15. 视觉SLAM总结-本质矩阵E分解
  16. 基于表情分析的智能语音陪伴机器人
  17. access函数的用法
  18. 第十一天-购物车订单系统的实现
  19. Windows提权流程及手法
  20. (附源码)SSM校园疫情防控系统JAVA计算机毕业设计项目

热门文章

  1. 用Python做一个Mean Rerversion策略
  2. java毕业设计学生社团管理与评价系统Mybatis+系统+数据库+调试部署
  3. 计算机毕业设计基于ssh学生请假管理系统
  4. PPT | 5G时代的视频云服务关键技术与实践
  5. Xcode里的-ObjC,-all_laod和-force_load的作用
  6. API接口怎么使用(教你使用api接口获取数据)
  7. 如何以正确的顺序重新安装驱动程序
  8. 登陆邮箱的方法有哪些?解析mail163邮箱如何误删恢复?
  9. 第十届蓝桥杯省赛Scratch编程真题解析
  10. 树莓派4b主板特点_树莓派4B的入手操作