安卓不支持mp3格式的錄製,可是能夠解碼mp3格式文件,lame庫是一個通用的編碼mp3庫,用c語言實現。這篇文章自制了lame庫的cmake腳本,實現了在安卓上將PCM數據轉換爲MP3。android
Mp3曾經以它優秀的壓縮率和較低的失真一橫行音樂行業,在那個存儲介質昂貴的時代大放光彩,隨着技術的發展,存儲已經不是瓶頸了,如今的音樂愛好者也開始追求音質,出現了高保真音樂,復古黑膠唱片等。可是做爲一個音頻開發者,基本的mp3知識仍是須要掌握的。git
MP3是一種有損壓縮格式,對它進行解碼不能還原PCM。通常CD品質的音頻文件是1411.2kbps(16bitpersample、*44100samplerate、*2channels),這個須要較高的帶寬才能保證傳輸的穩定性,可是通過MP3編碼後比特率基本結餘128kbps~320kbps,壓縮率爲12:1-10:1,這樣回放的質量低了,可是文件大小獲得了控制。 本篇文章討論的並不是是音樂播放器,而是一種編碼格式,而且以lame編碼器來說解文章格式,事實上lame編碼器被認爲是最好的MP3編碼器。github
MP3通常包含3個主要部分ID3v二、frame、ID3v1。其形式以下:shell
幀 | 說明 |
---|---|
ID3v2 | 包含了做者,做曲,專輯信息等,長度不固定,擴展了ID3v1的信息量 |
Frame | 一些列的幀,個數由文件的大小和幀長度決定 每一個frame包含幀頭和實體數據兩部分,幀頭記錄了mp3的位寬,採樣率,版本信息等,每一個幀之間相互獨立,可是每一個幀的長度不固定,由bitrate決定 |
ID3v1 | 包含了做者,做曲,專輯等信息,長度固定是123Byte |
下面分別說一下各個格式的信息api
ID3V2共有4個版本,但實際上用的最多的是ID3V2.3session
數據塊 | 數據描述 | 字節數(Byte) | 內容 |
---|---|---|---|
標籤頭 | ID3V2標識 | 3 | 固定字符"ID3",表示是ID3v2標籤 |
ID3v2的子版本號 | 2 | 0x0300表示是主版本號爲3,副版本號爲0,也就是ID3v2.3 | |
ID3v2標誌位 | 1 | abc00000,a-非同步編碼,b-擴展標籤頭,c-測試指示位,當這三位置是1時表示有效,通常狀況都是0 | |
ID3v2大小 | 4 | 每一個字節只有後七位有效,size=byte0:70x200000+byte1:70x4000+byte2:7*0x80+byte3:7 | |
擴展標籤頭 | 擴展標籤頭大小 | 4 | size=byte00x200000+byte10x4000+byte2*0x80+byte3 |
擴展標誌位 | 2 | xx | |
補空大小 | 4 | 能夠在全部的標籤幀後邊添加補空的數據,也能夠預留空間存放額外的幀,是的整個標籤大小比標籤頭的大小更大,通常不用 | |
標籤幀 | 幀標識 | 4 | 固定四個字符,每一個標籤幀都有一個10個本身的固定的頭和至少一個字節的不固定長度的內容組成,也就是下邊的幀大小和幀標誌必須有,而幀數據的內容不得小於1. |
幀大小 | 4 | 出去幀頭的全部長度,size=byte00x200000+byte10x4000+byte2*0x80+byte3 | |
標誌 | 2 | 標誌位,只定義6bit,abc00000 ijk00000通常爲0 | |
幀數據 | size | 存放的數據 | |
補空 | 補空大小 |
介紹一下經常使用的幀標識:函數
標識內容 | 描述 |
---|---|
TIT2 | 標題 |
TPE1 | 做者 |
TALB | 專輯 |
TRCK | 音軌N/M格式 |
TYER | 年代 |
TCON | 類型 |
COMM | 備註 |
有效數據幀的編碼在lame共有三種,CBR、VBR和ABR。測試
有效數據幀頭爲四個字節: 此處是1-32ui
偏移地址 | 位數(bits) | 內容 |
---|---|---|
1 | 12 | 幀同步標識,通常標識數據幀的開始,所有爲1 |
13 | 1 | MPEG音頻版本號 |
14 | 2 | Layer版本 |
16 | 1 | 保護位 |
17 | 4 | 比特率 |
21 | 2 | 採樣率 |
23 | 1 | 補空位大小 |
24 | 1 | 不知道啥 |
25 | 2 | 模式 |
27 | 2 | 模式拓展位 |
29 | 1 | 版權位 |
30 | 1 | 原始位 |
31 | 2 | 強調位 |
這個地方的內容較多,此處我不一一列舉,附上一個寫的比較詳細的博客:編碼
Lame是一個專門用編碼MP3的開源庫,它能夠提供多種不一樣比特率的支持,而且提供了各個平臺下的編譯源碼包,能夠直接在SourceForge下載。
官方並無提供專門的編譯文件,不過咱們能夠本身採用多種方式編譯:ndk-build和cmake,兩種方式都很是簡單。首先要下載源碼,而後解壓到一個文件夾內。
咱們須要編寫兩個文件,Android.mk和Application.mk。一個參考網址能夠少走一些坑(http://developer.samsung.com/technical-doc/view.do;jsessionid=32A9C99833A33F376D7DB8C787414B62?v=T000000090)[http://developer.samsung.com/technical-doc/view.do;jsessionid=32A9C99833A33F376D7DB8C787414B62?v=T000000090]
主要有四點:
將libmp3lame文件夾下的全部內容拷貝到一個指定的地方,而後再將lame.h文件考進來
找到util.h
文件,將其中的extern ieee754_float32_t fast_log2(ieee754_float32_t x);
替換爲 extern float fast_log2(float x);
找到set_get.h
文件。替換 #include <lame.h>
爲#include 「lame.h」
假如出現bcopy unrefrence的錯誤,在Application.mk文件中添加一個flag,最後添加一行,內容爲APP_CFLAGS += -DSTDC_HEADERS
這樣就能夠直接編譯生成so文件了。 假如配置好了ndk的全局變量,只須要運行ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk
就生成了對應的so文件了
.
├── arm64-v8a
│ └── libmp3lame.so
├── armeabi
│ └── libmp3lame.so
├── armeabi-v7a
│ └── libmp3lame.so
├── mips
│ └── libmp3lame.so
├── mips64
│ └── libmp3lame.so
├── x86
│ └── libmp3lame.so
└── x86_64
└── libmp3lame.so
複製代碼
下邊是兩個文件
APP_PLATFORM := android-18
APP_ABI := all
APP_BUILD_SCRIPT := Android.mk
APP_CFLAGS += -DSTDC_HEADERS
複製代碼
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libmp3lame
LOCAL_SRC_FILES := \
./libmp3lame/bitstream.c \
./libmp3lame/encoder.c \
./libmp3lame/fft.c \
./libmp3lame/gain_analysis.c \
./libmp3lame/id3tag.c \
./libmp3lame/lame.c \
./libmp3lame/mpglib_interface.c \
./libmp3lame/newmdct.c \
./libmp3lame/presets.c \
./libmp3lame/psymodel.c \
./libmp3lame/quantize.c \
./libmp3lame/quantize_pvt.c \
./libmp3lame/reservoir.c \
./libmp3lame/set_get.c \
./libmp3lame/tables.c \
./libmp3lame/takehiro.c \
./libmp3lame/util.c \
./libmp3lame/vbrquantize.c \
./libmp3lame/VbrTag.c \
./libmp3lame/version.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
複製代碼
cmake構建更加簡單,只須要將剛纔的libmp3lame文件夾和lame.h文件添加到src/main/cpp文件夾下,此處我和源文件夾保持一致,起名爲libmp3lame,而後編寫一個CMakeLists.txt文件以下:
add_definitions("-DSTDC_HEADERS")
add_library(mp3lame bitstream.c
encoder.c
fft.c
gain_analysis.c
id3tag.c
lame.c
mpglib_interface.c
newmdct.c
presets.c
psymodel.c
quantize.c
quantize_pvt.c
reservoir.c
set_get.c
tables.c
takehiro.c
util.c
vbrquantize.c
VbrTag.c
version.c)
複製代碼
而後在主文件夾下的CMakeList.txt中添加生成該庫的代碼:
set(LIB_MP3 Mp3Codec)
include_directories(
src/main/cpp/include #將lame.h文件複製到這個文件夾下,更加清晰一些,能夠做爲一個接口文件
)
add_subdirectory(src/main/cpp/libmp3lame)
複製代碼
假如要使用這個庫的話只須要假如target_link命令來鏈接便可。
我作了一個很是簡單的實例程序,首先是經過AudioRecorder錄製PCM數據,而後封裝爲wav格式,這個格式在安卓手機上是能夠直接播放的。而後在將wav文件經過jni層的lame調用轉碼爲MP3。
首先了解一下lame的api文檔:
獲取版本信息(可選的) const char * get_lame_version(void);
錯誤信息 默認狀況下lame會輸出錯誤信息到標準錯誤流中,可是咱們須要獲取錯誤信息的話,能夠調用以下方法來設置:
lame_set_errorf(gfp,error_handler_function);
lame_set_debugf(gfp,error_handler_function);
lame_set_msgf(gfp,error_handler_function);
複製代碼
經過這種方式,就能夠將調試或者錯誤信息發送到咱們本身的handler中。這個handler函數通常以下:
void my_debugf(const char *format, va_list ap) {
(void) vfprintf(stdout, format, ap);
}
複製代碼
#include "lame.h"
lame_global_flags *gfp;
gfp = lame_init();
/*The default (if you set nothing) is a J-Stereo, 44.1khz 128kbps CBR mp3 file at quality 5. */
lame_set_num_channels(gfp,2);
lame_set_in_samplerate(gfp,44100);
lame_set_brate(gfp,128);
lame_set_mode(gfp,1);
lame_set_quality(gfp,2); /* 2=high 5 = medium 7=low */
複製代碼
在lame.h文件中定義了lame_glob_flags的一種簡寫形式:typedef lame_global_flags *lame_t;
咱們就可使用lame_t。
zret_code = lame_init_params(gfp);
複製代碼
這個須要檢查錯誤,由於可能會有錯誤的參數。
mp3buffer_size (in bytes) = 1.25*num_samples + 7200.
複製代碼
接下來是將採樣數據生成爲mp3數據,存入上邊分配的緩衝區:
int lame_encode_buffer(lame_global_flags *gfp, short int leftpcm[], short int rightpcm[], int num_samples,char *mp3buffer,int mp3buffer_size);
複製代碼
編碼成功的話會返回編碼的數量,有可能爲0.假如編碼不成功就會返回一個負數。
int lame_encode_flush(lame_global_flags *,char *mp3buffer, int mp3buffer_size);
複製代碼
函數的返回值是最後的數據,大多數狀況下是0。
這個地方主要是寫入上邊提到的一些ID3等幀信息
void lame_mp3_tags_fid(lame_global_flags *,FILE* fid);
複製代碼
void lame_close(lame_global_flags *);
複製代碼
最後附上demo的github地址: github.com/rangaofei/A…
參考:
最後的最後,說一下最近本身的一點事。我普通211非計算機專業,11年畢業,畢業以後一直在央企工做,後來由於興趣緣由轉行作安卓開發,已過而立之年,目前在江蘇一個小城市作安卓開發,沒有大公司工做背景,想去上海試一試機會,經歷了無數次失敗了,包括阿里內推,中通等,這些經歷都使我認清了本身如今的劣勢。這也是一個很是沮喪的過程,由於多年的努力被一我的輕輕鬆鬆否認確實很喪氣。不過我有我本身的優點,個人技能不必定會匹配全部人的技能要求,運氣不在的時候須要練好內功,提高本身,如今的不承認不等於未來的不承認,留給個人時間很少了,但個人路還很長,但願和我同樣的小夥伴也能像我同樣,儘快調整過來。