前言
最近在研究wav,mp3,pcm之間的相互轉(zhuǎn)換,發(fā)現(xiàn)mp3的相關(guān)操作,都需要解碼mp3或者編碼mp3,無(wú)法直接對(duì)mp3文件做操作。下面是本文的相關(guān)知識(shí)點(diǎn)。
- wav 轉(zhuǎn) mp3
- pcm 轉(zhuǎn) mp3 (邊錄邊轉(zhuǎn))
- mp3 轉(zhuǎn) wav
- mp3 轉(zhuǎn) pcm (邊播邊轉(zhuǎn))
1. Android 使用 lame wav 轉(zhuǎn)碼 mp3
1.1 準(zhǔn)備工作
下載 lame_x.xx.x 包
Lame
Lame 是最好的mp3編碼器,速度快,效果好,特別是中高碼率和VBR編碼方面。
http://lame.sourceforge.net/
1.2 創(chuàng)建 android 項(xiàng)目 lame
創(chuàng)建jni目錄 并 復(fù)制 lame-x.xx.x 包下的libmp3lame 目錄下的所有 .c和.h文件和 include目錄下的lame.h
1.2.1 在jni目錄下創(chuàng)建 Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LAME_LIBMP3_DIR := lame_3.99.5_libmp3lame
LOCAL_MODULE := mp3lame
LOCAL_SRC_FILES := $(LAME_LIBMP3_DIR)/bitstream.c $(LAME_LIBMP3_DIR)/fft.c $(LAME_LIBMP3_DIR)/id3tag.c $(LAME_LIBMP3_DIR)/mpglib_interface.c $(LAME_LIBMP3_DIR)/presets.c $(LAME_LIBMP3_DIR)/quantize.c $(LAME_LIBMP3_DIR)/reservoir.c $(LAME_LIBMP3_DIR)/tables.c $(LAME_LIBMP3_DIR)/util.c $(LAME_LIBMP3_DIR)/VbrTag.c $(LAME_LIBMP3_DIR)/encoder.c $(LAME_LIBMP3_DIR)/gain_analysis.c $(LAME_LIBMP3_DIR)/lame.c $(LAME_LIBMP3_DIR)/newmdct.c $(LAME_LIBMP3_DIR)/psymodel.c $(LAME_LIBMP3_DIR)/quantize_pvt.c $(LAME_LIBMP3_DIR)/set_get.c $(LAME_LIBMP3_DIR)/takehiro.c $(LAME_LIBMP3_DIR)/vbrquantize.c $(LAME_LIBMP3_DIR)/version.c lame_util.c
include $(BUILD_SHARED_LIBRARY)
1.2.2 在jni目錄下創(chuàng)建 Application.mk文件
APP_PLATFORM := android-9
APP_ABI := all
APP_CFLAGS += -DSTDC_HEADERS
1.2.3 然后在gradle里面進(jìn)行配置(在使用該jni的gradle里進(jìn)行配置)
sourceSets.main {
jni.srcDirs = [] // This prevents the auto generation of Android.mk
jniLibs.srcDir 'src/main/libs' // This is not necessary unless you have precompiled libraries in your project.
}
1.2.4 最后ndk-build生成相應(yīng).so庫(kù)(ndk-build會(huì)生成相應(yīng)的.h頭文件)
1.3 編寫(xiě)wav轉(zhuǎn)mp3的lame_util.c
大致分為兩步
- 通過(guò)Jstring2CStr方法將java中的jstring類(lèi)型轉(zhuǎn)化成c語(yǔ)言的char字符串
- 然后再通過(guò)convert方法將wav轉(zhuǎn)碼成mp3文件
(convert方法的參數(shù)為,wav路徑,mp3路徑,采樣率,聲道數(shù),比特率)
下面為大家科普一下相關(guān)參數(shù)以及知識(shí)點(diǎn)
1.3.1 Lame相關(guān)參數(shù)
- 采樣率(sampleRate):采樣率越高聲音的還原度越好。
- 比特率(bitrate):每秒鐘的數(shù)據(jù)量,越高音質(zhì)越好。
- 聲道數(shù)(channels):聲道的數(shù)量,通常只有單聲道和雙聲道,雙聲道即所謂的立體聲。
- 比特率控制模式:ABR、VBR、CBR,這3中模式含義很容易查詢(xún)到,不在贅述
Lame采樣率支持(Hz)
| MPEG1 | MPEG2 | MPEG2.5 |
|---|---|---|
| 44100 | 22050 | 11025 |
| 48000 | 24000 | 12000 |
| 32000 | 16000 | 8000 |
Lame比特率支持(bit/s)
| MPEG1 | MPEG2 | MPEG2.5 |
|---|---|---|
| 32 | 8 | 8 |
| 40 | 16 | 16 |
| 48 | 24 | 24 |
| 56 | 32 | 32 |
| 64 | 40 | 40 |
| 80 | 48 | 48 |
| 96 | 56 | 56 |
| 112 | 64 | 64 |
| 128 | 80 | |
| 160 | 96 | |
| 192 | 112 | |
| 224 | 128 | |
| 256 | 144 | |
| 320 | 160 |
1.3.2 編碼流程
初始化編碼參數(shù)
-
lame_init:初始化一個(gè)編碼參數(shù)的數(shù)據(jù)結(jié)構(gòu),給使用者用來(lái)設(shè)置參數(shù)。
設(shè)置編碼參數(shù)
-
lame_set_in_samplerate:設(shè)置被輸入編碼器的原始數(shù)據(jù)的采樣率。 -
lame_set_out_samplerate:設(shè)置最終mp3編碼輸出的聲音的采樣率,如果不設(shè)置則和輸入采樣率一樣。 -
lame_set_num_channels:設(shè)置被輸入編碼器的原始數(shù)據(jù)的聲道數(shù)。 -
lame_set_mode:設(shè)置最終mp3編碼輸出的聲道模式,如果不設(shè)置則和輸入聲道數(shù)一樣。參數(shù)是枚舉,STEREO代表雙聲道,MONO代表單聲道。 -
lame_set_VBR:設(shè)置比特率控制模式,默認(rèn)是CBR,但是通常我們都會(huì)設(shè)置VBR。參數(shù)是枚舉,vbr_off代表CBR,vbr_abr代表ABR(因?yàn)锳BR不常見(jiàn),所以本文不對(duì)ABR做講解)vbr_mtrh代表VBR。 -
lame_set_brate:設(shè)置CBR的比特率,只有在CBR模式下才生效。 -
lame_set_VBR_mean_bitrate_kbps:設(shè)置VBR的比特率,只有在VBR模式下才生效。
其中每個(gè)參數(shù)都有默認(rèn)的配置,如非必要可以不設(shè)置。這里只介紹了幾個(gè)關(guān)鍵的設(shè)置接口,還有其他的設(shè)置接口可以參考lame.h(lame的文檔里只有命令行程序的用法,沒(méi)有庫(kù)接口的用法)。
初始化編碼器器
lame_init_params:根據(jù)上面設(shè)置好的參數(shù)建立編碼器
編碼PCM數(shù)據(jù)
-
lame_encode_buffer或lame_encode_buffer_interleaved:將PCM數(shù)據(jù)送入編碼器,獲取編碼出的mp3數(shù)據(jù)。這些數(shù)據(jù)寫(xiě)入文件就是mp3文件。 - 其中
lame_encode_buffer輸入的參數(shù)中是雙聲道的數(shù)據(jù)分別輸入的,lame_encode_buffer_interleaved輸入的參數(shù)中雙聲道數(shù)據(jù)是交錯(cuò)在一起輸入的。具體使用哪個(gè)需要看采集到的數(shù)據(jù)是哪種格式的,不過(guò)現(xiàn)在的設(shè)備采集到的數(shù)據(jù)大部分都是雙聲道數(shù)據(jù)是交錯(cuò)在一起。 - 單聲道輸入只能使用
lame_encode_buffer,把單聲道數(shù)據(jù)當(dāng)成左聲道數(shù)據(jù)傳入,右聲道傳NULL即可。 - 調(diào)用這兩個(gè)函數(shù)時(shí)需要傳入一塊內(nèi)存來(lái)獲取編碼器出的數(shù)據(jù),這塊內(nèi)存的大小lame給出了一種建議的計(jì)算方式:采樣率/20+7200。
結(jié)束編碼
lame_encode_flush:刷新編碼器緩沖,獲取殘留在編碼器緩沖里的數(shù)據(jù)。這部分?jǐn)?shù)據(jù)也需要寫(xiě)入mp3文件
銷(xiāo)毀編碼器
lame_close銷(xiāo)毀編碼器,釋放資源。
#include "lame_3.99.5_libmp3lame/lame.h"
#include "com_czt_mp3recorder_util_LameUtil.h"
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <sys/stat.h>
/**
* 返回值 char* 這個(gè)代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類(lèi)型轉(zhuǎn)化成一個(gè)c語(yǔ)言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個(gè)java字符串 "GB2312"
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr); // byte數(shù)組的長(zhǎng)度
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;
}
int flag = 0;
/**
* wav轉(zhuǎn)換mp3
*/
JNIEXPORT void JNICALL Java_com_czt_mp3recorder_util_LameUtil_convert
(JNIEnv * env, jobject obj, jstring jwav, jstring jmp3, jint inSamplerate, jint inChannel, jint outBitrate) {
char* cwav =Jstring2CStr(env,jwav) ;
char* cmp3=Jstring2CStr(env,jmp3);
//1.打開(kāi) wav,MP3文件
FILE* fwav = fopen(cwav,"rb");
FILE* fmp3 = fopen(cmp3,"wb+");
int channel = inChannel;//聲道數(shù)
short int wav_buffer[8192*channel];
unsigned char mp3_buffer[8192];
//1.初始化lame的編碼器
lame_t lameConvert = lame_init();
//2. 設(shè)置lame mp3編碼的采樣率
lame_set_in_samplerate(lameConvert , inSamplerate);
lame_set_out_samplerate(lameConvert, inSamplerate);
lame_set_num_channels(lameConvert,channel);
lame_set_mode(lameConvert, MONO);
// 3. 設(shè)置MP3的編碼方式
lame_set_VBR(lameConvert, vbr_default);
lame_init_params(lameConvert);
int read ; int write; //代表讀了多少個(gè)次 和寫(xiě)了多少次
int total=0; // 當(dāng)前讀的wav文件的byte數(shù)目
do{
if(flag==404){
return;
}
read = fread(wav_buffer,sizeof(short int)*channel, 8192,fwav);
total += read* sizeof(short int)*channel;
if(read!=0){
write = lame_encode_buffer(lameConvert, wav_buffer, NULL, read, mp3_buffer, 8192);
//write = lame_encode_buffer_interleaved(lame,wav_buffer,read,mp3_buffer,8192);
}else{
write = lame_encode_flush(lameConvert,mp3_buffer,8192);
}
//把轉(zhuǎn)化后的mp3數(shù)據(jù)寫(xiě)到文件里
fwrite(mp3_buffer,1,write,fmp3);
}while(read!=0);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
}
1.3.3 導(dǎo)入庫(kù)以及創(chuàng)建native方法
創(chuàng)建LameUtil類(lèi),并導(dǎo)入相應(yīng)的庫(kù),并創(chuàng)建convert方法
public class LameUtil {
static {
System.loadLibrary("mp3lame");
}
public native static void convert(String wavFile, String mp3File, int inSamplerate, int inChannel, int outBitrate);
}
1.3.4 調(diào)用native方法
new Thread(new Runnable() {
@Override
public void run() {
WavFileReader reader = new WavFileReader();
try {
if (reader.openFile(mWAVPathEt.getText().toString())) {
//讀取wav文件的頭信息
WavFileHeader wavFileHeader = reader.getmWavFileHeader();
//把獲取到的wav頭信息傳入natvie方法
LameUtil.convert(mWAVPathEt.getText().toString(), mMP3PathEt.getText().toString(), wavFileHeader.getmSampleRate(), wavFileHeader.getmNumChannel(), wavFileHeader.getmByteRate());
}
if (mFile.exists()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WAVTransMP3Activity.this, "轉(zhuǎn)碼成功:\t" + mFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(WAVTransMP3Activity.this, "轉(zhuǎn)碼失敗", Toast.LENGTH_SHORT).show();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
那么轉(zhuǎn)碼成功后就大功告成了嗎?
遺憾的告訴你并沒(méi)有那么簡(jiǎn)單,因?yàn)檗D(zhuǎn)碼出來(lái)的音頻最開(kāi)始會(huì)啪的一聲,沒(méi)錯(cuò),每一個(gè)音頻都有,無(wú)一幸免,意不意外,驚不驚喜!!!

這里啪的一聲是什么原因呢?
是因?yàn)檗D(zhuǎn)碼的時(shí)候把wav文件的頭信息也一起轉(zhuǎn)了,才會(huì)出現(xiàn)這種情況。那要怎么解決呢?
當(dāng)然是跳過(guò)這個(gè)頭信息,直接從數(shù)據(jù)開(kāi)始讀取。
fseek(fwav, 4*1024, SEEK_CUR);
對(duì),就是這一句話(huà)就可以了,完整代碼是這樣的
/**
* 返回值 char* 這個(gè)代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類(lèi)型轉(zhuǎn)化成一個(gè)c語(yǔ)言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個(gè)java字符串 "GB2312"
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr); // byte數(shù)組的長(zhǎng)度
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;
}
int flag = 0;
/**
* wav轉(zhuǎn)換mp3
*/
JNIEXPORT void JNICALL Java_com_czt_mp3recorder_util_LameUtil_convert
(JNIEnv * env, jobject obj, jstring jwav, jstring jmp3, jint inSamplerate, jint inChannel, jint outBitrate) {
char* cwav =Jstring2CStr(env,jwav) ;
char* cmp3=Jstring2CStr(env,jmp3);
//1.打開(kāi) wav,MP3文件
FILE* fwav = fopen(cwav,"rb");
fseek(fwav, 4*1024, SEEK_CUR);
FILE* fmp3 = fopen(cmp3,"wb+");
int channel = inChannel;//單聲道
short int wav_buffer[8192*channel];
unsigned char mp3_buffer[8192];
//1.初始化lame的編碼器
lame_t lameConvert = lame_init();
//2. 設(shè)置lame mp3編碼的采樣率
lame_set_in_samplerate(lameConvert , inSamplerate);
lame_set_out_samplerate(lameConvert, inSamplerate);
lame_set_num_channels(lameConvert,channel);
lame_set_mode(lameConvert, MONO);
// 3. 設(shè)置MP3的編碼方式
lame_set_VBR(lameConvert, vbr_default);
lame_init_params(lameConvert);
int read ; int write; //代表讀了多少個(gè)次 和寫(xiě)了多少次
int total=0; // 當(dāng)前讀的wav文件的byte數(shù)目
do{
if(flag==404){
return;
}
read = fread(wav_buffer,sizeof(short int)*channel, 8192,fwav);
total += read* sizeof(short int)*channel;
if(read!=0){
write = lame_encode_buffer(lameConvert, wav_buffer, NULL, read, mp3_buffer, 8192);
//write = lame_encode_buffer_interleaved(lame,wav_buffer,read,mp3_buffer,8192);
}else{
write = lame_encode_flush(lameConvert,mp3_buffer,8192);
}
//把轉(zhuǎn)化后的mp3數(shù)據(jù)寫(xiě)到文件里
fwrite(mp3_buffer,1,write,fmp3);
}while(read!=0);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
}
然后再轉(zhuǎn)碼之后,就沒(méi)有啪的一聲了,開(kāi)不開(kāi)心。
然而,你仔細(xì)觀察,發(fā)現(xiàn)是不是秒數(shù)不對(duì)了,想不想哭

那秒數(shù)沒(méi)有對(duì)是為啥呢?
因?yàn)樾枰獙?xiě)入相關(guān)的VBRTAG,也可以理解為mp3的頭信息。
寫(xiě)入VBRTAG
lame_mp3_tags_fid:向一個(gè)文件指針中寫(xiě)入規(guī)范的VBRTAG。VBRTAG的作用是記錄整個(gè)mp3的一些信息,通常用于VBR模式下的編碼,因?yàn)閂BR模式下比特率不固定,無(wú)法直接計(jì)算出播放的時(shí)長(zhǎng)和跳躍點(diǎn),所以在mp3的開(kāi)頭部分插入一個(gè)VBRTAG。
VBRTAG有幾種規(guī)范,但是lame支持的是最通用的規(guī)范。
注意
lame_mp3_tags_fid函數(shù)的參數(shù)需要一個(gè)FILE *類(lèi)型代表要寫(xiě)入的文件,這個(gè)文件一定是之前編碼時(shí)寫(xiě)入了mp3數(shù)據(jù)的文件,VBRTAG是需要卸載mp3的開(kāi)頭的,之前的編碼過(guò)程中會(huì)自動(dòng)空出寫(xiě)入VBRTAG所需要的空間,這個(gè)函數(shù)內(nèi)會(huì)自動(dòng)尋找合適的文件偏移然后覆蓋,所以當(dāng)前的文件偏移是無(wú)關(guān)緊要的,但是打開(kāi)文件的時(shí)候一定要以讀寫(xiě)模式打開(kāi)。注意我提到了之前的編碼過(guò)程中會(huì)自動(dòng)空出寫(xiě)入VBRTAG所需要的空間,所以如果結(jié)束編碼后不調(diào)用
lame_mp3_tags_fid寫(xiě)入VBRTAG就會(huì)導(dǎo)致這部分內(nèi)容為空,雖然不影響播放,但是會(huì)影響很多播放器對(duì)于時(shí)長(zhǎng)和跳躍點(diǎn)的計(jì)算。那么對(duì)于非VBR模式也需要寫(xiě)入VBRTAG嗎?是的,lame對(duì)于非VBR模式也會(huì)預(yù)留出VBRTAG的空間,所以非VBR模式的編碼最后也需要寫(xiě)入VBRTAG。
說(shuō)了那么多,意思就是這個(gè)函數(shù)是應(yīng)該在lame_encode_flush()之后調(diào), 當(dāng)所有數(shù)據(jù)都寫(xiě)入完畢了再調(diào)用。仔細(xì)想想也很合理, 這時(shí)才能確定文件的總幀數(shù)。
于是,我們就這樣寫(xiě)
/**
* 返回值 char* 這個(gè)代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類(lèi)型轉(zhuǎn)化成一個(gè)c語(yǔ)言中的char 字符串
*/
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個(gè)java字符串 "GB2312"
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr); // byte數(shù)組的長(zhǎng)度
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;
}
int flag = 0;
/**
* wav轉(zhuǎn)換mp3
*/
JNIEXPORT void JNICALL Java_com_czt_mp3recorder_util_LameUtil_convert
(JNIEnv * env, jobject obj, jstring jwav, jstring jmp3, jint inSamplerate, jint inChannel, jint outBitrate) {
char* cwav =Jstring2CStr(env,jwav) ;
char* cmp3=Jstring2CStr(env,jmp3);
//1.打開(kāi) wav,MP3文件
FILE* fwav = fopen(cwav,"rb");
fseek(fwav, 4*1024, SEEK_CUR);
FILE* fmp3 = fopen(cmp3,"wb+");
int channel = inChannel;//單聲道
short int wav_buffer[8192*channel];
unsigned char mp3_buffer[8192];
//1.初始化lame的編碼器
lame_t lameConvert = lame_init();
//2. 設(shè)置lame mp3編碼的采樣率
lame_set_in_samplerate(lameConvert , inSamplerate);
lame_set_out_samplerate(lameConvert, inSamplerate);
lame_set_num_channels(lameConvert,channel);
lame_set_mode(lameConvert, MONO);
// 3. 設(shè)置MP3的編碼方式
lame_set_VBR(lameConvert, vbr_default);
lame_init_params(lameConvert);
int read ; int write; //代表讀了多少個(gè)次 和寫(xiě)了多少次
int total=0; // 當(dāng)前讀的wav文件的byte數(shù)目
do{
if(flag==404){
return;
}
read = fread(wav_buffer,sizeof(short int)*channel, 8192,fwav);
total += read* sizeof(short int)*channel;
if(read!=0){
write = lame_encode_buffer(lameConvert, wav_buffer, NULL, read, mp3_buffer, 8192);
//write = lame_encode_buffer_interleaved(lame,wav_buffer,read,mp3_buffer,8192);
}else{
write = lame_encode_flush(lameConvert,mp3_buffer,8192);
}
//把轉(zhuǎn)化后的mp3數(shù)據(jù)寫(xiě)到文件里
fwrite(mp3_buffer,1,write,fmp3);
}while(read!=0);
lame_mp3_tags_fid(lameConvert,fmp3);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
}
再重新ndk-build,再編譯一次,重新安裝,再轉(zhuǎn)碼一次,大功終于告成了
2. Android 使用 lame pcm 轉(zhuǎn)碼 mp3(邊錄邊轉(zhuǎn))
邊錄邊轉(zhuǎn)的原理就是,拿到pcm數(shù)據(jù),馬上轉(zhuǎn)成mp3數(shù)據(jù)并寫(xiě)入相關(guān)文件,當(dāng)錄制結(jié)束,轉(zhuǎn)換也同時(shí)結(jié)束。
2.1 DataEncodeThread的編寫(xiě)
那么肯定需要跑一個(gè)線(xiàn)程來(lái)進(jìn)行解碼和寫(xiě)入的工作,但是每次寫(xiě)入和轉(zhuǎn)換肯定有很多次,這里使用HandlerThread來(lái)進(jìn)行。
public class DataEncodeThread extends HandlerThread implements AudioRecord.OnRecordPositionUpdateListener {
private StopHandler mHandler;
private static final int PROCESS_STOP = 1;
private byte[] mMp3Buffer;
private FileOutputStream mFileOutputStream;
private static class StopHandler extends Handler {
private DataEncodeThread encodeThread;
public StopHandler(Looper looper, DataEncodeThread encodeThread) {
super(looper);
this.encodeThread = encodeThread;
}
@Override
public void handleMessage(Message msg) {
if (msg.what == PROCESS_STOP) {
//處理緩沖區(qū)中的數(shù)據(jù)
while (encodeThread.processData() > 0);
// Cancel any event left in the queue
removeCallbacksAndMessages(null);
encodeThread.flushAndRelease();
getLooper().quit();
}
}
}
/**
* Constructor
* @param file file
* @param bufferSize bufferSize
* @throws FileNotFoundException file not found
*/
public DataEncodeThread(File file, int bufferSize) throws FileNotFoundException {
super("DataEncodeThread");
this.mFileOutputStream = new FileOutputStream(file);
mMp3Buffer = new byte[(int) (7200 + (bufferSize * 2 * 1.25))];
}
@Override
public synchronized void start() {
super.start();
mHandler = new StopHandler(getLooper(), this);
}
private void check() {
if (mHandler == null) {
throw new IllegalStateException();
}
}
public void sendStopMessage() {
check();
mHandler.sendEmptyMessage(PROCESS_STOP);
}
public Handler getHandler() {
check();
return mHandler;
}
@Override
public void onMarkerReached(AudioRecord recorder) {
// Do nothing
}
@Override
public void onPeriodicNotification(AudioRecord recorder) {
processData();
}
/**
* 從緩沖區(qū)中讀取并處理數(shù)據(jù),使用lame編碼MP3
* @return 從緩沖區(qū)中讀取的數(shù)據(jù)的長(zhǎng)度
* 緩沖區(qū)中沒(méi)有數(shù)據(jù)時(shí)返回0
*/
private int processData() {
if (mTasks.size() > 0) {
Task task = mTasks.remove(0);
short[] buffer = task.getData();
int readSize = task.getReadSize();
int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
if (encodedSize > 0){
try {
mFileOutputStream.write(mMp3Buffer, 0, encodedSize);
} catch (IOException e) {
e.printStackTrace();
}
}
return readSize;
}
return 0;
}
/**
* Flush all data left in lame buffer to file
*/
private void flushAndRelease() {
//將MP3結(jié)尾信息寫(xiě)入buffer中
final int flushResult = LameUtil.flush(mMp3Buffer);
if (flushResult > 0) {
try {
mFileOutputStream.write(mMp3Buffer, 0, flushResult);
} catch (IOException e) {
e.printStackTrace();
}finally{
if (mFileOutputStream != null) {
try {
mFileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
LameUtil.close();
}
}
}
private List<Task> mTasks = Collections.synchronizedList(new ArrayList<Task>());
public void addTask(short[] rawData, int readSize){
mTasks.add(new Task(rawData, readSize));
}
private class Task{
private short[] rawData;
private int readSize;
public Task(short[] rawData, int readSize){
this.rawData = rawData.clone();
this.readSize = readSize;
}
public short[] getData(){
return rawData;
}
public int getReadSize(){
return readSize;
}
}
}
下面是調(diào)用錄音的代碼
/**
* Start recording. Create an encoding thread. Start record from this
* thread.
*
* @throws IOException initAudioRecorder throws
*/
public void start() throws IOException {
if (mIsRecording) {
return;
}
mIsRecording = true; // 提早,防止init或startRecording被多次調(diào)用
initAudioRecorder();
mAudioRecord.startRecording();
new Thread() {
@Override
public void run() {
//設(shè)置線(xiàn)程權(quán)限
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
while (mIsRecording) {
int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize);
if (readSize > 0) {
mEncodeThread.addTask(mPCMBuffer, readSize);
calculateRealVolume(mPCMBuffer, readSize);
}
}
// release and finalize audioRecord
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
// stop the encoding thread and try to wait
// until the thread finishes its job
mEncodeThread.sendStopMessage();
}
/**
* 此計(jì)算方法來(lái)自samsung開(kāi)發(fā)范例
*
* @param buffer buffer
* @param readSize readSize
*/
private void calculateRealVolume(short[] buffer, int readSize) {
double sum = 0;
for (int i = 0; i < readSize; i++) {
// 這里沒(méi)有做運(yùn)算的優(yōu)化,為了更加清晰的展示代碼
sum += buffer[i] * buffer[i];
}
if (readSize > 0) {
double amplitude = sum / readSize;
mVolume = (int) Math.sqrt(amplitude);
}
}
}.start();
}
這樣也就實(shí)現(xiàn)了相關(guān)的功能,具體轉(zhuǎn)換的方法上面已經(jīng)提過(guò),這里就不再贅述了。
3. mp3 轉(zhuǎn) wav
既然是要實(shí)現(xiàn)mp3轉(zhuǎn)換為wav格式,那么必須先解碼mp3為pcm數(shù)據(jù),再將pcm數(shù)據(jù)寫(xiě)入相關(guān)的頭信息,這樣就實(shí)現(xiàn)了mp3轉(zhuǎn)換為wav。
下面是解碼mp3文件為pcm文件:
public static String fenLiData(String path, String newPath) throws IOException {
File file = new File(path);// 原文件
File file1 = new File(path + "01");// 分離ID3V2后的文件,這是個(gè)中間文件,最后要被刪除
File file2 = new File(newPath);// 分離id3v1后的文件
RandomAccessFile rf = new RandomAccessFile(file, "rw");// 隨機(jī)讀取文件
FileOutputStream fos = new FileOutputStream(file1);
byte ID3[] = new byte[3];
rf.read(ID3);
String ID3str = new String(ID3);
// 分離ID3v2
if (ID3str.equals("ID3")) {
rf.seek(6);
byte[] ID3size = new byte[4];
rf.read(ID3size);
int size1 = (ID3size[0] & 0x7f) << 21;
int size2 = (ID3size[1] & 0x7f) << 14;
int size3 = (ID3size[2] & 0x7f) << 7;
int size4 = (ID3size[3] & 0x7f);
int size = size1 + size2 + size3 + size4 + 10;
rf.seek(size);
int lens = 0;
byte[] bs = new byte[1024 * 4];
while ((lens = rf.read(bs)) != -1) {
fos.write(bs, 0, lens);
}
fos.close();
rf.close();
} else {// 否則完全復(fù)制文件
int lens = 0;
rf.seek(0);
byte[] bs = new byte[1024 * 4];
while ((lens = rf.read(bs)) != -1) {
fos.write(bs, 0, lens);
}
fos.close();
rf.close();
}
RandomAccessFile raf = new RandomAccessFile(file1, "rw");
byte TAG[] = new byte[3];
raf.seek(raf.length() - 128);
raf.read(TAG);
String tagstr = new String(TAG);
if (tagstr.equals("TAG")) {
FileOutputStream fs = new FileOutputStream(file2);
raf.seek(0);
byte[] bs = new byte[(int) (raf.length() - 128)];
raf.read(bs);
fs.write(bs);
raf.close();
fs.close();
} else {// 否則完全復(fù)制內(nèi)容至file2
FileOutputStream fs = new FileOutputStream(file2);
raf.seek(0);
byte[] bs = new byte[1024 * 4];
int len = 0;
while ((len = raf.read(bs)) != -1) {
fs.write(bs, 0, len);
}
raf.close();
fs.close();
}
if (file1.exists())// 刪除中間文件
{
file1.delete();
}
return file2.getAbsolutePath();
}
然后再進(jìn)行pcm文件寫(xiě)入頭信息:
/**
* pcm文件轉(zhuǎn)wav文件
*
* @param inFilename 源文件路徑
* @param outFilename 目標(biāo)文件路徑
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = 2;
long byteRate = 16 * mSampleRate * channels / 8;
byte[] data = new byte[mBufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加入wav文件頭
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W'; //WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
header[36] = 'd'; //data
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
以上便是mp3轉(zhuǎn)換為wav 的方法。
4. mp3 轉(zhuǎn) pcm (邊播邊轉(zhuǎn))
其實(shí)和pcm轉(zhuǎn)mp3邊錄邊轉(zhuǎn)的原理是一樣的,也是拿到數(shù)據(jù)再進(jìn)行解碼,不過(guò)這次要用到的mad庫(kù)來(lái)進(jìn)行解碼工作。
private void startDecode() {
if (ret == -1) {
Log.i("conowen", "Couldn't open file '" + mMP3PathEt.getText().toString() + "'");
} else {
mThreadFlag = true;
initAudioPlayer();
audioBuffer = new short[1024 * 1024];
mThread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (mThreadFlag) {
if (null != mAudioTrack && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
// ****從libmad處獲取data******/
MP3Decoder.getAudioBuf(audioBuffer,
mAudioMinBufSize);
if(null != mAudioTrack){
mAudioTrack.write(audioBuffer, 0, mAudioMinBufSize);
}
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
});
}
}
其中,MP3Decoder.getAudioBuf(audioBuffer,mAudioMinBufSize);是調(diào)用的mad的庫(kù)的方法,具體方法網(wǎng)上都有提供,這里只是貼出相應(yīng)的c代碼。
#define LOG_TAG "NativeMP3Decoder"
#include <fcntl.h>
#include <jni.h>
#include "mad/mad.h"
#include "NativeMP3Decoder.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <android/log.h>
#include "FileSystem.h"
#define INPUT_BUFFER_SIZE (8192/4)
#define OUTPUT_BUFFER_SIZE 8192 /* Must be an integer multiple of 4. */
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
//int g_size;
/**
* Struct holding the pointer to a wave file.
*/
typedef struct
{
int size;
int64_t fileStartPos;
T_pFILE file;
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
mad_timer_t timer;
int leftSamples;
int offset;
unsigned char inputBuffer[INPUT_BUFFER_SIZE];
} MP3FileHandle;
/** static WaveFileHandle array **/
static inline int readNextFrame( MP3FileHandle* mp3 );
static MP3FileHandle* Handle;
unsigned int g_Samplerate;
/**
* Seeks a free handle in the handles array and returns its index or -1 if no handle could be found
*/
extern int file_open(const char *filename, int flags);
extern int file_read(T_pFILE fd, unsigned char *buf, int size);
extern int file_write(T_pFILE fd, unsigned char *buf, int size);
extern int64_t file_seek(T_pFILE fd, int64_t pos, int whence);
extern int file_close(T_pFILE fd);
static inline void closeHandle()
{
file_close( Handle->file);
mad_synth_finish(&Handle->synth);
mad_frame_finish(&Handle->frame);
mad_stream_finish(&Handle->stream);
free(Handle);
Handle = NULL;
}
static inline signed short fixedToShort(mad_fixed_t Fixed)
{
if(Fixed>=MAD_F_ONE)
return(SHRT_MAX);
if(Fixed<=-MAD_F_ONE)
return(-SHRT_MAX);
Fixed=Fixed>>(MAD_F_FRACBITS-15);
return((signed short)Fixed);
}
int NativeMP3Decoder_init(char * filepath,unsigned long start/*,unsigned long size*/)
{
LOGI("bfp----->NativeMP3Decoder_init start filepath: %s",filepath);
LOGI("bfp----->NativeMP3Decoder_init start: %ld",start);
T_pFILE fileHandle = file_open( filepath, _FMODE_READ);
LOGI("bfp----->NativeMP3Decoder_init fileHandle: %ld",fileHandle);
if( fileHandle <= 0 )
return -1;
MP3FileHandle* mp3Handle = (MP3FileHandle*)malloc(sizeof(MP3FileHandle));
memset(mp3Handle, 0, sizeof(MP3FileHandle));
mp3Handle->file = fileHandle;
mp3Handle->fileStartPos= start;
file_seek( mp3Handle->file, start, SEEK_SET);
mad_stream_init(&mp3Handle->stream);
mad_frame_init(&mp3Handle->frame);
mad_synth_init(&mp3Handle->synth);
mad_timer_reset(&mp3Handle->timer);
Handle = mp3Handle;
readNextFrame( Handle );
g_Samplerate = Handle->frame.header.samplerate;
LOGI("bfp----->NativeMP3Decoder_init fileHandle: end");
return 1;
}
static inline int readNextFrame( MP3FileHandle* mp3 )
{
do
{
if( mp3->stream.buffer == 0 || mp3->stream.error == MAD_ERROR_BUFLEN )
{
int inputBufferSize = 0;
if( mp3->stream.next_frame != 0 )
{
int leftOver = mp3->stream.bufend - mp3->stream.next_frame;
int i;
for( i= 0; i < leftOver; i++ )
mp3->inputBuffer[i] = mp3->stream.next_frame[i];
int readBytes = file_read( mp3->file, mp3->inputBuffer + leftOver, INPUT_BUFFER_SIZE - leftOver);
if( readBytes == 0 )
return 0;
inputBufferSize = leftOver + readBytes;
}
else
{
int readBytes = file_read( mp3->file, mp3->inputBuffer, INPUT_BUFFER_SIZE);
if( readBytes == 0 )
return 0;
inputBufferSize = readBytes;
}
mad_stream_buffer( &mp3->stream, mp3->inputBuffer, inputBufferSize );
mp3->stream.error = MAD_ERROR_NONE;
}
if( mad_frame_decode( &mp3->frame, &mp3->stream ) )
{
if( mp3->stream.error == MAD_ERROR_BUFLEN ||(MAD_RECOVERABLE(mp3->stream.error)))
continue;
else
return 0;
}
else
break;
}
while( 1 );
mad_timer_add( &mp3->timer, mp3->frame.header.duration );
mad_synth_frame( &mp3->synth, &mp3->frame );
mp3->leftSamples = mp3->synth.pcm.length;
mp3->offset = 0;
return -1;
}
int NativeMP3Decoder_readSamples(short *target, int size)
{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf start size %d",size);
MP3FileHandle* mp3 = Handle;
int pos=0;
int idx = 0;
while( idx != size )
{
if( mp3->leftSamples > 0 )
{
for( ; idx < size && mp3->offset < mp3->synth.pcm.length; mp3->leftSamples--, mp3->offset++ )
{
int value = fixedToShort(mp3->synth.pcm.samples[0][mp3->offset]);
if( MAD_NCHANNELS(&mp3->frame.header) == 2 )
{
value += fixedToShort(mp3->synth.pcm.samples[1][mp3->offset]);
value /= 2;
}
target[idx++] = value;
}
}
else
{
pos = file_seek( mp3->file, 0, SEEK_CUR);
int result = readNextFrame( mp3);
if( result == 0 )
return 0;
}
}
if( idx > size )
return 0;
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf end pos %d",pos);
return pos;
}
int NativeMP3Decoder_getAduioSamplerate()
{
LOGI("bfp----->NativeMP3Decoder_getAduioSamplerate g_Samplerate %d",g_Samplerate);
return g_Samplerate;
}
void NativeMP3Decoder_closeAduioFile()
{
LOGI("bfp----->NativeMP3Decoder_closeAduioFile start Handle:%d",Handle->size);
if( Handle != 0 )
{
closeHandle();
Handle = 0;
}
LOGI("bfp----->NativeMP3Decoder_closeAduioFile end");
}
jint Java_com_czt_mp3recorder_NativeMP3Decoder_initAudioPlayer(JNIEnv *env, jobject obj, jstring file,jint startAddr)
{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_initAudioPlayer start");
char* fileString = (*env)->GetStringUTFChars(env,file, 0);
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_initAudioPlayer end");
return NativeMP3Decoder_init(fileString,startAddr);
}
jint Java_com_czt_mp3recorder_NativeMP3Decoder_getAudioBuf(JNIEnv *env, jobject obj ,jshortArray audioBuf,jint len)
{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf start len:%d",len);
int bufsize = 0;
int ret = 0;
if (audioBuf != NULL) {
bufsize = (*env)->GetArrayLength(env, audioBuf);
jshort *_buf = (*env)->GetShortArrayElements(env, audioBuf, 0);
memset(_buf, 0, bufsize*2);
ret = NativeMP3Decoder_readSamples(_buf, len);
(*env)->ReleaseShortArrayElements(env, audioBuf, _buf, 0);
}
else{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf getAudio failed");
}
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioBuf end ret:%d",ret);
return ret;
}
jint Java_com_czt_mp3recorder_NativeMP3Decoder_getAudioSamplerate(JNIEnv *env, jobject obj)
{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioSamplerate start");
return NativeMP3Decoder_getAduioSamplerate();
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_getAudioSamplerate end");
}
void Java_com_czt_mp3recorder_NativeMP3Decoder_closeAduioFile(JNIEnv *env, jobject obj)
{
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_closeAduioFile str");
NativeMP3Decoder_closeAduioFile();
LOGI("bfp----->Java_com_mediatek_factorymode_NativeMP3Decoder_closeAduioFile end");
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
void *venv;
LOGI("bfp----->JNI_OnLoad!");
if ((*vm)->GetEnv(vm, (void**) &venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("bfp--->ERROR: GetEnv failed");
return -1;
}
return JNI_VERSION_1_4;
}
最后
感謝大家的支持和閱讀,完整項(xiàng)目代碼已經(jīng)上傳,再次感謝大家
https://pan.baidu.com/s/1faWwLbvQhd7v1m-woXtHvA
十分感謝以下博客的分享:
https://www.imooc.com/article/27041?block_id=tuijian_wz
https://blog.csdn.net/aiyh0202/article/details/52815374
https://blog.csdn.net/qq634416025/article/details/51424556
http://www.cnblogs.com/ct2011/p/4080193.html
https://blog.csdn.net/bjrxyz/article/details/73435407?locationNum=15&fps=1
https://blog.csdn.net/haovip123/article/details/52356024
http://m.itdecent.cn/p/971fff236881