Android 音頻開發 目錄
- Android音頻開發(1):音頻相關知識
- Android音頻開發(2):使用AudioRecord錄製pcm格式音頻
- Android音頻開發(3):使用AudioRecord實現錄音的暫停和恢復
- Android音頻開發(4):PCM轉WAV格式音頻
- Android音頻開發(5):Mp3的錄製 - 編譯Lame源碼
- Android音頻開發(6):Mp3的錄製 - 使用Lame實時錄製MP3格式音頻
- Android音頻開發(7):音樂可視化-FFT頻譜圖
項目地址
https://github.com/zhaolewei/ZlwAudioRecorder
前言
上一篇介紹瞭如何去編譯so文件,這一篇主要介紹下如何實時將pcm數據轉換爲MP3數據。
一、實現過程:
AudioRecorder在開啓錄音後,通過read方法不斷獲取pcm的採樣數據,每次獲取到數據後交給lame去處理,處理完成後存入文件中。
這一篇相對之前代碼,增加了兩個類:Mp3Encoder.java 和 Mp3EncoderThread.java
- Mp3Encoder: 通過Jni調用so文件的c代碼,將pcm轉換成mp3格式數據
- Mp3EncodeThread: 將pcm轉換成mp3時需要開啓子線程進行統一管理,以及全部轉碼完成的回調
代碼實現
- Mp3Encoder.java
public class Mp3Encoder {
static {
System.loadLibrary("mp3lame");
}
public native static void close();
public native static int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
public native static int flush(byte[] mp3buf);
public native static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
public static void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate) {
init(inSampleRate, outChannel, outSampleRate, outBitrate, 7);
}
}
- Mp3EncodeThread.java
- 每次有新的pcm數據後將數據打包成ChangeBuffer 類型,通過addChangeBuffer()存放到線程隊列當中,線程開啓後會不斷輪詢隊列內容,當有內容後開始轉碼,無內容時進入阻塞,直到數據全部處理完成後,關閉輪詢。
public class Mp3EncodeThread extends Thread {
private static final String TAG = Mp3EncodeThread.class.getSimpleName();
/**
* mp3文件的碼率 32kbit/s = 4kb/s
*/
private static final int OUT_BITRATE = 32;
private List<ChangeBuffer> cacheBufferList = Collections.synchronizedList(new LinkedList<ChangeBuffer>());
private File file;
private FileOutputStream os;
private byte[] mp3Buffer;
private EncordFinishListener encordFinishListener;
/**
* 是否已停止錄音
*/
private volatile boolean isOver = false;
/**
* 是否繼續輪詢數據隊列
*/
private volatile boolean start = true;
public Mp3EncodeThread(File file, int bufferSize) {
this.file = file;
mp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];
RecordConfig currentConfig = RecordService.getCurrentConfig();
int sampleRate = currentConfig.getSampleRate();
Mp3Encoder.init(sampleRate, currentConfig.getChannelCount(), sampleRate, OUT_BITRATE);
}
@Override
public void run() {
try {
this.os = new FileOutputStream(file);
} catch (FileNotFoundException e) {
Logger.e(e, TAG, e.getMessage());
return;
}
while (start) {
ChangeBuffer next = next();
Logger.v(TAG, "處理數據:%s", next == null ? "null" : next.getReadSize());
lameData(next);
}
}
public void addChangeBuffer(ChangeBuffer changeBuffer) {
if (changeBuffer != null) {
cacheBufferList.add(changeBuffer);
synchronized (this) {
notify();
}
}
}
public void stopSafe(EncordFinishListener encordFinishListener) {
this.encordFinishListener = encordFinishListener;
isOver = true;
synchronized (this) {
notify();
}
}
private ChangeBuffer next() {
for (; ; ) {
if (cacheBufferList == null || cacheBufferList.size() == 0) {
try {
if (isOver) {
finish();
}
synchronized (this) {
wait();
}
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
}
} else {
return cacheBufferList.remove(0);
}
}
}
private void lameData(ChangeBuffer changeBuffer) {
if (changeBuffer == null) {
return;
}
short[] buffer = changeBuffer.getData();
int readSize = changeBuffer.getReadSize();
if (readSize > 0) {
int encodedSize = Mp3Encoder.encode(buffer, buffer, readSize, mp3Buffer);
if (encodedSize < 0) {
Logger.e(TAG, "Lame encoded size: " + encodedSize);
}
try {
os.write(mp3Buffer, 0, encodedSize);
} catch (IOException e) {
Logger.e(e, TAG, "Unable to write to file");
}
}
}
private void finish() {
start = false;
final int flushResult = Mp3Encoder.flush(mp3Buffer);
if (flushResult > 0) {
try {
os.write(mp3Buffer, 0, flushResult);
os.close();
} catch (final IOException e) {
Logger.e(TAG, e.getMessage());
}
}
Logger.d(TAG, "轉換結束 :%s", file.length());
if (encordFinishListener != null) {
encordFinishListener.onFinish();
}
}
public static class ChangeBuffer {
private short[] rawData;
private int readSize;
public ChangeBuffer(short[] rawData, int readSize) {
this.rawData = rawData.clone();
this.readSize = readSize;
}
short[] getData() {
return rawData;
}
int getReadSize() {
return readSize;
}
}
public interface EncordFinishListener {
/**
* 格式轉換完畢
*/
void onFinish();
}
}
使用
private class AudioRecordThread extends Thread {
private AudioRecord audioRecord;
private int bufferSize;
AudioRecordThread() {
bufferSize = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig()) * RECORD_AUDIO_BUFFER_TIMES;
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, currentConfig.getSampleRate(),
currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSize);
if (currentConfig.getFormat() == RecordConfig.RecordFormat.MP3 && mp3EncodeThread == null) {
initMp3EncoderThread(bufferSize);
}
}
@Override
public void run() {
super.run();
startMp3Recorder();
}
private void initMp3EncoderThread(int bufferSize) {
try {
mp3EncodeThread = new Mp3EncodeThread(resultFile, bufferSize);
mp3EncodeThread.start();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
}
}
private void startMp3Recorder() {
state = RecordState.RECORDING;
notifyState();
try {
audioRecord.startRecording();
short[] byteBuffer = new short[bufferSize];
while (state == RecordState.RECORDING) {
int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
if (mp3EncodeThread != null) {
mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
}
notifyData(ByteUtils.toBytes(byteBuffer));
}
audioRecord.stop();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
notifyError("錄音失敗");
}
if (state != RecordState.PAUSE) {
state = RecordState.IDLE;
notifyState();
if (mp3EncodeThread != null) {
mp3EncodeThread.stopSafe(new Mp3EncodeThread.EncordFinishListener() {
@Override
public void onFinish() {
notifyFinish();
}
});
} else {
notifyFinish();
}
} else {
Logger.d(TAG, "暫停");
}
}
}
}