音視頻開發(fā)進(jìn)階指南(第二章)

音視頻開發(fā)進(jìn)階指南(第二章)

書中示例源碼地址:
ffmpeg編譯參考鏈接
使用libmp3lame開源庫范抓,編碼PCM數(shù)據(jù)為MP3蚊伞,結(jié)尾有源碼叫编,分支為chapter_2.x

一杯活、新建NDK工程

網(wǎng)上一大堆。

二蜂绎、下載LAME源碼

lame源碼地址lame官網(wǎng)
下載后解壓栅表,本demo中中使用的是3.100版本

三、拷貝libmp3lame源碼到工程

在項(xiàng)目cpp目錄下新建libmp3lame目錄师枣,用來存放下載的源碼怪瓶。
找到解壓 的libmp3lame文件夾,將里面的.c和.h文件全部復(fù)制到項(xiàng)目的cpp/libmp3lame目錄中践美。 libmp3lame文件夾內(nèi)還包含其他文件夾洗贰,例如vector和i386是不需要的,可以忽略陨倡,然后敛滋,再找到解壓的include文件夾,將lame.h文件拷貝到cpp/libmp3lame目錄中兴革,一共拷貝43個(gè)文件绎晃。

移植過來的代碼要做一些修改才能編譯通過:

1)刪除fft.c文件的47行的”include “vector/lame_intrin.h”“
2)修改set_get.h文件的24行的#include“l(fā)ame.h”
3)將util.h文件的574行的”extern ieee754_float32_tfast_log2(ieee754_float32_t x);”  替換為 “extern float fast_log2(float x);”

四、編寫編碼工具類

mp3_encoder.h

#include <stdio.h>
#include "jni.h"
#include "../libmp3lame/lame.h"

class Mp3Encoder {
private:
    FILE *pcmFile;
    FILE *mp3File;
    lame_t lameClient;
public:
    Mp3Encoder();

    ~Mp3Encoder();

    int Init(const char *pcmFilePath, const char *mp3FilePath,
             int sampleRate, int channels, int bitRate);

    void Encode();

    void Destory();
};

mp3_encoder.cpp

//
// Created by bian on 2019/10/10.
//
#include "stdio.h"
#include <android/log.h>
#include <mp3_encoder.h>

#define LOG_TAG "Mp3Encorder"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);


int Mp3Encoder::Init(const char *pcmFilePath, const char *mp3FilePath,
                     int sampleRate, int channels, int bitRate) {
    int ret = -1;
    pcmFile = fopen(pcmFilePath, "rb");//rb-以二進(jìn)制讀取模式打開文件
    if (pcmFile) {
        mp3File = fopen(mp3FilePath, "wb");//wb-以二進(jìn)制寫入模式打開文件
        if (mp3File) {
            lameClient = lame_init();
            lame_set_in_samplerate(lameClient, sampleRate);//輸入采樣率
            lame_set_out_samplerate(lameClient, sampleRate);//輸出采樣率
            lame_set_num_channels(lameClient, channels);//聲道個(gè)數(shù)
            lame_set_brate(lameClient, bitRate / 1000);//比特率
            lame_init_params(lameClient);
            ret = 0;
            LOGI("set lameClient params done!")
        } else {
            LOGE("open mp3File failed! path=%s", mp3FilePath);
        }

    } else {
        LOGE("open pcmFile failed! path=%s", pcmFilePath);
    }
    return ret;
}

