本文源码: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编程(一)——无参函数之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下新建一个文件夹把这些源码也放在一起。
需要修改的部分:
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
#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