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



我正在尝试用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为这项任务编写了一个程序,它运行良好,使用了解压,例如:

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也用于获得正确的标头大小,但对于contet大小,它只是在没有任何转换的情况下打印N以"network"(big-endian)顺序排列的无符号长(32位)
C一个无符号的字符(八位字节)值
Z以null结尾的(ASCIZ)字符串,将被null填充

我的程序输出:
标题内容
标签:ID3
版本:3
版本:0
标志:0
尺寸:699

输出错误框架内容
ID:TPE1
大小:167772160
标志:

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

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

此外,正如文档所述,多字节数是Big Endian

ID3v2中的位顺序是最高有效位优先(MSB)多字节数中的字节顺序首先是最高有效字节(例如。$1234678将被编码为$12345678)。

然而,没有对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系统上它将什么都不做)。

您得到的值不是最初发布的1677772160,而是0x0A0000000,这立即表明您的字节与预期的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;
}

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

最新更新