void Mp3Encoder::Encode() {
    int channels = lame_get_num_channels(lameClient);
    LOGI("通道個(gè)數(shù)為:%d", channels);

    int bufferSize = 1024 * 256;//緩沖區(qū)大小
    short *buffer = new short[bufferSize / channels];//讀取pcmFile的緩沖區(qū)

    //雙聲道情況時(shí)杂曲,為左右聲道分配緩沖區(qū)
    short *leftBuffer = new short[bufferSize / 4]; //左聲道緩沖區(qū)
    short *rightBuffer = new short[bufferSize / 4];//右聲道緩沖區(qū)

    unsigned char *mp3_buffer = new unsigned char[bufferSize];

    size_t readBufferSize = 0;
    int frameSize = sizeof(short int) * channels;//一個(gè)幀的字節(jié)數(shù)
    LOGI("frameSize=%d", frameSize);
    while ((readBufferSize = fread(buffer, frameSize, bufferSize / channels,
                                   pcmFile)) > 0) {
        if (channels == 1) {
            LOGI("readBufferSize=%d", readBufferSize);
            //對(duì)pcm數(shù)據(jù)進(jìn)行編碼庶艾,單聲道情況,右聲道為空
            size_t wroteSize = lame_encode_buffer(lameClient,
                                                  (short int *) buffer,//左聲道
                                                  NULL,//右聲道
                                                  (int) (readBufferSize),
                                                  mp3_buffer,//編譯后的數(shù)據(jù)
                                                  bufferSize);
            LOGI("wroteSize=%d", wroteSize);
            fwrite(mp3_buffer, 1, wroteSize, mp3File);//寫入編碼后的數(shù)據(jù)到mp3File
        } else if (channels == 2) {
            for (int i = 0; i < readBufferSize; i++) {
                if (i % 2 == 0) {
                    leftBuffer[i / 2] = buffer[i];
                } else {
                    rightBuffer[i / 2] = buffer[i];
                }
            }
            //對(duì)pcm數(shù)據(jù)進(jìn)行編碼
            size_t wroteSize = lame_encode_buffer(lameClient,
                                                  (short int *) leftBuffer,//左聲道
                                                  (short int *) rightBuffer,//右聲道
                                                  (int) (readBufferSize / 2),
                                                  mp3_buffer,//編譯后的數(shù)據(jù)
                                                  bufferSize);
            fwrite(mp3_buffer, 1, wroteSize, mp3File);//寫入編碼后的數(shù)據(jù)到mp3File
        }
    }
    delete[] buffer;
    delete[] leftBuffer;
    delete[] rightBuffer;
    delete[] mp3_buffer;

}

void Mp3Encoder::Destory() {
    if (pcmFile) {
        fclose(pcmFile);
    }
    if (mp3File) {
        fclose(mp3File);
        lame_close(lameClient);
    }
}

Mp3Encoder::Mp3Encoder() {
}

Mp3Encoder::~Mp3Encoder() {
}

五擎勘、在java中調(diào)用

新建類Mp3Encoder.java

public class Mp3Encoder {
    public native int init(String pcmPath, int audioChannels,
                           int bitRate, int sampleRate, String mp3Path);

    public native void encode();
    public native void destroy();
}

生成頭文件
在Mp3Encoder右鍵落竹,如下圖


image.png

如果你沒有,請(qǐng)點(diǎn)擊File->Settings->Tools->External Tools,點(diǎn)擊+號(hào)货抄,按照下圖照抄即可


image.png

最后是JNI開發(fā)
Mp3Encoder.cpp

Mp3Encoder *encoder;

JNIEXPORT jint JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_init
        (JNIEnv *env, jobject jclazz, jstring pcmFileParam, jint channels,
         jint bitRate, jint sampleRate, jstring mp3FileParam) {
    int ret = -1;
    const char *pcmPath = env->GetStringUTFChars(pcmFileParam, NULL);
    const char *mp3Path = env->GetStringUTFChars(mp3FileParam, NULL);
    encoder = new Mp3Encoder();
    ret = encoder->Init(pcmPath, mp3Path, sampleRate, channels, bitRate);
    env->ReleaseStringUTFChars(mp3FileParam, mp3Path);
    env->ReleaseStringUTFChars(pcmFileParam, pcmPath);
    return ret;
}

JNIEXPORT void JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_encode(JNIEnv *env, jobject jclazz) {
    LOGI("encoder encode");
    encoder->Encode();
}

JNIEXPORT void JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_destroy
        (JNIEnv *env, jobject jclazz) {
    encoder->Destory();
}

六述召、編寫CMakeLists.txt

依賴lame庫的方式有幾種,我直接把lame的源碼與我自己的源碼放在一起進(jìn)行編譯蟹地。


#指定使用的cmake最低版本號(hào)
cmake_minimum_required(VERSION 3.4.1)

#指定工程名积暖,非必須
project(mp3encoder)

#指定頭文件路徑
include_directories(src/main/cpp/include/)

