JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3

本文源码:https://github.com/jt1024/JNIBasicForAndroid

环境

1.Android Studio 3.4.1
2.gradle-4.6-all
3.android sdk 27
4.lame 3.100

一、在Android Studio 新建安卓工程

工程结构如图,文件夹cpp/lamemp3里放lame源文件


JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3_第1张图片
c.jpg

二、基本配置

参照:
JNI/NDK编程(一)——无参函数之Hello world !
JNI/NDK编程(二)——带参函数之模拟登录
JNI/NDK编程(三)——C 调用 Java 成员变量
JNI/NDK编程(四)——C 调用 Java 类中的函数/方法
JNI/NDK编程(五)——通过打印日志debug

三、关于Lame的准备工作

1.下载Lame的源码[lame-3.100下载链接] (https://sourceforge.net/projects/lame/files/)
2.修改Lame的部分内容
将Lame的源码解压后,把libmp3lame文件夹下除了.h和.c的文件都去掉,vector和i386文件夹也都去掉。并将在libmp3lame里剩下的文件,都复制到AS的cpp目录下。同时还要将lame-3.100\include\lame.h这个头文件也复制过去。到为了好管理,可以在cpp下新建一个文件夹把这些源码也放在一起。

JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3_第2张图片
a.png

JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3_第3张图片
b.png

需要修改的部分:
1)util.h中574行将里面的一行 extern ieee754_float32_t fast_log2(ieee754_float32_t x); 改為 extern float fast_log2(float x); 因为Android下并不支持该类型

2)在id3tag.c和machine.h两个文件里,將HAVE_STRCHR和HAVE_MEMCPY的ifdef结构体注释掉。

#ifdef STDC_HEADERS
# include 
# include 
#else
/*# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif*/
char   *strchr(), *strrchr();
/*# ifndef HAVE_MEMCPY
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
# endif*/
#endif

3)fft.c中47行将vector/lame_intrin.h这个头文件注释了或者去掉

#ifdef HAVE_CONFIG_H
# include 
#endif

#include "lame.h"
#include "machine.h"
#include "encoder.h"
#include "util.h"
#include "fft.h"

//#include "vector/lame_intrin.h"

4)set_get.h中24行将include 改为include "lame.h"

#ifndef __SET_GET_H__
#define __SET_GET_H__

#include "lame.h"

四、编写代码

1.编写C++文件 Convert.cpp
路径:ComplieLame/app/src/main/cpp/Convert.cpp

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

#include 

#define LOG_TAG    "jiat"
#define LOGD(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)


char *Jstring2CStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("GB2312");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}


extern "C" JNIEXPORT void JNICALL
Java_com_tao_complielame_ConvertUtil_convertmp3 (JNIEnv *env, jobject instance, jstring jwav, jstring jmp3, jint samplerate, jint channels) {
    char *cwav = Jstring2CStr(env, jwav);
    char *cmp3 = Jstring2CStr(env, jmp3);

    LOGD("wav = %s", cwav);
    LOGD("mp3 = %s", cmp3);

    //打开 wav,MP3文件
    FILE *fwav = fopen(cwav, "rb");
    FILE *fmp3 = fopen(cmp3, "wb");

    short int wav_buffer[8192 * 2];
    unsigned char mp3_buffer[8192];

    //1.初始化lame的编码器
    lame_t lame = lame_init();
    //2. 设置lame mp3编码的采样率
    lame_set_in_samplerate(lame, samplerate);//samplerate=44100
    lame_set_num_channels(lame, channels);//channels=2
    // 3. 设置MP3的编码方式
    lame_set_VBR(lame, vbr_default);

    lame_init_params(lame);
    LOGD("lame init finish");

    int read;
    int write; //代表读了多少个次 和写了多少次
    int total = 0; // 当前读的wav文件的byte数目
    do {

        read = fread(wav_buffer, sizeof(short int) * 2, 8192, fwav);
        LOGD("converting ....%d", total);
        total += read * sizeof(short int) * 2;

        if (read != 0) {
            write = lame_encode_buffer_interleaved(lame, wav_buffer, read, mp3_buffer, 8192);
            //把转化后的mp3数据写到文件里
            fwrite(mp3_buffer, sizeof(unsigned char), write, fmp3);
        }
        if (read == 0) {
            lame_encode_flush(lame, mp3_buffer, 8192);
        }
    } while (read > 0);
    LOGD("convert  finish");
    lame_close(lame);
    fclose(fwav);
    fclose(fmp3);


    // 调用java代码,通知转码结束
    jclass jclazz = env->FindClass("com/tao/complielame/ConvertUtil");
    jmethodID jmethod = env->GetMethodID(jclazz, "convertCompleted", "()V");
    env->CallVoidMethod(instance, jmethod);

}

2.编写C++文件 GetVersion.cpp
路径:ComplieLame/app/src/main/cpp/GetVersion.cpp

#include 
#include "lame.h"

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_tao_complielame_ConvertUtil_getLameVersion(JNIEnv *env, jobject obj) {
    return env->NewStringUTF(get_lame_version());
}

3.编写Jave文件 ConvertUtil
路径:ComplieLame/app/src/main/java/com.tao.complielame.ConvertUtil

package com.tao.complielame;

import android.util.Log;


/**
 * 作者: 麦典威
 * 修改时间:2018/3/18 11:02
 * 版权声明:www.ekwing.com
 * 功能: ${TODO}
 */


public class ConvertUtil {
    private onConvertListener listener;

    static {
        System.loadLibrary("lamemp3");
    }

    public native String getLameVersion();

