音频转码(压缩)

来源:互联网 发布:centos配置网络 编辑:程序博客网 时间:2024/05/21 06:41

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字符串的差别,方法如下:
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中的<span style="line-height: 24px; font-family: Consolas;">updateProgress(int progress)方法</span>    (*env)->CallVoidMethod(env, obj, methodid, progress);}
给大家看下对应访问的Java中的方法:
Java代码就不贴了,就是简单的本地方法声明,以及编译头文件,当然为了照顾没有jni基础的同学,我会在下一章介绍下如何声明本地方法,并编译头文件。——jni-编译本地方法
最后还是要给到大家源码:LameForAndroid源码


1 0