Android使用lame将PCM文件转码为MP3文件

7 篇文章 0 订阅
6 篇文章 0 订阅

Android使用lame将PCM文件转码为MP3文件

1. 软件版本

  1. Android Studio版本:4.2
  2. lame版本:3.100
    编译参考:centos7使用ndk编译lame

2. 项目配置

2.1 新建Native C++项目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 引入lame库文件和头文件

在这里插入图片描述
头文件以及静态库位置:
在这里插入图片描述

2.2 编写build.gradle文件
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.icebear.testappwithffmpeg"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                // 1. 添加参数
                cppFlags "-frtti -fexceptions"
                // 2. 设置支持的架构 "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
                abiFilters "armeabi-v7a"
            }
        }
        // 3. 设置支持的架构 "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
        ndk {
            abiFilters "armeabi-v7a"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.10.2'
        }
    }
    buildFeatures {
        viewBinding true
    }
    // 4. 设置ndk版本,可以不添加
    // ndkVersion "21.0.6113669"
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
2.3 编写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("testappwithlame")

# 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.

# 设置文件位置的变量
file(GLOB SOURCE
        ${CMAKE_SOURCE_DIR}/*.cpp
        ${CMAKE_SOURCE_DIR}/*.h
        )

# 导入编译时的头文件
include_directories(${CMAKE_SOURCE_DIR}/include)

# 设置链接库目录
set(my_lib_path ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${my_lib_path}")

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).
        ${SOURCE} )

# 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.
        native-lib

        mp3lame

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

3. 文件编写

3.1 新建Mp3Encoder.hMp3Encoder.cpp

在这里插入图片描述

Mp3Encoder.h

#ifndef TESTAPPWITHLAME_MP3ENCODER_H
#define TESTAPPWITHLAME_MP3ENCODER_H


#include "include/lame/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 //TESTAPPWITHLAME_MP3ENCODER_H

Mp3Encoder.cpp

#include "Mp3Encoder.h"

Mp3Encoder::Mp3Encoder() {

}

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();
            lame_set_in_samplerate(lameClient, sampleRate);
            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 writeSize = lame_encode_buffer(lameClient, (short int *) leftBuffer,
                                              (short int *) rightBuffer, (int) (readBufferSize / 2),
                                              mp3_buffer, bufferSize);
        fwrite(mp3_buffer, 1, writeSize, mp3File);
    }
    delete[] buffer;
    delete[] leftBuffer;
    delete[] rightBuffer;
    delete[] mp3_buffer;
}

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


3.2 新建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();
}

3.3 修改native-lib.cpp
#include <jni.h>
#include <string>
#include "Mp3Encoder.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_icebear_testappwithlame_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

// 定义全局Mp3Encoder
Mp3Encoder *encoder = NULL;

extern "C"
JNIEXPORT jint JNICALL
Java_com_icebear_testappwithlame_lame_Mp3Encoder_init(JNIEnv *env, jobject thiz, jstring pcm_path,
                                                      jint audio_channels, jint bit_rate,
                                                      jint sample_rate, jstring mp3_path) {
    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, audio_channels, bit_rate);
    env->ReleaseStringUTFChars(pcm_path, pcmPath);
    env->ReleaseStringUTFChars(mp3_path, mp3Path);
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_icebear_testappwithlame_lame_Mp3Encoder_encode(JNIEnv *env, jobject thiz) {
    // TODO: implement encode()
    encoder->Encode();
}

extern "C"
JNIEXPORT void JNICALL
Java_com_icebear_testappwithlame_lame_Mp3Encoder_destroy(JNIEnv *env, jobject thiz) {
    // TODO: implement destroy()
    encoder->Destroy();
}
3.4 修改activity_main.xml

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/edit_pcm_file"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <EditText
        android:id="@+id/edit_mp3_file"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <Button
        android:id="@+id/btn_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始"
        />

</LinearLayout>
3.5 修改MainActivity.java
public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    private ActivityMainBinding binding;

    // 权限设置
    private int requestPermissionCode = 10000;
    private String[] requestPermission = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };

    // 全局
    private Context mContext;
    private Mp3Encoder mp3Encoder;

    // 控件
    private TextView infoText;
    private EditText pcmFileEdit;
    private EditText mp3FileEdit;
    private Button startBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        requestPermission();
        initData();
        initView();
    }

    /**
     * 获取写文件权限
     */
    public void requestPermission() {
        if (Build.VERSION.SDK_INT >= 30) {
            if (!Environment.isExternalStorageManager()) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
                return;
            }
        } else {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                if (PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
                    requestPermissions(requestPermission, requestPermissionCode);
                }
            }
        }
    }

    public void initData() {
        mContext = getBaseContext();
        mp3Encoder = new Mp3Encoder();

        infoText = findViewById(R.id.sample_text);
        pcmFileEdit = findViewById(R.id.edit_pcm_file);
        mp3FileEdit = findViewById(R.id.edit_mp3_file);

        startBtn = findViewById(R.id.btn_start);
    }

    public void initView() {
        pcmFileEdit.setText("/sdcard/Download/gg.pcm");
        mp3FileEdit.setText("/sdcard/Download/gg.mp3");
        startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String pcmFileName = pcmFileEdit.getText().toString();
                String mp3FileName = mp3FileEdit.getText().toString();
                // 参数使用ffprobe查看
                int ret = mp3Encoder.init(pcmFileName, 2, 1411000, 44100, mp3FileName);
                if (ret == 0) {
                    mp3Encoder.encode();
                    mp3Encoder.destroy();
                    infoText.setText("ok");
                } else {
                    infoText.setText("error");
                }
            }
        });
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

4. 结果

结果在这里插入图片描述

5. 问题

5.1 点击按钮后闪退

原因:C文件中的函数没有写返回值
在这里插入图片描述
参考:jni开发 android报错F libc : Fatal signal 4 (SIGILL), code 1 (ILL_ILLOPC), fault addr 0xc0ea1b10 in

5.2 调试时无法命中native方法所在断点

参考:AndroidStudio 调试 Native 代码,无法命中断点
原因:
由于使用的Android模拟器CPUx86的,但编译的架构只有armeabi-v7a,导致手机上运行的静态库架构和手机的 CPU架构不符,虽然代码可以运行,但是不能进行断点调试。
解决方法:
添加x86的静态库,并修改build.gradle文件
添加静态库

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.icebear.testappwithlame"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                // 1. 添加参数
                cppFlags "-frtti -fexceptions"
                // 2. 设置支持的架构 "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
                abiFilters "armeabi-v7a", "x86"
            }
        }

        // 3. 设置支持的架构 "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.10.2'
        }
    }
    buildFeatures {
        viewBinding true
    }

    // 4. 设置ndk版本,可以不添加
    // ndkVersion "21.0.6113669"
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

重新编译后,可以对native方法进行调试。

5.3 点击开始按钮后程序退出

在这里插入图片描述
在这里插入图片描述

可能原因:
文件路径错误,没有找到对应的文件
解决方法:
修改路径,或者添加文件

6. 参考

6.1 使用FFmpeg制作pcm文件参考
  1. ffmpeg提取音频文件命令并转化成pcm格式
  2. 使用ffmpeg 把mp3文件转换为pcm二进制流
6.2 针对报错的解决
  1. jni开发 android报错F libc : Fatal signal 4 (SIGILL), code 1 (ILL_ILLOPC), fault addr 0xc0ea1b10 in
  2. AndroidStudio 调试 Native 代码,无法命中断点

7. 项目地址

https://toscode.gitee.com/icebear221/AndroidFFmpeg

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值