Android MP3录音实现
2016-09-19 16:25
127 查看
版权声明:
欢迎转载,但请保留文章原始出处
作者:GavinCT
出处:http://www.cnblogs.com/ct2011/p/4080193.html
Android录音支持的格式有amr、aac,但这两种音频格式在跨平台上表现并不好。
MP3显然才是跨平台的最佳选择。
近期由于项目需要,实现了这个需求,代码托管在Github上,欢迎拍砖
使用方法见:README.md
对于Android来说,需要借助JNI来调用Lame的C语言代码,实现音频格式的转化。
Samsung Developers先录后转示例代码
显然,这种方案是不可取的。
我们需要的是边录边转的实现方式,这样在停止录音进行存储的时候,就不会花费太长时间。
构造器参数很多,我们一点一点来看:
audioSource : 声源,一般使用
sampleRateInHz :官方明确说到只有44100Hz是所有设备都支持的。其他22050、16000和11025只能在某些设备上使用。
channelConfig : 有立体声(CHANNEL_IN_STEREO)和单声道(CHANNEL_IN_MONO)两种。但只有单声道(CHANNEL_IN_MONO)是所有设备都支持的。
audioFormat : 有ENCODING_PCM_16BIT和ENCODING_PCM_8BIT两种音频编码格式。同样的,官方声明只有ENCODING_PCM_16BIT是所有设备都支持的。
bufferSizeInBytes : 录音期间声音数据的写入缓冲区大小(单位是字节)。
其实从上面的解释可以看到,类的参数很多,但为了保证在所有设备上可以使用,我们真正需要填写的只有一个参数:bufferSizeInBytes,其他都可以使用通用的参数而不用自己费心来选择。
在深究bufferSizeInBytes该传入什么之前,我们先略过这一段,先来说一下录音的读取与转换。
既然是不断,那么我们当然需要循环读取,意味着我们需要一个线程来单独读取录音,避免阻塞主线程。
还和UDP差不多的是,如果不及时读取,数据超过缓冲区大小,会造成这段录音数据的丢失。
上面提到过,我们想要实现的是边录边转。那么问题来了,如果我们读取完数据后接着将数据传给Lame进行MP3编码,Lame的编码时间是不确定的,是不是有可能造成数据的丢失呢?
答案当然是有可能,所以我们不能巧合编程。
我们需要另外一个线程,即数据编码线程来专门进行MP3编码,而当前的录音读取线程只负责读取录音PCM数据。
有了两条线程,我们还需要确认一点,什么时候编码线程开始处理数据?
这种方式显然也是低效的,因为无论我们让线程休息多久都可以判定为不合理。因为我们并不知道准确的时间。
那么还有别的方法么?
显然录音这个类是知道什么时候该处理数据,什么时候可以休息。
Don't call me , I will call you.
是的,我们应该去看看有没有监听器,让录音来通知编码线程开始工作。
AudioRecord为我们提供了这样的方法:
设置通知周期。 以帧为单位。
到这里,我们可以回来来解释bufferSizeInBytes大小的传入了。
这里的3个参数,其实我们都可以从构造器的参数里看到,因此传入并没有什么问题。
但关键在如上面我们设置了周期单位,如果获得的缓冲区大小不是周期单位的整数倍呢?
不是整数倍当然会如我们猜想的一样造成数据丢失,因此我们还需要一些数据的纠正来保证缓冲区大小是整数倍。
讲完了数据的获取线程和编码线程,我们来仔细看看帮助我们实现MP3编码的功臣:Lame
拷贝 lame.h (include目录下)
创建Android.mk
删除非
编辑 jni/utils.h。把
编译库文件。可能会报出警告,忽略即可。
inChannel : 输入声道数
outSamplerate : 输出采样频率 Hz
outBitrate : Encoded bit rate. KHz
quality : MP3音频质量。0~9。 其中0是最好,非常慢,9是最差。
推荐:
2 :near-best quality, not too slow
5 :good quality, fast
7 :ok quality, really fast
bufferRight:右声道数据
samples :每个声道输入数据大小
mp3buf :用于接收转换后的数据。7200 + (1.25 * buffer_l.length)
这里需要解释一下:
左右声道 :当前声道选的是单声道,因此两边传入一样的buffer。
输入数据大小 :录音线程读取到buffer中的数据不一定是占满的,所以read方法会返回当前大小size,即前size个数据是有效的音频数据,后面的数据是以前留下的废数据。 这个size同样需要传入到Lame编码器中用于编码。
mp3的buffer:官方规定了计算公式:7200 + (1.25 * buffer_l.length)。(可以在lame.h文件中看到)
传入参数:mp3buf至少7200字节。这里还是用以前定义的mp3buf来传入,避免创建过多的数组。
OK,到这里,核心的转换代码就完成了,我们再来点锦上添花的东西。
当然,我在这个库里也提供了。
那么怎么来计算音量呢?
我参考了三星的音量计算。
总结如下:
最大音量在三星的代码中给出的是4000,但是我在实际的测试中发现,这个计算公式得出的音量大小一般都在1500以内。
因此在我提供的录音库里面,我把最大音量规定为了2000。
这块儿欢迎大家来提宝贵意见。
日本人写的,感觉他的判断不完善,有点巧合编程的意思,也或许是我没看懂。
talzeus/AndroidMp3Recorder
比较严谨的代码。主要依据这个库进行的修改。
存在的问题:
AudioRecord传入参数很多没有按Android规定传入。如采样频率使用了22050Hz。
使用了自己构造的RingBuffer,看这有点头晕。 我在库里使用List来存储未编码的音频数据,更容易理解。
没有提供音量大小。
欢迎转载,但请保留文章原始出处
作者:GavinCT
出处:http://www.cnblogs.com/ct2011/p/4080193.html
Android录音支持的格式有amr、aac,但这两种音频格式在跨平台上表现并不好。
MP3显然才是跨平台的最佳选择。
近期由于项目需要,实现了这个需求,代码托管在Github上,欢迎拍砖
项目地址
GavinCT/AndroidMP3Recorder使用方法见:README.md
实现思路概述
在分析代码前,我们需要明确几个问题1. 如何最终生成MP3
实现MP3格式最好是借助Lame这个成熟的解决方案。对于Android来说,需要借助JNI来调用Lame的C语言代码,实现音频格式的转化。
2. 如何获取最初的音频数据
AudioRecord类可以直接帮助我们获取音频数据。3. 如何进行转换
网上有代码是先录制后转为MP3,这种效率比较低。因为如果录音时间过长,转换时间就会相应变长,用户在存储录音时需要等待的时间就会变长。Samsung Developers先录后转示例代码
显然,这种方案是不可取的。
我们需要的是边录边转的实现方式,这样在停止录音进行存储的时候,就不会花费太长时间。
实现代码介绍
既然是录音,我们上面也提到了需要使用AudioRecord类,我们就从这个类的构造器开始说起构造器
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
构造器参数很多,我们一点一点来看:
audioSource : 声源,一般使用
MediaRecorder.AudioSource.MIC表示来自于麦克风
sampleRateInHz :官方明确说到只有44100Hz是所有设备都支持的。其他22050、16000和11025只能在某些设备上使用。
channelConfig : 有立体声(CHANNEL_IN_STEREO)和单声道(CHANNEL_IN_MONO)两种。但只有单声道(CHANNEL_IN_MONO)是所有设备都支持的。
audioFormat : 有ENCODING_PCM_16BIT和ENCODING_PCM_8BIT两种音频编码格式。同样的,官方声明只有ENCODING_PCM_16BIT是所有设备都支持的。
bufferSizeInBytes : 录音期间声音数据的写入缓冲区大小(单位是字节)。
其实从上面的解释可以看到,类的参数很多,但为了保证在所有设备上可以使用,我们真正需要填写的只有一个参数:bufferSizeInBytes,其他都可以使用通用的参数而不用自己费心来选择。
在深究bufferSizeInBytes该传入什么之前,我们先略过这一段,先来说一下录音的读取与转换。
录音的读取与转换策略
录音的读取其实和UDP差不多,需要不断的读取数据。既然是不断,那么我们当然需要循环读取,意味着我们需要一个线程来单独读取录音,避免阻塞主线程。
还和UDP差不多的是,如果不及时读取,数据超过缓冲区大小,会造成这段录音数据的丢失。
上面提到过,我们想要实现的是边录边转。那么问题来了,如果我们读取完数据后接着将数据传给Lame进行MP3编码,Lame的编码时间是不确定的,是不是有可能造成数据的丢失呢?
答案当然是有可能,所以我们不能巧合编程。
我们需要另外一个线程,即数据编码线程来专门进行MP3编码,而当前的录音读取线程只负责读取录音PCM数据。
有了两条线程,我们还需要确认一点,什么时候编码线程开始处理数据?
编码线程处理数据的时机
传统的方法是当线程中有数据的时候开始处理,这就需要在这个线程里面不断循环查看是否有数据需要处理,有数据就开始处理,没有数据我们可以暂时休息几毫秒(当然一直不sleep也可以,但造成的系统消耗太多)。这种方式显然也是低效的,因为无论我们让线程休息多久都可以判定为不合理。因为我们并不知道准确的时间。
那么还有别的方法么?
显然录音这个类是知道什么时候该处理数据,什么时候可以休息。
Don't call me , I will call you.
是的,我们应该去看看有没有监听器,让录音来通知编码线程开始工作。
AudioRecord为我们提供了这样的方法:
public int setPositionNotificationPeriod (int periodInFrames) Added in API level 3 Sets the period at which the listener is called, if set with setRecordPositionUpdateListener(OnRecordPositionUpdateListener) or setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler). It is possible for notifications to be lost if the period is too small.
设置通知周期。 以帧为单位。
到这里,我们可以回来来解释bufferSizeInBytes大小的传入了。
缓冲区的大小
其实AudioRecord类提供了一个方便的方法getMinBufferSize来获取缓冲区的大小。public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)
这里的3个参数,其实我们都可以从构造器的参数里看到,因此传入并没有什么问题。
但关键在如上面我们设置了周期单位,如果获得的缓冲区大小不是周期单位的整数倍呢?
不是整数倍当然会如我们猜想的一样造成数据丢失,因此我们还需要一些数据的纠正来保证缓冲区大小是整数倍。
mBufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLING_RATE, DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT.getAudioFormat()); int bytesPerFrame = DEFAULT_AUDIO_FORMAT.getBytesPerFrame(); /* Get number of samples. Calculate the buffer size * (round up to the factor of given frame size) * 使能被整除,方便下面的周期性通知 * */ int frameSize = mBufferSize / bytesPerFrame; if (frameSize % FRAME_COUNT != 0) { frameSize += (FRAME_COUNT - frameSize % FRAME_COUNT); mBufferSize = frameSize * bytesPerFrame; }
讲完了数据的获取线程和编码线程,我们来仔细看看帮助我们实现MP3编码的功臣:Lame
Lame的获取与编译
Lame在线下载地址步骤
解压libmp3lame 到jni目录.拷贝 lame.h (include目录下)
创建Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := mp3lame LOCAL_SRC_FILES := bitstream.c fft.c id3tag.c mpglib_interface.c presets.c quantize.c reservoir.c tables.c util.c VbrTag.c encoder.c gain_analysis.c lame.c newmdct.c psymodel.c quantize_pvt.c set_get.c takehiro.c vbrquantize.c version.c include $(BUILD_SHARED_LIBRARY)
删除非
.c/
.h文件:GNU autotools, Makefile.am Makefile.in libmp3lame_vc8.vcproj logoe.ico depcomp, folders i386 等无用文件。
编辑 jni/utils.h。把
extern ieee754_float32_t fast_log2(ieee754_float32_t x);替换为
extern float fast_log2(float x);。如果忘了替换,编译时会报出以下错误:
[armeabi] Compile thumb : mp3lame <= bitstream.c In file included from jni/bitstream.c:36:0: jni/util.h:574:5: error: unknown type name 'ieee754_float32_t' jni/util.h:574:40: error: unknown type name 'ieee754_float32_t' make.exe: *** [obj/local/armeabi/objs/mp3lame/bitstream.o] Error 1
编译库文件。可能会报出警告,忽略即可。
Lame需要对外提供的方法
init 初始化
inSamplerate : 输入采样频率 HzinChannel : 输入声道数
outSamplerate : 输出采样频率 Hz
outBitrate : Encoded bit rate. KHz
quality : MP3音频质量。0~9。 其中0是最好,非常慢,9是最差。
推荐:
2 :near-best quality, not too slow
5 :good quality, fast
7 :ok quality, really fast
private static final int DEFAULT_LAME_MP3_QUALITY = 7; /** * 与DEFAULT_CHANNEL_CONFIG相关,因为是mono单声,所以是1 */ private static final int DEFAULT_LAME_IN_CHANNEL = 1; /** * Encoded bit rate. MP3 file will be encoded with bit rate 32kbps */ private static final int DEFAULT_LAME_MP3_BIT_RATE = 32; /* * Initialize lame buffer * mp3 sampling rate is the same as the recorded pcm sampling rate * The bit rate is 32kbps * */ LameUtil.init(DEFAULT_SAMPLING_RATE, DEFAULT_LAME_IN_CHANNEL, DEFAULT_SAMPLING_RATE, DEFAULT_LAME_MP3_BIT_RATE, DEFAULT_LAME_MP3_QUALITY);
encode
bufferLeft : 左声道数据bufferRight:右声道数据
samples :每个声道输入数据大小
mp3buf :用于接收转换后的数据。7200 + (1.25 * buffer_l.length)
这里需要解释一下:
Task task = mTasks.remove(0); short[] buffer = task.getData(); int readSize = task.getReadSize(); int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
左右声道 :当前声道选的是单声道,因此两边传入一样的buffer。
输入数据大小 :录音线程读取到buffer中的数据不一定是占满的,所以read方法会返回当前大小size,即前size个数据是有效的音频数据,后面的数据是以前留下的废数据。 这个size同样需要传入到Lame编码器中用于编码。
mp3的buffer:官方规定了计算公式:7200 + (1.25 * buffer_l.length)。(可以在lame.h文件中看到)
flush
将MP3结尾信息写入buffer中。传入参数:mp3buf至少7200字节。这里还是用以前定义的mp3buf来传入,避免创建过多的数组。
close
关闭释放LameOK,到这里,核心的转换代码就完成了,我们再来点锦上添花的东西。
音量
一般我们在做录音的时候,都会有一个需求,根据音量的大小显示一个动画,让录音显得更生动一些。当然,我在这个库里也提供了。
那么怎么来计算音量呢?
我参考了三星的音量计算。
总结如下:
/** * 此计算方法来自samsung开发范例 * * @param buffer * @param readSize */ private void calculateRealVolume(short[] buffer, int readSize) { int 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); } };
关于最大音量
其实对于音量,我不是特别明白。最大音量在三星的代码中给出的是4000,但是我在实际的测试中发现,这个计算公式得出的音量大小一般都在1500以内。
因此在我提供的录音库里面,我把最大音量规定为了2000。
这块儿欢迎大家来提宝贵意见。
MP3录音实现参考
yhirano/Mp3VoiceRecorderSampleForAndroid日本人写的,感觉他的判断不完善,有点巧合编程的意思,也或许是我没看懂。
talzeus/AndroidMp3Recorder
比较严谨的代码。主要依据这个库进行的修改。
存在的问题:
AudioRecord传入参数很多没有按Android规定传入。如采样频率使用了22050Hz。
使用了自己构造的RingBuffer,看这有点头晕。 我在库里使用List来存储未编码的音频数据,更容易理解。
没有提供音量大小。
相关文章推荐
- Android MP3录音实现
- Android MP3录音实现
- Android MP3录音实现
- Android MP3录音实现
- 浅谈Android实现伴奏录音合成MP3
- 利用libmp3lame实现在Android上录音MP3文件示例
- Android 使用AudioRecord而实现录音暂停以及wav文件转mp3文件
- 简易的android 通话录音实现【只能录制MIC的声音,不能录制对方的声音】
- android 实现电话录音(窃听)
- Android录音时指针摆动的实现(附源码)
- Android--实现自制录音/播放录音程序
- Android中用MediaRecorder实现录音
- Android录音时指针摆动的实现(附源码)
- Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能
- [Android] 录音与播放录音实现
- Android MP3录音功能(能暂停,含源码,可编译)
- Android录音时指针摆动的实现
- Android使用MediaRecorder实现录音功能
- android开发 之 通话录音实现
- android 之service实现电话监听并录音