音視頻開發(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右鍵落竹,如下圖
如果你沒有,請(qǐng)點(diǎn)擊File->Settings->Tools->External Tools,點(diǎn)擊+號(hào)货抄,按照下圖照抄即可
最后是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)