介于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;
- }
- }
- }