关于mp3
Mp3曾经以它优秀的压缩率和较低的失真一横行音乐行业,在那个存储介质昂贵的时代大放光彩,随着技术的发展,存储已经不是瓶颈了,现在的音乐爱好者也开始追求音质,出现了高保真音乐,复古黑胶唱片等。但是作为一个音频开发者,基本的mp3知识还是需要掌握的。
MP3是一种有损压缩格式,对它进行解码不能还原PCM。一般CD品质的音频文件是1411.2kbps(16bitpersample、44100samplerate、2channels),这个需要较高的带宽才能保证传输的稳定性,但是经过MP3编码后比特率基本结余128kbps~320kbps,压缩率为12:1-10:1,这样回放的质量低了,但是文件大小得到了控制。
本篇文章讨论的并非是音乐播放器,而是一种编码格式,并且以lame编码器来讲解文章格式,事实上lame编码器被认为是最好的MP3编码器。
MP3文件格式
MP3一般包含3个主要部分ID3v2、frame、ID3v1。其形式如下:
帧 | 说明 |
---|---|
ID3v2 | 包含了作者,作曲,专辑信息等,长度不固定,扩展了ID3v1的信息量 |
Frame | 一些列的帧,个数由文件的大小和帧长度决定 每个frame包含帧头和实体数据两部分,帧头记录了mp3的位宽,采样率,版本信息等,每个帧之间相互独立,但是每个帧的长度不固定,由bitrate决定 |
ID3v1 | 包含了作者,作曲,专辑等信息,长度固定是123Byte |
下面分别说一下各个格式的信息
ID3v2结构图
ID3V2共有4个版本,但实际上用的最多的是ID3V2.3
数据块 | 数据描述 | 字节数(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。
- CBR:帧长度固定,数据平均分配在各个帧,这种方式有利于计算播放时长,但是文件稍微大
- VBR:帧长度不固定,要获取真个播放时长必须知道帧的总数,文件较小
- ABR:帧长度不固定,介于CBR和VBR之间
有效数据帧头为四个字节:
此处是1-32
|偏移地址|位数(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|强调位
这个地方的内容较多,此处我不一一列举,附上一个写的比较详细的博客:
[MP3文件格式全解(https://blog.csdn.net/u013904227/article/details/52184038)
LAME的使用
Lame是一个专门用编码MP3的开源库,它可以提供多种不同比特率的支持,并且提供了各个平台下的编译源码包,可以直接在SourceForge下载。
安卓平台编译
官方并没有提供专门的编译文件,不过我们可以自己采用多种方式编译:ndk-build和cmake,两种方式都非常简单。首先要下载源码,然后解压到一个文件夹内。
ndk-build方式构建lame
我们需要编写两个文件,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文件了
1 | . |
下边是两个文件
- Application.mk
1 | APP_PLATFORM := android-18 |
- Android.mk
1 | LOCAL_PATH := $(call my-dir) |
cmake方式构建lame
cmake构建更加简单,只需要将刚才的libmp3lame文件夹和lame.h文件添加到src/main/cpp文件夹下,此处我和源文件夹保持一致,起名为libmp3lame,然后编写一个CMakeLists.txt文件如下:
1 | add_definitions("-DSTDC_HEADERS") |
然后在主文件夹下的CMakeList.txt中添加生成该库的代码:
1 | set(LIB_MP3 Mp3Codec) |
假如要使用这个库的话只需要假如target_link命令来连接即可。
lame转码pcm格式为mp3
我做了一个非常简单的实例程序,首先是通过AudioRecorder录制PCM数据,然后封装为wav格式,这个格式在安卓手机上是可以直接播放的。然后在将wav文件通过jni层的lame调用转码为MP3。
首先了解一下lame的api文档:
获取版本信息(可选的)
const char * get_lame_version(void);错误信息
默认情况下lame会输出错误信息到标准错误流中,但是我们需要获取错误信息的话,可以调用如下方法来设置:
1 | lame_set_errorf(gfp,error_handler_function); |
通过这种方式,就可以将调试或者错误信息发送到我们自己的handler中。这个handler函数一般如下:
1 | void my_debugf(const char *format, va_list ap) |
- 初始化编码器
初始化编码器并设置默认值:
1 |
|
在lame.h文件中定义了lame_glob_flags的一种简写形式:typedef lame_global_flags *lame_t;
我们就可以使用lame_t。
- 设置参数
1 | zret_code = lame_init_params(gfp); |
这个需要检查错误,因为可能会有错误的参数。
- 编码
输出源时PCM数据,输出时mp3的帧,我们需要先设置一个缓冲区,来存放编码后的mp3数据,这个数据的大小可以根据采样率和采样数来计算。一个公式如下:
1 | mp3buffer_size (in bytes) = 1.25*num_samples + 7200. |
接下来是将采样数据生成为mp3数据,存入上边分配的缓冲区:
1 | int lame_encode_buffer(lame_global_flags *gfp, |
编码成功的话会返回编码的数量,有可能为0.假如编码不成功就会返回一个负数。
- 编码结束
编码器可能会持有最后几个数据,需要调用这个函数:
1 | int lame_encode_flush(lame_global_flags *,char *mp3buffer, int mp3buffer_size); |
函数的返回值是最后的数据,大多数情况下是0。
- 写入tag
这个地方主要是写入上边提到的一些ID3等帧信息
1 | void lame_mp3_tags_fid(lame_global_flags *,FILE* fid); |
- 释放资源
最后我们需要调用
1 | void lame_close(lame_global_flags *); |
最后附上demo的github地址:
https://github.com/rangaofei/AudioApplication
写在最后
现代技术发展,MP3终究会被时代抛弃,流媒体格式一般不会采用MP3传输,这篇文章的目的是介绍一些基本的音频知识,而且AudioRecorder本身有一定的局限性,不如OpenSLES灵活,所以算是抛砖引玉,后边将会介绍如何直接将PCM转码为mp3.
参考: