1. 程式人生 > >Android5.0錄屏

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,開始沒有處理資源,手機卡死好幾次,電量也耗得。。。。
程式碼就不貼了
好了,就到這裡