【Android】MediaCodec详解
1 前言
MediaCodec 主要用于视频解码和编码操作,可以实现视频倍速播放、全关键帧转换、视频倒放等功能。
MediaCodec 的工作原理图如下:
MediaCodec 的主要接口如下:
//创建解码器(type为mime或name)
public static MediaCodec createDecoderByType(String type)
//创建编码器(type为mime或name)
public static MediaCodec createEncoderByType(String type)
//配置解码器和编码器(flag为0表示解码器,1表示编码器)
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
//启动编码器或解码器
public final void start()
//获取输入队列的一个空闲索引(timeoutUs:最多等待时间,-1表示一直等待,单位:微秒us)
public final int dequeueInputBuffer(long timeoutUs)
//获取输入队列的一个空闲缓存区(index:dequeueInputBuffer方法的返回值)
public ByteBuffer getInputBuffer(int index)
//提醒解码器或编码器处理数据(index:dequeueInputBuffer方法的返回值)
public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)
//创建BufferInfo类,用于存储解码或编码后的缓存数据的格式信息
public final static class BufferInfo
//获取输出队列的一个缓存区的索引,并将格式信息保存在info中(timeoutUs:最多等待时间,-1表示一直等待,单位:微秒us)
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
//获取输出队列的一个缓存区(index:dequeueOutputBuffer方法的返回值)
public ByteBuffer getOutputBuffer(int index)
//清除index指向的缓存区中的数据
public final void releaseOutputBuffer(int index, boolean render)
//结束解码或编码会话
public final void stop()
//释放资源
public final void release()
本文将以全关键帧转换为例,讲解 MediaCodec 的应用。
2 项目目录
3 代码
在 AndroidManifest.xml 中配置权限,如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
MainActivity.java
package com.example.codec;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
String[] permissions = {
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"};
if(Build.VERSION.SDK_INT>=23){
requestPermissions(permissions,1);
}
}
public void onclick(View v) {
Manager manager = new Manager("/sdcard/Pictures/WeiXin/a.mp4", "/sdcard/a.mp4");
manager.prepare();
manager.start();
manager.transform(); //转换为全关键帧
}
}
Manager.java
package com.example.codec;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Message;
import java.util.LinkedList;
public class Manager {
private Decoder decoder;
private Encoder encoder;
private LinkedList<byte[]> de2EnQue;
private String input_path;
private String output_path;
public static final int DECODE_ONE_FRAME = 1; //decoder解码了1帧
public static final int DECODER_RELEASE = 2; //decoder释放了资源
public static final int ENCODE_ONE_FRAME = 3; //encoder编码了1帧
public static final int ENCODER_RELEASE = 4; //encoder释放了资源
public Manager(String input_path, String output_path) {
this.input_path = input_path;
this.output_path = output_path;
de2EnQue = new LinkedList<>();
decoder = new Decoder(mainHandler, de2EnQue);
encoder = new Encoder(mainHandler, de2EnQue);
}
public void prepare() {
decoder.setPath(input_path);
encoder.setPath(output_path);
MediaFormat format = decoder.config();
encoder.config(format);
}
public void start() {
decoder.start();
encoder.start();
}
public void transform() {
decoder.decode();
}
private Handler mainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DECODE_ONE_FRAME:
encoder.encode(); //通知编码器编码
break;
case DECODER_RELEASE:
encoder.release(); //通知编码器释放资源
break;
case ENCODE_ONE_FRAME:
break;
case ENCODER_RELEASE:
break;
}
}
};
}
Decoder.java
package com.example.codec;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Handler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
public class Decoder {
private MediaCodec decoder;
private MediaExtractor extractor;
private MediaFormat format;
private LinkedList<byte[]> de2EnQue;
private Handler mainHandler;
private String input_path;
private int outputSize;
private final int timeout = 500;
public Decoder(Handler mainHandler, LinkedList<byte[]> de2EnQue) {
this.mainHandler = mainHandler;
this.de2EnQue = de2EnQue;
extractor = new MediaExtractor();
}
public void setPath(String input_path) {
this.input_path = input_path;
}
public MediaFormat config() {
try {
extractor.setDataSource(input_path);
} catch (IOException e) {
e.printStackTrace();
}
String mime = "";
int count = extractor.getTrackCount(); //获取轨道数
for (int i = 0; i < count; i++) {
format = extractor.getTrackFormat(i);
mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) { // mp4为“video/avc”
extractor.selectTrack(i);
break;
}
}
int width = format.getInteger(MediaFormat.KEY_WIDTH);
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
outputSize = width*height*3/2;
try {
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format,null,null,0);
} catch (IOException e) {
e.printStackTrace();
}
return format;
}
public void start() {
decoder.start();
}
public void decode() {
new Thread() {
@Override
public void run() {
execute();
release();
}
}.start();
}
public void execute() {
boolean isInputFinish = false; //输入结束标志
boolean isOutPutFinish = false; //输出结束标志
while (!isOutPutFinish) {
if (!isInputFinish) {
int inputIndex = decoder.dequeueInputBuffer(timeout);
if (inputIndex>=0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
inputBuffer.clear(); //清除以前数据
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize>0) {
decoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
extractor.advance();
} else {
isInputFinish = true;
decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
}
while (!isOutPutFinish) {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = decoder.dequeueOutputBuffer(bufferInfo, timeout);
if (outputIndex<0) {
break;
}
if (bufferInfo.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
if (bufferInfo.size==0) {
if (isInputFinish) {
isOutPutFinish = true;
}
break;
}
bufferInfo.flags = 0;
}
if (bufferInfo.size>0) {
ByteBuffer outputBuffer = decoder.getOutputBuffer(outputIndex);
byte[] tempBuffer = new byte[outputSize];
outputBuffer.get(tempBuffer, 0, outputSize);
de2EnQue.addLast(tempBuffer);
mainHandler.sendEmptyMessage(Manager.DECODE_ONE_FRAME);
decoder.releaseOutputBuffer(outputIndex, false);
}
}
}
}
public void release() {
decoder.stop();
decoder.release();
mainHandler.sendEmptyMessage(Manager.DECODER_RELEASE);
}
}
Encoder.java
package com.example.codec;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Handler;
import android.os.HandlerThread;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
public class Encoder {
private MediaCodec encoder;
private MediaMuxer muxer;
private LinkedList<byte[]> de2EnQue;
private Handler mainHandler;
private Handler handler;
private String output_path;
private int interval = 34483;
private volatile long pts = 0;
private final int timeout = 500;
private int trackIndex;
public Encoder(Handler mainHandler, LinkedList<byte[]> de2EnQue) {
this.mainHandler = mainHandler;
this.de2EnQue = de2EnQue;
}
public void setPath(String output_path) {
this.output_path = output_path;
}
public void config(MediaFormat mediaFormat) {
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
int frame_rate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0); //全I帧
format.setInteger(MediaFormat.KEY_FRAME_RATE, frame_rate);
format.setInteger(MediaFormat.KEY_BIT_RATE, 1500000);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
try {
encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
encoder.configure(format, null,null, MediaCodec.CONFIGURE_FLAG_ENCODE);
muxer = new MediaMuxer(output_path,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
encoder.start();
HandlerThread handlerThread = new HandlerThread("encoder");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
public void encode() {
handler.post(new Runnable() {
@Override
public void run() {
execute();
}
});
}
private void execute() {
int inputIndex = encoder.dequeueInputBuffer(timeout);
if (inputIndex>=0) {
if (!de2EnQue.isEmpty()) {
ByteBuffer inputBuffer = encoder.getInputBuffer(inputIndex);
inputBuffer.clear(); //清除以前数据
byte[] tempBuffer = de2EnQue.removeFirst();
inputBuffer.put(tempBuffer, 0, tempBuffer.length);
encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), pts, 0);
pts += interval;
}
}
while(true) {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = encoder.dequeueOutputBuffer(bufferInfo, timeout);
if (outputIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat outputFormat = encoder.getOutputFormat();
trackIndex = muxer.addTrack(outputFormat);
muxer.start();
break;
}
if (outputIndex<0) {
break;
}
if (bufferInfo.size>0) {
ByteBuffer outputBuffer = encoder.getOutputBuffer(outputIndex);
//这里可以通过修改 bufferInfo.presentationTimeUs 实现倍速播放
muxer.writeSampleData(trackIndex, outputBuffer, bufferInfo);
mainHandler.sendEmptyMessage(Manager.ENCODE_ONE_FRAME);
encoder.releaseOutputBuffer(outputIndex, false);
}
}
}
public void release() {
handler.post(new Runnable() {
@Override
public void run() {
encoder.stop();
encoder.release();
handler.getLooper().quit();
mainHandler.sendEmptyMessage(Manager.ENCODER_RELEASE);
}
});
}
}
4 拓展
可以通过修改 bufferInfo.presentationTimeUs 实现倍速播放,如下:
float speed = 3.0f; //播放速度
bufferInfo.presentationTimeUs = (long)(bufferInfo.presentationTimeUs/speed)
muxer.writeSampleData(trackIndex, outputBuffer, bufferInfo);
详见 → 使用MediaExtractor、MediaMuxer去掉视频文件中的音频数据 中拓展。
另外,也可以通过以下方式实现倍速播放视频:
float speed = 3.0f; //播放速度
encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), (long)(pts/speed), 0);
声明:本文转自【Android】MediaCodec详解
【Android】MediaCodec详解的更多相关文章
- Android Notification 详解(一)——基本操作
Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...
- Android Notification 详解——基本操作
Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...
- Android ActionBar详解
Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar 目录(?)[+] 第4 ...
- Android 签名详解
Android 签名详解 AndroidOPhoneAnt设计模式Eclipse 在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...
- Android编译系统详解(一)
++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...
- Android布局详解之一:FrameLayout
原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...
- 【整理修订】Android.mk详解
Android.mk详解 1. Android.mk 的应用范围 Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译. 一个Android.mk文件可以编 ...
- Android菜单详解(四)——使用上下文菜单ContextMenu
之前在<Android菜单详解(二)——创建并响应选项菜单>和<Android菜单详解(三)——SubMenu和IconMenu>中详细讲解了选项菜单,子菜单和图标菜单.今天接 ...
- Android签名详解(debug和release)
Android签名详解(debug和release) 1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包 ...
- Android菜单详解(一)——理解android中的Menu
前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...
随机推荐
- 《OnJava》——11内部类
内部类 利用内部类,可以将逻辑上存在关联的类组织在一起,而且可以控制一个类在另一个类中的可见性. 内部类和组合不同,内部类是一种代码隐藏机制:将代码放在其他类的内部. 11.1 创建内部类 创建内部类 ...
- 【面试题精讲】什么是websocket?如何与前端通信?
> 有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准`https://blog.zysicyj.top` [首发博客地址](https://blog.zysicyj.t ...
- [转帖]缓存与存储的一致性策略:从 CPU 到分布式系统
https://zhuanlan.zhihu.com/p/151745863 在计算机系统设计实践中,我们常常会遇到下图所示架构: 为了解决单个存储器读吞吐无法满足要求的问题,常常需要在存储器上面增加 ...
- [转帖]Intel“革命性”X86s架构,带来哪些颠覆及影响?
https://www.eet-china.com/mp/a221822.html 英特尔发布了一份新的白皮书(Intel X86-S扩展架构规格),计划简化其处理器指令集架构(ISA).英特尔提供了 ...
- Oracle DBCA 静默删除以及建库的脚本
No.1 背景 公司最近有一个测试环境需要重新备份恢复 但是里面有6个数据库实例 400多G的数据文件. 一般情况下 需要drop user xxx cascade ; 然后执行 drop table ...
- 让你轻松看懂defer和async
defer和async产生的原因 HTML 网页中,浏览器通过<script>标签加载 JavaScript 脚本. <!-- 页面内嵌的脚本 --> <script t ...
- file文件转为base64
场景描述 在工作中,我们经常需要进行文件上传. 比如在进行图片上传的时候, 我们需要将上传的图片展示出来. 这个时候我们就需要将file文件转化为base64. 将file文件转化为base64 // ...
- 同步存储读取vuex中store中的值
main.js import store from "./store"; Vue.prototype.$store = store; 在 store中的index.js中 impo ...
- 获取Unity和UGUUI内置组件的属性名
需求来源 在阅读UGUI的源码时,发现Unity对于私有字段才加了[[SerializeField]]标签,而public的没有,且在Editor扩展中,也是查找带序列化标签的私有字段进行修改,那么在 ...
- 从零开始匹配vim(2)——快捷键绑定
如果说 vim有什么最吸引人,我想vim允许你自由的定义各种快捷键算是一个原因吧.你可以通过绑定各种快捷键来使经常使用的功能更加便利.通俗的讲,快捷键映射就是我按下某个键,我想让vim将它当成另一个键 ...