WAV转码Mp3
这里为大家讲述,48K采样率,128比特率的WAV音频转MP3,当然也是支持其他采样率音频转码的,需要你自己修改下本地方法,内置了完整的Lame转码库,懂C的完全可以做出一个全能的音频格式转换器,这里只举例一种情况,第一是为了给大家提供一条思路,第二也是为了抛砖引玉!!!(注释方面有偏差的请高手指正)老规矩,先贴图,这里贴两幅图。
第一幅,音频转码图:
第二幅:验证结果图--时间以及内容
(时间看得到,内容只能你们自己下源码,转码后再听了,百分百正确),注意,这里用到的wav是assets资源里面的,48K采样率,其他采样率音频转码时间可能有差别,内容上没差别,如果要精益求精,需要你们自己改jni中的源码,将采样率和采样率抽离出来动态赋值,看效果吧:
图贴了,我们接下来就讲技术和思路了,首先大家熟知的可以用来解码的本地库有FFmpeg,但是这个玩意太大,对于一般的音频处理这块我们这里选择用Lame库,相信用过linux的同学应该对这个非常熟悉,音频解码、转码好帮手,库可以自己去网上下载,当然我上传的源码里也是有的,应该是最新版,8月份下的,这里我们贴下转码的关键代码:
JNIEXPORT void JNICALL Java_com_example_lameonandroid_activity_SongList_convert
(JNIEnv * env, jobject obj, jstring jwav, jstring jmp3){
//init lame
lame_global_flags* gfp = lame_init();
lame_set_num_channels(gfp, 2);//设置声道数
lame_set_in_samplerate(gfp, 48000);//设置采样率(这里很重要,必须跟音频源一致,如果低于音频源,时间会变短,反之亦然)
lame_set_brate(gfp, 128);//设置比特率
lame_set_mode(gfp,0);//* mode = 0,1,2,3 = stereo,jstereo,dualchannel(not supported),mono defaul
lame_set_quality(gfp,2); /* 2=high 5 = medium 7=low */
// 3. 设置MP3的编码方式
lame_set_VBR(gfp, vbr_max_indicator);
LOGE("init lame finished ...");
int initStatusCode = lame_init_params(gfp);
if(initStatusCode >= 0){
//将Java的字符串转成C的字符串
char* cwav = Jstring2CStr(env, jwav);
char* cmp3 = Jstring2CStr(env, jmp3);
FILE* fwav = fopen(cwav, "rt");
FILE* fmp3 = fopen(cmp3, "wb");
//获取文件总长度
int length = get_file_size(cwav);
LOGE("length = %d\n ",length);
//每次读取的数据长度
const int WAV_SIZE = 8192*2;//在模拟信号中每秒取8192*4信号点
const int MP3_SIZE = 8192*2;
short int wav_buffer[WAV_SIZE*2];//这里乘以2是因为取双声道,音频数据都是左声道一帧、右声道一帧循环方式的
unsigned char mp3_buffer[MP3_SIZE];
int read, write, total = 0;
do{
//从fwav中读取数据缓存到wav_buffer,每次读取sizeof(short int)*2,读8192次,
// 取出数据长度sizeof(short int)*2*WAV_SIZE
read = fread(wav_buffer,sizeof(short int)*2,WAV_SIZE,fwav);
if(read != 0){
total += read* sizeof(short int)*2;
publishJavaProgress(env, obj, total);
//第三个参数表示:每个通道取的数据长度
write = lame_encode_buffer_interleaved(gfp,wav_buffer,WAV_SIZE,mp3_buffer,MP3_SIZE);
LOGE("write=%d\n ",write);
}else{
//读到末尾
write = lame_encode_flush(gfp,mp3_buffer,MP3_SIZE);
}
//将转换后的数据缓存mp3_buffer写到fmp3文件里
fwrite(mp3_buffer,sizeof(unsigned char),write,fmp3);
}while(read != 0);
lame_close(gfp);
fclose(fwav);
fclose(fmp3);
LOGE("convert completed...");
}
}<pre name="code" class="java">//获取文件长度
int get_file_size(char* filename)
{
struct stat statbuf;
stat(filename,&statbuf);
int size=statbuf.st_size;
return size;
}
实际代码不多,当然这里有一个地方需要注意的是,从Java传过来的文件路径到了C这里需要稍微加点东西,不然会出错,加什么,加个“\0”,这是Java字符串和C字符串的差别,方法如下:
转码搞定了,但是我们怎么将转码的进度提示的数据传给Java用于进度条更新呢?这里就涉及到了C2Java,也是jni比较重要的部分,C2Java本质来讲是通过反射机制完成的,通过访问虚拟机生成的字节码文件达到访问Java类的目的,这里我贴下代码:char* Jstring2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env, "GB2312"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); //"\0" memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env, barr, ba, 0); return rtn; }
给大家看下对应访问的Java中的方法:jclass clazz = 0; jmethodID methodid = 0; void publishJavaProgress(JNIEnv * env, jobject obj, jint progress) { // 调用java代码 更新程序的进度条 // 1.找到java的LameActivity的class if(clazz == 0){ //注意这里初始化clazz会分配一块内存,不能重复初始化,否则易导致内存溢出,这里的路径必须是全路径 clazz = (*env)->FindClass(env, "com/example/lameonandroid/activity/SongList"); } if (clazz == 0) { LOGI("can't find clazz"); } LOGI(" find clazz"); //2 找到class 里面的方法定义 // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); if(methodid == 0){ methodid = (*env)->GetMethodID(env, clazz, "updateProgress", "(I)V"); } if (methodid == 0) { LOGI("can't find methodid"); } LOGI(" find methodid"); // 这里就是调用Java中的<span style="line-height: 24px; font-family: Consolas;">updateProgress(int progress)方法</span> (*env)->CallVoidMethod(env, obj, methodid, progress); }
Java代码就不贴了,就是简单的本地方法声明,以及编译头文件,当然为了照顾没有jni基础的同学,我会在下一章介绍下如何声明本地方法,并编译头文件。——jni-编译本地方法
最后还是要给到大家源码:LameForAndroid源码