android 录屏广播,Android 录屏
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 录屏相关推荐
- android 程序退出广播,android 利用广播实现程序的强制退出
利用广播机制,实现程序的强制退出. 前面有篇博文,写到了一键退出,这里利用到了那里的ActivityCollector的集合和BaseActivity 功能:打开程序后,先登录,登录成功后,进入主页面 ...
- Android usb 权限广播,[Android]USB开发
第一:请求权限和请求权限回调(通过广播回调) 注册一个广播接收器用于接收USB权限被同意或拒绝后发出的广播 //注册USB设备权限管理广播 IntentFilter filter = new Inte ...
- android动态静态广播,Android BroadcastReceiver实现自定义静态广播和动态广播(黏性广播)...
思维导图: 静态广播: 当广播发出去的时候 如果接收广播的应用程序已经退出,也能接收广播: 发送者代码: package com.example.g160628_android_21_sender; ...
- android系统广播 定向广播,Android之定向广播
Android中当多个应用都接收同一个广播时,会导致很多系统进程同时开启,这会导致系统卡顿.有了ssp我们可以定向的接收或发送某一特定广播达到优化系统的效果. 接收或发送定向广播需要用到android ...
- android 电池电量广播,Android查看电池电量的方法(基于BroadcastReceiver)
本文实例讲述了Android查看电池电量的方法.分享给大家供大家参考,具体如下: 程序如下: import android.app.Activity; import android.app.Dialo ...
- android系统关机广播,Android开机广播和关机广播
android开机广播:代码如下: import android.content.BroadcastReceiver; import android.content.Context; import a ...
- android 接收闹钟广播,android 设置闹钟及通知示例
简单说一下这次demo内容,首先做一个设置一次性闹钟,先得到alarmManager,打开一个时间对话框,在里面设置闹钟的时间,时间一到发送广播,然后广播接受者接到跳转到新的activity播放音乐. ...
- android常驻型广播,Android 常驻广播和非常驻广播
一.知识准备 ①常驻广播接受者:使用AndroidManifest.xml注册,接受者不随Activity的销毁而销毁,也就是拥有独立的生命周期. ②非常驻广播接受者:使用registerReceiv ...
- android充电状态广播,Android 获取电池是否充电状态、充电器类型
0. demo 下载 https://github.com/sufadi/BatteryInfo 1. 电池广播获取充电状态 // 电池状态 import static android.os.Batt ...
- android 蓝牙相关广播,Android 蓝牙相关的广播
Android 蓝牙相关的广播 监听蓝牙相关的广播并获得相关的信息,蓝牙相关的广播主要集中在BluetoothAdapter和BluetoothDevice类中, 可以通过在AndroidManife ...
最新文章
- OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算
- 第三十三课.一些经典的优化策略与神经网络变种
- GTDB:基因组分类数据库,物种注释和进化树构建工具GTDB-tk
- 开始测试鸿蒙系统,华为打起反击战!正式测试“鸿蒙系统”,谷歌认怂,恢复华为资格...
- AJPFX学习笔记JavaAPI之String类
- 学习笔记:linuxsocket通信基础
- 首发:徐亦达团队新论文推荐(ECCV2020):端到端多人多视角3d动态匹配网络
- MySQL Replace()函数
- Java Throwable initCause()方法与示例
- 如何给mac地址赋值_交换机工作基础——MAC地址表的构成与安全
- 04-windows上安装Kibana
- 36 Unicode和字节字符串
- java 中文乱码转换_java中文乱码怎么转换
- Python 找完美数
- 视觉SLAM总结-本质矩阵E分解
- 基于表情分析的智能语音陪伴机器人
- access函数的用法
- 第十一天-购物车订单系统的实现
- Windows提权流程及手法
- (附源码)SSM校园疫情防控系统JAVA计算机毕业设计项目
热门文章
- 用Python做一个Mean Rerversion策略
- java毕业设计学生社团管理与评价系统Mybatis+系统+数据库+调试部署
- 计算机毕业设计基于ssh学生请假管理系统
- PPT | 5G时代的视频云服务关键技术与实践
- Xcode里的-ObjC,-all_laod和-force_load的作用
- API接口怎么使用(教你使用api接口获取数据)
- 如何以正确的顺序重新安装驱动程序
- 登陆邮箱的方法有哪些?解析mail163邮箱如何误删恢复?
- 第十届蓝桥杯省赛Scratch编程真题解析
- 树莓派4b主板特点_树莓派4B的入手操作