本文将介绍AndroidMediaCodec的数据处理方式分析的详细情况,特别是关于androidmediacodec详解的相关信息。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题
本文将介绍Android MediaCodec的数据处理方式分析的详细情况,特别是关于android mediacodec详解的相关信息。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将涉及一些关于Android MediaCodec HEVC支持的分辨率、Android MediaCodec 状态(States)转换分析、Android MediaCodec 硬编码 H264 文件、Android MediaCodec 设置 MediaFormat.KEY_PROFILE 问题的知识。
本文目录一览:- Android MediaCodec的数据处理方式分析(android mediacodec详解)
- Android MediaCodec HEVC支持的分辨率
- Android MediaCodec 状态(States)转换分析
- Android MediaCodec 硬编码 H264 文件
- Android MediaCodec 设置 MediaFormat.KEY_PROFILE 问题
Android MediaCodec的数据处理方式分析(android mediacodec详解)
*由于工作需要,需要利用MediaCodec实现Playback及Transcode等功能,故在学习过程中翻译了Google官方的MediaCodec api文档,由于作者水平限制,文中难免有错误和不恰当之处,望批评指正。
*转载请注明出处:http://www.cnblogs.com/roger-yu/
概述
Android MediaCodec可以访问底层的media codecs,我们很容易利用MediaCodec来构建encoder或decoder来实现音视频编码和音视频解码的功能。
简单点儿理解,一个Codec(可以认为是一个MediaCodec的实例对象)就相当于一个“处理器”:处理输入数据,并产生输出数据。
如下图所示,每一个Codec都维护着一组 input buffers 和 output buffers。开始时Codec拥有所有buffers的所有权,Client(可以暂且理解为MediaCodec之外写的程序)无法向 input buffer 写入数据,也无法读取 output buffer 中的数据。数据处理开始后,Client向Codec请求一个(同步模式)或者接收到(异步模式)一个空的 input buffer,将要处理的数据写入到该buffer中,然后提交给Codec处理,Codec处理完数据后会将处理的结果写入到一个空的 output buffer 中,之后Client就可以请求或接收到这个存有结果的 output buffer,Client对结果使用完毕后就可以release这个output buffer,Codec就可以再次使用这个buffer,如此过程完成整个的处理。
Android MediaCodec主要有3种数据处理的方式:
1. 使用Buffers的异步处理方式(Asynchronous Processing using Buffers)
2. 使用Buffers的同步处理方式(Synchronous Processing using Buffers)
3. 使用Buffer数组的同步处理方式(Synchronous Processing using Buffer Arrays (deprecated))
依据Android版本不同可以采用不同的方式,如下图:
目前最常用的是前两种模式,故接下来重点讲解。
使用Buffers的异步处理方式(Asynchronous Processing using Buffers)
基本处理流程:
注意:
1. 在调用configure配置MediaCodec之前需要为MediaCodec设置callback,需要实现MediaCodec.Callback接口并重写其中的方法:onInputBufferAvailable 、onOutputBufferAvailable、onOutputFormatChanged、onError,工作时MediaCodec会利用 这四个回调方法来自动的通知Client什么时候input buffer有效,什么时候output buffer有效,什么时候media format发生变化,什么时候运行出错,也是在这些方法中Client向Codec送入数据并得到处理的结果及获取Codec的一些其他信息。
2. 异步模式下MediaCodec的状态转换会有些许不同,在调用start方法后会直接进入Running状态;
异步处理模式下,调用MediaCodec.start()后Codec 立即进入Running子状态,通过设置的callback中的回调方法onInputBufferAvailable()会自动收到可用(empty)的input buffer,此时可以根据input buffer id调用getInputBuffer(id)得到这个buffer,并将需要的处理的数据写入该buffer中,最后调用queueInputBuffer(id,...)将该buffer提交给Codec处理;Codec每处理完一帧数据就会将处理结果写入一个空的output buffer,并通过回调函数
onOutputBufferAvailable
来通知Client来读取结果,Client可以根据output bufffer id调用getoutputBuffer(id)获取该buffer并读取结果,完毕后可以调用releaSEOutputBuffer(id,...)释放该buffer给Codec再次使用。
典型的代码设计:
1 MediaCodec codec = MediaCodec.createByCodecName(name); 2 MediaFormat mOutputFormat; // member variable 3 异步模式下需要在configure之前设置callback 4 codec.setCallback(new MediaCodec.Callback() { 5 6 /** 7 * 在onInputBufferAvailable回调方法中,MediaCodec会通知什么时候input 8 * buffer有效,根据buffer id,调用getInputBuffer(id)可以获得这个buffer, 9 * 此时就可以向这个buffer中写入数据,最后调用queueInputBuffer(id,…)提交 10 * 给MediaCodec处理。 11 */ 12 @Override 13 void onInputBufferAvailable(MediaCodec mc,int inputBufferId) { 14 ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); 15 fill inputBuffer with valid data 16 … 17 codec.queueInputBuffer(inputBufferId,…); 18 } 19 20 21 * 在onOutputBufferAvailable回调方法中,MediaCodec会通知什么时候output 22 * buffer有效,根据buffer id,调用getoutputBuffer(id)可以获得这个buffer, 23 * 此时就可以读取这个buffer中的数据,最后调用releaSEOutputBuffer(id,…)释放 24 * 给MediaCodec再次使用。 25 26 27 28 void onOutputBufferAvailable(MediaCodec mc,1)"> outputBufferId,…) { 29 ByteBuffer outputBuffer = codec.getoutputBuffer(outputBufferId); 30 MediaFormat bufferFormat = codec.getoutputFormat(outputBufferId); option A 31 bufferFormat is equivalent to mOutputFormat 32 outputBuffer is ready to be processed or rendered. 33 … 34 codec.releaSEOutputBuffer(outputBufferId,1)">35 36 37 * 当MediaCodec的output format发生变化是会回调该方法,一般在start之后都会首先回调该方法 38 39 40 void onOutputFormatChanged(MediaCodec mc,MediaFormat format) { 41 Subsequent data will conform to new format. 42 Can ignore if using getoutputFormat(outputBufferId) 43 mOutputFormat = format; option B 44 45 46 * MediaCodec运行发生错误时会回调该方法 47 48 49 onError(…) { 50 51 52 }); 53 codec.configure(format,1)">54 mOutputFormat = codec.getoutputFormat(); 55 codec.start(); start 之后MediaCodec立即进入Running子状态,并会回调callback中的方法 56 wait for processing to complete 57 codec.stop(); stop后MediaCodec进入Uninitialized子状态 58 codec.release(); 使用完毕要释放掉MediaCdoec占用的资源
使用Buffers的同步处理方式(Synchronous Processing using Buffers)
基本处理流程:
同步模式下,MediaCodec调用start()方法后会进入Flushed子状态,然后第一次调用dequeueInputBuffer()后才会进入Running子状态。
这种模式下,程序需要在一个无限循环中通过调用dequeueInputBuffer(...)和dequeueOutputBuffer(...)来不断地请求Codec是否有可用的input buffer 或 output buffer:
> 如果有可用的input buffer:根据得到的buffer id,调用getInputBuffer(id)获取该buffer,并向其中写入待处理的数据,然后调用queueInputBuffer(id,..)提交到Codec进行处理
> 如果有可用的output buffer: 根据得到的buffer id,调用getoutputBuffer(id)获取该buffer,读取其中的处理结果,然后调用releaSEOutputBuffer(id,..)释放该buffer供Codec再次使用
> 处理过程中还可能受到一些特殊标记的buffer id,比如MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,要作出恰当处理
典型的代码设计:
1 MediaCodec codec = 2 codec.configure(format,...); 3 MediaFormat outputFormat = codec.getoutputFormat(); 4 codec.start(); start()方法后会进入Flushed子状态 5 6 * 在一个无限循环中不断地请求Codec是否有可用的input buffer 或 output buffer 7 8 for (;;) { 9 int inputBufferId = codec.dequeueInputBuffer(timeoutUs); 请求是否有可用的input buffer 10 if (inputBufferId >= 0) { 11 ByteBuffer inputBuffer = codec.getInputBuffer(...); 获取input buffer 12 13 ... 14 codec.queueInputBuffer(inputBufferId,...); 提交数据给Codec 15 } 16 int outputBufferId = codec.dequeueOutputBuffer(...); 请求是否有可用的output buffer 17 if (outputBufferId >= 018 ByteBuffer outputBuffer = codec.getoutputBuffer(outputBufferId); 获取output buffer 19 MediaFormat bufferFormat = codec.getoutputFormat(outputBufferId); 20 bufferFormat is identical to outputFormat 21 23 codec.releaSEOutputBuffer(outputBufferId,1)"> 释放output buffer供Codec再次使用 24 } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 25 26 27 outputFormat = codec.getoutputFormat(); 28 29 } 30 codec.stop(); 31 codec.release(); 释放资源
异步模式与同步模式的区别在于:
》异步模式下通过回调函数来自动的传递可用的input buffer 或 output buffer
》同步模式下需要通过dequeueInputBuffer(...)或dequeueOutputBuffer(...)来请求获取可用的input buffer 或 output buffer
微信扫一扫,关注玖零日记,获取更多相关资讯及源码 -- 虽无面朝大海,依旧春暖花开
Android MediaCodec HEVC支持的分辨率
有人在解码HEVC时知道Android MediaCodec支持的分辨率吗?
通过反复试验,我发现以下工作:
640x272
720x304
960x400
1280x528
1920x800
2560x1072
并且以下不:
512x216
3840x1600
有官方文件吗?
解决方法:
我怀疑是否有任何正式文档-实际上,您可能会依赖CTS测试的决议起作用的事实,但其他决议可能会以任何方式表现.
从Android MediaCodec for HEVC来看,我想您正在使用只有SW解码器的设备进行测试.我实际上会猜测,真正的硬件解码器将比软件解码器更强大或更兼容. (这听起来似乎违反直觉,但是从历史上看,尤其是H264 SW编码器的情况非常有限.)
第一个非工作分辨率的高度不能被16除(所有工作分辨率的宽度和高度都可以被16除),而另一个可能“太大”.如果您测试更多的分辨率,该假设是否成立?
Android MediaCodec 状态(States)转换分析
*由于工作需要,需要利用MediaCodec实现Playback及Transcode等功能,故在学习过程中翻译了Google官方的MediaCodec api文档,由于作者水平限制,文中难免有错误和不恰当之处,望批评指正。
*转载请注明出处:http://www.cnblogs.com/roger-yu/
概述
在MediaCodec的生命周期内存在三种状态:Stopped,Executing or Released,其中
Stopped状态包含三种子状态:Uninitialized,Configured and Error
Executing状态包含三种子状态:Flushed,Running and End-of-Stream
由于MediaCodec在不同的数据处理模式下状态间的转换会有些许差别,故接下来我们分别对同步处理模式及异步处理模式下的状态转换做详细分析
同步模式下的状态转换(Synchronous Processing using Buffers)
首先我们先看一下状态转换的流程图,如下:
1. 当通过 MediaCodec.createByCodecName(...) or MediaCodec.
createDecoderByType(...) or MediaCodec.
createEncoderByType(...)三种方法中的任一种创建一个MediaCodec对象实例后,Codec将会处于 Uninitialized 状态;
2. 当你调用 MediaCodec.configure(...)方法对Codec进行配置后,Codec将进入 Configured 状态;
3. 之后可以调用 MediaCodec.start() 方法启动Codec,Codec会转入 Executing 状态,start后Codec立即进入 Flushed 子状态,此时的Codec拥有所有的input and output buffers,Client无法操作这些buffers;
4. 一旦第一个input buffer 出队列,也即Client通过调用 MediaCodec.dequeueInputBuffer(...)请求得到了一个有效的input buffer index,Codec立即进入到了 Running 子状态,在这个状态下Codec会进行实际的数据处理(解码、编码)工作,度过它生命周期的主要阶段;
5. 当输入端入队列一个带有 end-of-stream 标记的input buffer时(queueInputBuffer(EOS)),Codec将转入 End of Stream 子状态。在此状态下,Codec不再接受新的input buffer数据,但仍会处理之前入队列而未处理完的input buffer并产生output buffer,直到end-of-stream 标记到达输出端,数据处理的过程也随即终止;
6. 在 Executing状态下可以调用 MediaCodec.flush()方法使Codec进入 Flushed 子状态;
7. 在 Executing状态下可以调用 MediaCodec.stop()方法使Codec进入
子状态,可以对Codec进行重新配置;Uninitialized
8. 极少数情况下Codec会遇到错误进入 Error 状态,可以调用 MediaCodec.reset() 方法使其再次可用;
9. 当MediaCodec数据处理任务完成时或不再需要MediaCodec时,可使用 MediaCodec.release()方法释放其资源。
异步模式下的状态转换(ASynchronous Processing using Buffers)
首先我们先看一下状态转换的流程图,如下:
异步模式下状态转换与同步模式下大同小异,主要有两点区别:
1. 调用 MediaCodec.start() 方法启动Codec,Codec会直接转入 Running 子状态;
2. 当调用 MediaCodec.flash() 方法进入 Flushed 子状态后,必须调用 MediaCodec.start() 方法Codec才会进入 Running 子状态。
其他情况下均与同步模式下相同,就不在此赘述。
微信扫一扫,关注玖零日记,获取更多相关资讯及源码 -- 虽无面朝大海,依旧春暖花开
Android MediaCodec 硬编码 H264 文件
在 Android 4.1 版本提供了 MediaCodec 接口来访问设备的编解码器,不同于 FFmpeg 的软件编解码,它采用的是硬件编解码能力,因此在速度上会比软解更具有优势,但是由于 Android 的碎片化问题,机型众多,版本各异,导致 MediaCodec 在机型兼容性上需要花精力去适配,并且编解码流程不可控,全交由厂商的底层硬件去实现,最终得到的视频质量不一定很理想。
虽然 MediaCodec 仍然存在一定的弊端,但是对于快速实现编解码需求,还是很值得参考的。
以将相机预览的 YUV 数据编码成 H264 视频流为例来解析 MediaCodec 的使用。
使用解析
MediaCodec 工作模型
下图展示了 MediaCodec 的工作方式,一个典型的生产者消费者模型,两边的 Client 分别代表输入端和输出端,输入端将数据交给 MediaCodec 进行编码或者解码,而输出端就得到编码或者解码后的内容。
输入端和输出端是通过输入队列缓冲区和输出队列缓冲区,两条缓冲区队列的形式来和 MediaCodec 传递数据。
首先从输入队列中出队得到一个可用的缓冲区,将它填满数据之后,再将缓冲区入队,交由 MediaCodec 去处理。
MediaCodec 处理完了之后,再从输出队列中出队得到一个可用的缓冲区,这个缓冲里面的数据就是编码或者解码后的数据了,把这些数据进行相应的处理之后,还需要释放这个缓冲区,让它回到队列中去,可供下一次使用。
MediaCodec 生命周期
另外,MediaCodec 也存在相应的 生命周期,如下图所示:
当创建了 MediaCodec 之后,是处于未初始化的 Uninitialized
状态,调用 configure 方法之后就处于 Configured
状态,调用了 start 方法之后,就处于 Executing
状态。
在 Executing
状态下开始处理数据,它又有三个子状态,分别是:
- Flushed
- Running
- End of Stream
当一调用 start 方法之后,就进入了 Flushed
状态,从输入缓冲区队列中取出一个缓冲区就进入了 Running
状态,当入队的缓冲区带有 EOS
标志时, 就会切换到 End of Stream
状态, MediaCodec 不再接受入队的缓冲区,但是仍然会对已入队的且没有进行编解码操作的缓冲区进行操作、输出,直到输出的缓冲区带有 EOS
标志,表示编解码操作完成了。
在 Executing
状态下可以调用 flush 方法,使 MediaCodec 切换到 Flushed
状态。
在 Executing
状态下可以调用 stop 方法,使 MediaCodec 切换到 Uninitialized
状态,然后再次调用 configure 方法进入 Configured
状态。另外,当调用 reset 方法也会进入到 Uninitialized
状态。
当不再需要 MediaCodec 时,调用 release 方法将它释放掉,进入 Released
状态。
当 MediaCodec 工作发生异常时,会进入到 Error
状态,此时还是可以通过 reset 方法恢复过来,进入 Uninitialized
状态。
MediaCodec 调用流程
理解了 MediaCodec 的生命周期和工作流程之后,就可以上手来进行编码工作了。
以 MediaCodec 同步调用为例,使用过程如下:
// 创建 MediaCodec,此时是 Uninitialized 状态
MediaCodec codec = MediaCodec.createByCodecName(name);
// 调用 configure 进入 Configured 状态
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
// 调用 start 进入 Executing 状态,开始编解码工作
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
}
}
// 调用 stop 方法进入 Uninitialized 状态
codec.stop();
// 调用 release 方法释放,结束操作
codec.release();
代码解析
MediaFormat 设置
首先需要创建并设置好 MediaFormat 对象,它表示媒体数据格式的相关信息,对于视频主要有以下信息要设置:
- 颜色格式
- 码率
- 码率控制模式
- 帧率
- I 帧间隔
其中,码率就是指单位传输时间传送的数据位数,一般用 kbps
即千位每秒来表示。而帧率就是指每秒显示的帧数。
其实对于码率有三种模式可以控制:
- BITRATE_MODE_CQ
- 表示不控制码率,尽最大可能保证图像质量
- BITRATE_MODE_VBR
- 表示 MediaCodec 会根据图像内容的复杂度来动态调整输出码率,图像负责则码率高,图像简单则码率低
- BITRATE_MODE_CBR
- 表示 MediaCodec 会把输出的码率控制为设定的大小
对于颜色格式,由于是将 YUV 数据编码成 H264,而 YUV 格式又有很多,这又涉及到机型兼容性问题。在对相机编码时要做好格式的处理,比如相机使用的是 NV21
格式,MediaFormat 使用的是 COLOR_FormatYUV420SemiPlanar
,也就是 NV12
模式,那么就得做一个转换,把 NV21
转换到 NV12
。
对于 I 帧间隔,也就是隔多久出现一个 H264 编码中的 I 帧。
完整 MediaFormat 设置示例:
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
// 马率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
// 调整码率的控流模式
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
// 设置帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
// 设置 I 帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
当开始编解码操作时,开启编解码线程,处理相机预览返回的 YUV 数据。
在这里用到了相机的一个封装库:
https://github.com/glumes/EzC...
编解码操作
编解码操作代码如下:
while (isEncoding) {
// YUV 颜色格式转换
if (!mEncodeDataQueue.isEmpty()) {
input = mEncodeDataQueue.poll();
byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2];
NV21ToNV12(input, yuv420sp, mWidth, mHeight);
input = yuv420sp;
}
if (input != null) {
try {
// 从输入缓冲区队列中拿到可用缓冲区,填充数据,再入队
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
// 计算时间戳
pts = computePresentationTime(generateIndex);
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
generateIndex += 1;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
// 从输出缓冲区队列中拿到编码好的内容,对内容进行相应处理后在释放
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
// flags 利用位操作,定义的 flag 都是 2 的倍数
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 配置相关的内容,也就是 SPS,PPS
mOutputStream.write(outData, 0, outData.length);
} else if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { // 关键帧
mOutputStream.write(outData, 0, outData.length);
} else {
// 非关键帧和SPS、PPS,直接写入文件,可能是B帧或者P帧
mOutputStream.write(outData, 0, outData.length);
}
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
}
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Log.e(TAG, e.getMessage());
}
}
}
首先,要把要把相机的 NV21
格式转换成 NV12
格式,然后 通过 dequeueInputBuffer
方法去从可用的输入缓冲区队列中出队取出缓冲区,填充完数据后再通过 queueInputBuffer
方法入队。
dequeueInputBuffer
返回缓冲区索引,如果索引小于 0 ,则表示当前没有可用的缓冲区。它的参数 timeoutUs
表示超时时间 ,毕竟用的是 MediaCodec 的同步模式,如果没有可用缓冲区,就会阻塞指定参数时间,如果参数为负数,则会一直阻塞下去。
queueInputBuffer
方法将数据入队时,除了要传递出队时的索引值,然后还需要传入当前缓冲区的时间戳 presentationTimeUs
和当前缓冲区的一个标识 flag
。
其中,时间戳通常是缓冲区渲染的时间,而标识则有多种标识,标识当前缓冲区属于那种类型:
- BUFFER_FLAG_CODEC_CONFIG
- 标识当前缓冲区携带的是编解码器的初始化信息,并不是媒体数据
- BUFFER_FLAG_END_OF_STREAM
- 结束标识,当前缓冲区是最后一个了,到了流的末尾
- BUFFER_FLAG_KEY_FRAME
- 表示当前缓冲区是关键帧信息,也就是 I 帧信息
在编码的时候可以计算当前缓冲区的时间戳,也可以直接传递 0 就好了,对于标识也可以直接传递 0 作为参数。
把数据传入给 MediaCodec 之后,通过 dequeueOutputBuffer
方法取出编解码后的数据,除了指定超时时间外,还需要传入 MediaCodec.BufferInfo
对象,这个对象里面有着编码后数据的长度、偏移量以及标识符。
取出 MediaCodec.BufferInfo
内的数据之后,根据不同的标识符进行不同的操作:
- BUFFER_FLAG_CODEC_CONFIG
- 表示当前数据是一些配置数据,在 H264 编码中就是 SPS 和 PPS 数据,也就是
00 00 00 01 67
和00 00 00 01 68
开头的数据,这个数据是必须要有的,它里面有着视频的宽、高信息。 - BUFFER_FLAG_KEY_FRAME
- 关键帧数据,对于 I 帧数据,也就是开头是
00 00 00 01 65
的数据, - BUFFER_FLAG_END_OF_STREAM
- 表示结束,MediaCodec 工作结束
对于返回的 flags ,不符合预定义的标识,则可以直接写入,那些数据可能代表的是 H264 中的 P 帧 或者 B 帧。
对于编解码后的数据,进行操作后,通过 releaseOutputBuffer
方法释放对应的缓冲区,其中第二个参数 render
代表是否要渲染到 surface 上,这里暂时不需要就为 false 。
停止编码
当想要停止编码时,通过 MediaCodec 的 stop
方法切换到 Uninitialized
状态,然后再调用 release
方法释放掉。
这里并没有采用使用 BUFFER_FLAG_END_OF_STREAM
标识符的方式来停止编码,而是直接切换状态了,在通过 Surface 方式进行录制时,再去采用这种方式了。
对于 MediaCodec 硬编码解析之相机内容编码成 H264 文件就到这里了,主要还是讲述了关于 MediaCodec 的使用,一旦熟悉使用了,完成编码工作也就很简单了。
阿里P6P7【安卓】进阶资料分享+加薪跳槽必备面试题
Android MediaCodec 设置 MediaFormat.KEY_PROFILE 问题
我在设置 MediaCodec profile 的时候,一直没有成功,看了源码之后才发现问题之所在:
https://android.googlesource.com/platform/frameworks/av/+/437ced8a14944bf5450df50c5e7e7a6dfe20ea40/media/libstagefright/ACodec.cpp
设置了 profile 之后,你还要设置一个 Level 属性,但是目前最新的 SDK 里面并没有提供这个 Key。
即使你手动的设置 level ,比如像这样:
MediaCodec codec = createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
MediaFormat format = = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
format.setInteger("level", Level_xxx);
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
还是不行的,因为 Android 强制将 profile 设置为 Baseline 了。
// XXX
if (h264type.eProfile != OMX_VIDEO_AVCProfileBaseline) {
ALOGW("Use baseline profile instead of %d for AVC recording",
h264type.eProfile);
h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
}
总之,Android 在使用 MediaCodec 进行 encode 的时候,只能使用 Baseline 的 profile。
Google 之后,发现也有其他的朋友发现了类似的问题,
https://code.google.com/p/android/issues/detail?id=163580
看来这个问题确实存在,不过不清楚为什么 Android 要强制使用 Baseline 的 profile。
今天关于Android MediaCodec的数据处理方式分析和android mediacodec详解的讲解已经结束,谢谢您的阅读,如果想了解更多关于Android MediaCodec HEVC支持的分辨率、Android MediaCodec 状态(States)转换分析、Android MediaCodec 硬编码 H264 文件、Android MediaCodec 设置 MediaFormat.KEY_PROFILE 问题的相关知识,请在本站搜索。
本文标签: