一 MediaCodec概述

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

1 数据格式

mediacodec的作用是处理输入的数据生成输出数据,有两种输入输出模式:

  1. surface模式-------输入/输出以surface作为源

  2. ByteBuffer模式--------输入/输出时以ByteBuffer作为源

MediaCodec接受三种数据格式:压缩数据,原始音频数据和原始视频数据。

这三种数据都可以使用ByteBuffer作为载体传输给MediaCodec来处理。但是当使用原视频数据时,最好采用Surface作为输入源来替代ByteBuffer,这样效率更高,效果更好,因为surface使用的更底层的视频数据,不会映射或者复制到ByteBuffer缓冲区。在使用surface作为输入源时,开发者不能访问到到原始视频数据,但是可以使用ImageReader来获取到原始未加密的视频数据,这个地方我理解的是imagereader的工作流程是接受自己的surface数据来生成image,将imagereadersurface传给mediacodec作为解码器的输出surface,就可以访问解码的数据,但是必须是未加密的,这种方式同样比使用ByteBuffer更快,因为native缓冲区会直接映射到directbytebuffer区域,这是一块nativejava共享的缓冲区。当使用ByteBuffer模式时可以使用Image来获取原始视频数据,MediaCodec提供了两个方法,getInput/OutputImage(int)

例如MediaCodec解码H264数据,我们必须将分割符和NALU单元作为一个完整的数据帧传给解码器才能正确解码,除非是标记了BUFFER_FLAG_PARTIAL_FRAME的数据,这种方式不常用。

注:客户端处理完数据后,必须手动释放output缓冲区,否则将会导致MediaCodec输出缓冲被占用,无法继续解码。

MediaCodec状态图,整体上分为三个大的状态:SotppedExecutingReleased

Stoped:包含了3个小状态:ErrorUninitializedConfigured

首先,新建MediaCodec后,会进入Uninitialized状态;

其次,调用configure方法配置参数后,会进入Configured

Executing:同样包含3个小状态:FlushedRunningEnd 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平台播放tsts切片的hls视频时,需要将Annex-B格式的SPS/PPS NALU转为AVCC格式的extradata,以及将其他以size方式分割的NALU转为start code方式。

初始化解码器,除了配置输入视频流的的编码格式、宽高以及输出格式之外,还需要配置一些额外的信息。 对于H.264视频,需要填充Annex-B格式的SPS/PPS信息。

基本流程:

  1. 创建和配置MediaCodec对象
  2. 进行以下循环:
    • 如果一个输入缓冲区准备好:
    • 读取部分数据,复制到缓冲区
    • 如果一个输出缓冲区准备好:
    • 复制到缓冲区
  3. 销毁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硬解流程的更多相关文章

  1. ffmpeg mediacodec 硬解初探

    ffmpeg mediacodec 硬解初探 1编译: ffmpeg自3.1版本加入了android mediacodec硬解支持,解码器如图 硬件加速器如图(还不清楚硬件加速器的功能) 编译带h26 ...

  2. 【Android】Android Camera实时数据采集及通过MediaCodec硬编码编码数据的流程

    吐槽: 其实常用流程都差不多,但是有时候还是会忘记某一步的详细用法,但是各位朋友请注意,官方已经不推荐Camera类的使用(现在是android.hardware.camera2),但无奈公司项目之前 ...

  3. EasyPusher安卓Android手机直播推送之MediaCodec 硬编码H264格式

    本文转自Holo的博客:http://blog.csdn.net/u013758734/article/details/50834770 最近在研究EasyDarwin的Push库EasyPusher ...

  4. iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

    来源:http://www.cnblogs.com/michaellfx/p/understanding_-VideoToolboxDemo.html iOS硬解H.264:-VideoToolbox ...

  5. 视频硬解api介绍

    在一个gpu如此强大的时代,视频解码怎么能少了gpu厂商的参加.为了用硬件加速视频解码,厂商定义了一些api. 好吧,一旦和硬件打交道,就会有os的参加,有了硬件与os参加,api肯定会变成很凌乱,看 ...

  6. 安卓平台 全面支持软解和硬解的SDK-Demo源代码开放

    专业做视频编解码的SDK开发工作. 2015年12月1日10:46:55: 更新到1.5.0版本 功能列表: 基本播放: 1,正常播放, 支持MP4,FLV,AVI,TS,3GP,RMVB,WM,WM ...

  7. 英伟达CUVID硬解,并通过FFmpeg读取文件

    虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SD ...

  8. Android视频播放软解与硬解的区别

    硬解,用自带播放器播放,android中的VideoView 软解,使用音视频解码库,比如FFmpeg 一.硬解码 硬解:就是调用GPU的专门模块编码来解,减少CPU运算,对CPU等硬件要求也相对低点 ...

  9. 【视频开发】【CUDA开发】英伟达CUVID硬解,并通过FFmpeg读取文件

    虽然FFmpeg本身有cuvid硬解,但是找不到什么好的资料,英伟达的SDK比较容易懂,参考FFmpeg源码,将NVIDIA VIDEO CODEC SDK的数据获取改为FFmpeg获取,弥补原生SD ...

  10. tp-link路由器后台_硬解

    title: 脚本_tp-link路由器后台_硬解 author: 杨晓东 permalink: 脚本 date: 2021-10-02 11:27:04 categories: - 投篮 tags: ...

