与我们熟知的MP3格式一样,AAC是一种音频编码格式,对比MP3格式,AAC在缩小30%的前题下可以提供更好的音质。这篇博客的主要内容就是通过AudioRecorder录制PCM音频,再通过MediaCodec将PCM数据硬编码为AAC格式的音频。
通常我们使用MediaCodec的流程如下:
MediaCodec的使用流程:
- createEncoderByType/createDecoderByType
- configure
- start
- while(1) {
- dequeueInputBuffer
- queueInputBuffer
- dequeueOutputBuffer
- releaseOutputBuffer
- }
- stop
- release
编解码器一个比较经典的工作原理图如下:
图中的Client一般就是我们开发者,解释一下就是:我们从Codec中拿到拿到空的input buffer,然后填充上我们需要进行编码的数据,再输送给Codec,Codec对数据进行编解码,编解码完成后,Codec将处理好的数据放进output buffer,我们取出后再清空返还给Codec,形成一个环形结构。可以看作一个生产者-消费者模式。
下面我们的编码流程也基本遵守上面的这个流程。为了便于大家理解,我画个流程图讲一下我整个代码的逻辑:
录音和编码分别在两个线程中进行,两个线程通过一个ArrayBlockQueue(这是一个线程安全的队列,想了解更多自己动手)队列共享数据,录音线程中的AudioRecorder通过read()将一帧数据put()到队尾,编码线程中的MediaCodec再通过take()取出队首的一帧数据进行编码。
首先看录音线程,为了使代码更加简洁易懂,我会省略掉一些代码,完整代码会在文末贴出。
/**
* 录音线程
*/
public class AudioRecorder extends Thread {
private AudioRecord mAudioRecord;
private boolean isRecording;
private int minBufferSize;
public AudioRecorder() {
isRecording = true;
initRecorder();
}
@Override
public void run() {
super.run();
startRecording();
}
/**
* 初始化录音
*/
public void initRecorder(){
minBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRateInHz, channelConfig, audioFormat, minBufferSize);
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
isRecording = false;
return;
}
}
/**
* 开始录音
*/
public void startRecording(){
if (mAudioRecord == null){
return;
}
mAudioRecord.startRecording();
while (isRecording) {
//自定义的一个类,用来存储一帧pcm数据,即byte[],下面给出具体定义,很简单
AudioDate audioDate = new AudioDate();
audioDate.buffer = ByteBuffer.allocateDirect(minBufferSize);
audioDate.size = mAudioRecord.read(audioDate.buffer, minBufferSize);
try {
if (queue != null) {
queue.put(audioDate);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
release();
}
}
录音线程比较简单,主要是先初始化录音器在initRecorder()中,然后通过AudioRecorder的read方法,获取到一帧数据,通过queue.put放入队尾。
然后是编码线程。
/**
* 音频编码线程
*/
public class AudioEncorder extends Thread {
private MediaCodec mEncorder;
private Boolean isEncording = false;
private int minBufferSize;
private OutputStream mFileStream;
public AudioEncorder() {
isEncording = true;
initEncorder();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)