音频转码(压缩)

WAV转码Mp3

这里为大家讲述,48K采样率,128比特率的WAV音频转MP3,当然也是支持其他采样率音频转码的,需要你自己修改下本地方法,内置了完整的Lame转码库,懂C的完全可以做出一个全能的音频格式转换器,这里只举例一种情况,第一是为了给大家提供一条思路,第二也是为了抛砖引玉!!!(注释方面有偏差的请高手指正)老规矩,先贴图,这里贴两幅图。

第一幅,音频转码图:

音频转码(压缩)_第1张图片

第二幅:验证结果图--时间以及内容

(时间看得到,内容只能你们自己下源码,转码后再听了,百分百正确),注意,这里用到的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...");
    }

}
//获取文件长度
int get_file_size(char* filename)
{
    struct stat statbuf;
    stat(filename,&statbuf);
    int size=statbuf.st_size;
    return size;
}
 
   实际代码不多,当然这里有一个地方需要注意的是,从Java传过来的文件路径到了C这里需要稍微加点东西,不然会出错,加什么,加个“\0”,这是Java字符串和C字符串的差别,方法如下: 
  
 
   
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用于进度条更新呢?这里就涉及到了C2Java,也是jni比较重要的部分,C2Java本质来讲是通过反射机制完成的,通过访问虚拟机生成的字节码文件达到访问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中的updateProgress(int progress)方法
    (*env)->CallVoidMethod(env, obj, methodid, progress);
}
给大家看下对应访问的Java中的方法:
Java代码就不贴了,就是简单的本地方法声明,以及编译头文件,当然为了照顾没有jni基础的同学,我会在下一章介绍下如何声明本地方法,并编译头文件。——jni-编译本地方法
最后还是要给到大家源码:LameForAndroid源码


你可能感兴趣的:(jni,lame)