广告位联系
返回顶部
分享到

Android硬件解码组件MediaCodec使用教程

Android 来源:互联网 作者:佚名 发布时间:2022-11-22 09:03:18 人浏览
摘要

1.MediaCodec 是什么 MediaCodec类可以访问底层媒体编解码器框架(StageFright 或 OpenMAX),即编解码组件。是Android 的低层多媒体基础设施的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、Im

1.MediaCodec 是什么

MediaCodec类可以访问底层媒体编解码器框架(StageFright 或 OpenMAX),即编解码组件。是Android 的低层多媒体基础设施的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用),它本身并不具备Codec能力,通过调动底层编解码组件获得了Codec的能力。

2.创建MediaCodec的方式

2.1按照格式创建

  • createDecoderByType(String type):创建解码器
  • createEncoderByType(String type):创建编码器

type是数据解析阶段的mimeType,如"video/avc"

2.2按照名字创建

createByCodecName(String name)

OMX.google.h264.decoder: 软解码

OMX.MTK.VIDEO.DECODER>AVC:硬解码

3.MediaCode硬件解码并进行播放实例

1

2

3

4

5

6

7

8

9

10

11

12

    private String mFilePath="/sdcard/DCIM/189017886849403.mp4";

    private DecodeThread mDecodeThread;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

//        setContentView(R.layout.activity_media_codec_decode);

        SurfaceView surfaceView=new SurfaceView(this);

        /*不自己维护缓冲区,等待屏幕的渲染引擎 将内容推送到用户前面*/

        surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        surfaceView.getHolder().addCallback(this);

        setContentView(surfaceView);

    }

  • 定义播放的视频路径
  • 定义解码的线程
  • 创建SurfaceView,并设置Callback

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@Override

public void surfaceCreated(@NonNull SurfaceHolder holder) {

}

@Override

public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    if (mDecodeThread ==null){

        mDecodeThread =new DecodeThread(holder.getSurface());

        mDecodeThread.start();

    }

}

@Override

public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

    if (mDecodeThread !=null){

        mDecodeThread.interrupt();   //停止线程的正确姿势

    }

}

  • 在SurfaceView的回调函数surfaceChanged 开启线程
  • 在SurfaceView的回调函数surfaceDestroyed 打断线程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

private class DecodeThread extends Thread{

        private MediaExtractor mMediaExtractor;

        private MediaCodec mMediaCodec;

        private Surface mSurface;

        /*通过构造方法将surface传递进来*/

        public DecodeThread(Surface surface){

            mSurface = surface;

        }

        @Override

        public void run() {

            super.run();

            mMediaExtractor = new MediaExtractor();

            try {

                mMediaExtractor.setDataSource(mFilePath);

            } catch (IOException e) {

                e.printStackTrace();

            }

            int trackCount = mMediaExtractor.getTrackCount();

            //从媒体提取器中拿到了 MIME 以及MediaFormat   通过MIME 创建的硬件解码器   通过MediaFormat配置的硬件解码器

            for (int i = 0; i < trackCount; i++) {

                MediaFormat trackFormat = mMediaExtractor.getTrackFormat(i);

                Log.d("lpf","trackFormat is "+trackFormat);

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

                Log.d("lpf","mime is "+mime);

                if (mime.startsWith("video/")){

                    mMediaExtractor.selectTrack(i);

                    try {

                        mMediaCodec=MediaCodec.createDecoderByType(mime);

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                    //这样配置之后,解码之后的数据就会 直接显示在mSurface 上边  这里是核心点

                    mMediaCodec.configure(trackFormat,mSurface,null,0);

                    break;

                }

            }

            if (mMediaCodec == null){

                return;

            }

            //调用Start 如果没有异常信息,表示成功构建组件

            mMediaCodec.start();

            ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

            ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();

            //每个Buffer的元数据包括具体的范围以及偏移大小,以及数据中心相关解码的buffer

            MediaCodec.BufferInfo info=new MediaCodec.BufferInfo();

            boolean isEOF=false;

            long startMs=System.currentTimeMillis();

            while (!Thread.interrupted()){//只要线程不中断

                if (!isEOF){

                    //返回有效的buffer 索引,如果没有相关的Buffer可用,就返回-1

                    //传入的timeoutUs为0表示立即返回

//                    如果数据的buffer可用,将无限期等待timeUs的单位是纳秒

                    int index =mMediaCodec.dequeueInputBuffer(10000);

                    if (index >= 0){

                        ByteBuffer byteBuffer=inputBuffers[index];

                        Log.d("lpf","bytebuffer is "+byteBuffer);

                        int sampleSize=mMediaExtractor.readSampleData(byteBuffer,0);

                        Log.d("lpf","sampleSize is "+sampleSize);

                        if (sampleSize < 0){

                            Log.d("lpf","inputBuffer is BUFFER_FLAG_END_OF_STREAMING");

                            mMediaCodec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);

                            isEOF=true;

                        }else{

                            mMediaCodec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);

                            mMediaExtractor.advance();  //下一帧数据

                        }

                    }

                }

                int outIndex=mMediaCodec.dequeueOutputBuffer(info,100000);

                switch (outIndex){

                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:

                        //当buffer变化时,必须重新指向新的buffer

                        outputBuffers=mMediaCodec.getOutputBuffers();

                        break;

                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:

                        //当Buffer的封装格式发生变化的时候,需重新指向新的buffer格式

                        Log.d("lpf","output  buffer changed");

                            break;

                    case MediaCodec.INFO_TRY_AGAIN_LATER:

                        //dequeueOutputBuffer 超时的时候会到这个case

                        Log.d("lpf","dequeueOutputBuffer timeout");

                        break;

                    default:

                        ByteBuffer buffer=outputBuffers[outIndex];

                        //由于配置的时候 将Surface 传进去了  所以解码的时候 将数据直接交给了Surface进行显示了

                        //使用简单的时钟的方式保持视频的fps(每秒显示的帧数),不然视频会播放的比较快

                        Log.d("lpf","解码之后的 buffer数据="+buffer);

                        while (info.presentationTimeUs/1000>System.currentTimeMillis()-startMs){

                            try {

                                Thread.sleep(10);

                            } catch (InterruptedException e) {

                                e.printStackTrace();

                            }

                        }

                        mMediaCodec.releaseOutputBuffer(outIndex,true);

                        break;

                }

                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){

                    Log.d("lpf","outputBuffer BUFFER_FLAG_END_OF_STREAM");

                    break;

                }

            }

            mMediaCodec.stop();

            mMediaCodec.release();// 释放组件

            mMediaExtractor.release();

        }

    }

  • 定义媒体提取器:MediaExtractor,通过媒体提取器,得到视频的MIME以及MediaFormat数据
  • 通过媒体提取器拿到的MIME 类型来创建硬件解码器MediaCodec,再通过上一步拿到的额MediaFormat来配置硬件解码器。
  • 配置完成后,调用硬件解码器的start函数,解码器就开始工作了
  • 从解码器上拿到输入和输出Buffer数组,用于解码使用
  • dequeueInputBuffer通过这个函数得到待解码的数据index,然后通过index拿到ByteBuffer
  • 然后mMediaExtractor调用readSampleData来读取数据,将数据得到ByteBuffer中去
  • 接下来将数据丢入编码队列,这个队列在MediaCodec中
  • 然后就可以从硬件解码器中获取数据了,由于配置的时候将Surface当做参数配置给了MediaCodec,所以数据会直接通过SurfaceView进行显示。

