美文网首页
使用Lame库实现Android平台JNI中MP3和pcm互转

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

作者: 小强开学前 | 来源:发表于2020-06-02 10:32 被阅读0次

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

    相关文章

      网友评论

          本文标题:使用Lame库实现Android平台JNI中MP3和pcm互转

          本文链接:https://www.haomeiwen.com/subject/cenyzhtx.html