Android 录屏

作者: 风吹尘埃 | 来源:发表于2020-09-22 18:48 被阅读0次

    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"属性

    且需要声明<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

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

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

    流程:

    AndroidManifest.xml

    <!-- 声明权限 -->
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     ​
     <!-- 声明属性 -->
     <service android:name=".service.MediaRecordService"
      android:foregroundServiceType="mediaProjection">
     </service>
    

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

    相关文章

      网友评论

        本文标题:Android 录屏

        本文链接:https://www.haomeiwen.com/subject/oufgyktx.html