4.MediaCodec 异步解码进行播放

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

public void startSyncPlay(Surface surface){

    mMediaExtractor = new MediaExtractor();

    try {

        mMediaExtractor.setDataSource(mFilePath);

    } catch (IOException e) {

        e.printStackTrace();

    }

    int trackCount = mMediaExtractor.getTrackCount();

    //从媒体提取器中拿到了 MIME 以及MediaFormat   通过MIME 创建的硬件解码器   通过MediaFormat配置的硬件解码器

    for (int i = 0; i < trackCount; i++) {

        MediaFormat trackFormat = mMediaExtractor.getTrackFormat(i);

        Log.d("lpf","trackFormat is "+trackFormat);

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

        Log.d("lpf","mime is "+mime);

        if (mime.startsWith("video/")) {

            mMediaExtractor.selectTrack(i);

            try {

                mMediaCodec=MediaCodec.createDecoderByType(mime);

                if (mMediaCodec == null){

                    return;

                }

                //这样配置之后,解码之后的数据就会 直接显示在mSurface 上边  这里是核心点

                mMediaCodec.configure(trackFormat,surface,null,0);

                mMediaCodec.setCallback(new MediaCodec.Callback() {

                    @Override

                    public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {

                        ByteBuffer inputBuffer = codec.getInputBuffer(index);

                        int sampleSize=mMediaExtractor.readSampleData(inputBuffer,0);

                        if (sampleSize>0) {

                            codec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);

                            mMediaExtractor.advance();  //下一帧数据

                        }else {

                            codec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);

                        }

                    }

                    @Override

                    public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {

                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){

                            Log.d("lpf","outputBuffer BUFFER_FLAG_END_OF_STREAM");

                            codec.stop();

                            codec.release();// 释放组件

                            mMediaExtractor.release();

                            return;

                        }

                        if (index>0){

                            if (startMs==-1){

                                startMs=System.currentTimeMillis();

                            }

                            sleepRender(info,startMs);

                        }

                        codec.releaseOutputBuffer(index,true);  //释放缓冲区,并交给Surface 进行播放

                    }

                    @Override

                    public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {

                    }

                    @Override

                    public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {

                    }

                });

                //调用Start 如果没有异常信息,表示成功构建组件

                mMediaCodec.start();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

}

异步的方式进行解码操作,比较简单,推荐使用这个方式

  • onInputBufferAvailable,将需要编码的数据从这个回调方法中添加到解码队列
  • onOutputBufferAvailable 在这个回调方法中就能拿到编码好的数据,可以说非常便利,思路也比同步的时候更加简洁。

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/u014078003/article/details/127952067
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计