目录
1.介绍MediaCodec类
2.创建MediaCodec的方式
3.MediaCodec流程
(1)配置编码参数
(2)创建编码器
(3)创建混合器
(4)开始编码
4.MediaCodec编码的工作方式
5.MediaCodec状态周期图
6.使用缓冲区的异步和同步处理
7.录制纯视频demo
1.介绍MediaCodec类
final public class MediaCodec
MediaCodec类可以访问底层媒体编解码框架(Stage或OpenMAX),即编解码组件。通常与MediaExtractor、MediaSync、MediaMuxer、Image、Surface和AudioTrack一起使用。它本身并不是Codec,它通过调用底层编解码组件获得了Codec的能力。
2.创建MediaCodec的方式
创建MediaCodec(按格式创建):
- MediaCodec createDecoderByType(String type):创建编码器。
- MediaCodec createEncoderByType(String type):创建解码器。
- type:数据解析阶段的mimeType,如”video/avc“。
创建MediaCodec(按Codec名字创建):
- MediaCodec createByCodecName(String name)。
- OMX.google.h264.decoder:软解码
- OMX.MTK.VIDEO.DECODER.AVC:硬解码
3.MediaCodec流程
(1)配置编码参数
视频类型的Mediaformat
可以通过如下代码创建视频类型Mediaformat:
MediaFormat videoFormat = MediaFormat.createVideoFormat(videoType, width, height);
videoType常用的有两种:
- MediaFormat.MIMETYPE_VIDEO_AVC(H.264)
- MediaFormat.MIMETYPE_VIDEO_HEVC(H.265)
以下配置是必须要指定的,否则会报错。
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
//色彩空间
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
//码率
format.setInteger(MediaFormat.KEY_BIT_RATE,500_000);
//帧率fps
format.setInteger(MediaFormat.KEY_FRAME_RATE,20);
//关键帧间隔
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,2);
音频类型的Mediaformat
可以通过如下代码创建音频类型Mediaformat:
MediaFormat.createAudioFormat(audioType, sampleRate, channelCount);
- audioType:常用的是MediaFormat.MIMETYPE_AUDIO_AAC;
- sampleRate:采样率
- channelCount:声道数
以下配置是必须要指定的,否则会报错。
//音频比特率(码率)
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
(2)创建编码器
配置解码器或者编码器configure函数
public void configure( @Nullable MediaFormat format,
@Nullable Surface surface,
@Nullable MediaCrypto crypto,@ConfigureFlag int flags)
函数的参数介绍:
- MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat.MediaFormat作为空的MediaFormat。
- Surface surface:指定Surface,用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如,不是视频解码器)和/或想配置解码器输出ByteBuffer,则传null。
- MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
- int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
使用示例:
-
mediaCodec=MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); mediaCodec.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start();
(3)创建混合器
创建混合器MediaMuxer函数
public MediaMuxer(@NonNull String path, @Format int format)
函数的参数介绍:
- path:输出文件的路径
- format:输出的格式
示例代码:
mMuxer=new MediaMuxer(path,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
);
mMuxer.setOrientationHint(degress);
(4)开始编码
介绍MediaCodec编码用到的四个方法:
1. dequeueInputBuffer
返回用于填充有效数据的输入buffer的索引,如果当前没有可用的buffer,则返回-1。
public final int dequeueInputBuffer(long timeoutUs)
函数的参数介绍:
- long timeoutUs:等待可用的输入buffer的时间。
- 如果timeoutUs == 0,则立即返回。
- 如果timeoutUs < 0,则无限期等待可用的输入buffer。
- 如果timeoutUs > 0,则等待“timeoutUs”微秒。
2. queueInputBuffer
在指定索引处填充输入buffer后,使用queueInputBuffer将buffer提交给组件。
public native final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
- int index:以前调用dequeueInputBuffer(long)返回的输入buffer的索引。
- int offset:数据开始时输入buffer中的字节偏移量。
- int size:有效输入数据的字节数。
- long presentationTimeUs:此buffer的PTS(以微秒为单位)。
- int flags:一个由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM标志组成的位掩码。虽然没有被禁止,但是大多数codec并不对输入buffer使用BUFFER_FLAG_KEY_FRAME标志。
3. dequeueOutputBuffer
从MediaCodec获取输出buffer。
public final int dequeueOutputBuffer(
@NonNull BufferInfo info, long timeoutUs)
- BufferInfo info:输出buffer的metadata。
- long timeoutUs:含义同dequeueInputBuffer中的timeoutUs参数。
- 返回值:已成功解码的输出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。返回INFO_TRY_AGAIN_LATER而timeoutUs指定为了非负值,表示超时了。返回INFO_OUTPUT_FORMAT_CHANGED表示输出格式已更改,后续数据将遵循新格式。
4. releaseOutputBuffer
使用此方法将输出buffer返回给codec或将其渲染在输出surface。
public void releaseOutputBuffer (int index, boolean render)
- index:以前调用dequeueOutputBuffer(long)返回的输入buffer的索引。
- boolean render:如果在配置codec时指定了一个有效的surface,则传递true会将此输出buffer在surface上渲染。一旦不再使用buffer,该surface将把buffer释放回codec。
示例代码:
public void queueEncode(byte[] buffer){
if(!isRecording){
Log.e("xxx","没开始录制");
return;
}
Log.e("xxx","开始录制");
mHandler.post(new Runnable() {
@Override
public void run() {
//立即得到有效输入缓冲区
int index=mediaCodec.dequeueInputBuffer(0);
if(index>=0){
ByteBuffer inputBuffer=mediaCodec.getInputBuffer(index);
inputBuffer.clear();
inputBuffer.put(buffer,0,buffer.length);
//填充数据后再加入队列
mediaCodec.queueInputBuffer(index,0,buffer.length,System.nanoTime()/1000,0);
}
while (true){
//获取输出缓冲区(编码后的数据从缓冲区获取)
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int encoderStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
//稍后重试
if(encoderStatus== MediaCodec.INFO_TRY_AGAIN_LATER){
break;
}else if(encoderStatus==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
//输出格式发生变化 第一次总会调用,所以在这里开启混合器
MediaFormat newFormat=mediaCodec.getOutputFormat();
videoTrack=mMuxer.addTrack(newFormat);
mMuxer.start();
}else if(encoderStatus==MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
//可以忽略
}else{
//正常则encoderStatus 获取缓冲区下标
ByteBuffer encodedData=mediaCodec.getOutputBuffer(encoderStatus);
//如果当前的buffer是配置信息,不管它,不用写出去
if((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){
bufferInfo.size=0;
}
if(bufferInfo.size!=0){
//设置从哪里开始读数据(读出来就是编码后的数据)
encodedData.position(bufferInfo.offset);
//设置能读数据的总长度
encodedData.limit(bufferInfo.offset+bufferInfo.size);
//写出为MP4
mMuxer.writeSampleData(videoTrack,encodedData,bufferInfo);
}
//释放这个缓冲区,后续可以存放新的编码后的数据
mediaCodec.releaseOutputBuffer(encoderStatus,false);
}
}
}
});
}
4.MediaCodec编码的工作方式
简单来说,可以看做是一个生产者-消费者模式,在MediaCodec里面有两个队列,一个是输入队列,一个是输出队列,数据放入输入队列,MediaCodec拿出数据进行编码,把编码完的数据放入输出队列。
5.MediaCodec状态周期图
在MediaCodec的生命周期内存在3种状态,即Stopped、Executing和Released。
Stopped状态还可处于三个状态:Uninitialized、Configures和Error。
Executing状态概念上的进展通过3个子状态进行:Flushed、Running和end-of-Stream。
MediaCodec状态周期图如下图:
6.使用缓冲区的异步和同步处理
使用缓冲区的异步处理:
在异步模式下,媒体编解码器通常像这样使用:
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is equivalent to mOutputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
}
@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Subsequent data will conform to new format.
// Can ignore if using getOutputFormat(outputBufferId)
mOutputFormat = format; // option B
}
@Override
void onError(…) {
…
}
@Override
void onCryptoError(…) {
…
}
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start();
// wait for processing to complete
codec.stop();
codec.release();
使用缓冲区的同步处理:
媒体编解码器在同步模式下通常这样使用:
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
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
}
}
codec.stop();
codec.release();
7.录制纯视频demo
功能介绍:
实现Camera1采集相机数据,设置预览画布TextureView显示采集的数据,然后MediaCode对图像数据编码,最后MediaMuxer把图像数据放入MP4盒子。
运行图:
这是我的项目代码:
(小白一个,代码只是能简单地实现功能)
https://github.com/Bookonpillow/mediacode