set(SRC_FILES
        src/main/cpp/Mp3Encoder.cpp
        src/main/cpp/mp3_encoder.cpp
        src/main/cpp/libmp3lame/bitstream.c
        src/main/cpp/libmp3lame/encoder.c
        src/main/cpp/libmp3lame/fft.c
        src/main/cpp/libmp3lame/gain_analysis.c
        src/main/cpp/libmp3lame/id3tag.c
        src/main/cpp/libmp3lame/lame.c
        src/main/cpp/libmp3lame/mpglib_interface.c
        src/main/cpp/libmp3lame/newmdct.c
        src/main/cpp/libmp3lame/presets.c
        src/main/cpp/libmp3lame/psymodel.c
        src/main/cpp/libmp3lame/quantize_pvt.c
        src/main/cpp/libmp3lame/quantize.c
        src/main/cpp/libmp3lame/reservoir.c
        src/main/cpp/libmp3lame/set_get.c
        src/main/cpp/libmp3lame/tables.c
        src/main/cpp/libmp3lame/takehiro.c
        src/main/cpp/libmp3lame/util.c
        src/main/cpp/libmp3lame/vbrquantize.c
        src/main/cpp/libmp3lame/VbrTag.c
        src/main/cpp/libmp3lame/version.c
        )
#編譯為共享庫,名稱audioencoder
add_library(
        audioencoder

        # Sets the library as a shared library.
        SHARED

        #源文件路徑
        ${SRC_FILES})



#在默認(rèn)路徑下查找log庫怪与,并保存在變量log-lib中
find_library(log-lib log)

# 鏈接庫
target_link_libraries( # Specifies the target library.
        audioencoder

        # 將Log-lib變量代表的庫夺刑,鏈接到audioencoder庫
        ${log-lib})

七、PCM源與參數(shù)問題

pcm音頻源文件分單通道和雙通道分别,采樣率遍愿,比特率,所以要轉(zhuǎn)碼時(shí)要注意根據(jù)PCM源文件的情況進(jìn)行區(qū)分耘斩,是進(jìn)行單通道編碼還是雙通道編碼沼填。例子中也有體現(xiàn)。否則可以會(huì)出現(xiàn)聲音變慢括授,變快坞笙,有雜音岩饼,有空白等情況。

github源碼
[PCM音頻數(shù)據(jù)](https://pan.baidu.com/s/136sjYRJlQY5uYy7F1hzPKg
提取碼:h7wv)

參考

使用libmp3lame庫編碼mp3
Android移植lame庫(采用CMake)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末薛夜,一起剝皮案震驚了整個(gè)濱河市籍茧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梯澜,老刑警劉巖寞冯,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異晚伙,居然都是意外死亡简十,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門撬腾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恢恼,你說我怎么就攤上這事民傻。” “怎么了场斑?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵漓踢,是天一觀的道長。 經(jīng)常有香客問我漏隐,道長喧半,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任青责,我火速辦了婚禮挺据,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脖隶。我一直安慰自己扁耐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布产阱。 她就那樣靜靜地躺著婉称,像睡著了一般。 火紅的嫁衣襯著肌膚如雪构蹬。 梳的紋絲不亂的頭發(fā)上王暗,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音庄敛,去河邊找鬼俗壹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藻烤,可吹牛的內(nèi)容都是我干的策肝。 我是一名探鬼主播肛捍,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼之众!你這毒婦竟也來了拙毫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤棺禾,失蹤者是張志新(化名)和其女友劉穎缀蹄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膘婶,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缺前,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悬襟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衅码。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脊岳,靈堂內(nèi)的尸體忽然破棺而出逝段,到底是詐尸還是另有隱情,我是刑警寧澤割捅,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布奶躯,位于F島的核電站,受9級(jí)特大地震影響亿驾,放射性物質(zhì)發(fā)生泄漏嘹黔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一莫瞬、第九天 我趴在偏房一處隱蔽的房頂上張望儡蔓。 院中可真熱鬧,春花似錦疼邀、人聲如沸浙值。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽开呐。三九已至,卻和暖如春规求,著一層夾襖步出監(jiān)牢的瞬間筐付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工阻肿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓦戚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓丛塌,卻偏偏與公主長得像较解,于是被迫代替她去往敵國和親畜疾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345