我们知道 Camera 采集回传的是 YUV 数据,AudioRecord 是 PCM,我们要对这些数据进行编码(压缩编码),这里我们来说在 Android 上音视频编解码逃不过的坑-MediaCodec

MediaCodec

PSMediaCodec 可以用来编/解码 音/视频

MediaCodec 简单介绍

MediaCodec 类可用于访问低级媒体编解码器,即编码器/解码器组件。 它是 Android 低级多媒体支持基础结构的一部分(通常与 MediaExtractor,MediaSync,MediaMuxer,MediaCrypto,MediaDrm,Image,Surface 和 AudioTrack 一起使用)。关于 MediaCodec 的描述可参看官方介绍MediaCodec

广义而言,编解码器处理输入数据以生成输出数据。 它异步处理数据,并使用一组输入和输出缓冲区。 在简单的情况下,您请求(或接收)一个空的输入缓冲区,将其填充数据并将其发送到编解码器进行处理。 编解码器用完了数据并将其转换为空的输出缓冲区之一。 最后,您请求(或接收)已填充的输出缓冲区,使用其内容并将其释放回编解码器。

PS 读者如果对生产者-消费者模型还有印象的话,那么 MediaCodec 的运行模式其实也不难理解。

下面是 MediaCodec 的简单类图

MediaCodec 状态机

在 MediaCodec 生命周期内,编解码器从概念上讲处于以下三种状态之一:Stopped,Executing 或 Released。Stopped 的集体状态实际上是三个状态的集合:Uninitialized,Configured 和 Error,而 Executing 状态从概念上讲经过三个子状态:Flushed,Running 和 Stream-of-Stream。

使用工厂方法之一创建编解码器时,编解码器处于未初始化状态。首先,您需要通过 configure(…)对其进行配置,使它进入已配置状态,然后调用 start()将其移至执行状态。在这种状态下,您可以通过上述缓冲区队列操作来处理数据。

执行状态具有三个子状态:Flushed,Running 和 Stream-of-Stream。在 start()之后,编解码器立即处于 Flushed 子状态,其中包含所有缓冲区。一旦第一个输入缓冲区出队,编解码器将移至“Running”子状态,在此状态下将花费大部分时间。当您将输入缓冲区与流结束标记排队时,编解码器将转换为 End-of-Stream 子状态。在这种状态下,编解码器将不再接受其他输入缓冲区,但仍会生成输出缓冲区,直到在输出端达到流结束为止。在执行状态下,您可以使用 flush()随时返回到“刷新”子状态。

调用 stop()使编解码器返回 Uninitialized 状态,随后可以再次对其进行配置。使用编解码器完成操作后,必须通过调用 release()释放它。

在极少数情况下,编解码器可能会遇到错误并进入“错误”状态。使用来自排队操作的无效返回值或有时通过异常来传达此信息。调用 reset()使编解码器再次可用。您可以从任何状态调用它,以将编解码器移回“Uninitialized”状态。否则,请调用 release()以移至终端的“Released”状态。

PSMediaCodec 数据处理的模式可分为同步和异步,下面我们会一一分析

MediaCodec 同步模式

上代码

    public H264MediaCodecEncoder(int width, int height) {
//设置MediaFormat的参数
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, width, height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);//FPS
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); try {
//通过MIMETYPE创建MediaCodec实例
mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
//调用configure,传入的MediaCodec.CONFIGURE_FLAG_ENCODE表示编码
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//调用start
mMediaCodec.start(); } catch (Exception e) {
e.printStackTrace();
} }
 

调用 putData 向队列中 add 原始 YUV 数据。

  public void putData(byte[] buffer) {
if (yuv420Queue.size() >= 10) {
yuv420Queue.poll();
}
yuv420Queue.add(buffer);
}
//开启编码
public void startEncoder() {
isRunning = true;
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
byte[] input = null;
while (isRunning) { if (yuv420Queue.size() > 0) {
//从队列中取数据
input = yuv420Queue.poll();
}
if (input != null) {
try {
//【1】dequeueInputBuffer
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_S);
if (inputBufferIndex >= 0) {
//【2】getInputBuffer
ByteBuffer inputBuffer = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex);
} else {
inputBuffer = mMediaCodec.getInputBuffers()[inputBufferIndex];
}
inputBuffer.clear();
inputBuffer.put(input);
//【3】queueInputBuffer
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, getPTSUs(), 0);
} MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//【4】dequeueOutputBuffer
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = mMediaCodec.getOutputFormat();
if (null != mEncoderCallback) {
mEncoderCallback.outputMediaFormatChanged(H264_ENCODER, newFormat);
}
if (mMuxer != null) {
if (mMuxerStarted) {
throw new RuntimeException("format changed twice");
}
// now that we have the Magic Goodies, start the muxer
mTrackIndex = mMuxer.addTrack(newFormat);
mMuxer.start(); mMuxerStarted = true;
}
} while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = null;
//【5】getOutputBuffer
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
} else {
outputBuffer = mMediaCodec.getOutputBuffers()[outputBufferIndex];
}
if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
bufferInfo.size = 0;
} if (bufferInfo.size > 0) { // adjust the ByteBuffer values to match BufferInfo (not needed?)
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
// write encoded data to muxer(need to adjust presentationTimeUs.
bufferInfo.presentationTimeUs = getPTSUs(); if (mEncoderCallback != null) {
//回调
mEncoderCallback.onEncodeOutput(H264_ENCODER, outputBuffer, bufferInfo);
}
prevOutputPTSUs = bufferInfo.presentationTimeUs;
if (mMuxer != null) {
if (!mMuxerStarted) {
throw new RuntimeException("muxer hasn't started");
}
mMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
} }
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
bufferInfo = new MediaCodec.BufferInfo();
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_S);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} }
});
}
 

