众多周知,mp3 是跨平台性最好的音频格式,由于采用了压缩率更高的有损压缩算法,文件大小是大约每分钟1M,使其在网络中传输更快,占用存储空间也更少;与此同时,它的声音质量也不错,尤其是人声(相声、评书、脱口秀),当然追求无损音乐的除外。
Android 中没有提供录制 mp3 的 API,需要使用开源库 lame,lame 是专门用于编码 mp3 的轻量高效的 c 代码库。由于采用 c 语言编写,故需要用到 jni。
下载lame库
- lame库下载(以下使用的是
lame
v3.100)
https://sourceforge.net/projects/lame/files/lame/
源码导入
解压下载的lame库,把libmp3lame
文件夹下后缀为.c .h
的文件(不包括子文件夹i386和vector下的)复制到cpp/lame文件夹内,同时把include
目录下的lame.h也复制到cpp/lame文件夹内,此时 lame文件夹内包含42个文件。
(可参考https://github.com/xmaihh/MFSocket/tree/master/liblame/src/main/cpp/lame)
修改库文件
打开刚刚拷贝的lame库文件,修改:
- util.h 文件,把 570 行的两处 ieee754_float32_t 改为 float 因为Android下并不支持该类型
- set_get.h 文件,把头部的 #include <lame.h> 改为 #include "lame.h"
- fft.c 文件,删除第47行 #include "vector/lame_intrin.h"
- id3tag.c和machine.h两个文件里,將HAVE_STRCHR和HAVE_MEMCPY的ifdef结构体删除或者注释
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
/*# ifndef HAVE_STRCHR
# define strchr index
# define strrchr rindex
# endif*/
char *strchr(), *strrchr();
/*# ifndef HAVE_MEMCPY
# define memcpy(d, s, n) bcopy ((s), (d), (n))
# define memmove(d, s, n) bcopy ((s), (d), (n))
# endif*/
#endif
可参考以下完整修改文件
diff --git a/VbrTag.c b/VbrTag.c
index 5800a44..36ee7b6 100644
--- a/VbrTag.c
+++ b/VbrTag.c
@@ -26,6 +26,8 @@
# include <config.h>
#endif
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/bitstream.c b/bitstream.c
index aa35915..a2fe294 100644
--- a/bitstream.c
+++ b/bitstream.c
@@ -29,6 +29,7 @@
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
diff --git a/encoder.c b/encoder.c
index 48f46c7..437067f 100644
--- a/encoder.c
+++ b/encoder.c
@@ -30,6 +30,7 @@
#endif
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/fft.c b/fft.c
index 4eea1ad..27febdb 100644
--- a/fft.c
+++ b/fft.c
@@ -44,7 +44,7 @@
#include "util.h"
--- a/fft.c
+++ b/fft.c
@@ -44,7 +44,7 @@
#include "util.h"
#include "fft.h"
-#include "vector/lame_intrin.h"
+//#include "vector/lame_intrin.h"
diff --git a/id3tag.c b/id3tag.c
index ac48510..8f148b8 100644
--- a/id3tag.c
+++ b/id3tag.c
@@ -41,17 +41,20 @@
# include <string.h>
# include <ctype.h>
#else
-# ifndef HAVE_STRCHR
-# define strchr index
-# define strrchr rindex
-# endif
+//# ifndef HAVE_STRCHR
+//# define strchr index
+//# define strrchr rindex
+//# endif
char *strchr(), *strrchr();
-# ifndef HAVE_MEMCPY
-# define memcpy(d, s, n) bcopy ((s), (d), (n))
-# endif
+//# ifndef HAVE_MEMCPY
+//# define memcpy(d, s, n) bcopy ((s), (d), (n))
+//# endif
#endif
+#include <malloc.h>
+#include <string.h>
+#include <stdlib.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/lame.c b/lame.c
index cb82225..299fd56 100644
--- a/lame.c
+++ b/lame.c
@@ -31,6 +31,8 @@
#endif
+#include <malloc.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
diff --git a/machine.h b/machine.h
index bf6fff2..c675c20 100644
--- a/machine.h
+++ b/machine.h
@@ -31,15 +31,15 @@
# include <stdlib.h>
# include <string.h>
#else
-# ifndef HAVE_STRCHR
-# define strchr index
-# define strrchr rindex
-# endif
+//# ifndef HAVE_STRCHR
+//# define strchr index
+//# define strrchr rindex
+//# endif
char *strchr(), *strrchr();
-# ifndef HAVE_MEMCPY
-# define memcpy(d, s, n) bcopy ((s), (d), (n))
-# define memmove(d, s, n) bcopy ((s), (d), (n))
-# endif
+//# ifndef HAVE_MEMCPY
+//# define memcpy(d, s, n) bcopy ((s), (d), (n))
+//# define memmove(d, s, n) bcopy ((s), (d), (n))
+//# endif
#endif
#if defined(__riscos__) && defined(FPA10)
diff --git a/newmdct.c b/newmdct.c
index 596cac9..ac98abd 100644
--- a/newmdct.c
+++ b/newmdct.c
@@ -30,6 +30,7 @@
# include <config.h>
#endif
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/psymodel.c b/psymodel.c
index 60076ee..1393c2a 100644
--- a/psymodel.c
+++ b/psymodel.c
@@ -145,7 +145,8 @@ blocktype_d[2] block type to use for previous granule
#endif
#include <float.h>
-
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/quantize.c b/quantize.c
index 9ba9c16..2906c00 100644
--- a/quantize.c
+++ b/quantize.c
@@ -28,6 +28,8 @@
# include <config.h>
#endif
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/quantize_pvt.c b/quantize_pvt.c
:
#endif
#include <float.h>
-
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/quantize.c b/quantize.c
index 9ba9c16..2906c00 100644
--- a/quantize.c
+++ b/quantize.c
@@ -28,6 +28,8 @@
# include <config.h>
#endif
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/quantize_pvt.c b/quantize_pvt.c
index d8d6447..3cd9966 100644
--- a/quantize_pvt.c
+++ b/quantize_pvt.c
@@ -36,6 +36,7 @@
#include "reservoir.h"
#include "lame-analysis.h"
#include <float.h>
+#include <string.h>
#define NSATHSCALE 100 /* Assuming dynamic range=96dB, this value should be 92 */
diff --git a/set_get.h b/set_get.h
index 37e4bcd..99ab73c 100644
--- a/set_get.h
+++ b/set_get.h
@@ -21,7 +21,7 @@
#ifndef __SET_GET_H__
#define __SET_GET_H__
-#include <lame.h>
+#include "lame.h"
#if defined(__cplusplus)
extern "C" {
diff --git a/takehiro.c b/takehiro.c
index 67aba1b..ca02f98 100644
--- a/takehiro.c
+++ b/takehiro.c
@@ -27,6 +27,7 @@
#endif
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/util.c b/util.c
index 43b457c..e9255fe 100644
--- a/util.c
+++ b/util.c
@@ -27,6 +27,7 @@
#endif
#include <float.h>
+#include <malloc.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
diff --git a/util.h b/util.h
index 13f0cd4..b6bf306 100644
--- a/util.h
+++ b/util.h
@@ -567,7 +567,7 @@ extern "C" {
/* log/log10 approximations */
extern void init_log_table(void);
- extern ieee754_float32_t fast_log2(ieee754_float32_t x);
+ extern float fast_log2(float x);
int isResamplingNecessary(SessionConfig_t const* cfg);
diff --git a/vbrquantize.c b/vbrquantize.c
index 0f703b7..60834d3 100644
--- a/vbrquantize.c
+++ b/vbrquantize.c
@@ -27,6 +27,8 @@
#endif
+#include <stdlib.h>
+#include <string.h>
#include "lame.h"
#include "machine.h"
#include "encoder.h"
编写CmakeList.txt
cmake_minimum_required(VERSION 3.6.0)
set(CURRENT_DIR ${CMAKE_SOURCE_DIR})
message("CURRENT_DIR:" ${CMAKE_SOURCE_DIR})
include_directories(${CMAKE_SOURCE_DIR}src/main/cpp/lame)
set(LAME_DIR src/main/cpp/lame)
message("LAME_DIR:" ${LAME_DIR})
aux_source_directory(src/main/cpp/lame SRC_LIST)
add_library(mp3lame
SHARED
src/main/cpp/MP3Recorder.c
${SRC_LIST})
#add_library(mp3lame
# SHARED
# src/main/cpp/MP3Recorder.c
# src/main/cpp/lame/bitstream.c
# src/main/cpp/lame/fft.c
# src/main/cpp/lame/id3tag.c
# src/main/cpp/lame/mpglib_interface.c
# src/main/cpp/lame/presets.c
# src/main/cpp/lame/quantize.c
# src/main/cpp/lame/reservoir.c
# src/main/cpp/lame/tables.c
# src/main/cpp/lame/util.c
# src/main/cpp/lame/VbrTag.c
# src/main/cpp/lame/encoder.c
# src/main/cpp/lame/gain_analysis.c
# src/main/cpp/lame/lame.c
# src/main/cpp/lame/newmdct.c
# src/main/cpp/lame/psymodel.c
# src/main/cpp/lame/quantize_pvt.c
# src/main/cpp/lame/set_get.c
# src/main/cpp/lame/takehiro.c
# src/main/cpp/lame/vbrquantize.c
# src/main/cpp/lame/version.c)
find_library( # Sets the name of the path variable.
log-lib
log)
target_link_libraries(mp3lame
${log-lib})
(可参考https://github.com/xmaihh/MFSocket/blob/master/liblame/CMakeLists.txt)
编写 java 类和 c 文件
public class MP3Recorder {
static {
System.loadLibrary("mp3lame");
}
/**
* 初始化 lame编码器
*
* @param inSampleRate
* 输入采样率
* @param outChannel
* 声道数
* @param outSampleRate
* 输出采样率
* @param outBitrate
* 比特率(kbps)
* @param quality
* 0~9,0最好
*/
public static native void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
/**
* 编码,把 AudioRecord 录制的 PCM 数据转换成 mp3 格式
*
* @param buffer_l
* 左声道输入数据
* @param buffer_r
* 右声道输入数据
* @param samples
* 输入数据的size
* @param mp3buf
* 输出数据
* @return
* 输出到mp3buf的byte数量
*/
public static native int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
/**
* 刷写
*
* @param mp3buf
* mp3数据缓存区
* @return
* 返回刷写的数量
*/
public static native int flush(byte[] mp3buf);
/**
* 关闭 lame 编码器,释放资源
*/
public static native void close();
}
生成.h文件
编写MP3Recorder.c
#include "lame/lame.h"
#include "MP3Recorder.h"
static lame_global_flags *glf = NULL;
/*
* Class: com_android_liblame_MP3Recorder
* Method: init
* Signature: (IIIII)V
*/
JNIEXPORT void JNICALL Java_com_android_liblame_MP3Recorder_init
(JNIEnv *env, jclass instance, 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);
}
/*
* Class: com_android_liblame_MP3Recorder
* Method: encode
* Signature: ([S[SI[B)I
*/
JNIEXPORT jint JNICALL Java_com_android_liblame_MP3Recorder_encode
(JNIEnv *env, jclass instance, jshortArray buffer_l, jshortArray buffer_r, jint samples,
jbyteArray mp3buf) {
jshort *j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);
jshort *j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte *j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
samples, j_mp3buf, mp3buf_size);
(*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
(*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
/*
* Class: com_android_liblame_MP3Recorder
* Method: flush
* Signature: ([B)I
*/
JNIEXPORT jint JNICALL Java_com_android_liblame_MP3Recorder_flush
(JNIEnv *env, jclass instance, jbyteArray mp3buf) {
const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
jbyte *j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);
int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);
(*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);
return result;
}
/*
* Class: com_android_liblame_MP3Recorder
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_android_liblame_MP3Recorder_close
(JNIEnv *env, jclass instance) {
lame_close(glf);
glf = NULL;
}
配置build.gradle
android {
....
...
..
.
//*
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
点一下小锤子 :hammer: MakeProject。
编译生成 so库。
录制MP3格式音频
// 录音状态
private boolean isRecording;
//开始录音
private void record() {
new Thread() {
@Override
public void run() {
// 音源
int audioSource = MediaRecorder.AudioSource.MIC;
// 采样率
int sampleRate = 44100;
// 声道
int channelConfig = AudioFormat.CHANNEL_IN_MONO;//单声道
// 采样位数
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
// 录音缓存区大小
int bufferSizeInBytes;
// 文件输出流
FileOutputStream fos;
// 录音最小缓存大小
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSizeInBytes);
try {
fos = new FileOutputStream(getExternalCacheDir() + "/demo.mp3");
MP3Recorder.init(sampleRate, 2, sampleRate, 128, 5);
short[] buffer = new short[bufferSizeInBytes];
byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 1.25)];
audioRecord.startRecording();
isRecording = true;
while (isRecording && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
int readSize = audioRecord.read(buffer, 0, bufferSizeInBytes);
if (readSize > 0) {
int encodeSize = MP3Recorder.encode(buffer, buffer, readSize, mp3buffer);
if (encodeSize > 0) {
try {
fos.write(mp3buffer, 0, encodeSize);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
int flushSize = MP3Recorder.flush(mp3buffer);
if (flushSize > 0) {
try {
fos.write(mp3buffer, 0, flushSize);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
audioRecord.stop();
audioRecord.release();
MP3Recorder.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}.start();
}
// 停止录音
private void stop() {
isRecording = false;
}
在 AndroidManifest 配置文件中添加录音权限:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
Android 6.0 以上还要动态获取权限。