java libmp3lame_利用libmp3lame实现在Android上录音MP3文件示例

这篇博客介绍了如何在Android上使用libmp3lame库将录音数据转换为MP3文件。通过AndroidRecorder获取PCM数据,然后使用JNI调用Lame库进行转换。示例包括关键类如Mp3Recorder、DataEncodeThread的实现,以及如何使用MediaPlayer播放录音。整个过程包括录音、PCM转MP3、文件保存和播放的详细步骤。
摘要由CSDN通过智能技术生成

之前项目需要实现MP3的录音,于是使用上了Lame这个库。这次做一个demo,使用AndroidStudio+Cmake+NDK进行开发。利用Android SDK提供的AndroidRecorder进行录音,得到PCM数据,并使用jni调用Lame这个C库将PCM数据转换为MP3文件。并使用MediaPlayer对录音的MP3文件进行播放。另外此次的按键是仿微信的语音按键,按下录音,松开结束,若中途上滑松开即取消

效果如下:

ffb6e612e4dc73894edda2ab669fe8bc.png

一、主要类的介绍

Mp3Recorder—— 是负责调用AudioRecorder进行录音的类

SimpleLame——是负责将MP3Recorder录制出的PCM数据转换成MP3文件

DataEncodeThread——是负责执行PCM转MP3的线程

LameMp3Manager——是对Mp3Recorder的多一次封装,增加了取消后删除之前录制的数据的逻辑

MediaPlayerUtil——是对系统的MediaPlayer进行简单的封装,使其只需要三步就可以播放音频文件

MediaRecorderButton ——是一个仿微信录音按键的控件,按下录制,松开结束,录制时上滑则取消录制

二、录制的流程

Mp3Recorder调用startRecording()开始录制并初始化DataEncoderThread线程,并定期将录制的PCM数据,传入DataEncoderThread中。

在DataEncoderThread里,SimpleLame将Mp3Recorder传入的PCM数据转换成MP3格式并写入文件,其中SimpleLame通过jni对Lame库进行调用

Mp3Recorder调用stopRecording()停止录制,并通知DataEncoderThread线程录制结束,DataEncoderThread将剩余的数据转换完毕。

三、主要的实现代码

Mp3Recorder

