在Android4.1以后google就提供了MediaCodec这个类来为用户提供音视频的编码解码功能(虽然支持的格式不是很多)。
对于MediaCodec类,我们需要大致介绍一下:
官网提供的概述:
In broad terms, a codec processes input data to generate output data. It processes data asynchronously and uses a set of input and output buffers. At a simplistic level, you request (or receive) an empty input buffer, fill it up with data and send it to the codec for processing. The codec uses up the data and transforms it into one of its empty output buffers. Finally, you request (or receive) a filled output buffer, consume its contents and release it back to the codec.
大致的意思是:MediaCodec处理输入数据然后产生输出数据,它异步处理数据,使用了一组输入和输入缓存。你请求到了一个空的输入缓存,将数据填满后,发送给编解码器进行处理。编解码器处理完后,将处理的结果输出到一个空的输出缓存中,我们能请求到这个输出缓存,并将缓存的数据使用。
MediaCode可以处理的数据类型:(1)压缩后的数据(2)原始音频数据(3)原始视频数据
再来了解一下它的状态:
从这个图中我们可以知道:MediaCodec有三种状态(1)Stopped 停止(2)Released释放(3)Executing执行
(1)Stopped停止状态:Error,Uninitialized,configured
(2)Released释放
(3)Executing执行状态:Flushed,Running,End Of Stream
当创建一个编解码器的时候就处于未初始化状态(Uninitialized),通过configured()配置状态,然后调用start()就到了执行状态了。
当start()后,编解码器就进入了Flushed状态,等待着输入缓存入队列中。当有输入缓存入队列后,编解码器就进入了Running状态。这个状态占据了大部分的时间,直到标有end - of - stream的缓存入队列后,编解码器就进入了End Of Stream状态。这个时候编解码器就不再接收输入缓存了,但它还是一直输出缓存,直到输出标有end - of - stream的缓存后,
当调用stop()方法编解码器就进入了Uninitialized状态,因此编解码器可以再次配置,使用完MediaCodec后,必须要执行release()方法来释放它。
MediaCodec介绍就到这里,需要了解更加详细的可以 猛戳这里。
现在就代码实现:
1.声明
private MediaCodec mMediaCodec;
private MediaCodec.BufferInfo mBufferInfo;
private final String mime = "audio/mp4a-latm";
private int bitRate = 96000;
private ByteBuffer[] inputBufferArray;
private ByteBuffer[] outputBufferArray;
private FileOutputStream fileOutputStream;
2.实例化
从Configured() ——> start()
try {
File root = Environment.getExternalStorageDirectory();
File fileAAc = new File(root,"生成的aac.aac");
if(!fileAAc.exists()){
fileAAc.createNewFile();
}
fileOutputStream = new FileOutputStream(fileAAc.getAbsoluteFile());
mMediaCodec = MediaCodec.createEncoderByType(mime);
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, mime);
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024 * 1024);
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
/*
第四个参数 编码的时候是MediaCodec.CONFIGURE_FLAG_ENCODE
解码的时候是0
*/
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//start()后进入执行状态,才能做后续的操作
mMediaCodec.start();
//获取输入缓存,输出缓存
inputBufferArray = mMediaCodec.getInputBuffers();
outputBufferArray = mMediaCodec.getOutputBuffers();
mBufferInfo = new MediaCodec.BufferInfo();
} catch (IOException e) {
e.printStackTrace();
}
3.编码
public void encodeData(byte[] data){
//dequeueInputBuffer(time)需要传入一个时间值,-1表示一直等待,0表示不等待有可能会丢帧,其他表示等待多少毫秒
int inputIndex = mMediaCodec.dequeueInputBuffer(-1);//获取输入缓存的index
if (inputIndex >= 0) {
ByteBuffer inputByteBuf = inputBufferArray[inputIndex];
inputByteBuf.clear();
inputByteBuf.put(data);//添加数据
inputByteBuf.limit(data.length);//限制ByteBuffer的访问长度
mMediaCodec.queueInputBuffer(inputIndex, 0, data.length, 0, 0);//把输入缓存塞回去给MediaCodec
}
int outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);//获取输出缓存的index
while (outputIndex >= 0) {
//获取缓存信息的长度
int byteBufSize = mBufferInfo.size;
//添加ADTS头部后的长度
int bytePacketSize = byteBufSize + 7;
ByteBuffer outPutBuf = outputBufferArray[outputIndex];
outPutBuf.position(mBufferInfo.offset);
outPutBuf.limit(mBufferInfo.offset+mBufferInfo.size);
byte[] targetByte = new byte[bytePacketSize];
//添加ADTS头部
addADTStoPacket(targetByte,bytePacketSize);
/*
get(byte[] dst,int offset,int length):ByteBuffer从position位置开始读,读取length个byte,并写入dst下
标从offset到offset + length的区域
*/
outPutBuf.get(targetByte,7,byteBufSize);
outPutBuf.position(mBufferInfo.offset);
try {
fileOutputStream.write(targetByte);
} catch (IOException e) {
e.printStackTrace();
}
//释放
mMediaCodec.releaseOutputBuffer(outputIndex,false);
outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo,0);
}
}
/**
* 给编码出的aac裸流添加adts头字段
*
* @param packet 要空出前7个字节,否则会搞乱数据
* @param packetLen
*/
private void addADTStoPacket(byte[] packet, int packetLen) {
int profile = 2; //AAC LC
int freqIdx = 4; //44.1KHz
int chanCfg = 2; //CPE
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
ByteBuffer这个类的方法使用得很多,要是不清楚的可以查查相关文档
我需要极其简单的介绍一下 AAC格式(目前还没有学过AAC格式)
AAC的音频文件格式有ADIF和ADTS:
ADIF:音频数据交换格式。这种格式的特点是,它只有一个统一的文件头,其余的都是音频数据。
ADTS:音频数据传输流。它是一个有同步字的比特流。每一帧都有头信息。
简单来说:ADIF不能随意解码,之后确定得到所有的数据以后才能解码,因为它只有一个头文件。
ADTS可以任意解码,因为每一帧都有一个头文件。
下一篇将代码 使用MediaCodec实现AAC文件格式解码(解压)成PCM格式的数据。