前言
上一篇介绍了如何去编译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, "暂停");
}
}
}
}
作者:android_赵乐玮
链接:https://www.jianshu.com/p/2722533e5a8a
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