读取MP3文件的二进制ID3标签

3
我试图在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;

上面的代码用于将二进制数据转换为ASCII数据。 在主函数内部

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

错误输出! 帧内容
ID:TPE1
大小:167772160
标志:

Perl 的正确输出! 帧内容
ID:TPE1
大小:10
标志:0


1
我很难理解你的问题。你说“它给了我大约140000字节”,但后来又说你看到了“大小: 1677772160”。这是一个错误还是我误解了你的意思? - Borodin
这是我的错误,现在应该已经被纠正了。 正确的输出是10,错误的输出是167772160。 对于1400000造成的混淆我很抱歉,可以忘记它,是我的错。 - Fredrik
3个回答

1

contentFrame.contentSize被定义为uint32_t,但打印时被视为(signed)int

此外,正如文档所述,多字节数字采用大端序

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() 在小端系统和大端系统上都可以使用(在大端系统上它将不起作用)。


我得到的是167772160而不是10。这是我的错误。我现在提供的数字应该是正确的。我会尝试你建议的! - Fredrik
没错,这正是大端序和小端序的区别。你所需要做的就是反转字节。 - Danny_ds
1
还要查看ntohl() - Danny_ds
现在我明白了,使用网络长整型的原因是因为它不依赖于硬件。 htonl() 函数用于将主机字节序转换为网络字节序的长整型数据。 ntohl() 函数用于将网络字节序转换为主机字节序的长整型数据。感谢您的帮助! - Fredrik

1

原本您发布的1677772160值,现在获取到的是167772160,即0x0A000000。这表明您的字节与您期望的0x0000000A(10进制)相反。

您已经安排Perl以大端格式读取此数据,使用N格式,但是您的C代码使用了一个简单的uint32_t,这是硬件相关的,可能是小端格式。

您需要为此字段编写一个字节反转子例程,其行为与您的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()将网络字节序转换为主机字节序。 - Fredrik

0

好的,我不确定您是否正确解释了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字段上,其中每个字节的所有八位都是有效的。 - Borodin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接