public class Mp3Recorder {

static {

System.loadLibrary("lamemp3");

}

//默认采样率

private static final int DEFAULT_SAMPLING_RATE = 44100;

//转换周期,录音每满160帧,进行一次转换

private static final int FRAME_COUNT = 160;

//输出MP3的码率

private static final int BIT_RATE = 32;

//根据资料假定的最大值。 实测时有时超过此值。

private static final int MAX_VOLUME = 2000;

private AudioRecord audioRecord = null;

private int bufferSize;

private File mp3File;

private int mVolume;

private short[] mPCMBuffer;

private FileOutputStream os = null;

private DataEncodeThread encodeThread;

private int samplingRate;

private int channelConfig;

private PCMFormat audioFormat;

private boolean isRecording = false;

private ExecutorService executor = Executors.newFixedThreadPool(1);

private OnFinishListener finishListener;

public interface OnFinishListener {

void onFinish(String mp3SavePath);

}

public Mp3Recorder(int samplingRate, int channelConfig, PCMFormat audioFormat) {

this.samplingRate = samplingRate;

this.channelConfig = channelConfig;

this.audioFormat = audioFormat;

}

public Mp3Recorder() {

this(DEFAULT_SAMPLING_RATE, AudioFormat.CHANNEL_IN_MONO, PCMFormat.PCM_16BIT);

}

public void startRecording(File mp3Save) throws IOException {

if (isRecording) return;

this.mp3File = mp3Save;

if (audioRecord == null) {

initAudioRecorder();

}

audioRecord.startRecording();

Runnable runnable = new Runnable() {

@Override

public void run() {

isRecording = true;

//循环的从AudioRecord获取录音的PCM数据

while (isRecording) {

int readSize = audioRecord.read(mPCMBuffer, 0, bufferSize);

if (readSize > 0) {

//待转换的PCM数据放到转换线程中

encodeThread.addChangeBuffer(mPCMBuffer,readSize);

calculateRealVolume(mPCMBuffer, readSize);

}

}

// 录音完毕,释放AudioRecord的资源

try {

audioRecord.stop();

audioRecord.release();

audioRecord = null;

// 录音完毕,通知转换线程停止,并等待直到其转换完毕

Message msg = Message.obtain(encodeThread.getHandler(), DataEncodeThread.PROCESS_STOP);

msg.sendToTarget();

encodeThread.join();

//转换完毕后回调监听

if(finishListener != null) finishListener.onFinish(mp3File.getPath());

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

if (os != null) {

try {

os.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

};

executor.execute(runnable);

}

public void stopRecording() throws IOException {

isRecording = false;

}

//计算音量大小

private void calculateRealVolume(short[] buffer, int readSize) {

double sum = 0;

for (int i = 0; i < readSize; i++) {

sum += buffer[i] * buffer[i];

}

if (readSize > 0) {

double amplitude = sum / readSize;

mVolume = (int) Math.sqrt(amplitude);

}

}

public int getVolume(){

if (mVolume >= MAX_VOLUME) {

return MAX_VOLUME;

}

return mVolume;

}

public int getMaxVolume(){

return MAX_VOLUME;

}

public void setFinishListener(OnFinishListener listener){

this.finishListener = listener;

}

private void initAudioRecorder() throws IOException {

int bytesPerFrame = audioFormat.getBytesPerFrame();

//计算缓冲区的大小,使其是设置周期帧数的整数倍,方便循环

int frameSize = AudioRecord.getMinBufferSize(samplingRate, channelConfig, audioFormat.getAudioFormat()) / bytesPerFrame;

if (frameSize % FRAME_COUNT != 0) {

frameSize = frameSize + (FRAME_COUNT - frameSize % FRAME_COUNT);

}

bufferSize = frameSize * bytesPerFrame;

audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, samplingRate, channelConfig, audioFormat.getAudioFormat(), bufferSize);

mPCMBuffer = new short[bufferSize];

SimpleLame.init(samplingRate, 1, samplingRate, BIT_RATE);

os = new FileOutputStream(mp3File);

// 创建转码的线程

encodeThread = new DataEncodeThread(os, bufferSize);

encodeThread.start();

//给AudioRecord设置刷新监听,待录音帧数每次达到FRAME_COUNT,就通知转换线程转换一次数据

audioRecord.setRecordPositionUpdateListener(encodeThread, encodeThread.getHandler());

audioRecord.setPositionNotificationPeriod(FRAME_COUNT);

}

}

DataEncodeThread

public class DataEncodeThread extends Thread implements AudioRecord.OnRecordPositionUpdateListener {

public static final int PROCESS_STOP = 1;

private StopHandler handler;

private byte[] mp3Buffer;

//用于存取待转换的PCM数据

private List mChangeBuffers = Collections.synchronizedList(new LinkedList());

private FileOutputStream os;

private CountDownLatch handlerInitLatch = new CountDownLatch(1);

private static class StopHandler extends Handler {

WeakReference encodeThread;

public StopHandler(DataEncodeThread encodeThread) {

this.encodeThread = new WeakReference<>(encodeThread);

}

@Override

public void handleMessage(Message msg) {

if (msg.what == PROCESS_STOP) {

DataEncodeThread threadRef = encodeThread.get();

//录音停止后,将剩余的PCM数据转换完毕

for (;threadRef.processData() > 0;);

removeCallbacksAndMessages(null);

threadRef.flushAndRelease();

getLooper().quit();

}

super.handleMessage(msg);

}

}

public DataEncodeThread(FileOutputStream os, int bufferSize) {

this.os = os;

mp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];

}

@Override

public void run() {

Looper.prepare();

handler = new StopHandler(this);

handlerInitLatch.countDown();

Looper.loop();

}

public Handler getHandler() {

try {

handlerInitLatch.await();

} catch (InterruptedException e) {

e.printStackTrace();

Log.e(TAG, "Error when waiting handle to init");

}

return handler;

}

@Override

public void onMarkerReached(AudioRecord recorder) {

// Do nothing

}

@Override

public void onPeriodicNotification(AudioRecord recorder) {

//由AudioRecord进行回调,满足帧数,通知数据转换

processData();

}

//从缓存区ChangeBuffers里获取待转换的PCM数据,转换为MP3数据,并写入文件

private int processData() {

if(mChangeBuffers != null && mChangeBuffers.size() > 0) {

ChangeBuffer changeBuffer = mChangeBuffers.remove(0);

short[] buffer = changeBuffer.getData();

int readSize = changeBuffer.getReadSize();

Log.d(TAG, "Read size: " + readSize);

if (readSize > 0) {

int encodedSize = SimpleLame.encode(buffer, buffer, readSize, mp3Buffer);

if (encodedSize < 0) {

Log.e(TAG, "Lame encoded size: " + encodedSize);

}

try {

os.write(mp3Buffer, 0, encodedSize);

} catch (IOException e) {

e.printStackTrace();

Log.e(TAG, "Unable to write to file");

}

return readSize;

}

}

return 0;

}

private void flushAndRelease() {

final int flushResult = SimpleLame.flush(mp3Buffer);

if (flushResult > 0) {

try {

os.write(mp3Buffer, 0, flushResult);

} catch (final IOException e) {

e.printStackTrace();

}

}

}

public void addChangeBuffer(short[] rawData, int readSize){

mChangeBuffers.add(new ChangeBuffer(rawData, readSize));

}

private class ChangeBuffer{

private short[] rawData;

private int readSize;

public ChangeBuffer(short[] rawData, int readSize){

this.rawData = rawData.clone();

this.readSize = readSize;

}

public short[] getData(){

return rawData;

}

public int getReadSize(){

return readSize;

}

}

}

SimpleLame 主要的逻辑是通过jni调用Lame库

public class SimpleLame {

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);

}

}

