Android屏幕鏡像一:屏幕采集 + MediaCodec編碼

接下來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Android屏幕鏡像功能松逊,主要涉及到以下這些知識(shí)點(diǎn):

  • 1盾沫、Android屏幕采集
  • 2沸毁、MediaCodec編解碼
  • 3、Android音頻數(shù)據(jù)采集
  • 4亥鬓、AudioTack播放pcm
  • 5完沪、tcpudp傳輸

一嵌戈、屏幕采集

Android5.0及更高版本支持屏幕采集覆积,屏幕采集需要用動(dòng)態(tài)申請(qǐng)權(quán)限。

    public static final int REQUEST_CODE = 1000;
    private MediaProjectionManager mMediaProjectionManager;
    private MediaProjection mediaProjection;

    private void requestScreenCapture() {
        mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(captureIntent, REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK || requestCode != REQUEST_CODE)
            return;
        mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
        if (mediaProjection == null) {
            return;
        }
        DisplayMetrics dm = getResources().getDisplayMetrics();
        mScreenCapture = new ScreenCapture(dm.widthPixels, dm.heightPixels, mediaProjection);
        mScreenCapture.setOnCaptureVideoCallback(mVideoCallback);
        mScreenCapture.startCapture();
    }

如上熟呛,獲取截屏權(quán)限后宽档,就可以開始截屏了。
通過(guò)MediaProjectioncreateVirtualDisplay方法可將截屏畫面給到指定的surface中庵朝,這里我們截屏的畫面不需要顯示吗冤,而是需要將畫面數(shù)據(jù)給到MediaCodec編碼又厉,所以這里需要通過(guò)MediaCodec生成一個(gè)surface用于接收截屏數(shù)據(jù)。

    public void initEncoder() {
        Log.i(TAG,"initEncoder");
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
        // 設(shè)置視頻輸入顏色格式椎瘟,這里選擇使用Surface作為輸入覆致,可以忽略顏色格式的問(wèn)題,并且不需要直接操作輸入緩沖區(qū)肺蔚。
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        // 碼率
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height);
        // 幀率
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
        // I幀間隔
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
        try {
            mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            Surface surface = mEncoder.createInputSurface();
            mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display", width, height, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
        } catch (Exception e) {
            Log.w(TAG, e);
            mEncoder = null;
        }
    }

二煌妈、獲取編碼數(shù)據(jù)

接下來(lái)調(diào)用MediaCodecstart方法,就可以獲取編碼數(shù)據(jù)

    public void startCapture() {
        if (isCapturing) {
            Log.w(TAG, "startCapture ignore 1");
            return;
        }

        initEncoder();
        if (mEncoder == null) {
            Log.w(TAG, "startCapture ignore 2");
            return;
        }
        Log.i(TAG, "startCapture");

        isCapturing = true;

        encodeAsync();
//        encodeSync();
//        encodeDeprecated();
    }

      /**
     * Android5.0之后異步獲取
     */
    private void encodeAsync() {
        mCaptureThread = new Thread(new Runnable() {
            @Override
            public void run() {
                mEncoder.setCallback(new MediaCodec.Callback() {
                    @Override
                    public void onInputBufferAvailable(MediaCodec codec, int index) {
                        Log.i(TAG, "onInputBufferAvailable");
                    }

                    @Override
                    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
                        //Log.i(TAG,"onOutputBufferAvailable");
                        ByteBuffer buffer = mEncoder.getOutputBuffer(index);
                        encodeFrame(buffer, info.size);
                        mEncoder.releaseOutputBuffer(index, false);
                    }

                    @Override
                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                        Log.i(TAG, "onError");
                    }

                    @Override
                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
                        Log.i(TAG, "onOutputFormatChanged");
                    }
                });
                mEncoder.start();
            }
        });
        mCaptureThread.start();
    }

由于是通過(guò)surface獲取的編碼數(shù)據(jù)宣羊,所以這里沒(méi)有onInputBufferAvailable回調(diào)璧诵,只需監(jiān)聽onOutputBufferAvailable方法,就可以獲得編碼數(shù)據(jù)仇冯。Android5.0之后系統(tǒng)推薦使用這種通過(guò)異步獲取編解碼數(shù)據(jù)的方式腮猖,同時(shí)Android5.0之后系統(tǒng)也提供了同步獲取編解碼數(shù)據(jù)的方式。

    /**
     * Android5.0之后同步獲取
     */
    private void encodeSync() {
        mCaptureThread = new Thread(new Runnable() {
            @Override
            public void run() {
                mEncoder.start();
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                while (isCapturing && mEncoder != null) {
                    try {
                        int index = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        if (index >= 0) {
                            // Log.i(TAG, "dequeueOutputBuffer " + outId);
                            ByteBuffer buffer = mEncoder.getOutputBuffer(index);
                            encodeFrame(buffer, bufferInfo.size);
                            mEncoder.releaseOutputBuffer(index, false);
                        }
                    } catch (Exception e) {
                        Log.w(TAG, e);
                    }
                }
            }
        });
        mCaptureThread.start();
    }

