Android媒体解码MediaCodec MediaExtractor学习

志不强者智不达,言不信者行不果。这篇文章主要讲述Android媒体解码MediaCodec MediaExtractor学习相关的知识,希望能为你提供帮助。

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

文章图片

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

【Android媒体解码MediaCodec MediaExtractor学习】音频的部分类似,完整源码请移步stefanJi/MediaPlaySimpleDemo

    推荐阅读