android 使用MediaCodec(根据设备状况硬编解码)来转码音频(MP3 to aac),并同时裁剪音频

Published on with 0 views and 0 comments

本来主要介绍如何使用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读取相关片段后直接写入即可,这样效率比较高。关于这个转码算法的效率还是比较低,主要原因是编码比较慢,虽然多线程解决了部分问题,但是效率还是比较慢,大家有兴趣可以提出修改意见,咱们一起优化一下这个算法!
由于准备匆忙,很多知识点都没有跟大家说清楚,望大家见谅!希望代码写得不是很乱,大家能看得懂,有不懂的地方,欢迎跟我沟通!

说你懂得生之微末,我便做了这壮大与你看,你说再热闹也终需离散,我便做了这一辈子与你看,你说冷暖自知,我便做了这冬花夏雪与你看,你说恋恋旧日好时光,我便做了这描金绣凤的浮世绘与你看。你说应愁高处不胜寒,我便拱手河山,讨你欢。