使用Lame庫(kù)實(shí)現(xiàn)Android平臺(tái)JNI中MP3和pcm互轉(zhuǎn)

基于Android Studio 4.0,使用CmakeList,感謝領(lǐng)路人East_Wu,如有疏漏歡迎指正。

1. 導(dǎo)入lame庫(kù)

可以編譯后直接導(dǎo)入相應(yīng)的SO文件,但是不知道為什么我編譯后提示缺少x86_64的一個(gè)什么東西,找半天無(wú)果,所以直接導(dǎo)入所有的包。希望有好心人編譯后甩個(gè)SO鏈接共享一下,感謝(直接導(dǎo)入的話build有很多警告,看著不爽)。

MP3轉(zhuǎn)PCM貌似涉及版權(quán)問(wèn)題,lame默認(rèn)屏蔽了相關(guān)代碼,把mpglib_interface.c中的下面代碼取消注釋即可,注意版權(quán)。

1.1 下載lame3.100

1.2 導(dǎo)入文件。

libmp3lame文件夾下面的所有.c.h文件(子文件夾不用管)拷貝到cpp文件夾下面的include文件夾(不帶s),然后復(fù)制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轉(zhuǎn)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轉(zhuǎn)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

我的標(biāo)準(zhǔn)流程是,init -> encoder -> flush -> writeTag->close。

如果這個(gè)是一次性的,可以寫在一個(gè)函數(shù)里面,因?yàn)槌绦蛑锌梢远啻伍_啟、關(guān)閉錄音,所以將其分開。

encoder用了shortArray是因?yàn)槲沂褂玫氖?code>ENCODING_PCM_16BIT的format

flush的意義在于,比如說(shuō)機(jī)器處理比較慢還有數(shù)據(jù)沒處理完,或者緩沖區(qū)還有剩余的數(shù)據(jù),我們需要將剩余的數(shù)據(jù)寫入文件。

writeTag方法是在文件頭部寫入mp3的tag,給播放器標(biāo)識(shí)用,lame默認(rèn)會(huì)在開始的時(shí)候空出文件開頭部分。

2.2 MP3 -> PCM

菜雞算法不會(huì)寫這東西,要我們前端處理,因?yàn)橹灰看芜M(jìn)入界面調(diào)用一次,所以這里寫在一個(gè)函數(shù)里面。

我的標(biāo)準(zhǔn)流程是,init -> encoder -> flush -> writeTag->close。

3 具體代碼

有一些是應(yīng)項(xiàng)目要求做的一些處理,各位自動(dòng)忽略。


static lame_global_flags *lame = nullptr;

extern "C" {

/************************************************************************
 *                              PCM 轉(zhuǎn) 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);
    //聲道數(shù)
    lame_set_num_channels(lame, channelCount);
    //輸出采樣率
    lame_set_out_samplerate(lame, sampleRate);
    //位寬
    lame_set_brate(lame, audioFormatBit);
    //音頻質(zhì)量
    lame_set_quality(lame, quality);
    //初始化參數(shù)配置
    lame_init_params(lame);
}

//輸入PCM,輸出MP3
jint JNICALL xxx_RecordService_encoder(JNIEnv *env, jobject, jshortArray pcmBuffer, jbyteArray mp3Buffer, jint sample_num) {
    //lame轉(zhuǎn)換需要short指針參數(shù)
    jshort *pcmBuf = env->GetShortArrayElements(pcmBuffer, JNI_FALSE);
    //獲取MP3數(shù)組長(zhǎng)度
    const jsize mp3_buff_len = env->GetArrayLength(mp3Buffer);
    //獲取buffer指針
    jbyte *mp3Buf =env->GetByteArrayElements(mp3Buffer, JNI_FALSE);

    //編譯后的bytes
    int encode_result;
    //根據(jù)輸入音頻聲道數(shù)判斷
    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;
}
// 清除緩沖區(qū)
jint JNICALL xxx_RecordService_flush(JNIEnv *env,jobject, jbyteArray mp3Buffer) {
    //獲取MP3數(shù)組長(zhǎng)度
    const jsize mp3_buff_len = env->GetArrayLength(mp3Buffer);
    //獲取buffer指針
    jbyte *mp3Buf = env->GetByteArrayElements(mp3Buffer, JNI_FALSE);
    //刷新編碼器緩沖,獲取殘留在編碼器緩沖里的數(shù)據(jù)
    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 轉(zhuǎn) 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強(qiáng)轉(zhuǎn)為float
    float* finalData = new float[forHH.size()];

    for(int z = 0;z<forHH.size();z++){
        finalData[z] = (float)forHH[z];
    }
    
    // 將數(shù)據(jù)給算法
    xxx_audio(finalData,forHH.size());
    return JNI_TRUE;
}
}


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容