#include

#include "SimpleLame.h"

#include "lamemp3/lame.h"

static lame_global_flags *glf = NULL;

void Java_com_clam314_lame_SimpleLame_close(JNIEnv *env, jclass type){

lame_close(glf);

glf = NULL;

}

jint Java_com_clam314_lame_SimpleLame_encode(JNIEnv *env, jclass type, jshortArray buffer_l_,

jshortArray buffer_r_, jint samples, jbyteArray mp3buf_) {

jshort *buffer_l = env->GetShortArrayElements(buffer_l_, NULL);

jshort *buffer_r = env->GetShortArrayElements(buffer_r_, NULL);

jbyte *mp3buf = env->GetByteArrayElements(mp3buf_, NULL);

const jsize mp3buf_size = env->GetArrayLength(mp3buf_);

int result =lame_encode_buffer(glf, buffer_l, buffer_r, samples, (u_char*)mp3buf, mp3buf_size);

env->ReleaseShortArrayElements(buffer_l_, buffer_l, 0);

env->ReleaseShortArrayElements(buffer_r_, buffer_r, 0);

env->ReleaseByteArrayElements(mp3buf_, mp3buf, 0);

return result;

}

jint Java_com_clam314_lame_SimpleLame_flush(JNIEnv *env, jclass type, jbyteArray mp3buf_) {

jbyte *mp3buf = env->GetByteArrayElements(mp3buf_, NULL);

const jsize mp3buf_size = env->GetArrayLength(mp3buf_);

int result = lame_encode_flush(glf, (u_char*)mp3buf, mp3buf_size);

env->ReleaseByteArrayElements(mp3buf_, mp3buf, 0);

return result;

}

void Java_com_clam314_lame_SimpleLame_init__IIIII(JNIEnv *env, jclass type, jint inSampleRate, jint outChannel,

jint outSampleRate, jint outBitrate, jint quality) {

if(glf != NULL){

lame_close(glf);

glf = NULL;

}

glf = lame_init();

lame_set_in_samplerate(glf, inSampleRate);

lame_set_num_channels(glf, outChannel);

lame_set_out_samplerate(glf, outSampleRate);

lame_set_brate(glf, outBitrate);

lame_set_quality(glf, quality);

lame_init_params(glf);

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值