
Парсинг метаданных WAV-файлов на Ассемблере

Парсинг метаданных WAV-файлов на Ассемблере
Формат WAV, разработанный совместно IBM и Microsoft, чаще всего используется для хранения несжатого потокового аудио. Несмотря на авторство MS и повсеместное использование WAV-файлов в Windows, системными средствами извлечь из них метаданные невозможно. Ну и ничего страшного, внутренний формат файлов достаточно простой, так что распарсим его самостоятельно.
Файл начинается с 12-байтного заголовка, который состоит из строки "RIFF", DWORD'а с размером файла без учета первых восьми символов и строки "WAVE", обозначающей формат файла. По этим значениям можно и нужно проверять корректность файла перед дальнейшей обработкой. Дальше следуют секции (чанки) с различными данными. Каждая секция начинается с 8-байтного заголовка, который, в свою очередь, состоит из 4 байт строки-идентификатора секции и DWORD'а с размером данных секции. Название идентификатора может состоять из заглавных и строчных букв, при обработке файла это надо учитывать.
Обычно метаданные записаны в секцию с идентификатором "LIST" и начинаются они с подзаголовка "INFO". Сами метаданные хранятся в следующем формате: четырехбайтное название тега, DWORD с размером данных тега и, соответственно, сами данные - строка в кодировке UTF-8. Список доступных названий тегов RIFF можно посмотреть здесь или здесь. В процессе исследования WAV-файлов, созданных различным софтом, выяснилось, что между тегами могут присутствовать заполнители из нулевых символов, которые не учитываются в длине данных тега. Не исключаю, что могут быть еще какие-нибудь нежданчики, но пока я нашел только этот. Секция "LIST" может располагаться как в конце файла, так и в его начале.
Кроме секции "LIST" в файле может присутствовать секция с идентификатором "ID3_" (в конце символ пробела), в которой, как мы можете догадаться, также записаны метаданные. Тут тоже не обошлось без своих особенностей. Во-первых, все секции выравниваются на границу 2 байт, причем дополнительный нулевой байт не входит в размер секции. Во-вторых, все размеры данных внутри секции ID3 записаны полноценными DWORD'ами, а не формируются из четырех 7-битных блоков, как это принято стандартом ID3. Но на 100% утверждать я это, к сожалению, тоже не могу, так как ориентируюсь только на готовые результаты из различных источников. В-третьих, секция "ID3_" в файле может присутствовать как совместно с секцией "LIST", так и вместо нее, а может и вовсе отсутствовать. Спецификацией WAV-файлов предусмотрено, что строчными буквами обозначаются идентификаторы обязательных секций, а заглавными - секции с пользовательскими данными. Так вот, для секции "ID3_" я встречал оба варианта написания, это в-четвертых. В остальном разбор этого контейнера ничем не отличается от описанного ранее ID3v2.3. Формат ID3 поддерживает юникод, поэтому при одновременном наличии в файле секций "ID3_" и "LIST", данные предпочтительнее брать именно из "ID3_" или же использовать их с бОльшим приоритетом, если данные отличаются.
Как вы поняли из вышесказанного, при парсинге придется учитывать немало особенностей. Я сделал парсер, который работает с обоими вариантами секций и корректно сглаживает все выявленные шероховатости.
Code (Assembler) : Убрать нумерацию
- ; Открыть файл для чтения
- invoke CreateFile,sample,GENERIC_READ,\
- FILE_SHARE_READ,0,OPEN_EXISTING,0,0
- mov [desc],eax
- ; Прочитать заголовок файла
- invoke _lread,[desc],buff,12
- ; Проверить корректность файла
- cmp dword [buff],'RIFF'
- jne loc_close_wav
- cmp dword [buff+8],'WAVE'
- jne loc_close_wav
- loc_read_section:
- ; Прочитать заголовок секции
- invoke _lread,[desc],buff,8
- cmp eax,-1
- je loc_close_wav
- ; Название секции
- mov eax,dword [buff]
- ; Перевести в нижний регистр
- or eax,20202020h
- ; Метаданные в секции LIST?
- cmp eax,'list'
- je loc_read_list
- ; Метаданные в секции ID3?
- cmp eax,'id3 '
- je loc_read_id3
- ; Следующая секция
- jmp loc_skip_section
- loc_read_id3:
- ;----------------------------------------------
- ; Обработка секции ID3
- ;----------------------------------------------
- ; Выделить память под метаданные
- mov ebx,dword [buff+4]
- invoke GlobalAlloc,GMEM_ZEROINIT,ebx
- mov [hMem],eax
- invoke GlobalLock,[hMem]
- mov [pMem],eax
- ; Прочитать метаданные
- invoke _lread,[desc],[pMem],ebx
- mov esi,[pMem]
- ; ID3v2.3
- cmp dword [esi],0x03334449
- je @f
- ; ID3v2.4
- cmp dword [esi],0x04334449
- je @f
- ; Освободить память
- invoke GlobalUnlock,[hMem]
- ; Следующая секция
- jmp loc_read_section
- @@:
- add esi,10
- loc_scan_id3_tags:
- ; Все теги обработали?
- mov eax,esi
- sub eax,[pMem]
- cmp eax,ebx
- jae loc_done_wav
- cmp byte[esi],0
- je loc_done_wav
- ; Проверить валидность названия тега
- xor ecx,ecx
- loc_check_id3_wav:
- mov dl,byte[esi+ecx]
- cmp dl,'0'
- jb loc_done_wav
- cmp dl,'9'
- jbe @f
- cmp dl,'A'
- jb loc_done_wav
- cmp dl,'Z'
- ja loc_done_wav
- @@:
- inc ecx
- cmp ecx,4
- jb loc_check_id3_wav
- ; Album
- mov edi,album
- cmp dword [esi],'TALB'
- je @f
- ; Artist
- mov edi,artist
- cmp dword [esi],'TPE1'
- je @f
- ; Title
- mov edi,title
- cmp dword [esi],'TIT2'
- je @f
- ; Пропустить тег
- lodsd
- ; Размер данных
- lodsd
- bswap eax
- add esi,eax
- lodsw
- jmp loc_scan_id3_tags
- @@:
- lodsd
- ; Размер данных
- lodsd
- bswap eax
- mov ecx,eax
- ; Размер без учета байта типа кодировки
- dec ecx
- lodsw
- ; Тип кодировки строки
- lodsb
- or al,al
- jz loc_load_utf8
- loc_load_utf16:
- ; Пропустить BOM
- cmp word[esi],0xFEFF
- jne @f
- lodsw
- dec ecx
- dec ecx
- @@:
- ; Просто скопировать строку UTF-16
- rep movsb
- xor eax,eax
- stosw
- jmp loc_scan_id3_tags
- loc_load_utf8:
- ; UTF-8 -> юникод
- push ecx
- invoke MultiByteToWideChar,CP_UTF8,0,esi,ecx,0,0
- invoke MultiByteToWideChar,CP_UTF8,0,esi,-1,edi,eax
- pop ecx
- ; Следующий тег
- add esi,ecx
- jmp loc_scan_id3_tags
- ;----------------------------------------------
- ; Обработка секции LIST
- ;----------------------------------------------
- loc_read_list:
- ; Выделить память под метаданные
- mov ebx,dword [buff+4]
- invoke GlobalAlloc,GMEM_ZEROINIT,ebx
- mov [hMem],eax
- invoke GlobalLock,[hMem]
- mov [pMem],eax
- ; Прочитать метаданные
- invoke _lread,[desc],[pMem],ebx
- ; Это метаданные?
- mov esi,[pMem]
- lodsd
- ; Перевести в нижний регистр
- or eax,20202020h
- cmp eax,'info'
- je loc_scan_list_tags
- ; Освободить память
- invoke GlobalUnlock,[hMem]
- ; Следующая секция
- jmp loc_read_section
- loc_scan_list_tags:
- ; Все теги обработали?
- mov eax,esi
- sub eax,[pMem]
- cmp eax,ebx
- jae loc_done_wav
- ; Пропустить финальные нули
- cmp byte[esi],0
- jne @f
- inc esi
- jmp loc_scan_list_tags
- @@:
- ; Наименование тега
- lodsd
- ; Перевести в нижний регистр
- or eax,20202020h
- mov edi,album
- cmp eax,'iprd'
- je @f
- mov edi,artist
- cmp eax,'iart'
- je @f
- mov edi,title
- cmp eax,'inam'
- je @f
- lodsd
- add esi,eax
- jmp loc_scan_list_tags
- @@:
- lodsd
- ; UTF-8 -> юникод
- push eax
- invoke MultiByteToWideChar,CP_UTF8,0,esi,eax,0,0
- invoke MultiByteToWideChar,CP_UTF8,0,esi,-1,edi,eax
- pop eax
- ; Следующий тег
- add esi,eax
- jmp loc_scan_list_tags
- loc_skip_section:
- ; Перейти к следующей секции
- mov eax,dword [buff+4]
- ; Учесть выравнивание секции
- inc eax
- and eax,0FFFFFFFEh
- invoke _llseek,[desc],eax,FILE_CURRENT
- ; Весь файл обработали?
- cmp eax,-1
- jne loc_read_section
- jmp loc_close_wav
- loc_done_wav:
- ; Данные собраны
- loc_free_wav:
- ; Освободить память
- invoke GlobalUnlock,[hMem]
- loc_close_wav:
- invoke CloseHandle,[desc]
Просмотров: 784 | Комментариев: 1
Метки: Assembler, мультимедиа

Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(21.03.2023 в 15:02):
Добавил информацию про выравнивание секций, пример и архив обновлены.

Добавить комментарий
Заполните форму для добавления комментария
