Android integrates LAME library to realize pcm to mp3 conversion

1. Cross compile the LAME library

LAME is a very good MP3 encoding engine. In the industry, when transcoding audio files into MP3 format, the most commonly used encoder is the LAME library.

1. Download the LAME library source code

https://sourceforge.net/projects/lame/files/lame/Enter
LAME official website to download LAME source code, I choose the latest version: 3.100

2. Configure the cross-compilation environment

Before compiling LAME, we need to configure the cross-compilation environment first.

Android NDK comes with a cross-tool chain, refer to this article for details: https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn

My NDK path is:/home/lorien/Android/Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin

3. Configure, compile and install LAME

First we need to compile some environment variables used:

#!/bin/bash

export TOOLCHAIN=/home/lorien/Android/Sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64
export TARGET=aarch64-linux-android
export API=21
export AR=$TOOLCHAIN/bin/llvm-ar
export CC=$TOOLCHAIN/bin/$TARGET$API-clang
export AS=$CC
export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++
export LD=$TOOLCHAIN/bin/ld
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$TOOLCHAIN/bin/llvm-strip

export CFLAGS="-fPIC"

The LAME source code to be decompressed next: lame-3.100.tar.gz, enter the root directory of the source code after decompression: /lame-3.100

Configuration:

./configure --host=arm-linux --disable-shared --disable-frontend --enable-static --prefix=/Users/zhanghao43/Desktop/lame/arm64-v8a

Compile:

make clean
make -j4

Install:

make install

After the installation is complete, the generated header files and library files will be under the path specified by prefix, namely: /Users/zhanghao43/Desktop/lame/arm64-v8a

In the generated files, the next files that need to be used are:

  • Header file: /Users/zhanghao43/Desktop/lame/arm64-v8a/include/lame/lame.h
  • Library file: /Users/zhanghao43/Desktop/lame/arm64-v8a/lib/lame/libmp3lame.a

At this point, the cross-compilation of the LAME library is completed.

2. Create an Android Native project using the LAME library

Next, we use the LAME library to create an Android Demo project to complete the recording of PCM audio and the function of converting PCM files to MP3.
We need to create an Android Natvie project.

1. Configuration project

We first put the header files and library files generated by compiling the LAME library into the project. The path is as shown in the figure below:
insert image description here
Then, we modify CMakeLists.txt to let CMake find the LAME header files and library files when compiling and linking. The content of the CMakeLists.txt file is as follows:

cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.

project("lame")

include_directories(
        ${CMAKE_SOURCE_DIR}/include/lame)

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_encoder.cpp)

add_library(mp3lame STATIC IMPORTED)
set_target_properties(mp3lame PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libmp3lame.a)

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 )

target_link_libraries(native-lib
        mp3lame)

2. C++ code

Next, you can write code. First look at mp3_encoder.h, mp3_encoder.cpp

#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
#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);
    }
}

Then we look at the JNI method in native-lib.cpp:

Mp3Encoder *encoder;

extern "C" JNIEXPORT jint JNICALL
Java_com_baidu_lame_MainActivity_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;
}

3. Java code

First paste the XML file of MainAcgtivity

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始录制pcm"
        android:onClick="startRecordPcm"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止录制pcm"
        android:onClick="stopRecordPcm"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="pcm转mp3"
        android:onClick="pcm2mp3"
        />

</LinearLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

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

    companion object {
    
    
        init {
    
    
            System.loadLibrary("native-lib")
        }
    }

    private lateinit var audioRecord: AudioRecord
    private var pcmFilePath: String = ""
    private var buffersize = 1024
    private var isRecord = false

    /**
     * 调用Native代码完成PCM文件转成MP3
     */
    fun pcm2mp3(view: View) {
    
    
        val pcmPath = getPCMFile().absolutePath
        val mp3Path = getMP3File().absolutePath
        val sampleRate = 44100
        val channel = 2
        val bitRate = 64000
        val ret = pcmToMp3JNI(pcmPath, mp3Path, sampleRate, channel, bitRate)
        Toast.makeText(this, "$ret", Toast.LENGTH_SHORT).show()
    }

    /**
     * 开始录制PCM音频文件
     */
    @SuppressLint("MissingPermission")
    fun startRecordPcm(view: View) {
    
    
        val frequency = 44100
        val channelConfig = AudioFormat.CHANNEL_IN_STEREO
        val audioEncoding = AudioFormat.ENCODING_PCM_16BIT
        buffersize = AudioRecord.getMinBufferSize(frequency, channelConfig, audioEncoding)
        audioRecord = AudioRecord(
            MediaRecorder.AudioSource.MIC,
            frequency,
            channelConfig,
            audioEncoding,
            buffersize)
        pcmFilePath = getPCMFile().absolutePath
        isRecord = true
        RecordThread().start()
    }

    /**
     * 结束录制PCM音频文件
     */
    fun stopRecordPcm(view: View) {
    
    
        isRecord = false
    }

    private fun getPCMFile(): File {
    
    
        val root = getExternalFilesDir(null)
        val csvDir = File(root, "/audio/")
        if (!csvDir.exists()) {
    
    
            // 创建csv 目录
            csvDir.mkdir()
        }
        return File(csvDir, "sing.pcm")
    }

    private fun getMP3File(): File {
    
    
        val root = getExternalFilesDir(null)
        val csvDir = File(root, "/audio/")
        if (!csvDir.exists()) {
    
    
            // 创建csv 目录
            csvDir.mkdir()
        }
        return File(csvDir, "sing.mp3")
    }

    /**
     * 录制PCM音频线程
     */
    inner class RecordThread : Thread() {
    
    
        override fun run() {
    
    
            audioRecord.startRecording()
            var fos: FileOutputStream? = null
            try {
    
    
                Log.d(TAG, "pcm文件:$pcmFilePath")
                fos = FileOutputStream(pcmFilePath)
                val bytes = ByteArray(buffersize)
                while (isRecord) {
    
    
                    audioRecord.read(bytes, 0, bytes.size)
                    fos.write(bytes, 0, bytes.size)
                    fos.flush()
                }
                Log.d(TAG, "停止录制")
                audioRecord.stop()
                fos.flush()
            } catch (e: Exception) {
    
    
                Log.d(TAG, "exception: $e")
            } finally {
    
    
                if (fos != null) {
    
    
                    try {
    
    
                        fos.close()
                    } catch (e: Exception) {
    
    
                    }
                }
            }
        }
    }
}

Finally, permissions:

<!--录音-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--读取SD卡-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

In the code, there is no dynamic application permission. After the APP is installed, you need to go to the settings page to enable the corresponding permission for the APP to record audio.

OK, this is the end of this article, thank you for reading.

Guess you like

Origin blog.csdn.net/H_Zhang/article/details/123552513