【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>,是我至 ...
随机推荐
- 【SHELL】百分比进度指示,原地踏步
百分比进度指示,原地踏步效果实现主要利用退格'\b',同行'\c' #!/bin/bash function percentage_progress() { progress=$(($1*100/$2 ...
- 2023浙江省大学生信息安全竞赛技能赛初赛 部分wp
CRYPTO 小小数学家 1.题目信息 查看代码 19+49=? 96-31=? 86-3=? 20+47=? 29+55=? 35+35=? 81+42=? 73-16=? 52+48=? 0+56 ...
- [转帖]JVM性能调优工具2之jcmd详解(覆盖全网最全的jcmd命令与说明文档)
上篇文章里<JVM常用性能调优工具详解1>我们已经探究了jps.jstat等监控工具,以及jinfo.jmap.jstack.jhat等故障排查工具,这里我单独拿出一篇文章,特别介绍jcm ...
- [转帖]clickhouse 超底层原理& 高可用集群 实操(史上最全)
https://www.cnblogs.com/crazymakercircle/p/16718469.html 文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵 ...
- [转帖]【InfluxDB V2.0】介绍与使用,flux查询、数据可视化
目录 一.关键概念 二.系统结构 三.配置文件 四.Flux查询语句 五.可视化数据 附录 一.关键概念 相比V1 移除了database 和 RP,增加了bucket. V2具有以下几个概念: ti ...
- 关于cockpit的学习
关于cockpit的学习 背景 使用node-exporter 可以监控很多资源使用情况 但是这个需要搭建一套prometheus和grafana的工具 并且每个机器都需要安装一套node-expor ...
- CentOS9上面使用rpm方式安装SQLServer2022的简单总结
CentOS9上面使用rpm方式安装SQLServer2022的简单总结 下载需要的资料 下载CentOS9 Stream的安装介质 https://mirrors.bfsu.edu.cn/cento ...
- vite多入口
创建多页面入口 1.在根目录下创建 demo1.htm1,demo2.htm1这两个文件 2.在vite.config.js文件中配置入口 3.在src下创建文件夹和文件,src/demo1/app. ...
- js正则手机号 验证
注意一下 现在手机号第二位是不是 只有3 4 5 7 8这几个数, 如果还有请告诉我,否则这个正则表达式式错误的. <div id="app"> <el-inpu ...
- TienChin 渠道管理-配置字典常量
在字典管理当中添加渠道状态 channel_status:渠道状态 分别为: 正常,键值为1,回显样式为 success 禁用,键值为0,回显样式为 info !> 有个注意点:Vue3 当中 ...