目录
前言
获取到音视频轨道(编解码格式),知道设备支持哪些编解码器,下一步就是创建编解码器去实现数据流的编解码过程了。在Android
开发中提供了实现音视频编解码工具MediaCodec
,针对对应音视频解码类型通过该类创建对应解码器就能实现对数据进行解码操作。
MediaCodec
MediaCodec
所支持的数据类型:压缩的音视频数据、原始音频数据和原始视频数据。 首先show代码,紧接着之前MediaExtactor
提取资源,MediaCodecList
遍历支持格式,确认设置支持该资源格式后通过MediaCodec
创建解码器(这里是做视频解码播放)。
// 加载资源 extractor.setDataSource(path); // 获取视频轨道 int trackIndex = getTrackIndex(extractor,"video/"); // 获取视频轨道参数 MediaFormatInfo mediaFormatInfo = MediaFormatInfo.buildUpVideoMediaFormatInfo(extractor.getTrackFormat(trackIndex)); // 选取上述的视频轨道 extractor.selectTrack(trackIndex); MediaCodecInfo mediaCodecInfo = CodecInfoInstance.getInstance().selectDeCodec(mediaFormatInfo.getMime()); // 判断设备是否支持该视频解码,创建视频编码器 if(mediaCodecInfo != null){ mediaCodec = MediaCodec.createDecoderByType(mediaFormatInfo.getMime()); mediaCodec.configure(mediaFormatInfo.getMediaFormat(),surface,null,0); }
编解码流程
MediaCodec
的功能其实很简单通过一个数据缓冲去,将数据填充到输入缓冲区给到Codec
Codec
通过异步方式处理输入缓冲区数据将处理好数据填充到输出缓冲区- 客户端从输出缓冲区获取到处理好的数据去消费,最后把缓冲区返还给
Codec
部分代码实现
- dequeueInputBuffer:从输入流队列取数据进行编码操作
- getInputBuffers: 获取需要编码数据的输入队列 返回ByteBuffer数组
- queueInoutBuffer: 输入流入队列
- dequeueOutputBuffer: 从输入队列中取出编码操作结果数据
- getOutPutBuffer: 获取编解码之后数据输出队列 返回一个ByterBuffer数组
- releaseOutPutBuffer: 处理完成释放ByterBuffer数组
while (!isEnd && !isInterrupted()){ if (!mIsEOS) { mIsEOS = dequeueInputBuffers(); } isEnd = dequeueOutputBuffers(); } //输入缓冲区 private boolean dequeueInputBuffers() { boolean isMediaEOS = false; // 等待编码器输入缓冲区数据出队 int inputBufferId = mediaCodec.dequeueInputBuffer(TIMEOUT_US); if (inputBufferId >= 0) { // 获取缓存区数据 ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId); // 读取数据 返回值sampleSize大于0表示还有数据,否则表示结束 int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { // 数据末尾 必须再次调用queueInputBuffer使用BUFFER_FLAG_END_OF_STREAM标识符输入到编码器 mediaCodec.queueInputBuffer(inputBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isMediaEOS = true; MediaLogUtils.printI(TAG + "end of stream"); } else { // 输入缓冲区数据入队 mediaCodec.queueInputBuffer(inputBufferId, 0, sampleSize, extractor.getSampleTime(), 0); extractor.advance(); } } return isMediaEOS; } //输出缓冲区 private synchronized boolean dequeueOutputBuffers() { MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo(); //输出缓冲区 int outputBufferId = mediaCodec.dequeueOutputBuffer(outBufferInfo, TIMEOUT_US); switch (outputBufferId) { case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: MediaLogUtils.printI(TAG+"INFO_OUTPUT_FORMAT_CHANGED"); break; case MediaCodec.INFO_TRY_AGAIN_LATER: MediaLogUtils.printI(TAG+ "INFO_TRY_AGAIN_LATER"); break; case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: MediaLogUtils.printI(TAG+ "INFO_OUTPUT_BUFFERS_CHANGED"); break; default: // 延迟解码 decodeDelay(outBufferInfo, mStartMs); // 释放输出缓冲区数据 render为true渲染到surface上 mediaCodec.releaseOutputBuffer(outputBufferId, true); break; } // 结尾 if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { MediaLogUtils.printI(TAG+"buffer stream end"); return true; } return false; }
生命周期
MediaCodec
生命周期分为三种状态:Stopped
、Executing
、Released
- Stopped具有三种子状态:Uninitialized、Configured、Error
- Executing具有三种子状态:Flushed、Running、End-of-Stream
Stopped
Uninitialized: Uninitialized
是MediaCodec
创建后初始状态。可通过reset()
复位到Uninitialized
Configured: MediaCodec
通过configure
方法设置配置(编解码器类型等)进入到Configured
状态。
Error: MediaCodec
发生异常情况下会进入Error
状态。
Executing
Flush:MediaCodec
调用start
方法后进入Flushed
,MediaCodec
就具备所有缓存能力。若可以在Executing
,调用flush()
回到Flushed
状态。
Running: 当第一次InputBuffer
输入缓存被移除队列,MediaCodec
就会进入到Running
状态。
End-of-Stream:将end-of-stream
标记输入InputBuffer
队列,MediaCodec
就会进入到 End-of-Stream
状态,MediaCodec
就不再接收InputBuffer
,但不影响输出队列OutBuffer
产出直到end-of-stream
标记输出为止(输入和输出中间是有一定处理时间)。
接口简介
createDecoderByType/createEncoderByType,创建解码器/编码器对象
createByCodecName,根据编解码器名称创建
configure,配置编解码器配置
start,配置完成后需要执行start完成配置
dequeueInputBuffer, 输入队列取数据编码操作
queueInputBuffer,输入入队列
dequeueOutputBuffer,从输出队列取出编码操作后的数据
releaseOutputBuffer,释放输出队列释放ByteBuffer数据
getInputBuffers,获取需要编码数据输入流队列,返回ByteBuffer数组
getOutputBuffers,获取编解码后数据输出流队列,返回ByteBuffer数组
flush,清空输入和输出端口
stop,终止decode/encode会话
release,释放编解码器资源