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

Shape Of My Heart.mp4

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

使用到了:

  • MediaCodec:负责媒体文件的编码和解码工作,内部方法均为native
  • MediaExtractor:负责将指定类型的媒体文件从文件中找到轨道,并填充到MediaCodec的缓冲区中
  • 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;    }

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

更多相关文章

  1. Android三种播放视频的方式
  2. Android 实时视频采集/编码/传输/解码/播放—方案调研
  3. Android 老罗视频教程笔记
  4. android视频播放
  5. 短视频app开源源码Android 如何实现手机震动
  6. Android 使用Vitamio实现播放视频(一)
  7. android实现视频播放的几种方式

随机推荐

  1. 在什么语言中字符串以\0标志字符串的结
  2. 声明动态数组的语句怎么写
  3. 详解C++虚成员函数和动态联编
  4. c语言中文本输出的函数名称是什么?
  5. c语言函数声明格式是什么?
  6. c语言绝对值怎么表示
  7. c语言中逻辑运算符优先级是什么?
  8. 对比分析C#与Java的区别
  9. c语言中double是什么意思
  10. c语言fopen函数的用法