上一篇写到PCM格式文件编码成AAC格式文件,这一步的原因是有利于传输。可以将PCM文件做了很大的压缩力度,使得包变得更小,便于传输。我使用播放器播放了AAC文件听到的是音速明显变快了,声音也变得尖锐了许多,AAC文件变小了很多。
本文主要是讲 AAC文件解码成PCM文件。接收到AAC文件以后,需要还原成PCM文件再播放。
需要用到的类:
MediaCodec :编解码。上一文简单介绍了它的工作过程,有兴趣请出门左转。
MediaExtractor :主要是用于分离各种轨道。
(这里有必要说明一下,比如说MP4文件,叫它MP4格式并不是很正确,它就像是一个标准,一个容器。因为一个MP4文件包含了许多信息,比如音频,视频,字幕等咱们常见的,而音频,视频等在MP4文件中是以轨道【track】形式存在的)。
ByteBuffer : 这个类在多媒体开发中经常用到。
mMediaExtractor.setDataSource();
支持本地文件(文件描述),网络文件。需要主要的是,设置的资源必须是可用的,不然程序会报错。
从MediaExtractor中获取音频轨道的MediaFormat:
mMediaDecode.configure(mediaFormat, null, null, 0);当解压的时候最后一个参数为0
(3)解码操作
这部分的操作完全是遵循MediaCodec的操作原理实现的:获取输入缓存,将数据存储到输入缓存中,将输入缓存放回MediaCodec中,获取输出缓存,处理输出缓存的数据。
站在巨人的肩膀上: http://yedaxia.me/Android-MediaExtractor-And-MediaCodec/
本文主要是讲 AAC文件解码成PCM文件。接收到AAC文件以后,需要还原成PCM文件再播放。
需要用到的类:
MediaCodec :编解码。上一文简单介绍了它的工作过程,有兴趣请出门左转。
MediaExtractor :主要是用于分离各种轨道。
(这里有必要说明一下,比如说MP4文件,叫它MP4格式并不是很正确,它就像是一个标准,一个容器。因为一个MP4文件包含了许多信息,比如音频,视频,字幕等咱们常见的,而音频,视频等在MP4文件中是以轨道【track】形式存在的)。
ByteBuffer : 这个类在多媒体开发中经常用到。
直接上代码:
(1)准备各种需要的类:
//用于分离出音频轨道
private MediaExtractor mMediaExtractor;
private MediaCodec mMediaDecode;
private File targetFile;
//类型
private String mime = "audio/mp4a-latm";
//输入缓存组
private ByteBuffer[] inputBuffers;
//输出缓存组
private ByteBuffer[] outputBuffers;
private MediaCodec.BufferInfo bufferInfo;
private File pcmFile;
private FileOutputStream fileOutputStream;
private int totalSize = 0;
(2)初始化
File root = Environment.getExternalStorageDirectory();
targetFile = new File(root, "生成的aac.aac");
pcmFile = new File(root, "解码的pcm.pcm");
if (!pcmFile.exists()) {
try {
pcmFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
fileOutputStream = new FileOutputStream(pcmFile.getAbsoluteFile());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
mMediaExtractor = new MediaExtractor();
try {
//设置资源
mMediaExtractor.setDataSource(targetFile.getAbsolutePath());
//获取含有音频的MediaFormat
MediaFormat mediaFormat = createMediaFormat();
mMediaDecode = MediaCodec.createDecoderByType(mime);
mMediaDecode.configure(mediaFormat, null, null, 0);//当解压的时候最后一个参数为0
mMediaDecode.start();//开始,进入runnable状态
//只有MediaCodec进入到Runnable状态后,才能过去缓存组
inputBuffers = mMediaDecode.getInputBuffers();
outputBuffers = mMediaDecode.getOutputBuffers();
bufferInfo = new MediaCodec.BufferInfo();
} catch (IOException e) {
Log.e("tag_ioException",e.getMessage()+"");
e.printStackTrace();
}
设置文件资源:
mMediaExtractor.setDataSource();
支持本地文件(文件描述),网络文件。需要主要的是,设置的资源必须是可用的,不然程序会报错。
从MediaExtractor中获取音频轨道的MediaFormat:
private MediaFormat createMediaFormat() {
//获取文件的轨道数,做循环得到含有音频的mediaFormat
for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {
MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(i);
//MediaFormat键值对应
String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.contains("audio/")) {
mMediaExtractor.selectTrack(i);
return mediaFormat;
}
}
return null;
}
mMediaDecode.configure(mediaFormat, null, null, 0);当解压的时候最后一个参数为0
(3)解码操作
public void decode() {
boolean inputSawEos = false;
boolean outputSawEos = false;
long kTimes = 5000;//循环时间
while (!outputSawEos) {
if (!inputSawEos) {
//每5000毫秒查询一次
int inputBufferIndex = mMediaDecode.dequeueInputBuffer(kTimes);
//输入缓存index可用
if (inputBufferIndex >= 0) {
//获取可用的输入缓存
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
//从MediaExtractor读取数据到输入缓存中,返回读取长度
int bufferSize = mMediaExtractor.readSampleData(inputBuffer, 0);
if (bufferSize <= 0) {//已经读取完
//标志输入完毕
inputSawEos = true;
//做标识
mMediaDecode.queueInputBuffer(inputBufferIndex, 0, 0, kTimes, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
long time = mMediaExtractor.getSampleTime();
//将输入缓存放入MediaCodec中
mMediaDecode.queueInputBuffer(inputBufferIndex, 0, bufferSize, time, 0);
//指向下一帧
mMediaExtractor.advance();
}
}
}
//获取输出缓存,需要传入MediaCodec.BufferInfo 用于存储ByteBuffer信息
int outputBufferIndex = mMediaDecode.dequeueOutputBuffer(bufferInfo, kTimes);
if (outputBufferIndex >= 0) {
int id = outputBufferIndex;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
mMediaDecode.releaseOutputBuffer(id, false);
continue;
}
//有输出数据
if (bufferInfo.size > 0) {
//获取输出缓存
ByteBuffer outputBuffer = outputBuffers[id];
//设置ByteBuffer的position位置
outputBuffer.position(bufferInfo.offset);
//设置ByteBuffer访问的结点
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
byte[] targetData = new byte[bufferInfo.size];
//将数据填充到数组中
outputBuffer.get(targetData);
try {
fileOutputStream.write(targetData);
} catch (IOException e) {
e.printStackTrace();
}
}
//释放输出缓存
mMediaDecode.releaseOutputBuffer(id, false);
//判断缓存是否完结
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
outputSawEos = true;
}
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = mMediaDecode.getOutputBuffers();
}else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
MediaFormat mediaFormat = mMediaDecode.getOutputFormat();
}
}
//释放资源
try {
fileOutputStream.flush();
fileOutputStream.close();
mMediaDecode.stop();
mMediaDecode.release();
mMediaExtractor.release();
} catch (IOException e) {
e.printStackTrace();
}
}
这部分的操作完全是遵循MediaCodec的操作原理实现的:获取输入缓存,将数据存储到输入缓存中,将输入缓存放回MediaCodec中,获取输出缓存,处理输出缓存的数据。
站在巨人的肩膀上: http://yedaxia.me/Android-MediaExtractor-And-MediaCodec/