此文章为skoootxl2008原创,转载请注明出处,尊重他人劳动成果。
http://blog.csdn.net/skoootxl2008/article/details/23948829
概述
ID3_V1,
ID3_V1_1,
ID3_V2_2,
ID3_V2_3,
ID3_V2_4,
};
ID3V2帧头ID3V2帧头长度为10个字节,位于文件首部,其数据结构如下:
char Header[3]; /* 字符串 "ID3" */
char Ver; /* 版本号ID3V2.3 就记录3 */
char Revision; /* 副版本号此版本记录为0 */
char Flag; /* 存放标志的字节,这个版本只定义了三位,很少用到,可以忽略 */
char Size[4]; /* 标签大小,除了标签头的10 个字节的标签帧的大小 */
标签大小为四个字节,但每个字节只用低7位,最高位不使用,恒为0,其格式如下:
0x0xxx 0x0xxx 0x0xxx 0x0xxx
计算公式如下:
ID3V2_frame_size = (int)(Size[0] & 0x7F) << 21
| (int)(Size[1] & 0x7F) << 14
| (int)(Size[2] & 0x7F) << 7
| (int)(Size[3] & 0x7F);具体的描述会在下面的源码分析中讲到,如果我们通过比较发现了这个帧头,我就可以开始获取ID3v2中的信息了。标签帧
标签帧的帧头同样也是10个字节
char FrameID[4]; /*用四个字符标识一个帧,说明其内容 ,从下面代码中可以看到,例如“TALB”*/
char Size[4]; /* 帧内容的大小,不包括帧头,不得小于1 */
char Flags[2]; /* 存放标志,只定义了6 位,此处不再说明 */
static const Map kMap[] = {
{ kKeyAlbum, "TALB", "TAL" },
{ kKeyArtist, "TPE1", "TP1" },
{ kKeyAlbumArtist, "TPE2", "TP2" },
{ kKeyComposer, "TCOM", "TCM" },
{ kKeyGenre, "TCON", "TCO" },
{ kKeyTitle, "TIT2", "TT2" },
{ kKeyYear, "TYE", "TYER" },
{ kKeyAuthor, "TXT", "TEXT" },
{ kKeyCDTrackNumber, "TRK", "TRCK" },
{ kKeyDiscNumber, "TPA", "TPOS" },
{ kKeyCompilation, "TCP", "TCMP" },
};
我们找到帧头后,根据帧内容的大小,就可以读取出帧标识(“TPE1”艺术家)所对应的内容(如:刘德华)。通过遍历我们把所有的这些信息保存在meta数据中,见如下代码:meta->setCString(kMap[i].key, s);其中s就是我们解析出来的内容。
下面就看看动力火车的一首歌的ID3V2信息:
可以看到从地址0000000h开始就是标识符“ID3”,说明这个有ID3V2信息,里面圈出来的就是标签帧的帧头中的FrameID。
下面看看一首没有ID3V2信息的歌曲,对比一下:
这个张国荣的一首歌暴风一族,它就没有ID3V2信息,直接就是数据帧的帧头(数据帧帧头格式下面再讲,相信我画红色框的4个字节就是数据帧的帧头)。
看完了帧头,再看看帧尾的ID3V1吧:
这个是动力火车的“背叛情歌”的结尾ID3V1,它的标志是“TAG”。ID3V2的标识是“ID3”。下面详细讲解ID3V1的帧结构。ID3V1
ID3V1的数据结构如下:
char Header[3]; /* 标签头必须是"TAG"否则认为没有标签 */
char Title[30]; /* 标题 ,可以看到上面选择部分确实是30个字节”*/
char Artist[30]; /* 作者 */
char Album[30]; /* 专集 */
char Year[4]; /* 出品年代 */
char Comment[28]; /* 备注 */
char reserve; /* 保留 */
char track;; /* 音轨 */
char Genre; /* 类型 */
从这样看来ID3V1的帧结构貌似比ID3v2要简单得多,所以选择好多MP3都没有使用ID3V2,它只是作为ID3V1的一个扩展。
名称
位长
说 明
同步信息
11
第1、2字节
所有位均为1,第1字节恒为FF。
版本
2
00-MPEG 2.5 01-未定义 10-MPEG 2 11-MPEG 1
层
2
00-未定义 01-Layer 3 10-Layer 2 11-Layer 1
CRC校验
1
0-校验 1-不校验
位率
4
第3字节
取样率,单位是kbps,例如采用MPEG-1 Layer 3,64kbps是,值为0101。
bits
V1,L1
V1,L2
V1,L3
V2,L1
V2,L2
V2,L3
0000
free
free
free
free
free
free
0001
32
32
32
32(32)
32(8)
8 (8)
0010
64
48
40
64(48)
48(16)
16 (16)
0011
96
56
48
96(56)
56(24)
24 (24)
0100
128
64
56
128(64)
64(32)
32 (32)
0101
160
80
64
160(80)
80(40)
64 (40)
0110
192
96
80
192(96)
96(48)
80 (48)
0111
224
112
96
224(112)
112(56)
56 (56)
1000
256
128
112
256(128)
128(64)
64 (64)
1001
288
160
128
288(144)
160(80)
128 (80)
1010
320
192
160
320(160)
192(96)
160 (96)
1011
352
224
192
352(176)
224(112)
112 (112)
1100
384
256
224
384(192)
256(128)
128 (128)
1101
416
320
256
416(224)
320(144)
256 (144)
1110
448
384
320
448(256)
384(160)
320 (160)
1111
bad
bad
bad
bad
bad
bad
V1 - MPEG 1 V2 - MPEG 2 and MPEG 2.5
L1 - Layer 1 L2 - Layer 2 L3 - Layer 3
"free" 表示位率可变 "bad" 表示不允许值采样频率
2
采样频率,对于MPEG-1: 00-44.1kHz 01-48kHz 10-32kHz 11-未定义
对于MPEG-2: 00-22.05kHz 01-24kHz 10-16kHz 11-未定义
对于MPEG-2.5: 00-11.025kHz 01-12kHz 10-8kHz 11-未定义
帧长调节
1
用来调整文件头长度,0-无需调整,1-调整,具体调整计算方法见下文。
保留字
1
没有使用。
声道模式
2
第4字节
表示声道, 00-立体声Stereo 01-Joint Stereo 10-双声道 11-单声道
扩充模式
2
当声道模式为01是才使用。
Value
强度立体声
MS立体声
00
off
off
01
on
off
10
off
on
11
on
on
版权
1
文件是否合法,0-不合法 1-合法
原版标志
1
是否原版, 0-非原版 1-原版
强调方式
2
用于声音经降噪压缩后再补偿的分类,很少用到,今后也可能不会用。
00-未定义 01-50/15ms 10-保留 11-CCITT J.17
MPEG 1
MPEG 2 (LSF)
MPEG 2.5 (LSF)
Layer I
384
384
384
Layer II
1152
1152
1152
Layer III
1152
576
576
概述
static bool Resync(
const sp<DataSource> &source, uint32_t match_header,
off64_t *inout_pos, off64_t *post_id3_pos, uint32_t *out_header) {
// inout_pos就是要开始同步的位置 ,即把inout_pos移向最近的一个帧头位置。
if (post_id3_pos != NULL) {
*post_id3_pos = 0;
}
if (*inout_pos == 0) {
//下面是个无限循环,开始查找ID3V2的标志“ID3”,虽然说是个
for (;;) {
uint8_t id3header[10];
if (source->readAt(*inout_pos, id3header, sizeof(id3header))
< (ssize_t)sizeof(id3header)) {
return false;
}
if (memcmp("ID3", id3header, 3)) {
ALOGD("------------>Find ID3, id3header");
break;
}
// Skip the ID3v2 header.
//这个地方是发现文件有ID3V2信息,我们要跳过去,所以要知道ID3V2信息占了多长
size_t len =
((id3header[6] & 0x7f) << 21)
| ((id3header[7] & 0x7f) << 14)
| ((id3header[8] & 0x7f) << 7)
| (id3header[9] & 0x7f);
len += 10;
ALOGD("len = %d, inout_pos = %p", len, inout_pos);
}
if (post_id3_pos != NULL) {
*post_id3_pos = *inout_pos;
}
}
off64_t pos = *inout_pos;
bool valid = false;
const size_t kMaxReadBytes = 1024;
const size_t kMaxBytesChecked = 128 * 1024;
uint8_t buf[kMaxReadBytes];
ssize_t bytesToRead = kMaxReadBytes;
ssize_t totalBytesRead = 0;
ssize_t remainingBytes = 0;
bool reachEOS = false;
uint8_t *tmp = buf;
ALOGD("post_id3_pos = %lld", post_id3_pos);
if (pos >= *inout_pos + kMaxBytesChecked) {
// Don't scan forever.
ALOGD("giving up at offset %lld", pos);
break;
}
//read 1024 byte to buf
if (remainingBytes < 4) {
if (reachEOS) {
} else {
memcpy(buf, tmp, remainingBytes);
bytesToRead = kMaxReadBytes - remainingBytes;
/*
* The next read position should start from the end of
* the last buffer, and thus should include the remaining
* bytes in the buffer.
*/
totalBytesRead = source->readAt(pos + remainingBytes,
buf + remainingBytes,
bytesToRead);
if (totalBytesRead <= 0) {
break;
}
reachEOS = (totalBytesRead != bytesToRead);
totalBytesRead += remainingBytes;
remainingBytes = totalBytesRead;
tmp = buf;
continue;
}
}
ALOGD("remainingBytes = %d", remainingBytes);
uint32_t header = U32_AT(tmp);
ALOGD("Go on to find header !!");
++pos;
++tmp;
--remainingBytes;
continue;
}
ALOGV("found possible 1st frame at %lld (header = 0x%08x)", pos, header);
int sample_rate, num_channels, bitrate;
if (!GetMPEGAudioFrameSize(
header, &frame_size,
&sample_rate, &num_channels, &bitrate)) {
--remainingBytes;
continue;
}
//在这里当前pos指向的这个header已经确认是正确的帧头了。
off64_t test_pos = pos + frame_size;
valid = true;
for (int j = 0; j < 3; ++j) {
uint8_t tmp[4];
if (source->readAt(test_pos, tmp, 4) < 4) {
valid = false;
break;
}
//获取下一帧的帧头
uint32_t test_header = U32_AT(tmp);
ALOGD("subsequent header is %08x", test_header);
if ((test_header & kMask) != (header & kMask)) {
valid = false;
break;
}
size_t test_frame_size;
if (!GetMPEGAudioFrameSize(
test_header, &test_frame_size)) {
valid = false;
break;
}
ALOGD("found subsequent frame #%d at %lld", j + 2, test_pos);
//继续跳到下一阵的帧头位置。
test_pos += test_frame_size;
}
if (valid) {
*inout_pos = pos;
if (out_header != NULL) {
*out_header = header;
}
} else {
ALOGD("no dice, no valid sequence of frames found.");
}
++pos;
++tmp;
--remainingBytes;
} while (!valid);
return valid;
}
&& GetMPEGAudioFrameSize(
header, &frame_size, &sample_rate, NULL,
&bitrate, &num_samples)) {
.......
break;
}
// Lost sync. 这里是没有指在帧头,所以要同步,调用Resync
off64_t pos = mCurrentPos;
if (!Resync(mDataSource, mFixedHeader, &pos, NULL, NULL)) {
// 同步不上就返回错误。
buffer->release();
buffer = NULL;
return ERROR_END_OF_STREAM;
}
mCurrentPos = pos;
//同步上了,得到新的地址,然后再去读取数据。
// Try again with the new position.
}