Android中LAME实现PCM到MP3的音频转码
LAME编码器集成与配置
LAME作为开源MP3编码器,在高比特率和VBR编码场景表现优异。在Android中集成需通过NDK实现,主要流程如下:
环境准备
- 从官网下载LAME源码(以3.100为例)
- 创建Android Studio C++项目
- 在cpp目录新建lamemp3文件夹,复制以下文件:
- libmp3lame目录所有.c/.h文件
- include/lame.h头文件
源码修改
- 移除fft.c中第47行:
#include "vector/lame_intrin.h" - 修改set_get.h第24行为:
#include "lame.h" - 替换util.h第574行为:
extern float fast_log2(float x);
Gradle配置
android {
defaultConfig {
externalNativeBuild {
cmake {
cFlags "-DSTDC_HEADERS"
}
}
}
}
CMake构建配置
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lameLibs/${ANDROID_ABI})
file(GLOB LAME_SRC "lamemp3/*.c")
file(GLOB LAME_CPP "lamemp3/*.cpp")
include_directories(${CMAKE_CURRENT_LIST_DIR}/lamemp3)
add_library(audio_codec SHARED
native-lib.cpp
${LAME_SRC}
${LAME_CPP}
)
find_library(log-lib log)
target_link_libraries(audio_codec ${log-lib})
音频转码核心实现
pcm2mp3.h头文件定义接口:
class PCMEncoder {
public:
int init(const char* pcmPath, const char* mp3Path,
int sampleRate, int channels, int bitrate);
void encode();
~PCMEncoder();
private:
FILE* pcmFile;
FILE* mp3File;
lame_global_flags* encoder;
};
转码逻辑实现:
int PCMEncoder::init(const char* pcmPath, const char* mp3Path,
int sampleRate, int channels, int bitrate) {
pcmFile = fopen(pcmPath, "rb");
mp3File = fopen(mp3Path, "wb");
if(!pcmFile || !mp3File) return -1;
encoder = lame_init();
lame_set_in_samplerate(encoder, sampleRate);
lame_set_num_channels(encoder, channels);
lame_set_brate(encoder, bitrate);
lame_set_quality(encoder, 2);
lame_init_params(encoder);
return 0;
}
void PCMEncoder::encode() {
const int BUFFER_SIZE = 131072; // 128KB
short* pcmBuffer = new short[BUFFER_SIZE];
short* leftChannel = new short[BUFFER_SIZE/2];
short* rightChannel = new short[BUFFER_SIZE/2];
unsigned char* mp3Buffer = new unsigned char[BUFFER_SIZE];
while(size_t read = fread(pcmBuffer, 2, BUFFER_SIZE, pcmFile)) {
// 分离声道
for(int i=0; i<read; i+=2) {
leftChannel[i/2] = pcmBuffer[i];
rightChannel[i/2] = pcmBuffer[i+1];
}
int encoded = lame_encode_buffer(encoder, leftChannel, rightChannel,
read/2, mp3Buffer, BUFFER_SIZE);
fwrite(mp3Buffer, 1, encoded, mp3File);
}
// 释放资源
delete[] pcmBuffer;
delete[] leftChannel;
delete[] rightChannel;
delete[] mp3Buffer;
}
JNI接口封装
Java层调用接口:
public class AudioEncoder {
static { System.loadLibrary("audio_codec"); }
public native String getVersion();
public native int encodePcmToMp3(String pcmPath, String mp3Path,
int sampleRate, int channels, int bitrate);
public native void release();
}
JNI实现层:
PCMEncoder* encoder;
JNIEXPORT jint JNICALL
Java_com_example_AudioEncoder_encodePcmToMp3(JNIEnv* env, jobject obj,
jstring pcmPath, jstring mp3Path,
jint sampleRate, jint channels, jint bitrate) {
const char* inPath = env->GetStringUTFChars(pcmPath, nullptr);
const char* outPath = env->GetStringUTFChars(mp3Path, nullptr);
encoder = new PCMEncoder();
int status = encoder->init(inPath, outPath, sampleRate, channels, bitrate);
if(status == 0) {
encoder->encode();
}
env->ReleaseStringUTFChars(pcmPath, inPath);
env->ReleaseStringUTFChars(mp3Path, outPath);
return status;
}
Java层调用示例
// 注意:根据实际设备调整采样率参数
audioEncoder.encodePcmToMp3(
pcmFile.getAbsolutePath(),
mp3File.getAbsolutePath(),
sampleRate / 2,
(channelConfig == AudioFormat.CHANNEL_IN_MONO) ? 1 : 2,
128
);