读取MP3 IDV2标签大小

时间:2016-12-11 15:50:27

标签: c mp3 id3v2 async-safe

我正在尝试读取ID3V2标签的大小。我的代码应该存储第一个头文件,其中包含此结构中的标识,版本,标志和大小。代码从第0位转到第9位并将其存储在此处

typedef struct
{
   uint32_t id:24; //"ID3"
   uint16_t version; // $04 00
   uint8_t flags; // %abcd0000
   uint32_t size; //4 * %0xxxxxxx
}__attribute__((__packed__))
ID3TAG;

读取:

fread(tag, sizeof(ID3TAG), 1, media);

然后将tag.size的值传递给此函数,该函数不同步大小的位:

int unsynchsafe(uint32_t in)
{
    int out = 0, mask = 0x7F000000;

    while (mask) {
        out >>= 1;
        out |= in & mask;
        mask >>= 8;
    }

    return out;
}

然而,synchsafe的返回值永远不会是标题的正确大小。我得到了248627840.我使用exif工具进行了双重检查,结果不正确。我真的很感激任何帮助

1 个答案:

答案 0 :(得分:1)

您遇到的问题与字节序有关。我假设你正在使用x86系统,或者是另一个小端的系统。 ID3 documentation表示:

  

多字节数字中的字节顺序是最重要的字节(例如,$ 12345678将编码为$ 12 34 56 78)。

因此size被存储为文件中的大端编号。在将文件的字节读入struct之后,需要将此字节顺序转换为little-endian,然后剥离四个零位以获得size的最终28位表示。这也是您必须将tag->id0x334449而不是0x494433进行比较的原因 - tag->id中存储的字节作为多字节值进行访问,并以little-endian进行解释顺序。

以下是我为完成此项工作所做的更改。我使用struct数组更改了uint8_t一点,以获得正确的字节数。我还使用memcmp()来验证tag->id。我自由地使用unsignedunsigned long类型,以避免比特转移的困境。转换为little-endian是原始的,并假定为8位字节。

这是您在第一篇文章中链接到的整个文件,包含我的更改。我将mp3文件更改为我可以测试的内容。

#include <stdint.h>
#include <stdio.h>
#include <string.h>  // for memcmp()

/**
 ** TAG is always present at the beggining of a ID3V2 MP3 file 
 ** Constant size 10 bytes
 **/

typedef struct
{
    uint8_t id[3];       //"ID3"
    uint8_t version[2];  // $04 00
    uint8_t flags;       // %abcd0000
    uint32_t size;        //4 * %0xxxxxxx
}__attribute__((__packed__))
ID3TAG;

unsigned int unsynchsafe(uint32_t be_in)
{
    unsigned int out = 0ul, mask = 0x7F000000ul;
    unsigned int in = 0ul;

    /* be_in is now big endian */
    /* convert to little endian */
    in = ((be_in >> 24) | ((be_in >> 8) & 0xFF00ul) |
          ((be_in << 8) & 0xFF0000ul) | (be_in << 24));

    while (mask) {
        out >>= 1;
        out |= (in & mask);
        mask >>= 8;
    }

    return out;
}

/**
 ** Makes sure the file is supported and return the correct size
 **/
int mp3Header(FILE* media, ID3TAG* tag)
{
    unsigned int tag_size;

    fread(tag, sizeof(ID3TAG), 1, media);

    if(memcmp ((tag->id), "ID3", 3))
    {
        return -1;
    }

    tag_size = unsynchsafe(tag->size);
    printf("tag_size = %u\n", tag_size);

    return 0;   
}

// main function
int main(void)
{
    // opens the file
    FILE* media = fopen("cognicast-049-carin-meier.mp3", "r");

    //checks if the file exists
    if(media == NULL)
    {
        printf("Couldn't read file\n");
        return -1;
    } 

    ID3TAG mp3_tag;
    // check for the format of the file
    if(mp3Header(media, &mp3_tag) != 0)
    {
        printf("Unsupported File Format\n");
        fclose(media);
        return -2;      
    }
    fclose(media);

    return 0;
}

顺便提一下,C标准库中已有一个执行此转换的功能。 ntohl()位于netinet/in.h头文件中,它将uint32_t数字从网络字节顺序(大端)转换为主机字节顺序。如果您的系统是big-endian,则该函数返回输入值不变。但如果您的系统是little-endian,则输入将转换为little-endian表示。这对于使用不同字节顺序约定的计算机之间传递数据很有用。还有相关的功能htonl()htons()ntohs()

通过将原始转换代码替换为:

,可以更改上述代码(更好)以使用ntohl()
#include <netinet/in.h>  // for ntohl()
...
/* convert to host-byte-order (little-endian for x86) */
in = ntohl(be_in);