介于ID3V2和ID3V1之间的部分称做MP3帧,这些帧构成了MP3的音频部分。每个MP3帧由帧头和数据块组成,之间还可能包含2个字节的CRC校验位。校验位是否存在依赖于帧头的第16比特位的值。以比特率为区分标准,MP3可以分为可变比特率和不变比特率两种格式。比特率代表每秒的数据量,一般单位是kb/s。比特率越高,MP3的音质越好,但是文件也越大。每个MP3帧的固定时长为26ms,因此可变比特率的帧大小可能是不同的,而不变比特率的帧大小是固定的,只要分析了第1个帧的大小就可以知道后面帧的大小。
帧头长度是4个字节,也就是32比特,其布局如下所示(每个比特的意义在表9-2中做了详细的介绍)。
AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
表9-2 帧头的比特描述
标 识 |
长 度 |
位 置 |
描 述 |
A |
11 |
31~21 |
11位的帧同步数据,可以通过查找同步位来确定帧的起始位置 |
B |
2 |
20~19 |
MPEG音频版本号,其中MPEG 2.5为非官方版本
00 MPEG 2.5
01 保留版本
10 MPEG 2
11 MPEG 1 |
C |
2 |
18~17 |
层(Layer)版本号
00 保留版本号
01 Layer 3
10 Layer 2
11 Layer 1 |
D |
1 |
16 |
保护位,0代表帧头后紧跟2个字节的CRC校验位;1代表无保护 |
E |
4 |
15~12 |
比特率索引值,根据表9-3中的内容可以查询比特率的值,单位是kb/s |
F |
2 |
11~10 |
抽样率索引值,根据表9-4中的内容可以查询抽样率的值,单位是Hz |
G |
1 |
9 |
填充位,0代表无填充,1代表有填充。对于Layer 1,填充位长度为4个字节;Layer 2和Layer 3的填充位长度为1个字节 |
H |
1 |
8 |
私有标志位 |
I |
2 |
7~6 |
声道模式
00 立体声
01 联合立体声
10 双声道
11 单声道 |
J |
2 |
5~4 |
模式的扩展,只有声道模式为01时才有意义 |
K |
1 |
3 |
版权标志位 |
L |
1 |
2 |
原版标志位 |
M |
2 |
1~0 |
目前此标志位很少使用 |
表9-3 比特率索引表(单位:kb/s)
比 特 位 |
V1 L1 |
V1 L2 |
V1 L3 |
V2 L1 |
V2 L2 |
V2 L3 |
0000 |
0 |
0 |
0 |
0 |
0 |
0 |
0001 |
32 |
32 |
32 |
32 |
32 |
8 |
0010 |
64 |
48 |
40 |
64 |
48 |
16 |
0011 |
96 |
56 |
48 |
96 |
56 |
24 |
0100 |
128 |
64 |
56 |
128 |
64 |
32 |
0101 |
160 |
80 |
64 |
160 |
80 |
64 |
0110 |
192 |
96 |
80 |
192 |
96 |
80 |
0111 |
224 |
112 |
96 |
224 |
112 |
56 |
1000 |
256 |
128 |
112 |
256 |
128 |
64 |
1001 |
288 |
160 |
128 |
288 |
160 |
128 |
1010 |
320 |
192 |
160 |
320 |
192 |
160 |
1011 |
352 |
224 |
192 |
352 |
224 |
112 |
1100 |
384 |
256 |
224 |
384 |
256 |
128 |
1101 |
416 |
320 |
256 |
416 |
320 |
256 |
1110 |
448 |
384 |
320 |
448 |
384 |
320 |
1111 |
0 |
0 |
0 |
0 |
0 |
0 |
表9-4 抽样率索引(单位:Hz)
比 特 位 |
MPEG 1 |
MPEG 2 |
MPEG 2.5 |
00 |
44 100 |
22 050 |
11 205 |
01 |
48 000 |
24 000 |
12 000 |
10 |
32 000 |
16 000 |
8 000 |
11 |
0 |
0 |
0 |
MP3帧体的大小由MPEG版本号、比特率、抽样率和填充位4个因素确定。计算公式为:
帧大小= ((MPEG版本号== 1?144:72) * 比特率)/抽样率+ 填充位
解析MP3帧是较复杂的,且直接关系到后面分割MP3文件的工作。对于不变比特率的情况比较简单,不需要完全解析整个MP3文件就可以知道帧数、帧的大小等信息。但是,对于可变比特率的情况就显得比较复杂了,必须逐个分析MP3帧才能确定帧的大小,也只有分析了整个MP3文件才能确定帧的数量。为了能兼顾可变和不变比特率两种情况,我们考虑解析整个MP3文件,然后把每个帧的大小和在文件中的位移存储在一个Vector中,这样就可以通过时间来定位到帧的位置,便于切割MP3文件。通常一个MP3文件可能包含10 000多个帧,如果所有帧都存储在Vector中,将消耗很大的内存空间,且Vector中的元素越多,查询的速度也就越慢。为了优化程序,把10个帧作为一个大帧存储在Vector中,这样在切割时依然可以精确到260ms,甚至还可以把20个帧作为一个整体,这样的效率会更高一些,内存使用更少一些,只是会丧失一些切割的精度。
Frames类的构造器中包含了MP3File类型的参数,这样可以方便获得MP3帧的起始位置。Frames类的源代码如下所示:
public class Frames {
private static int version;
private static int layer;
private MP3File file;
//存储帧在文件中的位移和大小
private Vector<F> v = new Vector<F>();
public Frames(MP3File file) throws MP3Exception {
//引用MP3File,方便获得MP3帧开始的位置
this.file = file;
try {
FileInputStream fis = new FileInputStream(file.getPath());
//定位到帧起始位置,开始解析
fis.skip(file.getFrameOffset());
parse(fis);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
//将传入的媒体时间转换为在文件中的位置
public long time2offset(long time) {
long offset = -1;
long index = time / 260;
offset = ((F) v.get((int) index)).offset;
return offset;
}
private void parse(InputStream is) throws MP3Exception {
try {
int position = file.getFrameOffset();
//帧的结束位置,也就是ID3V1的起始位置
long count = file.getLength() - 128;
//计算帧的个数,每10个帧放入到Vector中
int fc = 0;
//存储10个帧的大小
int fs = 0;
while (is.available() > 0 && position < count) {
//同步帧头位置
int first = is.read();
while (first != 255 && first != -1) {
first = is.read();
}
int second = is.read();
if (second > 224) {
int third = is.read();
int forth = is.read();
int i20 = getBit(second, 4);
int i19 = getBit(second, 3);
if (i20 == 0 & i19 == 0)
throw new MP3Exception("MPEG 2.5 is not supported");
//获得MPEG版本号
version = i19 == 0 ? 2 : 1;
int i18 = getBit(second, 2);
int i17 = getBit(second, 1);
layer = (4 - ((i18 << 1) + i17));
int i16 = getBit(second, 0);
int i15 = getBit(third, 7);
int i14 = getBit(third, 6);
int i13 = getBit(third, 5);
int i12 = getBit(third, 4);
//查表获得比特率
int bitRate = convertBitrate(i15, i14, i13, i12) * 1000;
int i11 = getBit(third, 3);
int i10 = getBit(third, 2);
//查表获得抽样率
int sampleRate = convertSamplerate(i11, i10);
int padding = getBit(third, 1);
//计算帧的大小
int size = ((version == 1 ? 144 : 72) * bitRate)
/ sampleRate + padding;
is.skip(size - 4);
fs += size;
fc++;
if (fc == 10) {
//每10帧存储一次
F f = new F(position, fs);
v.add(f);
fc = 0;
fs = 0;
}
position = position + size;
}
}
//将剩余的帧放入Vector中
if (fs != 0) {
v.add(new F(position, fs));
}
} catch (IOException e) {
e.printStackTrace();
}
}
//根据表9-4计算抽样率
protected int convertSamplerate(int in1, int in2) {
int sample = 0;
switch ((in1 << 1) | in2) {
case 0:
sample = 44100;
break;
case 1:
sample = 48000;
break;
case 2:
sample = 32000;
break;
case 3:
sample = 0;
break;
}
if (version == 1) {
return sample;
} else {
return sample / 2;
}
}
//根据表9-3计算比特率
protected int convertBitrate(int in1, int in2, int in3, int in4) {
int[][] convert = { { 0, 0, 0, 0, 0, 0 }, { 32, 32, 32, 32, 32, 8 },
{ 64, 48, 40, 64, 48, 16 }, { 96, 56, 48, 96, 56, 24 },
{ 128, 64, 56, 128, 64, 32 }, { 160, 80, 64, 160, 80, 64 },
{ 192, 96, 80, 192, 96, 80 }, { 224, 112, 96, 224, 112, 56 },
{ 256, 128, 112, 256, 128, 64 },
{ 288, 160, 128, 288, 160, 128 },
{ 320, 192, 160, 320, 192, 160 },
{ 352, 224, 192, 352, 224, 112 },
{ 384, 256, 224, 384, 256, 128 },
{ 416, 320, 256, 416, 320, 256 },
{ 448, 384, 320, 448, 384, 320 }, { 0, 0, 0, 0, 0, 0 } };
int index1 = (in1 << 3) | (in2 << 2) | (in3 << 1) | in4;
int index2 = (version - 1) * 3 + layer - 1;
return convert[index1][index2];
}
private int getBit(int input, int bit) {
return (input & (1 << bit)) > 0 ? 1 : 0;
}
class F {
int offset;
int size;
public F(int _offset, int _size) {
offset = _offset;
size = _size;
}
}
}