前言
上一篇介绍了如何去编译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