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. oracle 大表 迅速 导出(使用命令行)
  2. 在VS2013中使用Log4net
  3. 真机测试-Please enter a different string错误解决
  4. 在实例中说明java的类变量,成员变量和局部变量
  5. Oracle连接超时
  6. C++ STL算法系列4---unique , unique_copy函数
  7. storm Tutorial 的解读 + 个人理解
  8. ubuntu下配置hosts
  9. 一年成为Emacs高手(像神一样使用编辑器)
  10. cocos2d-x回收池原理
  11. JQuery select控件的相关操作
  12. GridView边线Border设置
  13. org.apache.tomcat.util.bcel.classfile.ClassFormatException: null is not a Java .class file
  14. IOS隐藏navigationItem左右按钮的方法
  15. 使用svn 的解决 处理svn状态冲突
  16. OAF实现下拉菜单联动
  17. ARP协议分析(Wireshark)
  18. 3种vue路由传参的基本模式
  19. Omi框架学习之旅 - 插件机制之omi-touch 及原理说明
  20. DER、CRT、CER、PEM格式的证书及转换

热门文章

  1. xpath 获取深圳房源信息并导出csv
  2. sql server备份策略
  3. python-windows环境安装
  4. Linux启用ftp服务及连接
  5. python中的二进制、八进制、十六进制
  6. poj 1523 求割点把一个图分成几个联通部分
  7. SSL延迟
  8. 淘宝内部分享:MySQL &amp; MariaDB性能优化
  9. 【日常学习】【搜索/递归】codevs2802 二的幂次方题解
  10. HDU 5191