Android MediaCodec數據采集與解碼

device-2020-03-11-152608.png

先上代碼:

public class ShareREC {
    private static final String TAG = ShareREC.class.getSimpleName();

    private static final long DEFAULT_TIMEOUT_US = 10000;
    private static final int DEFAULT_FRAME_RATE = 20;
    private static final int DEFAULT_I_FRAME_INTERVAL = 1;
    private static final int DEFAULT_BITRATE = 2000000;
    static final int VIDEO_WIDTH = 640;
    static final int VIDEO_HEIGHT = 480;

    private MediaProjection mMp;
    private VirtualDisplay mVd;
    private volatile boolean mRunning;

    public void start(MediaProjection data) {
        if (mRunning) {
            release();
            return;
        }
        mMp = data;
        Observable.create((Observable.OnSubscribe<Boolean>) subscriber -> {
            try {
                initEncoder();
                initDecode();
                mRunning = true;
                doRecord();
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
                if (mOnDecoderCallback != null) {
                    mOnDecoderCallback.onError();
                }
            } finally {
                release();
            }
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe();
    }

    private MediaCodec mEncoder; //編碼器
    private MediaCodec.BufferInfo mEncoderBufferInfo;
    private byte[] SPS;
    private byte[] PPS;

    // 初始化錄屏
    private void initEncoder() throws IOException {
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, VIDEO_WIDTH, VIDEO_HEIGHT);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, DEFAULT_BITRATE);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, DEFAULT_FRAME_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
        mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        Surface inputSurface = mEncoder.createInputSurface();
        mEncoder.start();
        mVd = mMp.createVirtualDisplay("ShareREC",
                VIDEO_WIDTH, VIDEO_HEIGHT, 1,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                inputSurface, null, null);
        mEncoderBufferInfo = new MediaCodec.BufferInfo();
        Log.d(TAG, "initEncoder()");
    }

    private MediaCodec mDeCodec; // 解碼器
    private MediaCodec.BufferInfo mDecodeBufferInfo;
    private OnDecoderCallback mOnDecoderCallback;

