前言
最近在研究wav,mp3,pcm之間的相互轉(zhuǎn)換,發(fā)現(xiàn)mp3的相關(guān)操作毕莱,都需要解碼mp3或者編碼mp3,無法直接對mp3文件做操作颅夺。下面是本文的相關(guān)知識點(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庫(ndk-build會生成相應(yīng)的.h頭文件)
1.3 編寫wav轉(zhuǎn)mp3的lame_util.c
大致分為兩步
- 通過Jstring2CStr方法將java中的jstring類型轉(zhuǎn)化成c語言的char字符串
- 然后再通過convert方法將wav轉(zhuǎn)碼成mp3文件
(convert方法的參數(shù)為拗慨,wav路徑廓八,mp3路徑,采樣率赵抢,聲道數(shù)剧蹂,比特率)
下面為大家科普一下相關(guān)參數(shù)以及知識點(diǎn)
1.3.1 Lame相關(guān)參數(shù)
- 采樣率(sampleRate):采樣率越高聲音的還原度越好。
- 比特率(bitrate):每秒鐘的數(shù)據(jù)量烦却,越高音質(zhì)越好宠叼。
- 聲道數(shù)(channels):聲道的數(shù)量,通常只有單聲道和雙聲道其爵,雙聲道即所謂的立體聲冒冬。
- 比特率控制模式:ABR、VBR摩渺、CBR简烤,這3中模式含義很容易查詢到,不在贅述
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
:初始化一個編碼參數(shù)的數(shù)據(jù)結(jié)構(gòu)摇幻,給使用者用來設(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,但是通常我們都會設(shè)置VBR蛙讥。參數(shù)是枚舉锯蛀,vbr_off
代表CBR,vbr_abr
代表ABR(因?yàn)锳BR不常見次慢,所以本文不對ABR做講解)vbr_mtrh
代表VBR旁涤。 -
lame_set_brate
:設(shè)置CBR的比特率,只有在CBR模式下才生效迫像。 -
lame_set_VBR_mean_bitrate_kbps
:設(shè)置VBR的比特率劈愚,只有在VBR模式下才生效。
其中每個參數(shù)都有默認(rèn)的配置闻妓,如非必要可以不設(shè)置菌羽。這里只介紹了幾個關(guān)鍵的設(shè)置接口,還有其他的設(shè)置接口可以參考lame.h
(lame的文檔里只有命令行程序的用法由缆,沒有庫接口的用法)注祖。
初始化編碼器器
lame_init_params
:根據(jù)上面設(shè)置好的參數(shù)建立編碼器
編碼PCM數(shù)據(jù)
-
lame_encode_buffer
或lame_encode_buffer_interleaved
:將PCM數(shù)據(jù)送入編碼器,獲取編碼出的mp3數(shù)據(jù)均唉。這些數(shù)據(jù)寫入文件就是mp3文件是晨。 - 其中
lame_encode_buffer
輸入的參數(shù)中是雙聲道的數(shù)據(jù)分別輸入的,lame_encode_buffer_interleaved
輸入的參數(shù)中雙聲道數(shù)據(jù)是交錯在一起輸入的浸卦。具體使用哪個需要看采集到的數(shù)據(jù)是哪種格式的署鸡,不過現(xiàn)在的設(shè)備采集到的數(shù)據(jù)大部分都是雙聲道數(shù)據(jù)是交錯在一起案糙。 - 單聲道輸入只能使用
lame_encode_buffer
限嫌,把單聲道數(shù)據(jù)當(dāng)成左聲道數(shù)據(jù)傳入,右聲道傳NULL即可时捌。 - 調(diào)用這兩個函數(shù)時(shí)需要傳入一塊內(nèi)存來獲取編碼器出的數(shù)據(jù)怒医,這塊內(nèi)存的大小lame給出了一種建議的計(jì)算方式:采樣率/20+7200。
結(jié)束編碼
lame_encode_flush
:刷新編碼器緩沖奢讨,獲取殘留在編碼器緩沖里的數(shù)據(jù)稚叹。這部分?jǐn)?shù)據(jù)也需要寫入mp3文件
銷毀編碼器
lame_close
銷毀編碼器,釋放資源拿诸。
#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* 這個代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類型轉(zhuǎn)化成一個c語言中的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"); // 得到一個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ù)組的長度
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.打開 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; //代表讀了多少個次 和寫了多少次
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ù)寫到文件里
fwrite(mp3_buffer,1,write,fmp3);
}while(read!=0);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
}
1.3.3 導(dǎo)入庫以及創(chuàng)建native方法
創(chuàng)建LameUtil類扒袖,并導(dǎo)入相應(yīng)的庫,并創(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)碼成功后就大功告成了嗎亩码?
遺憾的告訴你并沒有那么簡單季率,因?yàn)檗D(zhuǎn)碼出來的音頻最開始會啪的一聲,沒錯描沟,每一個音頻都有飒泻,無一幸免鞭光,意不意外,驚不驚喜!!!
這里啪的一聲是什么原因呢泞遗?
是因?yàn)檗D(zhuǎn)碼的時(shí)候把wav文件的頭信息也一起轉(zhuǎn)了惰许,才會出現(xiàn)這種情況。那要怎么解決呢史辙?
當(dāng)然是跳過這個頭信息汹买,直接從數(shù)據(jù)開始讀取。
fseek(fwav, 4*1024, SEEK_CUR);
對聊倔,就是這一句話就可以了卦睹,完整代碼是這樣的
/**
* 返回值 char* 這個代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類型轉(zhuǎn)化成一個c語言中的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"); // 得到一個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ù)組的長度
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.打開 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; //代表讀了多少個次 和寫了多少次
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ù)寫到文件里
fwrite(mp3_buffer,1,write,fmp3);
}while(read!=0);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
}
然后再轉(zhuǎn)碼之后,就沒有啪的一聲了方库,開不開心结序。
然而,你仔細(xì)觀察纵潦,發(fā)現(xiàn)是不是秒數(shù)不對了徐鹤,想不想哭
那秒數(shù)沒有對是為啥呢?
因?yàn)樾枰獙懭胂嚓P(guān)的VBRTAG邀层,也可以理解為mp3的頭信息返敬。
寫入VBRTAG
lame_mp3_tags_fid
:向一個文件指針中寫入規(guī)范的VBRTAG。VBRTAG的作用是記錄整個mp3的一些信息寥院,通常用于VBR模式下的編碼劲赠,因?yàn)閂BR模式下比特率不固定,無法直接計(jì)算出播放的時(shí)長和跳躍點(diǎn)秸谢,所以在mp3的開頭部分插入一個VBRTAG凛澎。
VBRTAG有幾種規(guī)范,但是lame支持的是最通用的規(guī)范估蹄。
注意
lame_mp3_tags_fid
函數(shù)的參數(shù)需要一個FILE *
類型代表要寫入的文件塑煎,這個文件一定是之前編碼時(shí)寫入了mp3數(shù)據(jù)的文件,VBRTAG是需要卸載mp3的開頭的臭蚁,之前的編碼過程中會自動空出寫入VBRTAG所需要的空間最铁,這個函數(shù)內(nèi)會自動尋找合適的文件偏移然后覆蓋,所以當(dāng)前的文件偏移是無關(guān)緊要的垮兑,但是打開文件的時(shí)候一定要以讀寫模式打開冷尉。注意我提到了之前的編碼過程中會自動空出寫入VBRTAG所需要的空間,所以如果結(jié)束編碼后不調(diào)用
lame_mp3_tags_fid
寫入VBRTAG就會導(dǎo)致這部分內(nèi)容為空系枪,雖然不影響播放雀哨,但是會影響很多播放器對于時(shí)長和跳躍點(diǎn)的計(jì)算。那么對于非VBR模式也需要寫入VBRTAG嗎嗤无?是的震束,lame對于非VBR模式也會預(yù)留出VBRTAG的空間怜庸,所以非VBR模式的編碼最后也需要寫入VBRTAG。
說了那么多垢村,意思就是這個函數(shù)是應(yīng)該在lame_encode_flush()
之后調(diào), 當(dāng)所有數(shù)據(jù)都寫入完畢了再調(diào)用割疾。仔細(xì)想想也很合理, 這時(shí)才能確定文件的總幀數(shù)。
于是嘉栓,我們就這樣寫
/**
* 返回值 char* 這個代表char數(shù)組的首地址
* Jstring2CStr 把java中的jstring的類型轉(zhuǎn)化成一個c語言中的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"); // 得到一個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ù)組的長度
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.打開 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; //代表讀了多少個次 和寫了多少次
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ù)寫到文件里
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ù)并寫入相關(guān)文件馋辈,當(dāng)錄制結(jié)束抚芦,轉(zhuǎn)換也同時(shí)結(jié)束。
2.1 DataEncodeThread的編寫
那么肯定需要跑一個線程來進(jìn)行解碼和寫入的工作迈螟,但是每次寫入和轉(zhuǎn)換肯定有很多次叉抡,這里使用HandlerThread
來進(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ù)的長度
* 緩沖區(qū)中沒有數(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é)尾信息寫入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è)置線程權(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ì)算方法來自samsung開發(fā)范例
*
* @param buffer buffer
* @param readSize readSize
*/
private void calculateRealVolume(short[] buffer, int readSize) {
double sum = 0;
for (int i = 0; i < readSize; 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)提過消返,這里就不再贅述了。
3. mp3 轉(zhuǎn) wav
既然是要實(shí)現(xiàn)mp3轉(zhuǎn)換為wav格式耘拇,那么必須先解碼mp3為pcm數(shù)據(jù)撵颊,再將pcm數(shù)據(jù)寫入相關(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后的文件,這是個中間文件秦驯,最后要被刪除
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文件寫入頭信息:
/**
* 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)行解碼,不過這次要用到的mad
庫來進(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
的庫的方法,具體方法網(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://www.reibang.com/p/971fff236881