使用Lame库实现Android平台JNI中MP3和pcm互转

基于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;
}
}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260