H.264是比较多开发者使用较多的一种数字视频压缩格式,主要用于直播流的传输与视频网站的视频流传输,也有很多开发者开始使用H.265进行视频压缩,性能较H.264提高较大。本篇文章着重介绍使用MediaCodec硬件H.264裸字节流数据的实现方式,有关于更多H.264的介绍能够查看参考文章中H.264的结构介绍。html
MediaCodec类Android提供的用于访问低层多媒体编/解码器接口,它是Android低层多媒体架构的一部分,一般与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编解码诸如H.26四、H.26五、AAC、3gp等常见的音视频格式。java
Android 底层多媒体模块采用的是 OpenMax 框架,任何 Android 底层编解码模块的实现,都必须遵循 OpenMax 标准。Google 官方默认提供了一系列的软件编解码器:包括:OMX.google.h264.encoder,OMX.google.h264.encoder, OMX.google.aac.encoder, OMX.google.aac.decoder 等等,而硬件编解码功能,则须要由芯片厂商依照 OpenMax 框架标准来完成,因此,通常采用不一样芯片型号的手机,硬件编解码的实现和性能是不一样的。android
Android 应用层统一由 MediaCodec API 来提供各类音视频编解码功能,由参数配置来决定采用何种编解码算法、是否采用硬件编解码加速等。git
编解码器处理输入数据并产生输出数据,MediaCodec 使用输入输出缓存,异步处理数据。简要地说,通常的处理步骤以下github
MediaCodec能够处理具体的视频流,主要有这几个方法:算法
初始化MediaCodec数组
/** * 视频类型 */
private final static String MIME_TYPE = "video/avc";
/** * 初始化播放 */
private void initVideo(SurfaceHolder holder) {
try {
// 初始化MediaCodec,方法有两种,分别是经过名称和类型来建立
// 这里使用经过类型来建立
mMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
// 获取视频的宽高
mVideoHeight = holder.getSurfaceFrame().width();
mVideoWidth = holder.getSurfaceFrame().height();
// MediaFormat,这个类包含了比特率、帧率、关键帧间隔时间等,其中比特率若是过低就会形成相似马赛克的现象。
mMediaFormat = MediaFormat.createVideoFormat(MIME_TYPE,
1080, 1920);
// 设置比特率
mMediaFormat.setInteger(KEY_BIT_RATE,
mVideoHeight * mVideoWidth * 5);
// 设置帧率
mMediaFormat.setInteger(KEY_FRAME_RATE, 30);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 描述编码器要使用的所需比特率模式的键
// BITRATE_MODE_CQ: 表示彻底不控制码率,尽最大可能保证图像质量
//BITRATE_MODE_CBR: 表示编码器会尽可能把输出码率控制为设定值
//BITRATE_MODE_VBR: 表示编码器会根据图像内容的复杂度(其实是帧间变化量的大小)来动态调整输出码率,图像复杂则码率高,图像简单则码率低;
mMediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
}
mMediaFormat.setInteger(KEY_I_FRAME_INTERVAL, 1);
byte[] headerSps = {0, 0, 0, 1, 103, 66, 0, 41, -115, -115, 64, 80,
30, -48, 15, 8, -124, 83, -128};
byte[] headerPps = {0, 0, 0, 1, 104, -54, 67, -56};
mMediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(headerSps));
mMediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(headerPps));
mMediaCodec.configure(mMediaFormat, holder.getSurface(), null, 0);
mMediaCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
视频解码部分代码缓存
将接收到或从文件读取到的byte[]传入onFrame中架构
/** * 解码数据并显示视频 * buf 视频数据组 * offset 数据偏移量 * length 有效长度 */
private void onFrame(byte[] buf, int offset, int length) {
try {
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(buf, offset, length);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, length, mCount
* 30, 0);
mCount++;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
mMediaCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (!isPlayingSound) {
mHandler.postDelayed(() -> isPlayingSound = true, 1000);
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
复制代码
使用与原理能够浏览参考文章3。 具体代码实现方式可参考此类app