MediaCodec硬解流程
一 MediaCodec概述
MediaCodec是Android 4.1(api 16)版本引入的低层编解码接口,同时支持音视频的编码和解码。通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。MediaCodec在编解码的过程中使用了一组输入/输出缓存区来同步或异步处理数据。

1 数据格式
mediacodec的作用是处理输入的数据生成输出数据,有两种输入输出模式:
surface模式-------输入/输出以surface作为源
ByteBuffer模式--------输入/输出时以ByteBuffer作为源
MediaCodec接受三种数据格式:压缩数据,原始音频数据和原始视频数据。
这三种数据都可以使用ByteBuffer作为载体传输给MediaCodec来处理。但是当使用原视频数据时,最好采用Surface作为输入源来替代ByteBuffer,这样效率更高,效果更好,因为surface使用的更底层的视频数据,不会映射或者复制到ByteBuffer缓冲区。在使用surface作为输入源时,开发者不能访问到到原始视频数据,但是可以使用ImageReader来获取到原始未加密的视频数据,这个地方我理解的是imagereader的工作流程是接受自己的surface数据来生成image,将imagereader的surface传给mediacodec作为解码器的输出surface,就可以访问解码的数据,但是必须是未加密的,这种方式同样比使用ByteBuffer更快,因为native缓冲区会直接映射到directbytebuffer区域,这是一块native和java共享的缓冲区。当使用ByteBuffer模式时可以使用Image来获取原始视频数据,MediaCodec提供了两个方法,getInput/OutputImage(int)。
例如MediaCodec解码H264数据,我们必须将分割符和NALU单元作为一个完整的数据帧传给解码器才能正确解码,除非是标记了BUFFER_FLAG_PARTIAL_FRAME的数据,这种方式不常用。
注:客户端处理完数据后,必须手动释放output缓冲区,否则将会导致MediaCodec输出缓冲被占用,无法继续解码。

MediaCodec状态图,整体上分为三个大的状态:Sotpped、Executing、Released。
Stoped:包含了3个小状态:Error、Uninitialized、Configured。
首先,新建MediaCodec后,会进入Uninitialized状态;
其次,调用configure方法配置参数后,会进入Configured;
Executing:同样包含3个小状态:Flushed、Running、End of Stream。
再次,调用start方法后,MediaCodec进入Flushed状态;
接着,调用dequeueInputBuffer方法后,进入Running状态;
最后,当解码/编码结束时,进入End of Stream(EOF)状态。
这时,一个视频就处理完成了。
Released:最后,如果想结束整个数据处理过程,可以调用release方法,释放所有的资源。
那么,Flushed是什么状态呢?
从图中我们可以看到,在Running或者End of Stream状态时,都可以调用flush方法,重新进入Flushed状态。
当我们在解码过程中,进入了End of Stream后,解码器就不再接收输入了,这时候,需要调用flush方法,重新进入接收数据状态。
或者,我们在播放视频过程中,想进行跳播,这时候,我们需要Seek到指定的时间点,这时候,也需要调用flush方法,清除缓冲,否则解码时间戳会混乱。