    // 初始化解碼器
    private void initDecode() throws IOException {
        // 獲取硬件編碼器支持的顏色格式牡借,一般是I420或者NV12
        mDecodeBufferInfo = new MediaCodec.BufferInfo();
        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, VIDEO_WIDTH, VIDEO_HEIGHT);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        mDeCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        mDeCodec.configure(format, null, null, 0);
        mDeCodec.start();
        Log.d(TAG, "initDecode()");
    }

    private void doRecord() {
        Log.d(TAG, "doRecord Start");
        while (mRunning && mEncoder != null) {
            int index = mEncoder.dequeueOutputBuffer(mEncoderBufferInfo, DEFAULT_TIMEOUT_US);
            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                MediaFormat newFormat = mEncoder.getOutputFormat();
                // 獲取編碼SPS和PPS信息
                SPS = newFormat.getByteBuffer("csd-0").array();
                PPS = newFormat.getByteBuffer("csd-1").array();
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // wait 10ms
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (index > 0) {
                ByteBuffer buffer = mEncoder.getOutputBuffer(index);
                if ((mEncoderBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    mEncoderBufferInfo.size = 0;
                }
                if (mEncoderBufferInfo.size == 0) {
                    buffer = null;
                }
                if (buffer != null) {
                    buffer.position(mEncoderBufferInfo.offset);
                    buffer.limit(mEncoderBufferInfo.offset + mEncoderBufferInfo.size);
                    byte[] h264Bytes;
                    if (mEncoderBufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
                        // 關鍵幀上添加sps,和pps信息
                        h264Bytes = new byte[mEncoderBufferInfo.size + SPS.length + PPS.length];
                        System.arraycopy(SPS, 0, h264Bytes, 0, SPS.length);
                        System.arraycopy(PPS, 0, h264Bytes, SPS.length, PPS.length);
                        buffer.get(h264Bytes, SPS.length + PPS.length, mEncoderBufferInfo.size);
                    } else {
                        h264Bytes = new byte[mEncoderBufferInfo.size];
                        buffer.get(h264Bytes, 0, mEncoderBufferInfo.size);
                    }
                    decode(h264Bytes);
                }
                mEncoder.releaseOutputBuffer(index, false);
            }
        }
        Log.d(TAG, "doRecord End");
    }

    private void decode(byte[] h264Data) {
        int inputBufferIndex = mDeCodec.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
        if (inputBufferIndex > 0) {
            ByteBuffer inputBuffer;
            inputBuffer = mDeCodec.getInputBuffer(inputBufferIndex);
            if (inputBuffer != null) {
                inputBuffer.clear();
                inputBuffer.put(h264Data, 0, h264Data.length);
                mDeCodec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0, 0);
            }
        }
        doDeCode();
    }

    private void doDeCode() {
        int outputBufferIndex = mDeCodec.dequeueOutputBuffer(mDecodeBufferInfo, DEFAULT_TIMEOUT_US);
        ByteBuffer outputBuffer;
        while (mRunning && outputBufferIndex > 0) {
            outputBuffer = mDeCodec.getOutputBuffer(outputBufferIndex);
            if (outputBuffer != null) {
                outputBuffer.position(0);
                outputBuffer.limit(mDecodeBufferInfo.offset + mDecodeBufferInfo.size);
                byte[] yuvData = new byte[outputBuffer.remaining()];
                outputBuffer.get(yuvData);
                MediaFormat mediaFormat = mDeCodec.getOutputFormat();
                switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT)) {
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar:
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411PackedPlanar:
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                        yuvData = yuv420spToYuv420P(yuvData);
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                    default:
                        break;
                }
                Log.d(TAG, "decode byte size " + yuvData.length);
                if (null != mOnDecoderCallback) {
                    mOnDecoderCallback.onFrame(yuvData);
                }
                mDeCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBuffer.clear();
            }
            outputBufferIndex = mDeCodec.dequeueOutputBuffer(mDecodeBufferInfo, DEFAULT_TIMEOUT_US);
        }
    }

    private static byte[] yuv420spToYuv420P(byte[] yuv420spData) {
        byte[] yuv420pData = new byte[ShareREC.VIDEO_WIDTH * ShareREC.VIDEO_HEIGHT * 3 / 2];
        int ySize = ShareREC.VIDEO_WIDTH * ShareREC.VIDEO_HEIGHT;
        System.arraycopy(yuv420spData, 0, yuv420pData, 0, ySize);   //拷貝 Y 分量

        for (int j = 0, i = 0; j < ySize / 2; j += 2, i++) {
            yuv420pData[ySize + i] = yuv420spData[ySize + j];   //U 分量
            yuv420pData[ySize * 5 / 4 + i] = yuv420spData[ySize + j + 1];   //V 分量
        }
        return yuv420pData;
    }

    void setDecoderCallback(OnDecoderCallback onDecoderCallback) {
        mOnDecoderCallback = onDecoderCallback;
    }

    public void stop() {
        mRunning = false;
        Log.d(TAG, "stop");
    }

    public void release() {
        mRunning = false;
        if (mEncoder != null) {
            mEncoder.stop();
            mEncoder.release();
            mEncoder = null;
        }
        if (mVd != null) {
            mVd.release();
            mVd = null;
        }
        if (mDeCodec != null) {
            mDeCodec.stop();
            mDeCodec.release();
            mDeCodec = null;
        }
        if (mMp != null) {
            mMp.stop();
        }
        Log.d(TAG, "release");
    }

    public interface OnDecoderCallback {
        void onFrame(byte[] yuvData);

        void onError();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末碑隆,一起剝皮案震驚了整個濱河市招刹,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖候引,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韧拒,死亡現場離奇詭異,居然都是意外死亡之景,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門膏潮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锻狗,“玉大人,你說我怎么就攤上這事焕参∏峒停” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵叠纷,是天一觀的道長刻帚。 經常有香客問我,道長讲岁,這世上最難降的妖魔是什么我擂? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任衬以,我火速辦了婚禮,結果婚禮上校摩,老公的妹妹穿的比我還像新娘看峻。我一直安慰自己,他們只是感情好衙吩,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布互妓。 她就那樣靜靜地躺著,像睡著了一般坤塞。 火紅的嫁衣襯著肌膚如雪冯勉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天摹芙,我揣著相機與錄音灼狰,去河邊找鬼。 笑死浮禾,一個胖子當著我的面吹牛交胚,可吹牛的內容都是我干的。 我是一名探鬼主播盈电,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝴簇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匆帚?” 一聲冷哼從身側響起熬词,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吸重,沒想到半個月后互拾,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡晤锹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年摩幔,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞭铆。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖焦影,靈堂內的尸體忽然破棺而出车遂,到底是詐尸還是另有隱情,我是刑警寧澤斯辰,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布舶担,位于F島的核電站,受9級特大地震影響彬呻,放射性物質發(fā)生泄漏衣陶。R本人自食惡果不足惜柄瑰,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剪况。 院中可真熱鬧教沾,春花似錦、人聲如沸译断。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孙咪。三九已至堪唐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翎蹈,已是汗流浹背淮菠。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荤堪,地道東北人合陵。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逞力,于是被迫代替她去往敵國和親曙寡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容