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详解的更多相关文章

  1. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  2. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  3. Android ActionBar详解

    Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar   目录(?)[+]   第4 ...

  4. Android 签名详解

    Android 签名详解 AndroidOPhoneAnt设计模式Eclipse  在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...

  5. Android编译系统详解(一)

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...

  6. Android布局详解之一:FrameLayout

      原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...

  7. 【整理修订】Android.mk详解

    Android.mk详解 1. Android.mk 的应用范围 Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译. 一个Android.mk文件可以编 ...

  8. Android菜单详解(四)——使用上下文菜单ContextMenu

    之前在<Android菜单详解(二)——创建并响应选项菜单>和<Android菜单详解(三)——SubMenu和IconMenu>中详细讲解了选项菜单,子菜单和图标菜单.今天接 ...

  9. Android签名详解(debug和release)

    Android签名详解(debug和release)   1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包 ...

  10. Android菜单详解(一)——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

随机推荐

  1. 单例bean与原型bean的区别

    在使用Spring开发时,Spring提供了五种scope,分别为singleton,prototype,request,session,global session. 上图为各个scope描述的官方 ...

  2. ChatGPT-NextWeb部署和调试打造属于自己的GPT

    首先我关注这个项目有一段时间了,不得不说作者和他的社区真的很猛! 首先这个项目截至目前已经有了40.9K的Start了,Fork也已经有了38.1K了,这个数据真的超级牛批了. 那么我们来看一下这款号 ...

  3. [转帖]clickHouse单机模式安装部署(RPM安装)

    关于版本和系统的选择 操作系统:Centos-7 ClickHouse: rpm 在安装,20.x 安装前的准备 CentOS7 打开文件数限 在 /etc/security/limits.conf ...

  4. [转帖]拯救关键业务上线:DBA 的惊魂24小时

    一个电话,打破深夜的宁静 9月20日晚上10点 刚完成外地一个重点项目为期2周的现场支持,从机场回家的路上,一阵急促的铃声惊醒了出租车上昏昏欲睡的我,多年的工作经验告诉我这么晚来电一定是出事了,接起电 ...

  5. 【转帖】eBay 流量管理之 Kubernetes 网络硬核排查案例

    https://www.infoq.cn/article/L4vyfdyvHYM5EV8d3CdD 一.引子 在 eBay 新一代基于 Kubernetes 的云平台 Tess 环境中,流量管理的实现 ...

  6. [转帖]Nacos 获取配置时启用权限认证

    默认情况下获取 Nacos 中的配置是不需要权限认证的, 这个估计是由其使用场景决定的(绝大多数都是仅内网可访问). 今天调查了下如何在获取配置时增加权限验证以提高其安全性. 1. 启用 Nacos ...

  7. 分布式日志追踪ID实战 | 京东物流技术团队

    本文通过介绍分布式应用下各个场景的全局日志ID透传思路,以及介绍分布式日志追踪ID简单实现原理和实战效果,从而达到通过提高日志查询排查问题的效率. 背景 开发排查系统问题用得最多的手段就是查看系统日志 ...

  8. express学会CRUD

    使用express 搭建项目 1==> express 项目名 -e 2==> 然后按照提示就可以了 cd 项目名 3==>进入项目 下载依赖 cnpm i 4==>启动项目 ...

  9. Linux挂载新磁盘到根目录

    1.添加磁盘到需要挂载的机器上2.lsblk查看硬盘挂载情况,sdb,sdc为我新挂载的磁盘 3.fdisk -l查看挂载之前的分区情况, 4.为新硬盘创建分区 fdisk /dev/sdb,终端会提 ...

  10. Redis如何批量删除指定前缀的key

    批量删除指定前缀的Key有两中方法,一种是借助 redis-cli,另一种是通过 SCAN 命令来遍历所有匹配前缀的 key,并使用 DEL 命令逐个删除它们. redis-cli 使用 Redis ...