zoukankan      html  css  js  c++  java
  • Android媒体解码MediaCodec MediaExtractor学习

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

    shapeofmyheart.jpeg

    使用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

  • 相关阅读:
    Apache、IIS、Nginx等绝大多数web服务器,都不允许静态文件响应POST请求
    转载:Struts2+Jquery实现ajax并返回json类型数据
    div 添加滚动条
    jsp页面 如何通过el表达式获取request属性值
    【转】通过Hibernate将数据 存入oracle数据库例子
    jsp 中 有没有类似java if else语句
    IDEA使用(一)
    Git进阶(二)
    JS语法记录
    Debian之MySQL
  • 原文地址:https://www.cnblogs.com/jiy-for-you/p/9563982.html
Copyright © 2011-2022 走看看