第一次写这样的博文,写得不好,望大家及时指出博文中的错误,以便于相互学习。
本来主要介绍如何使用mediacodec来转码音频,同时实现音频的裁剪。对于mediacodec相信大家都不陌生,没做过也听过。
转码音频需要的知识点
1、音频基础知识,什么是pcm,什么是音频格式(MP3、aac等),这里给大家推荐一篇博文,http://blog.csdn.net/kevindgk/article/details/52924779。
2、mediacodec的基础知识和api调用,mediacodec是典型的CS模式,也就是客户端-服务器模式,建议大家多看看官网api,
这里给大家推荐一篇中文版的api,MediaCodec中文api
转码流程
解码线程:FILE—解码—PCM队列
编码线程:PCM队列—编码—FILE
系统版本和相关Api类
sdk版本>=16;MediaCodec、MediaExtractor
解码线程
主要流程是通过MediaExtractor读取一帧数据,然后进行解码并放入PCM队列,流程分为prepare—decode—release;
相关代码分别如下:
主要线程里面的流程:
@Override
public void run() {
TransAacHandlerPure.logMsg("decodec run");
if (listener != null) {
listener.onStart();
}
boolean isPrepare = false;
try {
prepare();//初始化
isPrepare = true;
} catch (IOException e) {
e.printStackTrace();
}
TransAacHandlerPure.logMsg("decodec isPrepare " + isPrepare);
if (isPrepare) {
decode();//解码
}
release();//释放资源
if (!isPrepare && listener != null) {
listener.onFail();
}
isFinish = true;
}
prepare里面的代码如下:
private void prepare() throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(srcFile);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mine = format.getString(MediaFormat.KEY_MIME);
if (!TextUtils.isEmpty(mine) && mine.startsWith("audio")) {
extractor.selectTrack(i);
try {
duration = format.getInteger(MediaFormat.KEY_DURATION) / 1000;
} catch (Exception e) {
e.printStackTrace();
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(srcFile);
mediaPlayer.prepare();
duration = mediaPlayer.getDuration();
mediaPlayer.release();
}
codec = MediaCodec.createDecoderByType(mine);
codec.configure(format, null, null, 0);
codec.start();
TransAacHandlerPure.logMsg("New decode codec start:" + format.toString());
break;
}
}
// createFile(outFile + ".pcm", true);//测试 输出pcm格式
// mOutput = new DataOutputStream(new FileOutputStream(outFile + ".pcm"));
}
decode方法如下:
private void decode() {
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
TransAacHandlerPure.logMsg("loopDecode start");
if (rangeStart > 0) {//如果有裁剪,seek到裁剪的地方
extractor.seekTo(rangeStart * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
}
boolean isEOS = false;
while (true) {
long timestamp = 0;
if (!isEOS) {
int inIndex = codec.dequeueInputBuffer(TIME_OUT);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = extractor.readSampleData(buffer, 0);
long timestampTemp = extractor.getSampleTime();
timestamp = timestampTemp / 1000;
TransAacHandlerPure.logMsg("loopDecode readSampleData end sampleSize " + sampleSize + " buffer.capacity()=" + buffer.capacity());
TransAacHandlerPure.logMsg("loopDecode readSampleData end timestamp" + timestamp);
if (rangeEnd > 0 && timestamp > rangeEnd) {
sampleSize = -1;
}
if (sampleSize <= 0) {
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isEOS = true;
} else {
codec.queueInputBuffer(inIndex, 0, sampleSize, timestampTemp, 0);
extractor.advance();
}
}
}
int outIndex = codec.dequeueOutputBuffer(info, TIME_OUT);
// TransAacHandlerPure.logMsg(" switch (outIndex)");
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = codec.getOutputBuffers();
TransAacHandlerPure.logMsg("dequeueOutputBuffer INFO_OUTPUT_BUFFERS_CHANGED!");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat mf = codec.getOutputFormat();
//开始编码线程
EncodeTask encodeTask = new EncodeTask(outFile, this, listener);
int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int pcmEncoding = mf.getInteger(MediaFormat.KEY_PCM_ENCODING);
int channelCount = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
encodeTask.setAudioParams(sampleRate, pcmEncoding, channelCount);
new Thread(encodeTask).start();
TransAacHandlerPure.logMsg("New format " + mf.toString());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
TransAacHandlerPure.logMsg("dequeueOutputBuffer timed out!");
break;
default:
if (last == 0) {
last = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
TransAacHandlerPure.logMsg("解码时间:" + (now - last) + " info.size " + info.size);
last = now;
ByteBuffer buffer = outputBuffers[outIndex];
byte[] outData = new byte[info.size];
buffer.get(outData, 0, info.size);
codec.releaseOutputBuffer(outIndex, true);
try {
mOutput.write(outData);
} catch (IOException e) {
e.printStackTrace();
}
pushAvFrame(outData);
if (listener != null) {
listener.onProgress(rangeEnd > 0 ? (int) rangeEnd : duration, rangeStart > 0 ? (int) (timestamp - rangeStart) : (int) timestamp);
}
break;
}
// All decoded frames have been rendered, we can stop playing now
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
TransAacHandlerPure.logMsg("OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
release相关代码
private void release() {
if (extractor != null) {
extractor.release();
extractor = null;
}
if (codec != null) {
codec.stop();
codec.release();
codec = null;
}
}
编码线程
主要流程跟解码线程类似,需要说明的是编码线程开始是在解码返回
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED这个状态的时候,因为在这个时候可以拿到编码需要的一些必要参数,如采样率等等,虽然也可以在从MediaExtractor里面拿,但是这样可能有些机型拿不到一些数据,所以我认为在这里拿是比较合适的。闲话不多说,直接看代码:
run方法:
@Override
public void run() {
boolean isPrepare = false;
try {
prepare();
isPrepare = true;
} catch (IOException e) {
e.printStackTrace();
}
if (isPrepare && obtain != null) {
encode();
}
release();
if (listener != null) {
if (isPrepare) {
listener.onSuccess();
} else {
listener.onFail();
}
}
}
prepare方法,关于比特率的设置,大家多参考之前关于音频基础知识的那篇博文,这个直接影响到文件大小和音质
private void prepare() throws IOException {
String mime = MediaFormat.MIMETYPE_AUDIO_AAC;
encoder = MediaCodec.createEncoderByType(mime);
MediaFormat format = MediaFormat.createAudioFormat(mime, sampleRate, channelCount);
format.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
format.setInteger(MediaFormat.KEY_PCM_ENCODING, pcmEncoding);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 20 * 1024);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
logMsg(" New " + format.toString());
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
createFile(outFile, true);
mOutput = new DataOutputStream(new FileOutputStream(outFile));
}
encode方法,要注意aac写入的时候需要加入头部分,关于aac格式介绍,大家参考这篇博文- aac格式介绍
private void encode() {
boolean isFinish = false;
while (true) {
if (!isFinish) {
byte[] rawData = obtain.getRawFrame();
if (rawData == null) {
if (obtain.isFinish()) {
isFinish = true;
int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
encoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
continue;
}
ByteBuffer[] inputBuffers = encoder.getInputBuffers();
int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
if (inIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inIndex];
inputBuffer.clear();
inputBuffer.put(rawData);
encoder.queueInputBuffer(inIndex, 0, rawData.length, System.nanoTime(), 0);
}
}
ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
if (outIndex >= 0) {
if (last == 0) {
last = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
TransAacHandlerPure.logMsg("编码码时间:" + (now - last) + " info.size " + info.size);
last = now;
while (outIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outIndex];
int len = info.size + 7;
byte[] outData = new byte[len];
addADTStoPacket(outData, len);
outputBuffer.get(outData, 7, info.size);
encoder.releaseOutputBuffer(outIndex, false);
try {
mOutput.write(outData);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
}
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
VLog.d("encode OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
加入aac头方法
/**
* 给编码出的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;
}
release方法
private void release() {
if (encoder != null) {
encoder.stop();
encoder.release();
encoder = null;
}
if (mOutput != null) {
try {
mOutput.flush();
mOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
mOutput = null;
}
}
几个需要注意的地方
1、编码线程初始化的地方,获取初始化所需的必要参数
2、编解码线程之间的同步,由于解码线程一般比编码线程快,所以pcm的队列数据同步显得比较重要,当然了这个消费者模式的基础,相信大家很容易理解
3、aac文件的保存,需要添加头
纯净版MP3ToAAc(可以同时剪辑)所有代码
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 音频转换成为aac编码
*
* @author jake
* @since 2017/8/3 下午4:28
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public class TransAacHandlerPure {
private String srcFile;
private String outFile;
private long rangeStart = -1;
private long rangeEnd = -1;
private OnProgressListener listener;
public TransAacHandlerPure(String srcFile, String outFile) {
this(srcFile, outFile, null);
}
public TransAacHandlerPure(String srcFile, String outFile, OnProgressListener listener) {
this(srcFile, outFile, -1, -1, listener);
}
public TransAacHandlerPure(String srcFile, String outFile, long rangeStart, long rangeEnd, OnProgressListener listener) {
this.srcFile = srcFile;
this.outFile = outFile;
this.rangeStart = rangeStart;
this.rangeEnd = rangeEnd;
this.listener = listener;
}
public void start() {
DecodeTask task = new DecodeTask(srcFile, outFile, listener);
task.setRangeTime(rangeStart, rangeEnd);
new Thread(task).start();
}
public void setRangeTime(long rangeStart, long rangeEnd) {
this.rangeStart = rangeStart;
this.rangeEnd = rangeEnd;
}
public void setListener(OnProgressListener listener) {
this.listener = listener;
}
private static class DecodeTask implements Runnable, IDataObtain {
private static final long TIME_OUT = 5000;
private Queue<byte[]> mRawQueue;
private MediaExtractor extractor;
private boolean isFinish = false;
private String srcFile;
private MediaCodec codec;
private String outFile;
private OnProgressListener listener;
private long rangeStart;
private long rangeEnd;
private int duration = 0;
private OutputStream mOutput;
public void setRangeTime(long rangeStart, long rangeEnd) {
this.rangeStart = rangeStart;
this.rangeEnd = rangeEnd;
}
public DecodeTask(String srcFile, String outFile, OnProgressListener listener) {
this.srcFile = srcFile;
this.outFile = outFile;
this.listener = listener;
mRawQueue = new LinkedBlockingQueue<>();
}
private void pushAvFrame(byte[] frame) {
if (frame != null) {
int len = mRawQueue.size();
while (len > 10) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
len = mRawQueue.size();
}
synchronized (mRawQueue) {
mRawQueue.offer(frame);
}
}
}
@Override
public void run() {
TransAacHandlerPure.logMsg("decodec run");
if (listener != null) {
listener.onStart();
}
boolean isPrepare = false;
try {
prepare();
isPrepare = true;
} catch (IOException e) {
e.printStackTrace();
}
TransAacHandlerPure.logMsg("decodec isPrepare " + isPrepare);
if (isPrepare) {
decode();
}
release();
if (!isPrepare && listener != null) {
listener.onFail();
}
isFinish = true;
}
private void release() {
if (extractor != null) {
extractor.release();
extractor = null;
}
if (codec != null) {
codec.stop();
codec.release();
codec = null;
}
}
private void prepare() throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(srcFile);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mine = format.getString(MediaFormat.KEY_MIME);
if (!TextUtils.isEmpty(mine) && mine.startsWith("audio")) {
extractor.selectTrack(i);
try {
duration = format.getInteger(MediaFormat.KEY_DURATION) / 1000;
} catch (Exception e) {
e.printStackTrace();
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(srcFile);
mediaPlayer.prepare();
duration = mediaPlayer.getDuration();
mediaPlayer.release();
}
codec = MediaCodec.createDecoderByType(mine);
codec.configure(format, null, null, 0);
codec.start();
TransAacHandlerPure.logMsg("New decode codec start:" + format.toString());
break;
}
}
createFile(outFile + ".pcm", true);//测试 输出pcm格式
mOutput = new DataOutputStream(new FileOutputStream(outFile + ".pcm"));
}
long last;
private void decode() {
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
TransAacHandlerPure.logMsg("loopDecode start");
if (rangeStart > 0) {//如果有裁剪,seek到裁剪的地方
extractor.seekTo(rangeStart * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
}
boolean isEOS = false;
while (true) {
long timestamp = 0;
if (!isEOS) {
int inIndex = codec.dequeueInputBuffer(TIME_OUT);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = extractor.readSampleData(buffer, 0);
long timestampTemp = extractor.getSampleTime();
timestamp = timestampTemp / 1000;
TransAacHandlerPure.logMsg("loopDecode readSampleData end sampleSize " + sampleSize + " buffer.capacity()=" + buffer.capacity());
TransAacHandlerPure.logMsg("loopDecode readSampleData end timestamp" + timestamp);
if (rangeEnd > 0 && timestamp > rangeEnd) {
sampleSize = -1;
}
if (sampleSize <= 0) {
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isEOS = true;
} else {
codec.queueInputBuffer(inIndex, 0, sampleSize, timestampTemp, 0);
extractor.advance();
}
}
}
int outIndex = codec.dequeueOutputBuffer(info, TIME_OUT);
// TransAacHandlerPure.logMsg(" switch (outIndex)");
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = codec.getOutputBuffers();
TransAacHandlerPure.logMsg("dequeueOutputBuffer INFO_OUTPUT_BUFFERS_CHANGED!");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat mf = codec.getOutputFormat();
//开始编码线程
EncodeTask encodeTask = new EncodeTask(outFile, this, listener);
int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int pcmEncoding = mf.getInteger(MediaFormat.KEY_PCM_ENCODING);
int channelCount = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
encodeTask.setAudioParams(sampleRate, pcmEncoding, channelCount);
new Thread(encodeTask).start();
TransAacHandlerPure.logMsg("New format " + mf.toString());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
TransAacHandlerPure.logMsg("dequeueOutputBuffer timed out!");
break;
default:
if (last == 0) {
last = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
TransAacHandlerPure.logMsg("解码时间:" + (now - last) + " info.size " + info.size);
last = now;
ByteBuffer buffer = outputBuffers[outIndex];
byte[] outData = new byte[info.size];
buffer.get(outData, 0, info.size);
codec.releaseOutputBuffer(outIndex, true);
try {
mOutput.write(outData);
} catch (IOException e) {
e.printStackTrace();
}
pushAvFrame(outData);
if (listener != null) {
listener.onProgress(rangeEnd > 0 ? (int) rangeEnd : duration, rangeStart > 0 ? (int) (timestamp - rangeStart) : (int) timestamp);
}
break;
}
// All decoded frames have been rendered, we can stop playing now
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
TransAacHandlerPure.logMsg("OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
@Override
public byte[] getRawFrame() {
int len = mRawQueue.size();
if (len > 0) {
synchronized (mRawQueue) {
return mRawQueue.poll();
}
}
return null;
}
@Override
public boolean isFinish() {
return isFinish;
}
}
private static void logMsg(String msg) {
Log.d(TransAacHandlerPure.class.getSimpleName(), msg);
}
private static class EncodeTask implements Runnable {
private static final long TIME_OUT = 5000;
private IDataObtain obtain;
private String outFile;
private MediaCodec encoder;
private OutputStream mOutput;
private OnProgressListener listener;
private long last;
private int sampleRate;
private int pcmEncoding;
private int channelCount;
public EncodeTask(String outFile, IDataObtain obtain, OnProgressListener listener) {
this.obtain = obtain;
this.outFile = outFile;
this.listener = listener;
}
public void setAudioParams(int sampleRate, int pcmEncoding, int channelCount) {
this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding;
this.channelCount = channelCount;
}
@Override
public void run() {
boolean isPrepare = false;
try {
prepare();
isPrepare = true;
} catch (IOException e) {
e.printStackTrace();
}
if (isPrepare && obtain != null) {
encode();
}
release();
if (listener != null) {
if (isPrepare) {
listener.onSuccess();
} else {
listener.onFail();
}
}
}
private void release() {
if (encoder != null) {
encoder.stop();
encoder.release();
encoder = null;
}
if (mOutput != null) {
try {
mOutput.flush();
mOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
mOutput = null;
}
}
private void encode() {
boolean isFinish = false;
while (true) {
if (!isFinish) {
byte[] rawData = obtain.getRawFrame();
if (rawData == null) {
if (obtain.isFinish()) {
isFinish = true;
int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
encoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
continue;
}
ByteBuffer[] inputBuffers = encoder.getInputBuffers();
int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
if (inIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inIndex];
inputBuffer.clear();
inputBuffer.put(rawData);
encoder.queueInputBuffer(inIndex, 0, rawData.length, System.nanoTime(), 0);
}
}
ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
if (outIndex >= 0) {
if (last == 0) {
last = System.currentTimeMillis();
}
long now = System.currentTimeMillis();
TransAacHandlerPure.logMsg("编码码时间:" + (now - last) + " info.size " + info.size);
last = now;
while (outIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outIndex];
int len = info.size + 7;
byte[] outData = new byte[len];
addADTStoPacket(outData, len);
outputBuffer.get(outData, 7, info.size);
encoder.releaseOutputBuffer(outIndex, false);
try {
mOutput.write(outData);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
}
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
VLog.d("encode OutputBuffer BUFFER_FLAG_END_OF_STREAM");
break;
}
}
}
/**
* 给编码出的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;
}
private void prepare() throws IOException {
String mime = MediaFormat.MIMETYPE_AUDIO_AAC;
encoder = MediaCodec.createEncoderByType(mime);
MediaFormat format = MediaFormat.createAudioFormat(mime, sampleRate, channelCount);
format.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
format.setInteger(MediaFormat.KEY_PCM_ENCODING, pcmEncoding);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 20 * 1024);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
logMsg(" New " + format.toString());
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
createFile(outFile, true);
mOutput = new DataOutputStream(new FileOutputStream(outFile));
}
}
private static boolean createFile(String filePath, boolean recreate) {
if (TextUtils.isEmpty(filePath)) {
return false;
}
try {
File file = new File(filePath);
if (file.exists()) {
if (recreate) {
file.delete();
file.createNewFile();
}
} else {
// 如果路径不存在,先创建路径
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
file.createNewFile();
}
} catch (Exception e) {
return false;
}
return true;
}
public interface IDataObtain {
byte[] getRawFrame();
boolean isFinish();
}
public static interface OnProgressListener {
void onStart();
void onProgress(int max, int progress);
void onSuccess();
void onFail();
}
}
关于音频裁剪比较简单,如果相同格式不需要转码,直接用MediaExtractor读取相关片段后直接写入即可,这样效率比较高。关于这个转码算法的效率还是比较低,主要原因是编码比较慢,虽然多线程解决了部分问题,但是效率还是比较慢,大家有兴趣可以提出修改意见,咱们一起优化一下这个算法!
由于准备匆忙,很多知识点都没有跟大家说清楚,望大家见谅!希望代码写得不是很乱,大家能看得懂,有不懂的地方,欢迎跟我沟通!