Android音频开发(6):Mp3的录制 - 使用Lame实时录制MP3格式音频
16lz
2021-12-04
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 cacheBufferList = Collections.synchronizedList(new LinkedList()); 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的世界
- android 编程基础
- Android中数据存储的5种方法
- 【读书笔记《Android游戏编程之从零开始》】1.Android(安卓)平台
- 【读书笔记-《Android游戏编程之从零开始》】1.Android(安卓)平
- android 开发 经典网站
- Android开发实例大全
- 【安卓学习之开发工具】 Android(安卓)Studio学习 6 - Android(
- Android数据加密之Des加密