在本章开始之前我们先回顾一下什么是音视频软编码和硬编码。
软编码:使用CPU进行编码
硬编码:使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等
一般对于同一平台和硬件环境,硬编硬解的速度是快于软件编解码的。而且硬编码可以有效降低CPU占用率,所以在硬件支持的情况下,硬件编解码是我们的首选。
在Android 4.1以前,Android并没有提供硬编硬解的API,所以之前开发者都是使用FFMpeg来做视频软件编解码的,目前FFMpeg在Android的编解码上依旧广泛应用。Android 4.3之后增加了MediaCodec类用于进行硬件编解码的类,可以用于音频和视频编码。
MediaCodec类Android提供的用于访问低层多媒体编/解码器接口,它是Android低层多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用。我们可以简单的理解为它们共同组成了一个环形的传送带,客户端向获取到的编解码器输入缓存区写入要编解码的数据并将其提交给编解码器,待编解码器处理完毕后将其转存到编码器的输出缓存区,同时收回客户端对输入缓存区的所有权;然后,客户端从获取到编解码输出缓存区读取编码好的数据进行处理,待处理完毕后编解码器收回客户端对输出缓存区的所有权。不断重复整个过程,直至编码器停止工作或者异常退出。
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组 queueInputBuffer:输入流入队列 dequeueInputBuffer:从输入流队列中取数据进行编码操作 getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组 dequeueOutputBuffer:从输出队列中取出编码操作之后的数据 releaseOutputBuffer:处理完成,释放ByteBuffer数据
private void startMediaCodec() throws IOException {
mMediaCodec = MediaCodec.createByCodecName(mCodecInfo.getName());
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
}
MediaCodec的的configure方法:
public void configure(
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags) {
configure(format, surface, crypto, null, flags);
}
MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。
Surface surface:指定surface,一般用于解码器,设置后解码的内容会被渲染到所指定的surface上。无需要则传null
MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
前三个基本都是固定的,我们主要说一下MediaFormat,它主要用于设置编码的实体,它包含两个分别用于设置音频编码实体和视频编码实体方法:createVideoFormat/createAudioFormat
以音频为例:
MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
以视频为例:
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, this.mWidth, this.mHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
MediaFormat 可以为编码器设置一些特性参数,比如帧率,gop(关键帧 帧间 间隔)等等。
MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。
以音频为例:
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
以视频为例:
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据dequeueOutputBuffer,使用完输出buffer的数据之后,将其释放回编解码器。
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
创建混合器实例
MediaMuxer mediaMuxer = new MediaMuxer(filePath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
在混合之前需要通过编码器获取一个音轨视频轨的索引
以音频为例:
mediaMuxerRunnable.addTrackIndex(MediaMuxerThread.TRACK_AUDIO, format);
以视频为例:
mediaMuxer.addTrackIndex(MediaMuxerThread.TRACK_VIDEO, newFormat);
然后每次从编码器中取出分别音频和视频录制到的ByteBuffer,写入(writeSampleData)到编码器所在的轨道中
MuxerData data = muxerDatas.remove(0);
int track;
if (data.trackIndex == TRACK\_VIDEO) {
track = videoTrackIndex;
} else {
track = audioTrackIndex;
}
Log.e(TAG, "写入混合数据 " + data.bufferInfo.size);
try {
mediaMuxer.writeSampleData(track, data.byteBuf,
data.bufferInfo);
} catch (Exception e) {
Log.e(TAG, "写入混合数据失败!" + e.toString());
}
循环上述输入buffer,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码器,直到编码完成。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。