基於Android Studio 4.0,使用CmakeList,感謝領路人East_Wu,如有疏漏歡迎指正。
1. 導入lame庫
可以編譯後直接導入相應的SO文件,但是不知道爲什麼我編譯後提示缺少
x86_64
的一個什麼東西,找半天無果,所以直接導入所有的包。希望有好心人編譯後甩個SO鏈接共享一下,感謝(直接導入的話build有很多警告,看着不爽)。
MP3轉PCM貌似涉及版權問題,lame默認屏蔽了相關代碼,把
mpglib_interface.c
中的下面代碼取消註釋即可,注意版權。
1.1 下載lame3.100
1.2 導入文件。
將libmp3lame
文件夾下面的所有.c
和.h
文件(子文件夾不用管)拷貝到cpp文件夾下面的include
文件夾(不帶s),然後複製mpglib
文件夾下面的所有.c
和.h
文件到include
文件夾。
1.3 cMakeList
引用該文件。
add_library(
# Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp
# MP3轉PCM需要的文件
include/libmp3lame/common.c
include/libmp3lame/common.h
include/libmp3lame/huffman.h
include/libmp3lame/interface.c
include/libmp3lame/interface.h
include/libmp3lame/l2tables.h
include/libmp3lame/layer1.c
include/libmp3lame/layer1.h
include/libmp3lame/layer2.c
include/libmp3lame/layer2.h
include/libmp3lame/layer3.c
include/libmp3lame/layer3.h
include/libmp3lame/mpg123.h
include/libmp3lame/mpglib.h
include/libmp3lame/tabinit.c
include/libmp3lame/tabinit.h
include/libmp3lame/dct64_i386.c
include/libmp3lame/dct64_i386.h
include/libmp3lame/decode_i386.c
include/libmp3lame/decode_i386.h
# PCM轉MP3需要的文件
include/libmp3lame/bitstream.c
include/libmp3lame/encoder.c
include/libmp3lame/fft.c
include/libmp3lame/gain_analysis.c
include/libmp3lame/id3tag.c
include/libmp3lame/lame.c
include/libmp3lame/mpglib_interface.c
include/libmp3lame/newmdct.c
include/libmp3lame/presets.c
include/libmp3lame/psymodel.c
include/libmp3lame/quantize.c
include/libmp3lame/quantize_pvt.c
include/libmp3lame/reservoir.c
include/libmp3lame/set_get.c
include/libmp3lame/tables.c
include/libmp3lame/takehiro.c
include/libmp3lame/util.c
include/libmp3lame/vbrquantize.c
include/libmp3lame/VbrTag.c
include/libmp3lame/version.c
)
2. 基本使用
2.1 PCM -> MP3
我的標準流程是,init
-> encoder
-> flush
-> writeTag
->close
。
如果這個是一次性的,可以寫在一個函數裏面,因爲程序中可以多次開啓、關閉錄音,所以將其分開。
encoder
用了shortArray是因爲我使用的是ENCODING_PCM_16BIT
的format
flush
的意義在於,比如說機器處理比較慢還有數據沒處理完,或者緩衝區還有剩餘的數據,我們需要將剩餘的數據寫入文件。
writeTag
方法是在文件頭部寫入mp3的tag,給播放器標識用,lame默認會在開始的時候空出文件開頭部分。
2.2 MP3 -> PCM
菜雞算法不會寫這東西,要我們前端處理,因爲只要每次進入界面調用一次,所以這裏寫在一個函數裏面。
我的標準流程是,init
-> encoder
-> flush
-> writeTag
->close
。
3 具體代碼
有一些是應項目要求做的一些處理,各位自動忽略。
static lame_global_flags *lame = nullptr;
extern "C" {
/************************************************************************
* PCM 轉 MP3 *
***********************************************************************/
void JNICALL xxx_RecordService_init(JNIEnv *env,jobject,jint sampleRate,
jint channelCount, jint audioFormatBit,jint quality){
lame = lame_init();
//輸入採樣率
lame_set_in_samplerate(lame, sampleRate);
//聲道數
lame_set_num_channels(lame, channelCount);
//輸出採樣率
lame_set_out_samplerate(lame, sampleRate);
//位寬
lame_set_brate(lame, audioFormatBit);
//音頻質量
lame_set_quality(lame, quality);
//初始化參數配置
lame_init_params(lame);
}
//輸入PCM,輸出MP3
jint JNICALL xxx_RecordService_encoder(JNIEnv *env, jobject, jshortArray pcmBuffer, jbyteArray mp3Buffer, jint sample_num) {
//lame轉換需要short指針參數
jshort *pcmBuf = env->GetShortArrayElements(pcmBuffer, JNI_FALSE);
//獲取MP3數組長度
const jsize mp3_buff_len = env->GetArrayLength(mp3Buffer);
//獲取buffer指針
jbyte *mp3Buf =env->GetByteArrayElements(mp3Buffer, JNI_FALSE);
//編譯後的bytes
int encode_result;
//根據輸入音頻聲道數判斷
if (lame_get_num_channels(lame) == 2) {
encode_result = lame_encode_buffer_interleaved(lame, pcmBuf, sample_num / 2,(unsigned char *) mp3Buf,mp3_buff_len);
} else {
encode_result = lame_encode_buffer(lame, pcmBuf, pcmBuf, sample_num,(unsigned char *) mp3Buf, mp3_buff_len);
}
//釋放資源
env->ReleaseShortArrayElements(pcmBuffer, pcmBuf, 0);
env->ReleaseByteArrayElements(mp3Buffer, mp3Buf, 0);
return encode_result;
}
// 清除緩衝區
jint JNICALL xxx_RecordService_flush(JNIEnv *env,jobject, jbyteArray mp3Buffer) {
//獲取MP3數組長度
const jsize mp3_buff_len = env->GetArrayLength(mp3Buffer);
//獲取buffer指針
jbyte *mp3Buf = env->GetByteArrayElements(mp3Buffer, JNI_FALSE);
//刷新編碼器緩衝,獲取殘留在編碼器緩衝裏的數據
int flush_result = lame_encode_flush(lame, (unsigned char *)mp3Buf, mp3_buff_len);
env->ReleaseByteArrayElements(mp3Buffer, mp3Buf, 0);
return flush_result;
}
// 寫入MP3的Tag
void JNICALL xxx_RecordService_writeTag(JNIEnv *env,jobject,jstring mp3FilePath){
FILE *mp3File = fopen(jstring2string(env,mp3FilePath).c_str(),"ab+");
lame_mp3_tags_fid(lame, mp3File);
fclose(mp3File);
}
//釋放編碼器
void JNICALL xxx_RecordService_close(JNIEnv *env,jobject) {
lame_close(lame);
}
/************************************************************************
* PCM 轉 MP3 *
***********************************************************************/
// 可以不傳pcmPath,不要保存pcm文件
jboolean JNICALL xxx_RecordService_Mp3ToPcm(
JNIEnv *env,
jobject, jstring mp3Path,jstring pcmPath) {
vector<short> forHH;
int read, i, samples;
long cumulative_read = 0;
const int PCM_SIZE = 8192;
const int MP3_SIZE = 8192;
// 輸出左右聲道
short int pcm_l[PCM_SIZE], pcm_r[PCM_SIZE];
unsigned char mp3_buffer[MP3_SIZE];
//input輸入MP3文件
FILE *mp3 = fopen(jstring2string(env,mp3Path).c_str(), "rb");
FILE *pcm = fopen(jstring2string(env,pcmPath).c_str(), "wb");
fseek(mp3, 0, SEEK_SET);
lame = lame_init();
lame_set_decode_only(lame, 1);
hip_t hip = hip_decode_init();
mp3data_struct mp3data;
memset(&mp3data, 0, sizeof(mp3data));
int nChannels = -1;
int mp3_len;
while ((read = fread(mp3_buffer, sizeof(char), MP3_SIZE, mp3)) > 0) {
mp3_len = read;
cumulative_read += read * sizeof(char);
do{
samples = hip_decode1_headers(hip, mp3_buffer, mp3_len, pcm_l, pcm_r, &mp3data);
// 頭部解析成功
if(mp3data.header_parsed == 1){
nChannels = mp3data.stereo;
}
if(samples > 0){
for(i = 0 ; i < samples; i++){
forHH.push_back(pcm_l[i]);
fwrite((char*)&pcm_l[i], sizeof(char), sizeof(pcm_l[i]), pcm);
if(nChannels == 2){
forHH.push_back(pcm_r[i]);
fwrite((char*)&pcm_r[i], sizeof(char), sizeof(pcm_r[i]), pcm);
}
}
}
mp3_len = 0;
}while(samples>0);
}
hip_decode_exit(hip);
lame_close(lame);
fclose(mp3);
fclose(pcm);
// 下面是將forHH中的short強轉爲float
float* finalData = new float[forHH.size()];
for(int z = 0;z<forHH.size();z++){
finalData[z] = (float)forHH[z];
}
// 將數據給算法
xxx_audio(finalData,forHH.size());
return JNI_TRUE;
}
}