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();

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值