    /**
     * wav转码为MP3
     *
     * @param wavPath    wav音频路径
     * @param mp3Path    mp3音频路径
     * @param samplerate 采样率(实测可以设值为44100)
     * @param channels   声道数(实测可以设值为2)
     */
    public native void convertmp3(String wavPath, String mp3Path, int samplerate, int channels);

    /**
     * 音频转码成功后,C触发此方法
     */
    public void convertCompleted() {
        Log.e("ConvertUtil", "convertCompleted——>no implement ");
        if (listener != null) {
            listener.showMsg();
        }
    }

    /**
     * 音频转码成功后的回调接口
     */
    interface onConvertListener {
        void showMsg();
    }

    public void setListener(onConvertListener listener) {
        if (listener != null) {
            this.listener = listener;
        }
    }

}

4.编写Activity文件 MainActivity
路径:ComplieLame/app/src/main/java/com.tao.complielame.MainActivity

package com.tao.complielame;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity /*implements onConvertListener*/ {

    private static final String TAG = "MainActivity";

    private EditText et_wav;
    private EditText et_mp3;
    private Button button2, button1;
    private ProgressDialog pd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_wav = (EditText) this.findViewById(R.id.editText1);
        et_mp3 = (EditText) this.findViewById(R.id.editText2);
        button1 = (Button) this.findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                convert();
            }
        });
        button2 = (Button) this.findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getversion();
            }
        });
        pd = new ProgressDialog(this);
    }

    public void getversion() {
        if (convertUtil == null) {
            convertUtil = new ConvertUtil();
        }
        String version = convertUtil.getLameVersion();
        Toast.makeText(this, version, Toast.LENGTH_LONG).show();
    }

    private ConvertUtil convertUtil;

    public void convert() {
        if (convertUtil == null) {
            convertUtil = new ConvertUtil();
        }

        //暂时写死音频路径,实际可以通过EditText输入
        final String wavPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/encode.wav";
        final String mp3path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cuba.mp3";


        File file = new File(wavPath);
        int size = (int) file.length();
        System.out.println("文件大小 " + size);
        if ("".equals(mp3path) || "".equals(wavPath)) {
            Toast.makeText(this, "路径不能为空", Toast.LENGTH_LONG).show();
            return;
        }

        convertUtil.setListener(convertListener);
        new Thread() {
            @Override
            public void run() {
                convertUtil.convertmp3(wavPath, mp3path, 44100, 2);
            }
        }.start();
    }


    ConvertUtil.onConvertListener convertListener = new ConvertUtil.onConvertListener() {
        @Override
        public void showMsg() {
            Log.e(TAG, "jt——>C调用了java的 showResult——>has implement ");
        }
    };
}

5.编写 activity_main.xml





    

    

    

    

6.编写CMakeLists.txt文件,以后如果编写其他jni类,只需替换里面的“lame”

cmake_minimum_required(VERSION 3.4.1)

#设置变量SRC_DIR为lamemp3的所在路径
set(SRC_DIR src/main/cpp/lamemp3)

#指定头文件所在,可以多次调用,指定多个路径
include_directories(src/main/cpp/lamemp3)

#添加自自定义的so库时,有两种方式,一种添加一个目录,一种一个个文件添加

#方式一:设定一个目录
aux_source_directory(src/main/cpp/lamemp3 SRC_LIST)


#方式二:一个个文件的加
#add_library(lame-mp3
#            SHARED
#            ${SRC_DIR}/bitstream.c
#            ${SRC_DIR}/encoder.c
#            ${SRC_DIR}/fft.c
#            ${SRC_DIR}/gain_analysis.c
#            ${SRC_DIR}/id3tag.c
#            ${SRC_DIR}/lame.c
#            ${SRC_DIR}/mpglib_interface.c
#            ${SRC_DIR}/newmdct.c
#            ${SRC_DIR}/presets.c
#            ${SRC_DIR}/psymodel.c
#            ${SRC_DIR}/quantize.c
#            ${SRC_DIR}/quantize_pvt.c
#            ${SRC_DIR}/reservoir.c
#            ${SRC_DIR}/set_get.c
#            ${SRC_DIR}/tables.c
#            ${SRC_DIR}/takehiro.c
#            ${SRC_DIR}/util.c
#            ${SRC_DIR}/vbrquantize.c
#            ${SRC_DIR}/VbrTag.c
#            ${SRC_DIR}/version.c
#            )


#将前面目录下所有的文件都添加进去
add_library( # Sets the name of the library.
             lamemp3

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/GetVersion.cpp
             src/main/cpp/Convert.cpp
             ${SRC_LIST} )


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( # Specifies the target library.
                       lamemp3

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

五、编译

点击build——>Rebuild Project 或 Make Progject
参照:
JNI/NDK编程(一)——无参函数之Hello world !
JNI/NDK编程(二)——带参函数之模拟登录
JNI/NDK编程(三)——C 调用 Java 成员变量
JNI/NDK编程(四)——C 调用 Java 类中的函数/方法
JNI/NDK编程(五)——通过打印日志debug

六、运行程序

附:JNI/NDK编程基础系列导航

JNI/NDK编程(一)——无参函数之Hello world !

JNI/NDK编程(二)——带参函数之模拟登录

JNI/NDK编程(三)——C 调用 Java 成员变量

JNI/NDK编程(四)——C 调用 Java 类中的函数/方法

JNI/NDK编程(五)——通过打印日志debug

JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3

你可能感兴趣的:(JNI/NDK编程(六)——综合实战:通过编译lame实现wav转mp3)