最近分析了一个关于MediaCodec的花屏问题,记录一下文档以备后面使用。
MediaCodec这个类是Android4.1开始引入的,API16。这个类可以在设备上直接访问媒体的解码,一般称为硬解码。
在Android4.3之后,API18,MediaCodec扩展了一个方法createInputSurface,提供了一个通过Surface作为输入。这样允许输入来自于摄像头的预览或者OpenGL ES渲染。
在Android5.0之后,API21,开启了异步模式,它提供了一个回调方法,当缓冲器允许的时候才开始执行。
基本的用法:
我们先使用同步模式开始,异步模式单独介绍。只有三步
- 创建和配置MediaCodec对象
- 一个循环 循环内容如下
- 释放MediaCodec对象
- 判断输入缓冲是否准备好,如果准备好,读一块输入到缓存中
- 判断输出缓冲是否准备好,如果准备好,拷贝缓存内容到输出
1、创建和配置MediaCodec对象
//配置格式,长度,宽度等参数
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
//解码器的创建和配置
MediaCodec mEncoder;
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//输入的数据来源
Surface mInputSurface = mEncoder.createInputSurface();
//解码开始
mEncoder.start();
3、释放对象,为了记住这一点,可以先写好,后面再写循环体
//配置需要释放么?不需要,自己再想想
//解码器,需要停下来,然后释放
if (mEncoder != null) {
mEncoder.stop();
mEncoder.release();
mEncoder = null;
}
2、中间的解码和循环
//解码所需的缓存
MediaCodec.BufferInfo mBufferInfo;
ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
//循环一直讲视频播放完毕
while (true) {
//解码
int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
//这个地方忽略异常处理,调整位置
if (mBufferInfo.size != 0) {
encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
}
//将解码好的数据扔到窗体上
mEncoder.releaseOutputBuffer(encoderStatus, false);
//确认是文件结束,退出循环
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
什么?MediaCodec的硬解码就这么简单,对,就这么简单,这就是核心技术,O(∩_∩)O哈哈~
我们看看谷歌官方的代码,复习一下,拾遗补漏
第一步
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
第三步,呵呵
codec.stop();
codec.release()
第二步:
//一样是个循环,中文注释是我加的,英语都是谷歌的
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
}
}
官方即使是同步模式,还有两种处理方式,而且不能混用哦!
1、使用缓存的同步处理方式(Synchronous Processing using Buffers)
2、使用缓存数组的同步处理方式(Synchronous Processing using Buffer Arrays)
就到这来吧,今天先写到这里