安卓不支持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年毕业,毕业以后一直在央企工做,后来由于兴趣缘由转行作安卓开发,已过而立之年,目前在江苏一个小城市作安卓开发,没有大公司工做背景,想去上海试一试机会,经历了无数次失败了,包括阿里内推,中通等,这些经历都使我认清了本身如今的劣势。这也是一个很是沮丧的过程,由于多年的努力被一我的轻轻松松否认确实很丧气。不过我有我本身的优点,个人技能不必定会匹配全部人的技能要求,运气不在的时候须要练好内功,提高本身,如今的不承认不等于未来的不承认,留给个人时间很少了,但个人路还很长,但愿和我同样的小伙伴也能像我同样,尽快调整过来。