Android 录屏

Android录屏

参考

  • Android录屏的三种方案

  • 使用MediaCodec和RTMP做直播推流

  • android.media.projection

概念

通过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


 
 
 
 
 

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 录制保存到本地
  1. 初始化 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();
    }
}
  1. 创建投影时,将 MediaRecorder 的 Surface 设为输出位置
// mediaRecorder.getSurface() 获取要记录的 Surface
virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000, 
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null);
  1. 开始
mediaRecorder.start()
使用 MediaCodec 编码

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

  1. 初始化 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();
    }
}
  1. 创建投影时,将 MediaCodec的 Surface 设为输出位置
virtualDisplay = mediaProjection.createVirtualDisplay("record-video", width, height, 6000000, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, inputSurface, null, null);
  1. 读取解码后数据
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();
  1. 使用 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();
        }
    }
}

源码
MediaRecordActivity.java
MediaRecordService.java

你可能感兴趣的:(Android 录屏)