Android媒体解码MediaCodec MediaExtractor学习

Android提供了MediaPlayer播放器播放媒体文件,其实MediaPlyer只是对Android Media包下的MediaCodec和MediaExtractor进行了包装,方便使用。但是最好理解下Android媒体文件的解码,编码和渲染流程。

使用android.media包下的MediaCodec和MediaExtractor实现一个简单的视频解码渲染。

使用到了:

  • MediaCodec:负责媒体文件的编码和解码工作,内部方法均为native方法
  • MediaExtractor:负责将指定类型的媒体文件从文件中找到轨道,并填充到MediaCodec的缓冲区中,内部方法均为native方法
  • AudioTrack:负责将解码之后的音频播放
  • SurfaceView:展示解码之后的视频

视频被播放主要分为以下步骤:

  1. 将资源加载到extractor
  2. 获取视频所在轨道
  3. 设置extractor选中视频所在轨道
  4. 创将解码视频的MediaCodec,decoder
  5. 开始循环,直到视频资源的末尾
  6. 将extractor中资源以一个单位填充进decoder的输入缓冲区
  7. decoder将解码之后的视频填充到输出缓冲区
  8. decoder释放输出缓冲区的同时,将缓冲区中数据渲染到surface

音频的播放类似,只多了AudioTrack部分,少了渲染到surface部分。

MediaCodec.releaseOutputBuffer(int outputBufferIndex, boolean render);

  • render为true就会渲染到surface


播放的控制,视频和音频各自拥有一个Thread。

    public void play() {
        isPlaying = true;
        if (videoThread == null) {
            videoThread = new VideoThread();
            videoThread.start();
        }
        if (audioThread == null) {
            audioThread = new AudioThread();
            audioThread.start();
        }
    }

    public void stop() {
        isPlaying = false;
    }

VideoThread

private class VideoThread extends Thread {
        @Override
        public void run() {
            MediaExtractor videoExtractor = new MediaExtractor();
            MediaCodec videoCodec = null;
            try {
                videoExtractor.setDataSource(filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
            int videoTrackIndex;
            //获取视频所在轨道
            videoTrackIndex = getMediaTrackIndex(videoExtractor, "video/");
            if (videoTrackIndex >= 0) {
                MediaFormat mediaFormat = videoExtractor.getTrackFormat(videoTrackIndex);
                int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
                int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
                //视频长度:秒
                float time = mediaFormat.getLong(MediaFormat.KEY_DURATION) / 1000000;
                callBack.videoAspect(width, height, time);
                videoExtractor.selectTrack(videoTrackIndex);
                try {
                    videoCodec = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
                    videoCodec.configure(mediaFormat, surface, null, 0);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (videoCodec == null) {
                Log.v(TAG, "MediaCodec null");
                return;
            }
            videoCodec.start();

            MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
            ByteBuffer[] inputBuffers = videoCodec.getInputBuffers();
//            ByteBuffer[] outputBuffers = videoCodec.getOutputBuffers();
            boolean isVideoEOS = false;

            long startMs = System.currentTimeMillis();
            while (!Thread.interrupted()) {
                if (!isPlaying) {
                    continue;
                }
                //将资源传递到解码器
                if (!isVideoEOS) {
                    isVideoEOS = putBufferToCoder(videoExtractor, videoCodec, inputBuffers);
                }
                int outputBufferIndex = videoCodec.dequeueOutputBuffer(videoBufferInfo, TIMEOUT_US);
                switch (outputBufferIndex) {
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        Log.v(TAG, "format changed");
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        Log.v(TAG, "解码当前帧超时");
                        break;
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        //outputBuffers = videoCodec.getOutputBuffers();
                        Log.v(TAG, "output buffers changed");
                        break;
                    default:
                        //直接渲染到Surface时使用不到outputBuffer
                        //ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                        //延时操作
                        //如果缓冲区里的可展示时间>当前视频播放的进度,就休眠一下
                        sleepRender(videoBufferInfo, startMs);
                        //渲染
                        videoCodec.releaseOutputBuffer(outputBufferIndex, true);
                        break;
                }

                if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    Log.v(TAG, "buffer stream end");
                    break;
                }
            }//end while
            videoCodec.stop();
            videoCodec.release();
            videoExtractor.release();
        }
    }

获取指定类型媒体文件所在轨道

    //获取指定类型媒体文件所在轨道
    private int getMediaTrackIndex(MediaExtractor videoExtractor, String MEDIA_TYPE) {
        int trackIndex = -1;
        for (int i = 0; i < videoExtractor.getTrackCount(); i++) {
            //获取视频所在轨道
            MediaFormat mediaFormat = videoExtractor.getTrackFormat(i);
            String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith(MEDIA_TYPE)) {
                trackIndex = i;
                break;
            }
        }
        return trackIndex;
    }

将缓冲区传递至解码器

    //将缓冲区传递至解码器
    private boolean putBufferToCoder(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] inputBuffers) {
        boolean isMediaEOS = false;
        int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            int sampleSize = extractor.readSampleData(inputBuffer, 0);
            if (sampleSize < 0) {
                decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isMediaEOS = true;
                Log.v(TAG, "media eos");
            } else {
                decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            }
        }
        return isMediaEOS;
    }

音频的部分类似,完整源码请移步stefanJi/MediaPlaySimpleDemo

