使用MediaCodec 播放视频

MediaCodec框架剖析中讲到了可以使用MediaCodec来播放视频,我们现在就来试下如何使用MediaCodec 播放视频。

使用MediaCodec 播放视频_第1张图片
视频播放流程

上图是播放器播放视频的主要流程,其中深色的部分是视频流处理的流程;

  • 从视频中分离出音频流和视频流;
  • 要处理视频流,就要对视频流数据进行解码,Android平台上自然要用到MediaCodec 解码工具;
  • 解码出来的数据要写入到Surface中,借助SurfaceFlinger来合成一个个surface数据;

1.分离音频视频流

    mExtractor = new MediaExtractor();
    try {
        mExtractor.setDataSource(mVideoPath);
    } catch (Exception e) {
        e.printStackTrace();
    }

    for(int index = 0; index < mExtractor.getTrackCount(); index++) {
        MediaFormat format = mExtractor.getTrackFormat(index);
        Log.i(TAG, "index="+index+", format="+format);

        String mime = format.getString(MediaFormat.KEY_MIME);

        if(mime.startsWith("video/")) {
            //Do something.
        }
    }

分离音频视频流当然要借助MediaExtractor 执行,我们只需要播放视频就行,音频不需要管;取出视频的MediaFormat,打印出视频的MediaFormat信息:

2020-02-03 15:04:30.696 16773-16810/? I/ACodec: [OMX.qcom.video.decoder.avc]configureCodec AMessage(what = 'conf', target = 1) = {
      int32_t track-id = 1
      int32_t level = 512
      string mime = "video/avc"
      int32_t profile = 1
      string language = "```"
      int32_t color-standard = 1
      int32_t display-width = 1280
      Buffer csd-1 = {
        00000000:  00 00 00 01 68 ce 06 e2                           ....h...
      }
      int32_t color-transfer = 3
      int64_t durationUs = 21393466
      int32_t display-height = 720
      int32_t width = 1280
      int32_t color-range = 2
      int32_t rotation-degrees = 90
      int32_t max-input-size = 156947
      int32_t frame-rate = 30
      int32_t height = 720
      Buffer csd-0 = {
        00000000:  00 00 00 01 67 42 80 1f  da 01 40 16 e9 58 30 30  [email protected]
        00000010:  30 36 85 09 a8                                    06...
      }
      RefBase *native-window = 0x7f2325b000
    }

哇,很赞,连 csd-0 和 csd-1都打印出来了;
csd-0 表示 H264 的SPS;SPS是必须的,如果缺失,视频播不出来;
csd-1 表示 H264 的PPS;PPS可以缺失;

2.创建MediaCodec实例

上一步把视频的mime解析出来,是 string mime = "video/avc",

                mExtractor.selectTrack(index);

                try {
                    mCodec = MediaCodec.createDecoderByType(mime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                mCodec.configure(format, mSurface, null, 0);
        if (mCodec == null) {
            return;
        }
        mCodec.start();

这个函数执行的流程上一章已经讲得非常清楚了

3.输入/输出缓冲区处理

    ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
    ByteBuffer[] outputBuffers = mCodec.getOutputBuffers();

我们通常认为MediaCodec 的工作方式是提供一个input数据就会对应一个output数据,实际上不是这样;
MediaCodec读入input数据之后,会根据代码逻辑来处理编码数据,或者解码数据,然后将处理的结果输出,MediaCodec的input 和output之间没有对应关系。

我们知道MediaCodec有同步解码和异步解码两种;对于网络资源,还是尽量使用异步解码,这样性能可以保证;

        ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
        ByteBuffer[] outputBuffers = mCodec.getOutputBuffers();

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        boolean isEOS = false;
        long start = System.currentTimeMillis();

        while(!Thread.interrupted()) {
            if(!isEOS) {
                int inIndex = mCodec.dequeueInputBuffer(10 * 1000); //10ms
                if (inIndex >= 0) {
                    ByteBuffer buffer = inputBuffers[inIndex];
                    int sampleSize = mExtractor.readSampleData(buffer, 0);

                    if (sampleSize < 0) {
                        mCodec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        isEOS = true;
                    } else {
                        mCodec.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
                        mExtractor.advance();
                    }
                }
            }

            int outIndex = mCodec.dequeueOutputBuffer(bufferInfo, 10 * 1000);
            switch (outIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    outputBuffers = mCodec.getOutputBuffers();
                    break;
                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    break;
                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    break;
                default:
                    ByteBuffer buffer = outputBuffers[outIndex];

                    while(bufferInfo.presentationTimeUs / 1000 > System.currentTimeMillis() - start) {
                        try {
                            sleep(10);
                        } catch (Exception e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                    mCodec.releaseOutputBuffer(outIndex, true);
                    break;
            }

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                break;
            }
        }

        mCodec.stop();
        mCodec.release();
        mExtractor.release();
使用MediaCodec 播放视频_第2张图片
MediaCodec工作流程
  • MediaCodec 处理输入数据产生数据数据,当异步处理数据时,使用一组 inputBuffers 和 ouputBuffers;
  • 客户端请求数据填入预先设定的空输入缓冲区,就是定义好的MediaCodec.BufferInfo 对象,inputBuffers 填满数据后将其传递到 MediaCodec 并进行编解码处理;
  • MediaCodec 编解码后的数据被填充到 ouputBuffers 中;
  • 客户端请求 ouputBuffers ,消耗 ouputBuffers 中的内容,用完后释放,给MediaCodec 重新填充 ouputBuffers;

一定一定要注意的是:
必须要保证输入 和 输出队列同时非空,至少有一个输入buffer和输出buffer才能正常工作;

下面附在demo地址:https://github.com/JeffMony/MediaCodecDemo

感谢关注公众号JeffMony,持续给你带来音视频方面的知识。

使用MediaCodec 播放视频_第3张图片

你可能感兴趣的:(使用MediaCodec 播放视频)