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_" (в конце символ пробела), в которой, как мы можете догадаться, также записаны метаданные. Тут тоже не обошлось без своих особенностей. Во-первых, эта секция располагается в самом конце файла и ей предшествует нулевой байт, который не входит в размер предпоследней секции. При этом сама секция "ID3_" общей структуре WAV-файла вполне соответствует. Во-вторых, все размеры данных внутри секции ID3 записаны полноценными DWORD'ами, а не формируются из четырех 7-битных блоков, как это принято стандартом ID3. Но на 100% утверждать я это, к сожалению, тоже не могу, так как ориентируюсь только на готовые результаты из различных источников. В-третьих, секция "ID3_" в файле может присутствовать как совместно с секцией "LIST", так и вместо нее, а может и вовсе отсутствовать. Спецификацией WAV-файлов предусмотрено, что строчными буквами обозначаются идентификаторы обязательных секций, а заглавными - секции с пользовательскими данными. Так вот, для секции "ID3_" я встречал оба варианта написания, это в-четвертых. В остальном разбор этого контейнера ничем не отличается от описанного ранее ID3v2.3.

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

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

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


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 181 | Комментариев: 0

Комментарии

Отзывы посетителей сайта о статье
Комментариeв нет

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

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

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