Reputation: 494
I'am trying to read a mp3 file in c++ and show the id3 information that the file contains. The problem I have is when i read the frame header the size of the content that it holds is wrong. Instead of giving me a integer of 10 bytes it gives me 167772160 bytes. 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;
The code above is used in order to translate the binary data to ASCCI data. Inside of main
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;
I have written a program for this task in Perl and it works fine, there unpack is used such as:
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 is also used in order to get the size of the header correct but for the contet size it is only to print witout any conversion
N An unsigned long (32-bit) in "network" (big-endian) order.
C An unsigned char (octet) value.
Z A null-terminated (ASCIZ) string, will be null padded.
The output from my program:
Header content
Tag: ID3
Ver: 3
Rev: 0
Flags: 0
Size: 699
WRONG Output!
Frame content
ID: TPE1
size: 167772160
Flags:
Correct output from Perl!
Frame content
ID: TPE1
size: 10
Flags: 0
Upvotes: 3
Views: 2298
Reputation: 11406
contentFrame.contentSize
is defined as uint32_t
, but printed as (signed)int
.
Also, as the document states multibyte numbers are Big Endian:
The bitorder in ID3v2 is most significant bit first (MSB). The byteorder in multibyte numbers is most significant byte first (e.g. $12345678 would be encoded $12 34 56 78).
No conversion is done for contentFrame.contentSize
however. Those bytes should be reversed too, as in ID3_sync_safe_to_int()
, but this time shifted in multiples of 8 instead of 7 (or use ntohl()
- network-to-host order).
You say that you get 1677772160 instead of 18, but even with manipulation of the bits/bytes for the above, they don't seem to make sense. Are you sure those are the right numbers? On top of your post you have other values:
Instead of giving me a low integear under 100 bytes it gives me around 140000 bytes.
Did you have a look at the bytes in memory after calling file.read((char*)&contentFrame, FRAMESIZE);
? However if your ID shows TPE1
the position should be ok. I just wonder if the numbers you provided are the correct ones, because they don't make sense.
Update with nthol()
conversion:
//Read frame header
file.read((char*)&contentFrame, FRAMESIZE);
uint32_t frame_size = ntohl(contentFrame);
cout << "Frame size: " << frame_size << endl;
ntohl()
will work on LE-systems and on BE-systems (on BE-systems it will simply do nothig).
Upvotes: 1
Reputation: 98
okay I am not sure if you have interpreted your frame size in ID3_sync_safe_to_int
method correctly.
Edit: I have no idea what causes this problem but you can read your framesize with fread separately or do this:
#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;
}
Upvotes: 0
Reputation: 126762
Instead of 1677772160 that you posted originally, the value you are getting is 167772160, which is 0x0A000000, which shows immediately that your bytes are reversed from the 0x0000000A (10 decimal) that you expect
You have arranged for Perl to read this in big-endian format using the N format, but your C code uses a simple uint32_t
, which is hardware-dependent and presumably little-endian
You need to write a byte reversal subroutine for this field that behaves the same as your ID3_sync_safe_to_int
for your header field but uses all 32 bits of the value. Something like this
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;
}
Upvotes: 1