Android录音支持的格式有amr、aac、3gp,但这三种音频格式在跨平台上表现并不好。而mp3格式是跨平台最好的音频格式,所以如果能转成mp3格式的音频文件,那是极好的。
那转成mp3格式又有两种方式:
一、录音完毕再转,再将amr、aac、3gp三种音频文件转成mp3格式的文件。
二、边录边转,使用libmp3lame直接转为mp3格式。
这里先主要介绍第二种,即 使用lame库实现边录边转mp3的方式。这样在录音完毕时,也就转完了,效率比较高。
录音需要使用到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 : 录音期间声音数据的写入缓冲区大小(单位是字节)。
这里参数audioSource 、sampleRateInHz 、channelConfig 、audioFormat 都是可以根据需要进行选择,只有bufferSizeInBytes 这个参数,需要通过计算来获得。在介绍计算方法之前,先看一下音频数据的读取与转换。
录音过程中需要不断的读取数据。既然是不断,那么我们当然需要循环读取,意味着我们需要一个线程来单独读取录音,避免阻塞主线程。如果不及时读取,数据超过缓冲区大小,会造成这段录音数据的丢失。
上面提到过,我们想要实现的是边录边转。那么问题来了,如果我们读取完数据后接着将数据传给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在线下载地址
步骤
1、解压libmp3lame 到jni目录.
2、拷贝 lame.h (include目录下)。
3、创建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)
4、删除非.c/.h文件:GNU autotools, Makefile.am Makefile.in libmp3lame_vc8.vcproj logoe.ico depcomp, folders i386 等无用文件。
5、编辑 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
init 初始化
inSamplerate : 输入采样频率 Hz
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
//=======================AudioRecord Default Settings=======================
private static final int DEFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
/**
* 以下三项为默认配置参数。Google Android文档明确表明只有以下3个参数是可以在所有设备上保证支持的。
*/
private static final int DEFAULT_SAMPLING_RATE = 44100;//模拟器仅支持从麦克风输入8kHz采样率
private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
//立体声的选择
private static final int STEREO_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
/**
* 下面是对此的封装
* private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
*/
private static final PCMFormat DEFAULT_AUDIO_FORMAT = PCMFormat.PCM_16BIT;
//======================Lame Default Settings=====================
private static final int DEFAULT_LAME_MP3_QUALITY = 7;
/**
* 与DEFAULT_CHANNEL_CONFIG相关,因为是mono单声,所以是1
*/
//private static final int DEFAULT_LAME_IN_CHANNEL = 1;
/**
* STEREO_CHANNEL_CONFIG左声道和右声道,实现立体声的选择
*/
private static final int STEREO_LAME_IN_CHANNEL = 2;
/**
* Encoded bit rate. MP3 file will be encoded with bit rate 32kbps
*/
private static final int DEFAULT_LAME_MP3_BIT_RATE = 32;
LameUtil.init(DEFAULT_SAMPLING_RATE, STEREO_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
关闭释放Lame
OK,到这里,核心的转换代码就完成了,我们再来点锦上添花的东西。
音量
一般我们在做录音的时候,都会有一个需求,根据音量的大小显示一个动画,让录音显得更生动一些。
当然,我在这个库里也提供了。
那么怎么来计算音量呢?
我参考了三星的音量计算。
总结如下:
/**
* 此计算方法来自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);
}
};
现有的一个项目:AndroidMP3Recorder
原文:http://www.cnblogs.com/ct2011/p/4080193.html