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

使用android.media包下的MediaCodec和MediaExtractor实现一个简单的视频解码渲染。
使用到了:
MediaCodec:负责媒体文件的编码和解码工作,内部方法均为native方法
MediaExtractor:负责将指定类型的媒体文件从文件中找到轨道,并填充到MediaCodec的缓冲区中,内部方法均为native方法
AudioTrack:负责将解码之后的音频播放
SurfaceView:展示解码之后的视频
视频被播放主要分为以下步骤:
- 将资源加载到extractor
- 获取视频所在轨道
- 设置extractor选中视频所在轨道
- 创将解码视频的MediaCodec,decoder
- 开始循环,直到视频资源的末尾
- 将extractor中资源以一个单位填充进decoder的输入缓冲区
- decoder将解码之后的视频填充到输出缓冲区
- 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
Android媒体解码MediaCodec MediaExtractor学习的更多相关文章
- Android媒体解码MediaCodec,MediaExtractor
Android提供了MediaPlayer播放器播放媒体文件,其实MediaPlyer只是对Android Media包下的MediaCodec和MediaExtractor进行了包装,方便使用.但是 ...
- Android MediaPlayer 和 MediaCodec 的区别和联系(一)
目录: (1)概念解释 : 硬解.软解 (2)Intel关于Android MediaCodec的相关说明 正文: 一.硬解.软解 (1)概念: a.硬 ...
- Android 媒体编解码器(转)
媒体编解码器 MediaCodec类是用来为低级别的媒体编码和解码的媒体编解码器提供访问.您可以实例化一个MediaCodec类通过调用createEncoderByType()方法来进行对媒体文件进 ...
- Android 媒体存储服务(二)
Android 媒体存储服务 简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套 ...
- Android 媒体存储服务(一)
Android 媒体存储服务 本文介绍如何在 Android 中,开发者的 APP 如何使用媒体存储服务(包含MediaScanner.MediaProvider以及媒体信息解析等部分),包括如何把 ...
- Android中的SQLite使用学习
Android中的SQLite使用学习 SQLite是非常流行的嵌入式关系型数据库,轻载, 速度快,而且是开源.在Android中,runtime提供SQLite,所以我们可以使用SQLite,而且是 ...
- android的helloworld工程目录学习
android的helloworld工程目录学习 Android工程的主要目录有src.gen.Android X.X.bin.res等文件夹. 1. Src文件夹 Src文件夹包含java源 ...
- 专题合集:深入Android媒体存储服务
Android 有一套媒体存储服务,进程名是 android.process.media,主要负责把磁盘中的文件信息保存到数据库当中,供其他 APP 使用以及 MTP 模式使用.这里包含了数据库管理. ...
- 深入Android媒体存储服务(二):磁盘扫描流程
简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套媒体存储服务,进程名是 and ...
随机推荐
- (C/C++学习)13.C语言字符串处理函数(一)
说明:字符串处理的函数很多,本文将例举经常遇到的一些函数加以说明. 一.字符串的输入输出 头文件:<stdio.h> 1.利用标准输出函数 printf() 来输出,将格式设置为 s% . ...
- hdu_1012_u Calculate e_201310121519
u Calculate eTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tota ...
- 【ACM】hdu_2004_成绩转换_201307261516
成绩转换Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...
- POJ 1190 生日蛋糕 剪枝
Description 7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体. 设从下往上数第i(1 <= i <= M)层蛋糕是半径为Ri ...
- java GC垃圾回收机制G1、CMS
CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间.对于要求服务器响应速度的应用上,这种垃圾回收器非常适合.在启动JVM参数加上-XX:+UseConcMa ...
- 洛谷 P3184 [USACO16DEC]Counting Haybales数草垛
P3184 [USACO16DEC]Counting Haybales数草垛 题目描述 Farmer John has just arranged his NN haybales (1 \leq N ...
- Python常用模块【sys】
sys.argv 参数 「argv」是「argument variable」参数变量的简写形式.一般在命令行调用的时候由系统传递给程序.这个变量其实是一个List列表,argv[0] 一般是“被 ...
- Android之自己定义(上方标题随ViewPager手势慢慢滑动)
近期非常蛋疼,项目要模仿网易新闻的样式去做.上次把仿网易新闻client的下拉刷新写出来了.这次是ViewPager的滑动,同一时候ViewPager的上面标题下划线尾随者移动.本来通过ViewPag ...
- cocos2d-x 3.2 之 2048 —— 第二篇
***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...
- log4net preserveLogFileNameExtension 和 watch
preserveLogFileNameExtension <log4net> <appender name="fileappender" type="l ...