PS 编解码这种耗时操作要在单独的线程中完成,我们这里有个缓冲队列ArrayBlockingQueue<byte[]> yuv420Queue = new ArrayBlockingQueue<>(10);用来接收从 Camera 回调中传入的 byte[] YUV 数据,我们又新建立了一个现成来从缓冲队列 yuv420Queue中循环读取数据交给 MediaCodec 进行编码处理,编码完成的格式是由 mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);指定的,这里输出的是目前最为广泛使用的H264格式

完整代码请看H264MediaCodecEncoder

MediaCodec 异步模式

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public H264MediaCodecAsyncEncoder(int width, int height) {
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, width, height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);//FPS
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); try {
mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC);
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//设置回调
mMediaCodec.setCallback(new MediaCodec.Callback() {
@Override
/**
* Called when an input buffer becomes available.
*
* @param codec The MediaCodec object.
* @param index The index of the available input buffer.
*/
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
Log.i("MFB", "onInputBufferAvailable:" + index);
byte[] input = null;
if (isRunning) {
if (yuv420Queue.size() > 0) {
input = yuv420Queue.poll();
}
if (input != null) {
ByteBuffer inputBuffer = codec.getInputBuffer(index);
inputBuffer.clear();
inputBuffer.put(input);
codec.queueInputBuffer(index, 0, input.length, getPTSUs(), 0);
}
}
} @Override
/**
* Called when an output buffer becomes available.
*
* @param codec The MediaCodec object.
* @param index The index of the available output buffer.
* @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}.
*/
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
Log.i("MFB", "onOutputBufferAvailable:" + index);
ByteBuffer outputBuffer = codec.getOutputBuffer(index); if (info.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
info.size = 0;
} if (info.size > 0) { // adjust the ByteBuffer values to match BufferInfo (not needed?)
outputBuffer.position(info.offset);
outputBuffer.limit(info.offset + info.size);
// write encoded data to muxer(need to adjust presentationTimeUs.
info.presentationTimeUs = getPTSUs(); if (mEncoderCallback != null) {
//回调
mEncoderCallback.onEncodeOutput(H264_ENCODER, outputBuffer, info);
}
prevOutputPTSUs = info.presentationTimeUs;
if (mMuxer != null) {
if (!mMuxerStarted) {
throw new RuntimeException("muxer hasn't started");
}
mMuxer.writeSampleData(mTrackIndex, outputBuffer, info);
} }
codec.releaseOutputBuffer(index, false);
} @Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) { } @Override
/**
* Called when the output format has changed
*
* @param codec The MediaCodec object.
* @param format The new output format.
*/
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
if (null != mEncoderCallback) {
mEncoderCallback.outputMediaFormatChanged(H264_ENCODER, format);
}
if (mMuxer != null) {
if (mMuxerStarted) {
throw new RuntimeException("format changed twice");
}
// now that we have the Magic Goodies, start the muxer
mTrackIndex = mMuxer.addTrack(format);
mMuxer.start(); mMuxerStarted = true;
}
}
});
mMediaCodec.start(); } catch (Exception e) {
e.printStackTrace();
} }

  

完整代码请看H264MediaCodecAsyncEncoder

MediaCodec 小结

MediaCodec 用来音视频的编解码工作(这个过程有的文章也称为硬解),通过MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC)函数中的参数来创建音频或者视频的编码器,同理通过MediaCodec.createDecoderByType(MIMETYPE_VIDEO_AVC)创建音频或者视频的解码器。对于音视频编解码中需要的不同参数用MediaFormat来指定。

小结

本篇文章详细的对 MediaCodec 进行了分析,读者可根据博客对应 Demo 来进行实际操练。

放上 Demo 地址详细Demo

