0

I have been attempting to retrieve ID3V2 Tag Frames by parsing through the mp3 file and retrieving each frame's size. So far I have had no luck.

I have effectively allocated memory to a buffer to aid in reading the file and have been successful in printing out the header version but am having difficulty in retrieving both the header and frame sizes. For the header framesize I get 1347687723, although viewing the file in a hex editor I see 05 2B 19.

Two snippets of my code:

typedef struct{ //typedef structure used to read tag information
char tagid[3];              //0-2  "ID3"
unsigned char tagversion;   //3    $04
unsigned char tagsubversion;//4     00
unsigned char flags;        //5-6   %abc0000
uint32_t size;              //7-10  4 * %0xxxxxxx
}ID3TAG;

if(buff){
    fseek(filename,0,SEEK_SET); 
    fread(&Tag, 1, sizeof(Tag),filename); 
    if(memcmp(Tag.tagid,"ID3", 3) == 0)
    {
        printf("ID3V2.%02x.%02x.%02x \nHeader Size:%lu\n",Tag.tagversion, 
    Tag.tagsubversion, Tag.flags ,Tag.size);    
    }

}
James Z
  • 12,209
  • 10
  • 24
  • 44
  • 3
    Perhaps you need a packed `struct`, there might be padding bytes for alignment of `uint32_t size`. There are 10 bytes-worth of storage used, but MSVC reports the `struct` size to be 12. One safe way would be to read the data into an array of `unsigned char` and pick out the values from the array. – Weather Vane Nov 25 '17 at 09:27

3 Answers3

1

Due to memory alignment, the compiler has set 2 bytes of padding between flags and size. If your struct were putted directly in memory, size would be at address 6 (from the beginning of the struct). Since an element of 4 bytes size must be at an address multiple of 4, the compiler adds 2 bytes, so that size moves to the closest multiple of 4 address, which is here 8. So when you read from your file, size contains bytes 8-11. If you try to print *(&Tag.size - 2), you'll surely get the correct result.

To fix that, you can read fields one by one.

  • Thank you for the reply. Printing with *(&Tag.size-2) with %lu printed out 53691465, printing with %x printed out 3334449. I'm still a bit confused about these values. 05 2B 19 from hex to decimal is 338713, I would assume this value represents bytes, therefore it should be approximately 2709704 bits. – KolacheMaster Nov 25 '17 at 18:37
  • Maybe there are some other missed parameters. Try to read fields one by one, and tell me if it works. –  Nov 25 '17 at 20:03
1

ID3v2 header structure is consistent across all ID3v2 versions (ID3v2.0, ID3v2.3 and ID3v2.4).

Its size is stored as a big-endian synch-safe int32

Synchsafe integers are integers that keep its highest bit (bit 7) zeroed, making seven bits out of eight available. Thus a 32 bit synchsafe integer can store 28 bits of information.

Example:
255 (%11111111) encoded as a 16 bit synchsafe integer is 383 (%00000001 01111111).

Source : http://id3.org/id3v2.4.0-structure § 6.2


Below is a straightforward, real-life C# implementation that you can easily adapt to C

public int DecodeSynchSafeInt32(byte[] bytes)
{
    return                 
        bytes[0] * 0x200000 +   //2^21
        bytes[1] * 0x4000 +     //2^14
        bytes[2] * 0x80 +       //2^7
        bytes[3];
}

=> Using values you read on your hex editor (00 05 EB 19), the actual tag size should be 112025 bytes.

Paul W
  • 149
  • 1
  • 6
  • No: ID3v2.2 uses 24bit in 3 bytes, v2.3 uses 32bit in 4 bytes, v2.4 uses 28bit in 4 bytes - see https://id3.org/id3v2.3.0#ID3v2_frame_overview and https://id3.org/id3v2-00 – AmigoJack May 22 '20 at 10:14
  • @AmigoJack what you're describing relates to the _frame header_, not the _ID3v2 header_. Judging by the OP's code, he's trying to read the ID3v2 header – Paul W May 22 '20 at 14:17
  • 1
    Yes, you're right. OP states "both the header and frame sizes" tho, hence I wonder why information about frames is missing. – AmigoJack May 22 '20 at 16:01
-1

By coincidence I am also working on an ID3V2 reader. The doc says that the size is encoded in four 7-bit bytes. So you need another step to convert the byte array into an integer... I don't think just reading those bytes as an int will work because of the null bit on top.