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

Binary reading ID3 tag of mp3 file

我正在尝试用 C++ 读取一个 mp3 文件并显示该文件包含的 id3 信息。我遇到的问题是,当我阅读框架 header 时,它包含的内容的大小是错误的。它没有给我一个 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 为这个任务编写了一个程序,它运行良好,解包是这样使用的:

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 也用于使 header 的大小正确,但对于内容大小,它只是打印而不进行任何转换 N "network" (big-endian) 顺序的无符号长整数(32 位)。
C 一个无符号字符(八位字节)值。
Z A null-terminated (ASCIZ) 字符串,将被空填充。

我程序的输出:
Header内容
标签:ID3
版本:3
修订:0
标志:0
尺码: 699

输出错误! 框架内容
ID:TPE1
尺寸:167772160
标志:

Perl 的正确输出! 框架内容
ID:TPE1
尺寸:10
标志:0

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

此外,由于 document 声明多字节数字是 Big Endian:

The bitorder in ID3v2 is most significant bit first (MSB). The byteorder in multibyte numbers is most significant byte first (e.g. 345678 would be encoded 34 56 78).

但是 contentFrame.contentSize 没有转换。这些字节也应该反转,如 ID3_sync_safe_to_int(),但这次以 8 的倍数而不是 7 的倍数移动(或使用 ntohl() - 网络到主机顺序)。

你说你得到 1677772160 而不是 18,但即使对上面的 bits/bytes 进行了操作,它们似乎也没有意义。你确定这些是正确的数字吗?在您的 post 之上,您还有其他值:

Instead of giving me a low integear under 100 bytes it gives me around 140000 bytes.

调用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 系统上它什么都不做)。

好的,我不确定您是否正确解释了 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;
}

您最初发布的不是 1677772160,而是您得到的值是 167772160,即 0x0A000000,这立即表明您的字节与您期望的 0x0000000A(十进制 10)相反

您已安排 Perl 使用 N 格式以 big-endian 格式读取此内容,但您的 C 代码使用了简单的 uint32_t,即 hardware-dependent 并且可能是 little-endian

您需要为此字段编写一个字节反转子例程,其行为与 header 字段的 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;
}