原文地址:https://www.cnblogs.com/jiy-for-you/p/9563982.html

时间: 2024-11-04 16:26:47

Android媒体解码MediaCodec MediaExtractor学习的相关文章

Android 媒体编解码器(转)

媒体编解码器 MediaCodec类是用来为低级别的媒体编码和解码的媒体编解码器提供访问.您可以实例化一个MediaCodec类通过调用createEncoderByType()方法来进行对媒体文件进行编码或者调用createDecoderByType()来对媒体文件进行解码.每一个方法都要采取一个MIME类型为你想要编码或者解码的媒体文件类型,例如“video/3gpp”或者“audio/vorbis”. 在MediaCodec实例创建之后,你可以调用configure()方法来指定例如媒体格

Android中关于JNI 的学习(五)在C文件中使用LogCat

事实上,本文是在Peter Jerde的How much information can be stored by ordering 52 playing cards文章基础上翻译.改编和扩展而来的.当然这是经过Jerde本人首肯的. 注意本文方法并非最优,也没有完全利用所有的信息空间,只是简单的尝试. 有数字的地方就有信息.所以扑克牌中保存信息不是什么新鲜事. PDF文档点这里:下载 原文(英文)点这里:访问 这里有两个DEMO. 编码DEMO,解码DEMO 首先是"DEEP IN SHALL

Android Activity和Intent机制学习笔记

Activity Android中,Activity是所有程序的根本,所有程序的流程都运行在Activity之中,Activity具有自己的生命周期(见http://www.cnblogs.com/feisky/archive/2010/01/01/1637427.html,由系统控制生命周期,程序无法改变,但可以用onSaveInstanceState保存其状态). 对于Activity,关键是其生命周期的把握(如下图),其次就是状态的保存和恢复(onSaveInstanceState onR

Android自复制传播APP原理学习(翻译)

 Android自复制传播APP原理学习(翻译) 1 背景介绍 论文链接:http://arxiv.org/abs/1511.00444 项目地址:https://github.com/Tribler/self-compile-Android 吃完晚饭偶然看到这篇论文,当时就被吸引了,马上翻译总结了一下.如有错误欢迎斧正. 该论文的研究出发点比较高大上这里我们就不多说了,简而言之就是想通过移动设备来实现一个自组网,在发生灾难的时候,手机之间能够自动传输关键数据,减少损失.整个目标通过设计一个能够

舌尖上的安卓(android触控事件机制学习笔记录)

对于一个"我们从来不生产代码,我们只是大自然代码的搬运工"的码农来说.对android的触控机制一直是模棱两可的状态,特别是当要求一些自定义的控件和androide的自带控件(比如ViewPager,ListView,ScrollView)高度嵌套在一起使用时. 花了点时间梳理了下,做个笔记.对于一个触控的事件从用户输入到传递到Actigvity到最外层的Viewgroup在到子View,中间过程还可能穿插多个Viewgroup,android在ViewGroup提供了3个方法来控制流

quick cocos2d x 手机(Android端)启动过程学习

简要学习下quick cocos2d x 在安卓端启动的过程. 首先需要了解一点:quick cocos2d x是依托于Android的activity和GLSurfaceView(继承自SurfaceView)的环境来显示quick层的游戏界面. (1)首先quick类的android游戏从AndroidManifest.xml文件指定的activity(假设AC)启动. (2)AC继承父类的Cocos2dxActivity. (3)调用静态初始化块,加载cocos2dx的动态库.也就是一些C

Android中关于JNI 的学习(零)简单的例子,简单地入门

Android中JNI的作用,就是让Java能够去调用由C/C++实现的代码,为了实现这个功能,需要用到Anrdoid提供的NDK工具包,在这里不讲如何配置了,好麻烦,配置了好久... 本质上,Java去调用C/C++的代码其实就是去调用C/C++提供的方法,所以,第一步,我们要创建一个类,并且定义一个Native方法,如下: JniTest类: public class JniTest { public native String getTestString(); } 可以看到,在这个方法的前

一样的Android,不一样的学习

这几年,Android开始慢慢流行起来,很多项目也开始涉及这部分内容,所以学习Android也就变的很有意义了. 学什么 学习Android应该学什么,很多人有不同的见解.一般程序员可能只是学习Android的UI控件和Android的API使用等.高级程序员可能会研究一下Android的FrameWork和Android的实现原理.相对于这两者,顶级的程序员会深入的了解Android的层次架构和设计思想并将这些架构和设计思想加以改造.优化之后应用在其他方面.你会怎么做呢? Android层次架

Android中关于JNI 的学习(四)简单的例子,温故而知新

在第零篇文章简单地介绍了JNI编程的模式之后,后面两三篇文章,我们又针对JNI中的一些概念做了一些简单的介绍,也不知道我到底说的清楚没有,但相信很多童鞋跟我一样,在刚开始学习一个东西的时候,入门最好的方式就是一个现成的例子来参考,慢慢研究,再学习概念,再回过来研究代码,加深印象,从而开始慢慢掌握. 今天我们就再来做一个小Demo,这个例子会比前面稍微复杂一点,但是如果阅读过前面几篇文章的话,理解起来也还是很简单的.很多东西就是这样,未知的时候很可怕,理解了就很简单了. 1)我们首先定义一个Jav