本文是iOS 使用AUGraph錄音同時播放(并轉(zhuǎn)碼成MP3)中關(guān)于使用Lame將PCM轉(zhuǎn)碼成MP3的詳細說明。看看時間戳真是觸目驚心,時隔一年我才來填坑,慚愧懺愧。
有錄音需求的朋友請先閱讀上半部分,如果只需要轉(zhuǎn)碼MP3,看本文就夠了。
下載DEMO
DEMO做了兩件事情:
- 一邊錄音一邊轉(zhuǎn)碼成MP3
- 將Wav文件轉(zhuǎn)碼成MP3文件
區(qū)別在于前者直接將內(nèi)存中的數(shù)據(jù)轉(zhuǎn)為Mp3,后者要先把文件讀進內(nèi)存再轉(zhuǎn)碼。
編譯Lame庫
1、下載Lame源碼:lame.sourceforge.net
2、下載編譯腳本:lame-ios-build
最近19大,Lame官網(wǎng)我都上不去了,嫌麻煩的可以直接到我的Github上去下載編譯好的Lame庫,兩個文件分別是lame.h
和libmp3lame.a
,拖進項目即可食用。
準備
轉(zhuǎn)碼文件的話要先搞清楚PCM源的幾個參數(shù):
- 采樣率
- 單聲道/雙聲道
- 每個采樣點的位數(shù)
我DEMO中的測試音頻信息如下,采樣率44.1kHz,雙聲道(Stereo),16bit
開始使用
代碼節(jié)選自LameConver.m
lame_t lame; //1.聲明一個全局變量
@implementation LameConver
/**
wav文件轉(zhuǎn)mp3文件
@param wavPath wav文件路徑(輸入)
@param mp3Path mp3文件路徑(輸出)
*/
- (void)converWav:(NSString *)wavPath toMp3:(NSString *)mp3Path successBlock:(successBlock)block{
@try {
FILE *fwav = fopen([wavPath cStringUsingEncoding:NSASCIIStringEncoding], "rb");
fseek(fwav, 1024*4, SEEK_CUR); //跳過源文件的信息頭,不然在開頭會有爆破音
FILE *fmp3 = fopen([mp3Path cStringUsingEncoding:NSASCIIStringEncoding], "wb");
lame = lame_init(); //初始化
lame_set_in_samplerate(lame, 44100.0); //設(shè)置wav的采樣率
lame_set_num_channels(lame, 2); //聲道,不設(shè)置默認為雙聲道
lame_init_params(lame);
const int PCM_SIZE = 640 * 2; //雙聲道*2 單聲道640即可
const int MP3_SIZE = 8800; //計算公式pcm_size * 1.25 + 7200
short int pcm_buffer[PCM_SIZE];
unsigned char mp3_buffer[MP3_SIZE];
int read, write;
do {
//將文件讀進內(nèi)存
read = fread(pcm_buffer, sizeof(short int), PCM_SIZE, fwav);
if (read == 0) {
//當read為0,說明pcm文件已經(jīng)全部讀取完畢,調(diào)用lame_encode_flush即可。
write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
} else { //當read不為0,調(diào)用lame_encode_buffer_xxx進行轉(zhuǎn)碼
//雙聲道千萬要使用lame_encode_buffer_interleaved這個函數(shù)
//32位、單聲道需要調(diào)用其他函數(shù),具體看代碼后面的說明
write = lame_encode_buffer_interleaved(lame, pcm_buffer, read/2, mp3_buffer, MP3_SIZE);
}
//保存mp3文件
fwrite(mp3_buffer, write, 1, fmp3);
} while (read != 0);
//記得各種關(guān)閉
lame_close(lame);
fclose(fmp3);
fclose(fwav);
} @catch (NSException *exception) {
NSLog(@"catch exception");
} @finally {
block(); //成功轉(zhuǎn)碼后調(diào)用
}
}
- (void)
整個過程分為三步:
- 聲明Lame全局變量
- 初始化Lame并設(shè)置各種轉(zhuǎn)碼參數(shù)
- 開始轉(zhuǎn)碼
第2步只設(shè)置了采樣率和聲道,實際上還有很多參數(shù)可以設(shè)置:
-
lame_set_quality(lame_global_flags *, int);
設(shè)置壓縮品質(zhì),quality=0..9. 0=best (very slow). 9=worst. 品質(zhì)越好轉(zhuǎn)碼速度越慢 -
lame_set_out_samplerate(lame_global_flags *, int);
設(shè)置輸出MP3的采樣率 -
lame_set_VBR(lame_global_flags *, vbr_mode);
設(shè)置VBR類型 - ......
lame.h
頭文件中可以看到很多可設(shè)置參數(shù),我沒用到所以沒有仔細看。有興趣的可以去瞧瞧,注釋也很全。
第3步中函數(shù)的調(diào)用,搞清楚輸入源的聲道和每個采樣點的位數(shù),調(diào)用對應(yīng)的函數(shù):
-
lame_encode_buffer
單聲道,16位 -
lame_encode_buffer_interleaved
雙聲道,16位 -
lame_encode_buffer_float
單聲道,32位
Lame就是這么方便簡單,到這里就結(jié)束啦。如果本文對你有幫助,請Star,非常感謝!
fopen
補充一個fopen
的知識點
頭文件:#include<stdio.h>
定義函數(shù):FILE * fopen(const char * path,const char * mode);
函數(shù)參數(shù)說明:
path
:字符串包含欲打開的文件路徑及文件名。
mode
:字符串則代表著流形態(tài),取值如下:
-
r
打開只讀文件,該文件必須存在。 -
r+
打開可讀寫的文件,該文件必須存在。 -
w
打開只寫文件,若文件存在則文件長度清為0,即該文件內(nèi)容會消失。若文件不存在則建立該文件。 -
w+
打開可讀寫文件,若文件存在則文件長度清為零,即該文件內(nèi)容會消失。若文件不存在則建立該文件。 -
a
以附加的方式打開只寫文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數(shù)據(jù)會被加到文件尾,即文件原先的內(nèi)容會被保留。 -
a+
以附加方式打開可讀寫的文件。若文件不存在,則會建立該文件,如果文件存在,寫入的數(shù)據(jù)會被加到文件尾后,即文件原先的內(nèi)容會被保留。
上述的形態(tài)字符串都可以再加一個b
字符,如rb
、w+b
或ab+
等組合,加入b
字符用來告訴函數(shù)庫打開的文件為二進制文件,而非純文字文件。
返回值:
文件順利打開后,指向該流的文件指針就會被返回,如果文件打開失敗則返回NULL。
參考
stackoverflow
文件操作函數(shù)fread,fwrite,fseek,fopen,fclose解析
關(guān)于我
我是可樂,在職iOS開發(fā),業(yè)余時間獨立開發(fā)App,現(xiàn)有上架作品:Mini記賬
vx公z號:沙拉可樂, 分享獨立開發(fā)的干貨和背后的故事