2015-12-26 11 views
3

Я пытаюсь прочитать mp3-файл на C++ и показать информацию о файле id3, содержащуюся в этом файле. Проблема в том, что когда я читаю заголовок фрейма, размер содержимого, который он держит, является неправильным. Вместо того, чтобы дать мне целое число в 10 байт, он дает мне 167772160 байт. http://id3.org/id3v2.3.0#ID3v2_frame_overviewБинарное чтение ID3-тега mp3-файла

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 также используется в чтобы получить размер заголовка правильно, но для размера контура он должен печатать без каких-либо преобразований N Незнакомый длинный (32-разрядный) в «сети» (big-endia n) порядок.
C Значение без знака (октет).
Z Строка с нулевым завершением (ASCIZ), будет заполняться нулями.

Выход из моей программы:
содержание заголовка
Тэг: ID3
Ver: 3
Rev: 0
Флаги: 0
Размер: 699

НЕПРАВИЛЬНО выход! содержание кадров
ID: TPE1
размер: 167772160
Флаги:

Правильный выход из Perl! содержание кадра
ID: TPE1
размер: 10
Флаги: 0

+1

У меня возникли проблемы после вашего вопроса. Вы говорите * «он дает мне около 140000 байт» *, а затем позже вы говорите, что видите «размер: 1677772160». Это ошибка или я вас не понимаю? – Borodin

+0

Это была ошибка с моей стороны, теперь она должна была быть исправлена. Правильный выход - 10, а неправильный вывод - 167772160. Прошу прощения за путаницу около 1400000, которую вы можете забыть, мой плохой. – Fredrik

ответ

1

contentFrame.contentSize определяется как uint32_t, но печатается как (signed)int.

Кроме того, в качестве document состояний многобайтового номер Big Endian:

bitorder в ID3v2 наиболее значимых бит первый (MSB). Байт в многобайтовых номерах является самым значимым байтом (например, $ 12345678 будет зашифрован $ 12 34 56 78).

Передача не производится для contentFrame.contentSize.Эти байты также должны быть отменены, как в ID3_sync_safe_to_int(), но на этот раз сдвинуты в несколько раз вместо 8 вместо 7 (или используйте ntohl() - порядок от сети к хосту).

Вы говорите, что вы получаете 1677772160 вместо 18, но даже при манипулировании битами/байтами для вышеизложенного они, похоже, не имеют смысла. Вы уверены, что это правильные цифры? На верхней части вашего поста у вас есть и другие значения:

Вместо того, чтобы давать мне низкую integear до 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-системах это будет просто сделать nothig).

+0

Я получаю 167772160 вместо 10. Это было мое плохо. Числа, которые я сейчас предоставил, должны быть правильными. Я попробую то, что вы предложили! – Fredrik

+0

Да, это точно Большой эндиан против Маленького эндиана. Все, что вам нужно сделать, это изменить байты. –

+1

Также проверьте 'ntohl()'. –

0

ОК Я не уверен, что вы правильно интерпретировали размер своей рамки в методе ID3_sync_safe_to_int.

Edit: я понятия не имею, что вызывает эту проблему, но вы можете читать FrameSize с 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; 
} 
+0

Это поле находится в заголовке, и самый старший бит гарантированно равен нулю.Значимость получена правильно; Это поле 'contentSize' в' ContentFrame', которое является неправильным, где все восемь бит в каждом байте являются значимыми – Borodin

1

Вместо 1677772160, что вы публикуемую изначально, то значение, которое вы получаете, 167772160, который равен 0x0A000000, что сразу показывает, что ваши байты меняются на обратную сторону от 0x0000000A (10 десятичных знаков), которые вы ожидаете

Вы подготовили Perl для чтения этого формата в формате big-endian с использованием формата 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; 
} 
+0

Спасибо за четкое описание проблемы под лежачей. Как указал Danny_ds, есть функция, которая делает это. ntohl() преобразует из сети в хост – Fredrik

Смежные вопросы