Android 音视频 - MediaCodec 编解码音视频的更多相关文章

  1. iOS8系统H264视频硬件编解码说明

    公司项目原因,接触了一下视频流H264的编解码知识,之前项目使用的是FFMpeg多媒体库,利用CPU做视频的编码和解码,俗称为软编软解.该方法比较通用,但是占用CPU资源,编解码效率不高.一般系统都会 ...

  2. 转:关于视频H264编解码的应用实现

    转:http://blog.csdn.net/scalerzhangjie/article/details/8273410 项目要用到视频编解码,最近半个月都在搞,说实话真是走了很多弯路,浪费了很多时 ...

  3. Android中使用MediaCodec硬件解码,高效率得到YUV格式帧,快速保存JPEG图片(不使用OpenGL)(附Demo)

    MediaCodec的使用demo: https://github.com/vecio/MediaCodecDemo https://github.com/taehwandev/MediaCodecE ...

  4. IOS和Android支持的音频编解码

    1.IOS编码 参考文档地址:https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/Multimedi ...

  5. Android 音视频编解码——YUV视频格式详解

    一.YUV 介绍 YUV是一种颜色编码方方式,通常由彩色摄像机进行取像,然后把取得的彩色图像信号经过分色.分别放大校正后得到RGB,再经过矩阵变换得到亮度信号Y和两个色差信号B-Y(即U).R-Y(即 ...

  6. 音视频编解码——YUV视频格式详解

    一.YUV 介绍 YUV是一种颜色编码方方式,通常由彩色摄像机进行取像,然后把取得的彩色图像信号经过分色.分别放大校正后得到RGB,再经过矩阵变换得到亮度信号Y和两个色差信号B-Y(即U).R-Y(即 ...

  7. FFMpeg笔记(二) 使用FFmpeg对视频进行编解码的一般流程

    1. 编码: 1.对编码资源的初始化 AVCodec* m_pVideoEncoder;// 特定编码器的参数信息 AVCodecContext* m_pVideoEncoderContext;// ...

  8. 音视频编解码技术(一):MPEG-4/H.264 AVC 编解码标准

    一.H264 概述 H.264,通常也被称之为H.264/AVC(或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC) 1. H.264视频编解码的意义 H.264的出现就是为了创 ...

  9. FFmpeg音视频编解码实践总结

    PS:由于目前开发RTSP服务器传输模块时用到了h264文件,所以攻了一段时间去实现h264的视频编解码,借用FFmpeg SDK实现了任意文件格式之间的转换,并实现了流媒体实时播放,目前音视频同步需 ...

  10. ffmpeg编解码视频导致噪声增大的一种解决方法

    一.前言 ffmpeg在视音频编解码领域算是一个比较成熟的解决方案了.公司的一款视频编辑软件正是基于ffmpeg做了二次封装,并在此基础上进行音视频的编解码处理.然而,在观察编码后的视频质量时,发现图 ...

随机推荐

  1. FFmpeg 命令行

    FFmpeg命令行帮助 #>ffmpeg -h #>ffmpeg -h long #>ffmpeg -h full 将视频按照指定的宽高输出 #>ffmpeg -i input ...

  2. 第二章:用Python对不同的商品销售数据进行预测分析

    文章目录 项目背景 获取数据 线性数据预测 非线性数据预测 源码地址 本文分享知识: os 模块获取上一级目录的绝对地址 pands 读取 sqlite3 数据库中的数据 用sklearn中的线性回归 ...

  3. SQL-建表注释

    ddl 是对表结构的操作 create(创建)命令.alter(修改)命令.drop(删除)dml 是对表数据的操作 insert(插入)命令.update(更新)命令.delete(删除) alte ...

  4. 在Unity3D中开发的Ghost Shader

    SwordMaster Ghost Shader 特点 此Shader是顶点片元Shader,由本人手动编写完成 此Shader已经在移动设备真机上进行过测试,可以直接应用到您的项目中 所支持的Uni ...

  5. hive:使用concat_ws实现 array转string案例

    concat_ws(',',collect_set(if(step_name <> '',step_name,null))) AS step_names,

  6. QT搭建Ffmpeg开发环境gcc版本

    1 先安装qt 解压ffmpeg包 2打开qt创建工程 3 导入头文件和库文件 这里一定要注意gcc版本和库的版本一定要一致 4 添加一下简单的源代码 1 #include <libavcode ...

  7. element table合计行自定义及单元格合并

    问题:项目需求要求table下面加合计一行 图片展示: 代码示例: TEMPLATE:   span-method是自定义table单元 show-summary是展示合并行 summary-meth ...

  8. oracle 存过调试 stepinto stepover stepout

    step into:单步执行,遇到子函数就进入并且继续单步执行(简而言之,进入子函数): step over:在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止, ...

  9. Liunx mosquitto卸载

    1.查询文件: whereis mosquitto whereis mosquitto_sub 2.删除查询到的文件,命令: rm -rf /etc/mosquitto rm -f /usr/loca ...

  10. 微信小程序ECharts通过调用api接口实现图表的数据可视化

    小程序ECharts使用接口调入数据 首先附上js文件链接:axios.js 提取码:AxIo 将此放到小程序目录下的utils文件夹下 在已经完成图表的js文件中完成以下修改: ①引用axios.j ...