Android 集成 Lame 实现 pcm 转 mp3

2022年5月30日

下载 lame 库

https://lame.sourceforge.io/

安装 NDK 和 CMake

新建 Android Native Module

修改源码

然后将 lame/includelame/libmp3lame 目录下的所有 .h.c 文件复制到 module 中

修改 lame 中的文件

  • 删除 fft.c 文件中 47 行的 include "vector/lame_intrin.h"
  • 修改 set_get.h 文件的 24 行的 #include <lame.h>#include "lame.h"
  • util.h 文件的 574 行的 extern ieee754_float32_t fast_log2(ieee754_float32_t x); 替换为 extern float fast_log2(float x);

修改 CMakeLists.txt 文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("lib_lame")

#设置so库的输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        lib_lame

        # Sets the library as a shared library.
        SHARED

        # 主要是修改这里,native-lib.cpp 为调用c方法的接口
        native-lib.cpp
        lame/bitstream.c
        lame/encoder.c
        lame/fft.c
        lame/gain_analysis.c
        lame/id3tag.c
        lame/lame.c
        lame/mpglib_interface.c
        lame/newmdct.c
        lame/presets.c
        lame/psymodel.c
        lame/quantize.c
        lame/quantize_pvt.c
        lame/reservoir.c
        lame/set_get.c
        lame/tables.c
        lame/takehiro.c
        lame/util.c
        lame/vbrquantize.c
        lame/VbrTag.c
        lame/version.c
        )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        lib_lame

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

修改 build.gradle

android {
    compileSdk 31

    defaultConfig {
        // ...

        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
                cFlags "-DSTDC_HEADERS"
            }
        }

        ndk{
            abiFilters 'x86','x86_64','armeabi-v7a','arm64-v8a'
        }
        
        // ...
    }
    // ...
}

编写 native-lib.cpp 文件

获取 lame 版本

#include "lame/lame.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_github_crayonxiaoxin_lib_1lame_NativeLib_getLameVersion(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF(get_lame_version());
}

在 java 文件中定义对应的 c 方法

class NativeLib {

    /**
     * A native method that is implemented by the 'lib_lame' native library,
     * which is packaged with this application.
     */
//    external fun stringFromJNI(): String
    external fun getLameVersion(): String

    companion object {
        // Used to load the 'lib_lame' library on application startup.
        init {
            System.loadLibrary("lib_lame")
        }
    }
}

然后点击 “Make Project”

调用

引入 lib-lame 库,在 log 中调用

继续添加 mp3 Encoder

创建 mp3_encoder.h

#ifndef LAME_MP3ENCODER_H
#define LAME_MP3ENCODER_H

#include <stdio.h>
#include "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 Destroy();
};

#endif //LAME_MP3ENCODER_H

创建 mp3_encoder.cpp


#include "mp3_encoder.h"
#include "lame.h"

Mp3Encoder::Mp3Encoder() {
}

int Mp3Encoder::Init(const char* pcmFilePath, const char* mp3FilePath, int sampleRate, int channels, int bitRate) {
    int ret = -1;
    pcmFile = fopen(pcmFilePath, "rb");
    if (pcmFile) {
        mp3File = fopen(mp3FilePath, "wb");
        if (mp3File) {
            lameClient = lame_init();
            // in 采样率
            lame_set_in_samplerate(lameClient, sampleRate);
            // out 采样率
            lame_set_out_samplerate(lameClient, sampleRate);
            lame_set_num_channels(lameClient, channels);
            lame_set_brate(lameClient, bitRate / 1000);
            lame_init_params(lameClient);
            ret = 0;
        }
    }
    return ret;
}

void Mp3Encoder::Encode() {
    int bufferSize = 1024 * 256;
    short *buffer = new short[bufferSize / 2];
    short *leftBuffer = new short[bufferSize / 4];
    short *rightBuffer = new short[bufferSize / 4];
    unsigned char* mp3_buffer = new unsigned char[bufferSize];
    size_t readBufferSize = 0;
    while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFile)) > 0) {
        for (int i = 0; i < readBufferSize; i++) {
            if (i % 2 == 0) {
                leftBuffer[i / 2] = buffer[i];
            } else {
                rightBuffer[i / 2] = buffer[i];
            }
        }
        size_t wroteSize = lame_encode_buffer(lameClient, (short int *) leftBuffer, (short int *) rightBuffer, (int)(readBufferSize / 2), mp3_buffer, bufferSize);
        fwrite(mp3_buffer, 1, wroteSize, mp3File);
    }

    delete [] buffer;
    delete [] leftBuffer;
    delete [] rightBuffer;
    delete [] mp3_buffer;
}

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

Mp3Encoder::~Mp3Encoder() {

}

在 CMakeLists.txt 中添加源文件

修改 native-lib.cpp

#include <cstdio>
#include <jni.h>
#include <malloc.h>
#include <cstring>
#include <android/log.h>
#include <jni.h>
#include <string>
#include "lame/lame.h"
#include "lame/mp3_encoder.h"

#define LOG_TAG "System.out.c"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


extern "C" JNIEXPORT jstring JNICALL
Java_com_github_crayonxiaoxin_lib_1lame_NativeLib_getLameVersion(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF(get_lame_version());
}


Mp3Encoder *encoder;

extern "C" JNIEXPORT jint JNICALL
Java_com_github_crayonxiaoxin_lib_1lame_NativeLib_pcmToMp3JNI(
        JNIEnv *env,
        jobject,
        jstring pcm_path,
        jstring mp3_path,
        jint sample_rate,
        jint channel,
        jint bit_rate) {
    const char *pcmPath = env->GetStringUTFChars(pcm_path, NULL);
    const char *mp3Path = env->GetStringUTFChars(mp3_path, NULL);

    encoder = new Mp3Encoder();
    encoder->Init(pcmPath, mp3Path, sample_rate, channel, bit_rate);
    encoder->Encode();

    env->ReleaseStringUTFChars(pcm_path, pcmPath);
    env->ReleaseStringUTFChars(mp3_path, mp3Path);
    return 0;
}

在 java/NativeLib.kt 中添加

external fun pcmToMp3JNI(
        pcmPath: String, mp3Path: String,
        sampleRate: Int, channel: Int, bitRate: Int
    ): Int

参考资料

Android借助LAME库实现MP3编码
安卓音频开发(二)lame编译

小鑫

写写代码, 掉掉头发。

文章评论