介于ID3V2和ID3V1之间的部分称作MP3帧,这些帧构成了MP3的音频部分。每个MP3帧由帧头和数据块组成,之间还可能包含2个字节的CRC校验位,校验位是否存在依赖于帧头的第16比特位的值。以比特率为区分标准,MP3可以分为可变比特率和不变比特率两种格式。比特率代表每秒钟的数据量,一般单位是kbps。比特率越高,MP3的音质越好,但是文件也越大。每个MP3帧固定时长为26ms,因此可变比特率的帧大小可能是不同的,而不变比特率的帧大小是固定的,只要分析了第1个帧的大小就可以知道后面帧的大小。
帧头长度是4个字节,也就是32比特,其布局如下所示。每个比特的意义在表7-3中做了详细的介绍。
- AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
表7-3 帧头的比特描述
标识
|
长度
|
位置
|
描述
|
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
|
比特率索引值,根据表7-4中的内容
可以查询比特率的值,单位是kbps
|
F
|
2
|
11~10
|
抽样率索引值,根据表7-5中的内容
可以查询抽样率的值,单位是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
|
目前此标志位很少使用
|
表7-4 比特率索引表(单位:kbps)
比特位
|
V
|
V
|
V
|
V
|
V
|
V
|
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
|
续表
比特位
|
V
|
V
|
V
|
V
|
V
|
V
|
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
|
表7-5 抽样率索引(单位:Hz)
比特位
|
MPEG 1
|
MPEG 2
|
MPEG 2.5
|
00
|
44100
|
22050
|
11205
|
01
|
48000
|
24000
|
12000
|
10
|
32000
|
16000
|
8000
|
11
|
0
|
0
|
0
|
MP3帧体的大小由MPEG版本号、比特率、抽样率和填充位4个因素确定。计算公式为:
帧大小= ((MPEG版本号== 1?144:72) * 比特率)/抽样率 + 填充位
解析MP3帧是较复杂的,且直接关系到后面分割MP3文件的工作。对于不变比特率的情况比较简单,不需要完全解析整个MP3文件就可以知道帧数、帧的大小等信息。但是,对于可变比特率的情况就显得比较复杂了,必须逐个分析MP3帧才能确定帧的大小,也只有分析了整个MP3文件才能确定帧的数量。为了能兼顾可变和不变比特率两种情况,我们考虑解析整个MP3文件,然后把每个帧的大小和在文件中的位移存储在一个Vector中,这样就可以通过时间来定位到帧的位置,便于切割MP3文件。通常一个MP3文件可能包含10000多个帧,如果所有帧都存储在Vector中,将消耗很大的内存空间,且Vector中的元素越多,查询的速度也就越慢。为了优化程序,把10个帧作为一个大帧存储在Vector中,这样在切割时依然可以精确到260ms,甚至还可以把20个帧作为一个整体,这样的效率会更高一些,内存使用更少一些,只是会丧失一些切割的精度。
Frames类的构造器中包含了MP3File类型的参数,这样可以方便获得MP3帧的起始位置。Frames类的源码如下所示:
- package com.ophone.chapter7_5;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Vector;
- 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;
- }
- positionposition = position + size;
- }
- }
- //将剩余的帧放入Vector中
- if (fs != 0) {
- v.add(new F(position, fs));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- //根据表7-5计算抽样率
- 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;
- }
- }
- //根据表7-4计算比特率
- 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;
- }
- }
- }