Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Парсинг метаданных 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_" или же использовать их с бОльшим приоритетом, если данные отличаются.

Как вы поняли из вышесказанного, при парсинге придется учитывать немало особенностей. Я сделал парсер, который работает с обоими вариантами секций и корректно сглаживает все выявленные шероховатости.
  1.         ; Открыть файл для чтения
  2.         invoke  CreateFile,sample,GENERIC_READ,\
  3.                 FILE_SHARE_READ,0,OPEN_EXISTING,0,0
  4.         mov     [desc],eax
  5.  
  6.         ; Прочитать заголовок файла
  7.         invoke  _lread,[desc],buff,12
  8.  
  9.         ; Проверить корректность файла
  10.         cmp     dword [buff],'RIFF'
  11.         jne     loc_close_wav
  12.         cmp     dword [buff+8],'WAVE'
  13.         jne     loc_close_wav
  14. loc_read_section:
  15.         ; Прочитать заголовок секции
  16.         invoke  _lread,[desc],buff,8
  17.         cmp     eax,-1
  18.         je      loc_close_wav
  19.  
  20.         ; Название секции
  21.         mov     eax,dword [buff]
  22.         ; Перевести в нижний регистр
  23.         or      eax,20202020h
  24.         ; Метаданные в секции LIST?
  25.         cmp     eax,'list'
  26.         je      loc_read_list
  27.         ; Метаданные в секции ID3?
  28.         cmp     eax,'id3 '
  29.         je      loc_read_id3
  30.         ; Следующая секция
  31.         jmp     loc_skip_section
  32.  
  33. loc_read_id3:
  34.         ;----------------------------------------------
  35.         ; Обработка секции ID3
  36.         ;----------------------------------------------
  37.         ; Выделить память под метаданные
  38.         mov     ebx,dword [buff+4]
  39.         invoke  GlobalAlloc,GMEM_ZEROINIT,ebx
  40.         mov     [hMem],eax
  41.         invoke  GlobalLock,[hMem]
  42.         mov     [pMem],eax
  43.  
  44.         ; Прочитать метаданные
  45.         invoke  _lread,[desc],[pMem],ebx
  46.  
  47.         mov     esi,[pMem]
  48.         ; ID3v2.3
  49.         cmp     dword [esi],0x03334449
  50.         je      @f
  51.         ; ID3v2.4
  52.         cmp     dword [esi],0x04334449
  53.         je      @f
  54.  
  55.         ; Освободить память
  56.         invoke  GlobalUnlock,[hMem]
  57.         ; Следующая секция
  58.         jmp     loc_read_section
  59. @@:
  60.         add     esi,10
  61. loc_scan_id3_tags:
  62.         ; Все теги обработали?
  63.         mov     eax,esi
  64.         sub     eax,[pMem]
  65.         cmp     eax,ebx
  66.         jae     loc_done_wav
  67.         cmp     byte[esi],0
  68.         je      loc_done_wav
  69.  
  70.         ; Проверить валидность названия тега
  71.         xor     ecx,ecx
  72. loc_check_id3_wav:
  73.         mov     dl,byte[esi+ecx]
  74.         cmp     dl,'0'
  75.         jb      loc_done_wav
  76.         cmp     dl,'9'
  77.         jbe     @f
  78.         cmp     dl,'A'
  79.         jb      loc_done_wav
  80.         cmp     dl,'Z'
  81.         ja      loc_done_wav
  82. @@:
  83.         inc     ecx
  84.         cmp     ecx,4
  85.         jb      loc_check_id3_wav
  86.  
  87.         ; Album
  88.         mov     edi,album
  89.         cmp     dword [esi],'TALB'
  90.         je      @f
  91.         ; Artist
  92.         mov     edi,artist
  93.         cmp     dword [esi],'TPE1'
  94.         je      @f
  95.         ; Title
  96.         mov     edi,title
  97.         cmp     dword [esi],'TIT2'
  98.         je      @f
  99.  
  100.         ; Пропустить тег
  101.         lodsd
  102.         ; Размер данных
  103.         lodsd
  104.         bswap   eax
  105.         add     esi,eax
  106.         lodsw
  107.         jmp     loc_scan_id3_tags
  108. @@:
  109.         lodsd
  110.         ; Размер данных
  111.         lodsd
  112.         bswap   eax
  113.         mov     ecx,eax
  114.         ; Размер без учета байта типа кодировки
  115.         dec     ecx
  116.         lodsw
  117.         ; Тип кодировки строки
  118.         lodsb
  119.         or      al,al
  120.         jz      loc_load_utf8
  121. loc_load_utf16:
  122.         ; Пропустить BOM
  123.         cmp     word[esi],0xFEFF
  124.         jne     @f
  125.         lodsw
  126.         dec     ecx
  127.         dec     ecx
  128. @@:
  129.         ; Просто скопировать строку UTF-16
  130.         rep     movsb
  131.         xor     eax,eax
  132.         stosw
  133.         jmp     loc_scan_id3_tags
  134.  
  135. loc_load_utf8:
  136.         ; UTF-8 -> юникод
  137.         push    ecx
  138.         invoke  MultiByteToWideChar,CP_UTF8,0,esi,ecx,0,0
  139.         invoke  MultiByteToWideChar,CP_UTF8,0,esi,-1,edi,eax
  140.         pop     ecx
  141.         ; Следующий тег
  142.         add     esi,ecx
  143.         jmp     loc_scan_id3_tags
  144.  
  145.         ;----------------------------------------------
  146.         ; Обработка секции LIST
  147.         ;----------------------------------------------
  148. loc_read_list:
  149.         ; Выделить память под метаданные
  150.         mov     ebx,dword [buff+4]
  151.         invoke  GlobalAlloc,GMEM_ZEROINIT,ebx
  152.         mov     [hMem],eax
  153.         invoke  GlobalLock,[hMem]
  154.         mov     [pMem],eax
  155.  
  156.         ; Прочитать метаданные
  157.         invoke  _lread,[desc],[pMem],ebx
  158.  
  159.         ; Это метаданные?
  160.         mov     esi,[pMem]
  161.         lodsd
  162.         ; Перевести в нижний регистр
  163.         or      eax,20202020h
  164.         cmp     eax,'info'
  165.         je      loc_scan_list_tags
  166.  
  167.         ; Освободить память
  168.         invoke  GlobalUnlock,[hMem]
  169.         ; Следующая секция
  170.         jmp     loc_read_section
  171. loc_scan_list_tags:
  172.         ; Все теги обработали?
  173.         mov     eax,esi
  174.         sub     eax,[pMem]
  175.         cmp     eax,ebx
  176.         jae     loc_done_wav
  177.         ; Пропустить финальные нули
  178.         cmp     byte[esi],0
  179.         jne     @f
  180.         inc     esi
  181.         jmp     loc_scan_list_tags
  182. @@:
  183.         ; Наименование тега
  184.         lodsd
  185.         ; Перевести в нижний регистр
  186.         or      eax,20202020h
  187.         mov     edi,album
  188.         cmp     eax,'iprd'
  189.         je      @f
  190.         mov     edi,artist
  191.         cmp     eax,'iart'
  192.         je      @f
  193.         mov     edi,title
  194.         cmp     eax,'inam'
  195.         je      @f
  196.         lodsd
  197.         add     esi,eax
  198.         jmp     loc_scan_list_tags
  199. @@:
  200.         lodsd
  201.  
  202.         ; UTF-8 -> юникод
  203.         push    eax
  204.         invoke  MultiByteToWideChar,CP_UTF8,0,esi,eax,0,0
  205.         invoke  MultiByteToWideChar,CP_UTF8,0,esi,-1,edi,eax
  206.         pop     eax
  207.         ; Следующий тег
  208.         add     esi,eax
  209.         jmp     loc_scan_list_tags
  210.  
  211. loc_skip_section:
  212.         ; Перейти к следующей секции
  213.         mov     eax,dword [buff+4]
  214.         ; Учесть выравнивание секции
  215.         inc     eax
  216.         and     eax,0FFFFFFFEh
  217.  
  218.         invoke  _llseek,[desc],eax,FILE_CURRENT
  219.         ; Весь файл обработали?
  220.         cmp     eax,-1
  221.         jne     loc_read_section
  222.         jmp     loc_close_wav
  223.  
  224. loc_done_wav:
  225.         ; Данные собраны
  226.  
  227. loc_free_wav:
  228.         ; Освободить память
  229.         invoke  GlobalUnlock,[hMem]
  230. loc_close_wav:
  231.         invoke  CloseHandle,[desc]
В приложении пример программы с исходным текстом, которая парсит метаданные из WAV-файла и выводит их.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

WAV.Metadata.Demo.zip (479,756 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 784 | Комментариев: 1

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (21.03.2023 в 15:02):
Добавил информацию про выравнивание секций, пример и архив обновлены.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2025
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.11 сек. / MySQL: 2 (0.0056 сек.) / Память: 4.5 Mb
Наверх