Android5.0之前的同步獲取方式已經(jīng)標(biāo)記廢棄了赞枕。

    /**
     * Android5.0之前同步獲取澈缺,已廢棄
     */
    private void encodeDeprecated() {
        mCaptureThread = new Thread(new Runnable() {
            @Override
            public void run() {
                mEncoder.start();
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                while (isCapturing && mEncoder != null) {
                    try {

                        ByteBuffer[] buffers = mEncoder.getOutputBuffers();
                        int index = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                            Log.i(TAG, "captureDeprecated INFO_OUTPUT_FORMAT_CHANGED");
                        } else if (index >= 0) {
                            // Log.i(TAG, "dequeueOutputBuffer " + outId);
                            ByteBuffer buffer = buffers[index];
                            encodeFrame(buffer, bufferInfo.size);
                            mEncoder.releaseOutputBuffer(index, false);
                        }
                    } catch (Exception e) {
                        Log.w(TAG, e);
                    }
                }
            }
        });
        mCaptureThread.start();
    }

接下來(lái)可以在encodeFrame方法中處理h264數(shù)據(jù)

    private void encodeFrame(ByteBuffer buffer, int bufferSize) {
        final byte[] bytes = new byte[bufferSize];
        buffer.get(bytes);
        if (null != mVideoCallback) {
            mVideoCallback.onCaptureVideoCallback(bytes);
        }
    }

    private OnCaptureVideoCallback mVideoCallback;

    public void setOnCaptureVideoCallback(OnCaptureVideoCallback videoCallback) {
        mVideoCallback = videoCallback;
    }

    public interface OnCaptureVideoCallback {
        void onCaptureVideoCallback(byte[] bytes);
    }

這里將數(shù)據(jù)回調(diào)給調(diào)用者處理,調(diào)用者可以儲(chǔ)存為文件炕婶,也可以直接通過(guò)socket發(fā)送到服務(wù)器處理姐赡。

三、關(guān)閉屏幕采集柠掂、釋放編碼器

結(jié)束屏幕錄制的時(shí)候要及時(shí)釋放這些系統(tǒng)資源

    public void stopCapture() {
        Log.i(TAG, "stopCapture");
        isCapturing = false;
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
            mVirtualDisplay = null;
        }
        if (mMediaProjection != null) {
            mMediaProjection.stop();
            mMediaProjection = null;
        }
        if (mEncoder != null) {
            mEncoder.stop();
            mEncoder.release();
            mEncoder = null;
        }
    }

屏幕的采集編碼暫到這里项滑,接下來(lái)我們要在本機(jī)完成解碼顯示。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涯贞,一起剝皮案震驚了整個(gè)濱河市枪狂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宋渔,老刑警劉巖州疾,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異皇拣,居然都是意外死亡严蓖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門氧急,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颗胡,“玉大人,你說(shuō)我怎么就攤上這事吩坝《疽蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵钉寝,是天一觀的道長(zhǎng)弧呐。 經(jīng)常有香客問(wèn)我鸳址,道長(zhǎng),這世上最難降的妖魔是什么泉懦? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任稿黍,我火速辦了婚禮,結(jié)果婚禮上崩哩,老公的妹妹穿的比我還像新娘巡球。我一直安慰自己,他們只是感情好邓嘹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布酣栈。 她就那樣靜靜地躺著,像睡著了一般汹押。 火紅的嫁衣襯著肌膚如雪矿筝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天棚贾,我揣著相機(jī)與錄音窖维,去河邊找鬼。 笑死妙痹,一個(gè)胖子當(dāng)著我的面吹牛铸史,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怯伊,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼琳轿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了耿芹?” 一聲冷哼從身側(cè)響起崭篡,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吧秕,沒(méi)想到半個(gè)月后琉闪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寇甸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年塘偎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疗涉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拿霉。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咱扣,靈堂內(nèi)的尸體忽然破棺而出绽淘,到底是詐尸還是另有隱情,我是刑警寧澤闹伪,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布沪铭,位于F島的核電站壮池,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杀怠。R本人自食惡果不足惜椰憋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赔退。 院中可真熱鬧橙依,春花似錦、人聲如沸硕旗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漆枚。三九已至创译,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墙基,已是汗流浹背软族。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留残制,地道東北人互订。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像痘拆,于是被迫代替她去往敵國(guó)和親仰禽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355