利用libmp3lame实现在Android上录音MP3文件示例
程序员文章站
2022-07-21 15:02:07
之前项目需要实现mp3的录音,于是使用上了lame这个库。这次做一个demo,使用androidstudio+cmake+ndk进行开发。利用android sdk提供的a...
之前项目需要实现mp3的录音,于是使用上了lame这个库。这次做一个demo,使用androidstudio+cmake+ndk进行开发。利用android sdk提供的androidrecorder进行录音,得到pcm数据,并使用jni调用lame这个c库将pcm数据转换为mp3文件。并使用mediaplayer对录音的mp3文件进行播放。另外此次的按键是仿微信的语音按键,按下录音,松开结束,若中途上滑松开即取消
效果如下:
项目地址: lamemp3forandroid_jb51.rar
一、主要类的介绍
- 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<changebuffer> mchangebuffers = collections.synchronizedlist(new linkedlist<changebuffer>()); private fileoutputstream os; private countdownlatch handlerinitlatch = new countdownlatch(1); private static class stophandler extends handler { weakreference<dataencodethread> 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 <cwchar> #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); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 河南大学和郑州大学哪个好?河南一本最好的是哪个学校?
下一篇: 什么是中餐,有哪些中餐值得推荐