ru_dmitriid


О королях и капусте


Previous Entry Share Next Entry
Erlang Bit Syntax
happy
dmitriid wrote in ru_dmitriid
Решил я посмотреть, насколько все легко и просто в Эрланге с двоичными данными, как рассказывается.

Вернее, так. Один мой товарищ возился с форматом OMA, использующемся в аудиоплеерах Sony. Единственно отличие этого формата от других, человеческих, вроде MP3 в том, что он не документирован, а та информация, что об этом формате есть противоречит самому же формату :)

Добрые люди разобрались, что в этом формате к чему и написали программу на Java для работы с ним. Эта программа лежит здесь: http://dmitriid.com/files/projects/erlang/OMA.zip. Там же валяется документация по директориям на плеерах Sony, а также информация о собственно формате OMA.

Формат файла можно увидеть здесь: http://dmitriid.com/files/projects/erlang/OMA.html. По виду прост до безобразия. Ну-с, посмотрим, как с ним справится Erlang.

Первая же наприятность меня ждала в приложенном в качестве примера файле trunk/dist/OMGAUDIO/10F00/10000001.OMA. По правилам он должен был начинаться с
"E" "A" "3" 3 0 0 0 0 17  76 "T" "I" "T" "2" 0 0
Агащазблин. Начинается он с
"e" "a" "3" 3 0 0 0 0 17 "v" "G" "E" "O" "B" 0 0
И до TIT2 еще ого сколько пилить и пилить. Hex-редактор в руки, и вперед, сравнивать спеку с файлом. Вроде, указанные в спеке тэги есть, но не обязательно на своих местах. Вывод? Будем побайтово продвигаться по файлу и собирать тэги до тех пор, пока не наткнемся на конец заголовка.

Забегая вперед, скажу, что я смухлевал. Смухлевал потому, что я не читаю формат файла, а останавливаюсь на
"E" "A" "3" 2 0 60 ff ff
Итак, код

Открываем и считываем файл
parse_header(File) ->
    case file:open(File, [read, binary, raw]) of
        {ok, S} ->
            {ok, Header} = file:pread(S, 0, 16#0c60),
            H = case read_header(Header) of
                error ->
                    {error, invalid_header};
                Data ->
                    Data
            end,
            file:close(S),
            H;
        _ ->
            {error, file_cannot_be_opened}
    end.
Ну, тут все легко. Открываем файл. Считываем его. Вызываем функцию read_header для, собственно, парсинга заголовка и возвращаем значение, полученное из этой функции.

Функция read_header проста до безобразия
read_header(<<$e, $a, $3, Rest/binary>>) ->
    Data = decode_header(Rest, []),
    Data;

read_header(_) ->
    error.
Проста он до безобразия своим паттерн матчингом. Если входящий кусок начинается с
"e" "a" "3"
то вызывается верхняя функция, а если нет, то нижняя.

Само дейстивие по разбору заголовка находится в нескольких функциях decode_header, которые занимаются разбором какого-то одного, своего куска заголовка. Вот типичный пример:
% Title
decode_header(<<$T, $I, $T, $2, _:2/binary,
      Var:2/integer-unit:8, _:4/binary,
      Rest/binary>>, L) ->

        TitleLength = Var - 2,
        <<Title:TitleLength/binary, Rest2/binary>> = Rest,
        decode_header(Rest2, [L|{title, Title}]);
Что здесь происходит? Для этого надо перед глазами держать спеку.

Итак. Название песни записывается так
T I T 2 0 0 Var1 Var1 0 0 2 TitleString
где TitleString - это [0x0 String]. Что мы, собственно, и записали в паттерне:
T        $T
I        $I
T        $T
2        $2
0        _:2/binary            просто пропускаем два нуля
0
Var1     Var:2/integer-unit:8  число, состоящее из двух 8-битовых байтов
Var1
0        _:4/binary            просто пропускаем 0 0 2 0
0
2
0
TitleString Rest/binary
Теперь нам осталось выцепить из Rest название песни. Опять таки опытыным путем выяснено, что, в целом, длина названия в нашем случае - это Var - 2, после чего мы выдираем само название опять же паттерн матчингом:
        TitleLength = Var - 2,
        <<Title:TitleLength/binary, Rest2/binary>> = Rest,
Таким образом, все, что осталось сделать, это записать парсинг всех тэгов согласно правилам и протестировать на реальном файле. Реальный файл подкидывает подляну в виде игнорирования (пусть и неофициальной, но) спецификации и сует тэги куда угодно, добавляя некоторое количество неизвестных тэгов. Поэтому decode_header поплняется еще двумя функциями:
decode_header(<<>>, L) ->
    L;

decode_header(Bin, L) ->
    {_, NewBin} = split_binary(Bin, 1),
    decode_header(NewBin, L).
Мы по байту перемещаемся вперед по заголовку. Если мы натыкаемся на известный тэг, его перехватит decode_header с соответствующим паттерн-матчингом. Если мы дошли до конца заголовка, процесс завершится на decode_header(<<>>, L). Если ни того ни другого не произошло, мы переместимся еще на один байт вперед и все повторится снова.

Полностью весь файл доступен здесь: http://erltag.googlecode.com/svn/trunk/src/oma.erl. Кто хочет потренироваться на кошках, качайте http://erltag.googlecode.com/files/erltag-release.zip(6.1 MB). Там в папке test есть аудиофайл.

Ах да, код не претендует на эффективность и не показывает, как правильно писать программы на Эрланг'е. Скорее всего, так писать не надо :)
Tags:

  • 1
Другими средствами было бы больше геморра :)

Другие средства подходят немного по-другому :) Например, мой товарищ просто делает fseek до нужных тегов и потом их вычитывает. Но мне кажется, что с паттерн-матчингом намного понятнее.

Мне кажется всё таки понятнее с fseek :)
Но зачод тебе в том шо практическая задача, и довольно императивная, имха. Побольше бы таких примерчиков. :)
Кстати как насчёт эффективности?

> Мне кажется всё таки понятнее с fseek

Я просто с битами не люблю и не умею работать по определению :) А тут - прямой перенос спеки в код :)

> Побольше бы таких примерчиков

Был бы я умным, составил бы :)

> Кстати как насчёт эффективности?

Не замерял :) Но на том файле срабатывает сразу :)

  • 1
?

Log in