mp3文件的二进制读取ID3标签
Posted
技术标签:
【中文标题】mp3文件的二进制读取ID3标签【英文标题】:Binary reading ID3 tag of mp3 file 【发布时间】:2015-12-26 19:18:52 【问题描述】:我正在尝试用 c++ 读取一个 mp3 文件并显示该文件包含的 id3 信息。我遇到的问题是当我读取帧头时,它所包含的内容的大小是错误的。它没有给我一个 10 个字节的整数,而是给了我 167772160 个字节。 http://id3.org/id3v2.3.0#ID3v2_frame_overview
struct Header
char tag[3];
char ver;
char rev;
char flags;
uint8_t hSize[4];
;
struct ContentFrame
char id[4];
uint32_t contentSize;
char flags[2];
;
int ID3_sync_safe_to_int(uint8_t* sync_safe)
uint32_t byte0 = sync_safe[0];
uint32_t byte1 = sync_safe[1];
uint32_t byte2 = sync_safe[2];
uint32_t byte3 = sync_safe[3];
return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
const int FRAMESIZE = 10;
上面的代码用于将二进制数据转换为 ASCCI 数据。 主要内部
Header header;
ContentFrame contentFrame;
ifstream file(argv[1], fstream::binary);
//Read header
file.read((char*)&header, FRAMESIZE);
//This will print out 699 which is the correct filesize
cout << "Size: " << ID3_sync_safe_to_int(header.hSize) << endl << endl;
//Read frame header
file.read((char*)&contentFrame, FRAMESIZE);
//This should print out the frame size.
cout << "Frame size: " << int(contentFrame.contentSize) << endl;
我在 Perl 中为此任务编写了一个程序,它运行良好,使用了 unpack,例如:
my($tag, $ver, $rev, $flags, $size) = unpack("Z3 C C C N"), "header");
my($frameID, $FrameContentSize, $frameFlags) = unpack("Z4 N C2", "content");
sync_safe_to_int 也用于获得正确的标题大小,但对于内容大小,它只是在没有任何转换的情况下打印 N 一个“网络”(大端)顺序的无符号长整数(32 位)。 C 一个无符号字符(八位字节)值。 Z 一个以 null 结尾的 (ASCIZ) 字符串,将填充为 null。
我的程序的输出: 标题内容 标签:ID3 版本:3 版本:0 标志:0 尺寸:699
输出错误! 框架内容 编号:TPE1 尺寸:167772160 标志:
Perl 的正确输出! 框架内容 编号:TPE1 尺寸:10 标志:0
【问题讨论】:
我无法回答您的问题。你说“它给了我大约 140000 字节”,然后你说你看到了size: 1677772160
。这是一个错误还是我误解了你?
这是我的错误,现在应该更正了。正确的输出是 10,错误的输出是 167772160。对于 1400000 的混淆,您可以忘记,我很抱歉。
【参考方案1】:
contentFrame.contentSize
定义为uint32_t
,但打印为(signed)int
。
另外,document 声明多字节数是Big Endian:
ID3v2 中的位顺序是最高有效位在前 (MSB)。 该 多字节数字中的字节顺序是最重要的字节优先(例如 $12345678 将编码为 $12 34 56 78)。
但是,contentFrame.contentSize
没有进行任何转换。这些字节也应该反转,如ID3_sync_safe_to_int()
,但这次以 8 的倍数而不是 7 移动(或使用 ntohl()
- 网络到主机的顺序)。
您说您得到 1677772160 而不是 18,但即使对上述位/字节进行操作,它们似乎也没有任何意义。你确定这些数字是正确的吗?除了您的帖子之外,您还有其他价值观:
它没有给我一个低于 100 字节的低整数,而是给了我周围 140000 字节。
您在调用file.read((char*)&contentFrame, FRAMESIZE);
后查看内存中的字节了吗?但是,如果您的 ID 显示 TPE1
,则该位置应该没问题。我只是想知道您提供的数字是否正确,因为它们没有意义。
更新nthol()
转换:
//Read frame header
file.read((char*)&contentFrame, FRAMESIZE);
uint32_t frame_size = ntohl(contentFrame);
cout << "Frame size: " << frame_size << endl;
ntohl()
将在 LE 系统上工作和在 BE 系统上(在 BE 系统上它根本不会做任何事情)。
【讨论】:
我得到 167772160 而不是 10。这是我的错。我现在提供的数字应该是正确的。我会尝试你的建议! 是的,这正是大端与小端。您需要做的就是反转字节。 也可以查看ntohl()
。
现在我明白了,使用网络长的原因是因为它不依赖于硬件。 htonl() host to network long ntohl() network to host long 感谢您的帮助!【参考方案2】:
您获得的值不是您最初发布的 1677772160,而是 167772160,即 0x0A000000,这立即表明您的字节与您期望的 0x0000000A(十进制 10)相反
您已经安排 Perl 使用 N 格式以 big-endian 格式读取此内容,但您的 C 代码使用简单的 uint32_t
,它依赖于硬件并且可能是 little-endian
您需要为此字段编写一个字节反转子例程,其行为与您的标题字段的ID3_sync_safe_to_int
相同,但使用该值的所有 32 位。像这样的
uint32_t reverse_endian(uint32_t val)
typedef union
uint32_t val;
uint8_t byte[4];
split;
split *original = (split *) &val;
split new;
new.byte[0] = original->byte[3];
new.byte[1] = original->byte[2];
new.byte[2] = original->byte[1];
new.byte[3] = original->byte[0];
return new.val;
【讨论】:
感谢您对潜在问题的清晰描述。正如 Danny_ds 指出的那样,有一个函数可以做到这一点。 ntohl() 从网络转换为主机 long【参考方案3】:好的,我不确定您是否正确解释了 ID3_sync_safe_to_int
方法中的帧大小。
编辑:我不知道是什么导致了这个问题,但你可以用 fread 单独读取你的帧大小,或者这样做:
#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
using namespace std;
struct Header
char tag[3];
char ver;
char rev;
char flags;
uint8_t hSize[4];
;
struct ContentFrame
char id[4];
char contentSize[4];
char flags[2];
;
int ID3_sync_safe_to_int(uint8_t* sync_safe)
uint32_t byte0 = sync_safe[0];
uint32_t byte1 = sync_safe[1];
uint32_t byte2 = sync_safe[2];
uint32_t byte3 = sync_safe[3];
return byte0 << 21 | byte1 << 14 | byte2 << 7 | byte3;
const int FRAMESIZE = 10;
int main ( int argc, char **argv )
Header header;
ContentFrame contentFrame;
ifstream file(argv[1], fstream::binary);
//Read header
file.read((char*)&header, FRAMESIZE);
//This will print out 699 which is the correct filesize
cout << "Size: " << ID3_sync_safe_to_int(header.hSize) << endl << endl;
//Read frame header
file.read((char*)&contentFrame, FRAMESIZE);
//This should print out the frame size.
int frame_size = (contentFrame.contentSize[3] & 0xFF) |
((contentFrame.contentSize[2] & 0xFF) << 7 ) |
((contentFrame.contentSize[1] & 0xFF) << 14 ) |
((contentFrame.contentSize[0] & 0xFF) << 21 );
cout << "Frame size: " << frame_size << endl;
//cout << "Frame size: " << int(contentFrame.contentSize) << endl;
【讨论】:
该字段在标头中,并且最高有效位保证为零。那里的值被正确检索;ContentFrame
中的字段 contentSize
是错误的,其中每个字节中的所有八位都是有效的以上是关于mp3文件的二进制读取ID3标签的主要内容,如果未能解决你的问题,请参考以下文章
通过javascript从文件夹中的mp3文件中读取id3标签