随机推荐

  1. java将集合里面的元素拼接为一条String字符串

    java将集合里面的元素拼接为一条String字符串 1️⃣ 随便创建一个list集合,往里面塞入元素 2️⃣ 第一种方式:通过foreach循环实现 但是通过这种方式只能将list集合里面的元素取出 ...

  2. HarmonyOS 实战小项目开发(一)

    HarmonyOS 实战小项目开发(一) 日常逼逼叨 在经过一周多的Harmonyos 开发基础知识的学习后,自己通过对于Harmonyos基础知识的学习之后,结合自己的一些想法,独自完成了利用Ark ...

  3. (C语言)每日代码||2023.12.24||fwrite()可以写入字符数组中的'\0'

    void test() { FILE* fp = fopen("test.txt", "w"); if (fp == NULL) { perror(" ...

  4. Hive-安装和部署(Hive3.1.2)

    (一)安装前提 (1) 安装JDK1.8及以上版本 (2) 已经安装MySQL,推荐5.7. (3) 已经安装Hadoop. JDK.MySQL.Hadoop的安装,本文不再介绍. (二)安装Hive ...

  5. C++——数据类型笔记

    在C++编程中,了解各类数据类型也是至关重要的.下面我会总结一下C++中的数据类型,包括基本类型,符合类型和自定义类型.方便自己整理和理解. 1,基本类型 C++中的基本类型是构建其他数据类型的基础, ...

  6. Libata Error Message 解析

    Libata error messages     Contents [hide]  1 Overview 2 Prefix 3 Exception line 4 Input taskfile 5 O ...

  7. Ubuntu 20.04 出现 SSL_connect: error:1425F102 .. unsupported protocol问题的解决

    在安装完Ubuntu 20.04后, 这个问题影响了好几个软件, 包括MySQL Workbench, Openfortigui等等, 出现的错误都是 ERROR: SSL_connect: erro ...

  8. BUG管理系统(Mantis)迁移实战

    Mantis迁移实战 名词解释 Mantis:  开源的BUG管理平台Mantis,也做MantisBT.           同档次产品有EasyBUG,QC,BugFree,Bugzila. Xa ...

  9. "explicit" 的使用

    今天在编译项目时,代码审查提示 "Single-parameter constructors should be marked explicit" 于是就在构造函数前加上 expl ...

  10. [Android逆向] 重打包时报BrutException

    执行apktool b --use-aapt2 进行重打包时,重打包失败,抛出异常 apktool b /Users/***/work/appsApk/testApp --use-aapt2 I: U ...