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阅读 221,888评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,677评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,386评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,726评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,729评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,337评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,902评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,807评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,349评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,439评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,567评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,242评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,933评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,420评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,531评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,995评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,585评论 2 359

推荐阅读更多精彩内容