Android5.0录屏

Android L新增了MediaProjection录屏的api,捣鼓了大半天,照着github上的demo撸了一遍代码,梳理梳理。
录屏实现依赖MediaProjectionManager
通过
mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE)
拿到是实例
然后创建intent,并startActivityForResult

Intent intent = mediaProjectionManager.createScreenCaptureIntent();
            startActivityForResult(intent, 123);

在onActvitiyResult()中拿到MediaProjection实例

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 123) {
            mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
            if (mediaProjection != null ) {
                mRecorder = new ScreenRecorder(mWidth, mHeight, mediaProjection, mDensty);
            }
            new Thread() {
                @Override
                public void run() {
                    mRecorder.startRecorder();
                }
            }.start();
            btnRecorder.setText("停止录屏");
            moveTaskToBack(true);
        }
    }

录屏很消耗资源,另起一个线程操作

前面的这些步骤呢,主要目的就是一个,开启录屏。

然后,通过MediaProjection实例创建一个虚拟屏幕,剩下的工作主要就是通过MediaCodec,MediaMuxer
将virtualDisplay的进行编码输出到MP4文件中。
思路还是比较简短的,但是呢,我也是踩了不少坑,之前完全不熟悉相关的API只能一遍一遍照着demo撸。
归纳一下实现步骤
1.拿到MediaProjectionManager服务 getSystemService();
2.创建intent,并启动 mediaprojectionmanager.createCpatureIntent();
3.初始化编码器 MediaCodec.createEncoderByType(), 并创建一个suface
mEncoder.createInputSuface()
启动编码器,mEncoder.start();
4.使用suface创建虚拟屏幕 mediaprojection.createVirtualDisplay()
5.创建一个混合器,MediaMuxer;
6.编码并输出。

具体代码

1.拿到MediaProjectionManager服务 getSystemService();

mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);

2.创建intent,并启动 mediaprojectionmanager.createCpatureIntent();

Intent intent = mediaProjectionManager.createScreenCaptureIntent();
            startActivityForResult(intent, 123);

拿到mediaprojection对象

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == 123) {
            mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
            if (mediaProjection != null ) {
                mRecorder = new ScreenRecorder(mWidth, mHeight, mediaProjection, mDensty);
            }
            new Thread() {
                @Override
                public void run() {
                    mRecorder.startRecorder();
                }
            }.start();
            btnRecorder.setText("停止录屏");
            moveTaskToBack(true);
        }
    }

ScreenRecorder构造方法主要是进行了传值工作

    public ScreenRecorder(int mWidth, int mHeight, MediaProjection mediaProjection, int mDensty) {
        this.mWidth = mWidth;
        this.mHeight = mHeight;
        this.mediaProjection = mediaProjection;
        this.mDensty = mDensty;

        File file = new File(savePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

在onActivityResult中 另起一个线程调用了startRecorder()方法
看代码

    public void startRecorder() {
        prepareRecorder();
        startRecording();
    }

在prepareRecorder()中初始化编码器,并设置一些参数
设置了bit率,影响清晰度的4000000-6000000b比较合适
framerate 帧率 就是常说的fps
其他的设置还是看google文档吧,我也不是特别清楚,还在学习中

private void prepareRecorder() {
        mBufferInfo = new MediaCodec.BufferInfo();  //元数据,描述bytebuffer的数据,尺寸,偏移
        //创建格式化对象 MIMI_TYPE 传入的 video/avc 是H264编码格式
        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
        int frameRate = 45; 
        format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
        format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);

        /*MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);*/

        try {
            mEncorder = MediaCodec.createEncoderByType(MIME_TYPE);
            mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mInputSurface = mEncorder.createInputSurface();
            mEncorder.start();
        } catch (IOException e) {
            e.printStackTrace();
            releaseEncorders();
        }
    }

将format传给mEncorder,然后调用start,编码器就开始工作了,会自动将数据写入到MediaCodec的缓冲区内,我们拿出缓冲区内编码好的数据,使用MediaMuxer进行混合输出就ok了,看到有大神将,声音和视频的混合之后一起输出,就实现了录屏和录音同步。这个后面可以尝试下的

接着看

private void startRecording() {
        File saveFile = new File(savePath, System.currentTimeMillis() + ".mp4");
        try {
            mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            Log.e("TAG", "mwidth " + mWidth + " heigh "  + mHeight + "  mdensty " + mDensty + "   Flag "
                    + DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + "  surface " + String.valueOf(mInputSurface == null));
            mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                    mInputSurface, null, null);
            drainEncoder();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            releaseEncorders();
        }

    }

MediaMuxer需要一个文件来保存输出的视频,并传入一个输出格式
MediaMuxer输出格式目前只支持MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
MUXER_OUTPUT_WEBM两种。

创建虚拟屏幕之后
就开始编码输出了,

private void drainEncoder() {
        while (!mQuit.get()) {
            Log.e("TAG", "drain.....");
            int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0);
            if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
                if (!mMuxerStarted && mTrackIndex >= 0) {
                    mMuxer.start();
                    mMuxerStarted = true;
                }
            }
            if (bufferIndex >= 0) {
                Log.e("TAG", "drain...write..");
                ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex);
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    mBufferInfo.size = 0;
                }
                if (mBufferInfo.size != 0) {
                    if (mMuxerStarted) {
                        bufferData.position(mBufferInfo.offset);
                        bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
                        mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
                    }
                }
                mEncorder.releaseOutputBuffer(bufferIndex, false);
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    break;
                }
            }
        }
    }

这段代码呢先拿到输出buffer的index
index的含义:
MediaCodec.INFO_TRY_AGAIN_LATER 超时
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 格式改变
我们在MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 这个的时候会开启muxer

if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
                if (!mMuxerStarted && mTrackIndex >= 0) {
                    mMuxer.start();
                    mMuxerStarted = true;
                }
          }

也比较容易理解
之前是muxer并没有调用start(),所以这个时候是没有输出的,混合也是不能起任何作用的

MediaCodec.BUFFER_FLAG_END_OF_STREAM 输出结尾
MediaCodec.BUFFER_FLAG_CODEC_CONFIG 开始编码 我们需要忽略

index大于0,写数据

if (mBufferInfo.size != 0) {
                    if (mMuxerStarted) {
                        bufferData.position(mBufferInfo.offset);
                        bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
                        mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
                    }
                }

不要忘了释放资源,这个是真消耗资源
我用的三星s6 9200,开始没有处理资源,手机卡死好几次,电量也耗得。。。。
代码就不贴了
好了,就到这里

你可能感兴趣的:(android学习)