二 MediaCodec用法
Android的硬解码接口MediaCodec只能接收Annex-B格式的H.264数据,而iOS平台的VideoToolBox则相反,只支持AVCC格式。这就导致:
在Android平台硬解播放flv/mp4/mkv等封装的视频时,需要将AVCC格式的extradata以及NALU数据转为Annex-B格式;
在iOS平台播放ts或ts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。
初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充Annex-B格式的SPS/PPS信息。
基本流程:
- 创建和配置MediaCodec对象
- 进行以下循环:
- 如果一个输入缓冲区准备好:
- 读取部分数据,复制到缓冲区
- 如果一个输出缓冲区准备好:
- 复制到缓冲区
- 销毁MediaCodec对象
MediaCodec API接口:
//根据视频编码创建解码器,这里是解码AVC编码的视频
MediaCodec mediaCodec =MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//创建视频格式信息
MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, width, height);
//配置
mediaCodec.configure(mediaFormat, surfaceView.getHolder().getSurface(), null, 0);
mediaCodec.start();
//停止解码,此时可以再次调用configure()方法
mediaCodec.stop();
//释放内存
mediaCodec.release();
//一下是循环解码接口
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组
queueInputBuffer:输入流入队列
dequeueInputBuffer:从输入流队列中取数据进行编码操作
getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组
dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
releaseOutputBuffer:处理完成,释放ByteBuffer数据
创建编/解码器
MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,它们均需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式如下:
● “video/x-vnd.on2.vp8” - VP8 video (i.e. video in .webm)
● “video/x-vnd.on2.vp9” - VP9 video (i.e. video in .webm)
● “video/avc” - H.264/AVC video
● “video/mp4v-es” - MPEG4 video
● “video/3gpp” - H.263 video
● “audio/3gpp” - AMR narrowband audio
● “audio/amr-wb” - AMR wideband audio
● “audio/mpeg” - MPEG1/2 audio layer III
● “audio/mp4a-latm” - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
● “audio/vorbis” - vorbis audio
● “audio/g711-alaw” - G.711 alaw audio
● “audio/g711-mlaw” - G.711 ulaw audio
MediaCodec还提供了createByCodecName (String name)方法,支持使用组件的具体名称来创建编解码器。但是该方法使用起来有些麻烦,且官方是建议最好是配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。
配置编/解码器
编解码器配置使用的是MediaCodec的configure方法,该方法首先对MediaFormat存储的数据map进行提取,然后调用本地方法native-configure实现对编解码器的配置工作。在配置时,configure方法需要传入format、surface、crypto、flags参数,其中format为MediaFormat的实例,它使用”key-value”键值对的形式存储多媒体数据格式信息;surface用于指明解码器的数据源来自于该surface;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。
MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480); // 创建MediaFormat
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600); // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30); // 指定帧率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat); // 指定编码器颜色格式
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
//码率控制模式有三种:
CQ 表示完全不控制码率,尽最大可能保证图像质量;
CBR 表示编码器会尽量把输出码率控制为设定值,即我们前面提到的“不为所动”;
VBR 表示编码器会根据图像内容的复杂度(实际上是帧间变化量的大小)来动态调整输出码率,图像复杂则码率高,图像简单则码率低;
// extradata中是Annex-B格式的SPS、PPS NALU数据
//SPS设为"csd-0", PPS设为"csd-1"
mediaFormat.setByteBuffer("csd-0", extradata);
// ...
mediaCodec.configure(mediaFormat, surface, 0, 0);
// ...
对于mp4/flv/mkv等封装,我们得到的是AVCC格式的extradata,需要先将该extradata转换为Annex-B格式的两个NALU, 然后用startcode进行分割。
///
如果编解码音频数据,则调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)的方法
Camera预览采集的图像流通常为NV21或YV12,那么编码器需要指定相应的颜色格式,否则编码得到的数据可能会出现花屏、叠影、颜色失真等现象。MediaCodecInfo.CodecCapabilities.存储了编码器所有支持的颜色格式,常见颜色格式映射如下:
原始数据 编码器
NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
NV21 ———-> COLOR_FormatYUV420SemiPlanar
YV12(I420) ———-> COLOR_FormatYUV420Planar
启动编/解码器
当编解码器配置完毕后,就可以调用MediaCodec的start()方法,该方法会调用低层native_start()方法来启动编码器,并调用低层方法ByteBuffer[] getBuffers(input)来开辟一系列输入、输出缓存区。start()方法源码如下:
public final void start() {
native_start();
synchronized(mBufferLock) {
cacheBuffers(true /* input */);
cacheBuffers(false /* input */);
}
}
数据处理
MediaCodec支持两种模式编解码器,即同步synchronous、异步asynchronous。本文主要介绍用得较多的同步编解码。当编解码器被启动后,每个编解码器都会拥有一组输入和输出缓存区,但是这些缓存区暂时无法被使用,只有通过MediaCodec的dequeueInputBuffer/dequeueOutputBuffer方法获取输入输出缓存区授权,通过返回的ID来操作这些缓存区。下面我们通过一段官方提供的代码,进行扩展分析:
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
从上面代码可知,当编解码器start后,会进入一个for(;;)循环,该循环是一个死循环,以实现不断地去从编解码器的输入缓存池中获取包含数据的一个缓存区,然后再从输出缓存池中获取编解码好的输出数据。
AAC解码为PCM的示例
package com.maniu.h264player;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class AACToPCM {
private static final String TAG = "AACToPCM";
public static final int ERROR_INPUT_INVALID = 100;
public static final int ERROR_OUTPUT_FAILED = 200;
public static final int ERROR_OPEN_CODEC = 300;
public static final int OK = 0;
private static final int TIMEOUT_USEC = 0;
private MediaExtractor mExtractor;
private MediaFormat mFormat;
private MediaCodec mDecoder;
private FileOutputStream mFos;
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private boolean mDecodeEnd;
public AACToPCM() {}
private int checkPath(String path) {
if (path == null || path.isEmpty()) {
Log.d(TAG, "invalid path, path is empty");
return ERROR_INPUT_INVALID;
}
File file = new File(path);
if (!file.isFile()) {
Log.d(TAG, "path is not a file, path:" + path);
return ERROR_INPUT_INVALID;
} else if (!file.exists()) {
Log.d(TAG, "file not exists, path:" + path);
return ERROR_INPUT_INVALID;
} else {
Log.d(TAG, "path is a file, path:" + path);
}
return OK;
}
public int decodeAACToPCM(String audioPath, String pcmPath) {
int ret;
if (OK != (ret = openInput(audioPath))) {
return ret;
}
if (OK != (ret = openOutput(pcmPath))) {
return ret;
}
if (OK != (ret = openCodec(mFormat))) {
return ret;
}
mDecodeEnd = false;
while (!mDecodeEnd) {
if (OK != (ret = decode(mDecoder, mExtractor))) {
Log.d(TAG, "decode failed, ret=" + ret);
break;
}
}
close();
return ret;
}
private int decode(MediaCodec codec, MediaExtractor extractor) {
Log.d(TAG, "decode");
int inputIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
if (inputIndex >= 0) {
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
inputBuffer = codec.getInputBuffer(inputIndex);
} else {
inputBuffer = mInputBuffers[inputIndex];
}
inputBuffer.clear();
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {//read end
codec.queueInputBuffer(inputIndex, 0, 0, 0L,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
codec.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//TIMEOUT
Log.d(TAG, "INFO_TRY_AGAIN_LATER");//TODO how to declare this info
return OK;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.d(TAG, "output format changed");
return OK;
} else if (outputIndex < 0) {
Log.d(TAG, "outputIndex=" + outputIndex);
return OK;
} else {
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
outputBuffer = codec.getOutputBuffer(outputIndex);
} else {
outputBuffer = mOutputBuffers[outputIndex];
}
byte[] buffer = new byte[bufferInfo.size];
outputBuffer.get(buffer);
try {
Log.d(TAG, "output write, size="+ bufferInfo.size);
mFos.write(buffer);
mFos.flush();
} catch (IOException e) {
e.printStackTrace();
return ERROR_OUTPUT_FAILED;
}
codec.releaseOutputBuffer(outputIndex, false);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
mDecodeEnd = true;
}
}
return OK;
}
private int openCodec(MediaFormat format) {
Log.d(TAG, "openCodec, format mime:" + format.getString(MediaFormat.KEY_MIME));
try {
mDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
} catch (IOException e) {
e.printStackTrace();
return ERROR_OPEN_CODEC;
}
mDecoder.configure(format, null, null, 0);
mDecoder.start();
if (Build.VERSION.SDK_INT < 21) {
mInputBuffers = mDecoder.getInputBuffers();
mOutputBuffers = mDecoder.getOutputBuffers();
}
return OK;
}
private int openInput(String audioPath) {
Log.d(TAG, "openInput audioPath:" + audioPath);
int ret;
if (OK != (ret = checkPath(audioPath))) {
return ret;
}
mExtractor = new MediaExtractor();
int audioTrack = -1;
boolean hasAudio = false;
try {
mExtractor.setDataSource(audioPath);
for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
MediaFormat format = mExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "mime=" + mime);
if (mime.startsWith("audio/")) {
audioTrack = i;
hasAudio = true;
mFormat = format;
break;
}
}
if (!hasAudio) {
Log.d(TAG, "input contain no audio");
return ERROR_INPUT_INVALID;
}
mExtractor.selectTrack(audioTrack);
} catch (IOException e) {
return ERROR_INPUT_INVALID;
}
return OK;
}
private int openOutput(String outputPath) {
Log.d(TAG, "openOutput outputPath:" + outputPath);
try {
mFos = new FileOutputStream(outputPath);
} catch (IOException e) {
return ERROR_OUTPUT_FAILED;
}
return OK;
}
private void close() {
mExtractor.release();
mDecoder.stop();
mDecoder.release();
try {
mFos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
原文链接:https://ffmpeg.0voice.com/forum.php?mod=viewthread&tid=567&extra=
原文链接:https://blog.csdn.net/wangbuji/article/details/125315474
android硬编解码MediaCodec
[Android 进阶]MediaCodec系列之MediaCodec简介
MediaCodec专题(一):简介
MediaCodec专题(二):使用
NDK中使用 MediaCodec 编解码视频
MediaCodec的使用介绍
Android平台MediaCodec避坑指北
MediaCodec硬解流程的更多相关文章
- ffmpeg mediacodec 硬解初探
ffmpeg mediacodec 硬解初探 1编译: ffmpeg自3.1版本加入了android mediacodec硬解支持,解码器如图 硬件加速器如图(还不清楚硬件加速器的功能) 编译带h26 ...
- 【Android】Android Camera实时数据采集及通过MediaCodec硬编码编码数据的流程
吐槽: 其实常用流程都差不多,但是有时候还是会忘记某一步的详细用法,但是各位朋友请注意,官方已经不推荐Camera类的使用(现在是android.hardware.camera2),但无奈公司项目之前 ...
- EasyPusher安卓Android手机直播推送之MediaCodec 硬编码H264格式
本文转自Holo的博客:http://blog.csdn.net/u013758734/article/details/50834770 最近在研究EasyDarwin的Push库EasyPusher ...
- iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]
来源:http://www.cnblogs.com/michaellfx/p/understanding_-VideoToolboxDemo.html iOS硬解H.264:-VideoToolbox ...
- 视频硬解api介绍
在一个gpu如此强大的时代,视频解码怎么能少了gpu厂商的参加.为了用硬件加速视频解码,厂商定义了一些api. 好吧,一旦和硬件打交道,就会有os的参加,有了硬件与os参加,api肯定会变成很凌乱,看 ...
- 安卓平台 全面支持软解和硬解的SDK-Demo源代码开放
专业做视频编解码的SDK开发工作. 2015年12月1日10:46:55: 更新到1.5.0版本 功能列表: 基本播放: 1,正常播放, 支持MP4,FLV,AVI,TS,3GP,RMVB,WM,WM ...
- 英伟达CUVID硬解,并通过FFmpeg读取文件
虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SD ...
- Android视频播放软解与硬解的区别
硬解,用自带播放器播放,android中的VideoView 软解,使用音视频解码库,比如FFmpeg 一.硬解码 硬解:就是调用GPU的专门模块编码来解,减少CPU运算,对CPU等硬件要求也相对低点 ...
- 【视频开发】【CUDA开发】英伟达CUVID硬解,并通过FFmpeg读取文件
虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SD ...
- tp-link路由器后台_硬解
title: 脚本_tp-link路由器后台_硬解 author: 杨晓东 permalink: 脚本 date: 2021-10-02 11:27:04 categories: - 投篮 tags: ...
随机推荐
- 【二叉树】二叉树的深度优先遍历DFS(前中后序遍历)和广度优先遍历BFS(层序遍历)详解【力扣144,94,145,102】【超详细的保姆级别教学】
[二叉树]二叉树的深度优先遍历(前中后序遍历)和广度优先遍历(层序遍历)详解[超详细的保姆级别教学] 先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常 ...
- 17.3 给内存映射文件指定基地址--《Windows核心编程》
可以使用 MapViewOfFileEx 函数,建议系统把文件映射到指定的地址. 其他参数与 MapViewOfFile 相同,最后一个参数 pvBaseeAddress 指定目标地址.同 Virtu ...
- CF1886
A 分类讨论. B 二分. C 题意:给定一个字符串 \(s\).记 \(s_i\) 为将 \(s\) 删去 \(i\) 个字符,使得剩余字符串字典序最小得到的字符串.令 \(S=s_0+s_1+\d ...
- 【Unity3D】UGUI回调函数
1 简述 UGUI 回调函数主要指鼠标进入.离开.点下.点击中.抬起.开始拖拽.拖拽中.拖拽结束 UI 控件触发的回调.使用 UGUI 回调函数时,需要引入 UnityEngine.EventSy ...
- Oracle字符串行专列(字符串聚合技术)
原文链接:http://oracle-base.com/articles/misc/string-aggregation-techniques.php 1 String Aggregation ...
- 易语言读取Mysql表数据
源码下载: https://download.csdn.net/download/IndexMan/12029860 1.界面设计 2.效果展示 3.源码展示 程序集变量: 读取数据按钮: 读取数据子 ...
- Django实战之文件上传下载
项目介绍 最近学习django,通过文件上传下载这个小项目,总结下常用的知识点. 做这个案例我有以下需求: 1.要支持一次上传多个文件 2.支持上传后记录上传的数据以及列表展示 3.支持下载和删除文件 ...
- QT - Day 1
Date: 2021/3/12开始学习 教程视频: QT基本介绍: 跨平台图形界面引擎 优点 跨平台 接口简单,容易上手 一定程度上简化了内存回收 创建第一个QT程序 点击创建项目后,选择项目路径 ...
- pikachu SQL-inject insert/update注入
insert 注入 (修改信息处是update注入,和此处同理) 注册页面,用户处输入 1' 发现报错信息 You have an error in your SQL syntax; check th ...
- Acrobat 教程
https://helpx.adobe.com/cn/acrobat/using/pdf-form-field-properties.html