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*)&amp;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标签的主要内容,如果未能解决你的问题,请参考以下文章

读取远程 mp3 文件的 ID3 标签?

通过javascript从文件夹中的mp3文件中读取id3标签

从 mp3 id3v2 标签中识别歌曲信息

删除 id3v2 标签 c++

使用 JavaFX MediaPlayer 从 MP3 读取 ID3v2 标签

拒绝从外部mp3获取不安全的标题“Content-Length”/读取ID3标签