Описание формата OGM
Что есть в этом документе
Формат OGM-файла, принципы объединения нескольких элементарных потоков в один физический файл, структура заголовков аудио, видео и текстовых потоков.
Чего в этом документе нет
Структры данных внутри аудио, видео и текстовых потоков.
Вступление
Некоторое время назад, на смену формата mp3 был создан формат OGG Vorbis. Для хранения звукового потока vorbis был разработан стандарт OGG Bitstream, который в дальнейшем применили для хранения видео.
1. Порядок бит и байт.
Порядок бит в байте: 01234567
(другими словами, последний, правый бит имеет
макисмальное значение) Порядок байт при записи многобайтовых чисел: 0123
(0xAB
0xCD
0xEF
0x12
в файле означают число 0x12EFCDAB
) (little-endian)
2. Основные концепции.
- Логический поток — данные одного типа (например, видео-поток, первый аудио-поток, второй аудио-поток)
- Пакет — порция данных логического потока (его структура целиком и полностью лежит на совести кодера/декодера).
- Сегмент — кусок данных длиной в
255
байт (и менее) длиной. Сегмент с нулевой длиной так же допустим. У сегмента так же нет ни заголовка ни каких-либо данных помимо содержимого (content'а). - Страница — несколько сегментов с заголовком страницы.
- Физический поток — объединение нескольких логических потоков. OGM-файл и представляет из себя этот самый физический поток.
3. Методы объединения страниц нескольких логических потоков внутри одного физического потока
Chaining (объединение по цепочке)
stream1 | stream2 | stream3 |
Grouping (чередование)
page1 | page1 | page1 | page2 | page3 | page2 | page2 | page3 | page4 | ... |
(В случае AVI-файла это называют interleave)
У каждого логического потока есть признаки начала потока (Begin of Stream, BoS) и конца потока (End of Stream, EoS). Эти признаки обозначаются флагами в заголовке страницы. (BoS - в первой странице логического потока, EoS — в последней). Соответственно, в физическом потоке может быть несколько BoS и EoS. (в случае одного видео и двух аудио, например, три BoS и три EoS).
В случае нескольких логических потоков внутри физического, все страницы с BoS (т.е. "начала логических потоков") должны быть расположены до первой страницы без флага BoS.
Порядок чередования страниц из разных логических потоков может быть произвольным, гарантируется только, что для каждого логического потока страницы расположены в хронологическом порядке (т.е. N-ая страница будет расположена в файле всегда позже, чем N-1).
В случае смешанного типа объединения (т.е. и Chaining'a и Grouping'а) все EoS (последние) страницы первой группы должны появиться до первой (BoS) страницы второй группы.
4. Структуры пакета, сегмента и страницы
Структура пакета, как было уже сказано выше, целиком и полностью определяется декодером.
Пакет режется в несколько сегментов.
Сегмент так же не имеет собственной структруры. По длинне он должен
быть не более 255
байт (в частности, сегмент может быть нулевой длины,
т.е. не содержать в себе информации).
Сегменты объединяются в страницы, к ним добавляется заголовок страницы.
(не более 255
сегментов в странице)
Примечания
- пакет всегда начинается с начала страницы.
- пакет может быть в размере больше, чем максимально допустимый размер страницы, в этом случае он продолжается в следующей страницы, а в заголовке такой страницы (которая содержит продолжение пакета) выставляется соответствующий флаг (continued_packet_flag)
5. Заголовок страницы (page_header)
смещение | размер | тип | имя, значение, комментарии | ||||||||||||||||||||||||||||||||
0x0 | 4 | FourCC | capture_pattern, 'OggS' , 0x4f 0x67 0x67 0x53
0x4 | 1 | ? | должна быть | 0 , версия структуры заголовка
0x5 | 1 | bitfield | header_type_flag, | 0x1 — страница содержит продолжение пакета0x2 — BoS (Begin of Stream, начало потока)0x4 — EoS (End of Stream, конец потока)
0x6 | 8 | int64 | absolute_granule_position, номер последнего
сэмпла/фрейма целиком закодированного в
этом пакете (сэмплы, закодированные в пакете,
который не умещается в эту страницу, а
продолжаются дальше, не засчитываются).
Число | -1 означает что в указанной странице нет
целиком закодированных сэмплов. N.B. little-endian формат числа (как у Интела) 0xE | 4 | int32 | page_serial_number, номер логического потока в
указанном физическом потоке.
Должно быть уникальным в рамках одного
физического потока для всех логических.
| 0x12 | 4 | int32 | Page_sequence_no, порядковый номер страницы
в логическом потоке.
| 0x16 | 4 | uint32 | Page_checksum, CRC код страницы
| 0x1A | 1 | byte | page_segments, число сегментов в странице,
диапазон значений | [0-255] (0 и 255 допустимы)
0x1B | * | byte[] | segment_table, таблица размеров сегментов в
странице. Количество записей в таблице
определяется page_segment, в случае нулевого
значения, segment_table отсутствует.
| |
Замечания
- Страница может быть одновременной первой и последней в потоке (BoS и EoS установленны одновременно)
- Размер данных страницы может быть нулевой, в этом случае страница просто пропускается.
- Максимальный размер данных в странице составляет
255*255=65025
байт. - Если последний сегмент в странице имеет размер
255
байт, то после него, в segment_table заносятся данные о "нулевом" сегменте (сегменте с длиной0
).
Выглядит это так (например, данные у нас занимают765
байт):
число сегментов:4
таблица сегментов:0xFF 0xFF 0xFF 0x0
Исключение — сегмент с размером данных65025
байт, т.к. нулевой сегмент просто некуда дописывать.
Если последний сегмент отличен от0xFF
, то нулевой после него не добавляется.
6. Определение размера сегмента и данных
размер данных в сегменте: сумма всех значений в segment_table. Количество значений указано в page_segments.
размер сегмента: 0x1B
+page_segments+сумма всех значений segment_table. (это
значение всегда меньше 64k
).
7. Стуктура заголовка видео-потока
смещение | тип | имя, значение, комментарии |
0x0 | char | тип пакета, для заголовка должен быть 0x1
|
0x1 | char[8] | streamtype, для видео должен быть 'video '
последние три символа — 0x0 (не пробелы)
|
0x9 | char[4] | subtype, используемый кодек (FourCC) |
0xD | int32 | размер структуры |
0x11 | int64 | time_unit, интервал времени между "юнитами",
в 1/10000000 секунды
|
0x19 | int64 | samples_per_unit, кадров в "юнит".
fps=10000000 *samples_per_unit/time_unit
|
0x21 | int32 | default_len (честно, не знаю что такое, обычно равно 1 )
|
0x25 | int32 | buffersize — размер необходимого буфера для декодирования |
0x29 | int32 | bit_per_sample, глубина цвета (битов на пиксел) N.B. В оригинальной спецификации указано, что эта величина - int16, но практика показывает, что всё-таки int32. |
0x2D | int32 | width, ширина картинки (в пикселах) |
0x31 | int32 | Heigth, высота картинки (в пикселах) |
8. Стурктура заголовка аудио-потока (для vorbis-audio)
смещение | тип | имя, значение, комментарии |
0x0 | byte | тип пакета, для заголовка должен быть 0x1
|
0x1 | char[6] | для vorbis-audio должен быть 'vorbis'
|
0x7 | int32 | vorbis version, в настоящий момент 0 .
|
0xB | byte | число каналов |
0xC | int32 | частота дискретизации сигнала |
0x10 | int32 | минимальный битрейт (обычно 0 )
|
0x14 | int32 | средний битрейт |
0x18 | int32 | макисмальный битрейт (обычно 0 )
|
0x1C | byte | размеры блоков для декодирования (экспонента двойки) старшие 4 бита - для blocksize_0 младшие 4 бита - для blocksize_1 |
0x1D | byte | младший бит - framing_flag |
9. Стуктура заголовка текстового потока
смещение | тип | имя, значение, комментарии |
0x0 | byte | тип пакета, для заголовка должен быть 0x1
|
0x1 | char[8] | тип потока, для текста должен быть 'text '
(последние четыре символы не пробелы, а 0x0 )
|
0x9 | char [4] | subtype, для текста 0 .
|
0xD | int32 | размер структуры |
0x11 | int64 | длительность отсчёта (для текста 1мс, 0x2710 )
|
0x19 | int64 | всегда 1
|
0x21 | int32 | default_len, 1 .
|
0x25 | int32 | увы, назначения не знаю (обычно или 96 или 60) |
0x29 | 4 x int32 | unknown, usually 0. |
10. Комментарии
Каждый поток может иметь комментарии. Они обычно находятся во второй или третьей странице потока.
N.B. в комментариях строки не являются классическими C string'ами (ASCIIZ), и НЕ заканчиваются нулевым байтом. Строка состоит из размера (4 байта) и самой строки (что-то вроде строк в Pascal).
Структура заголовка коментария
смещение | тип | имя, значение, комментарии |
0x0 | byte | тип пакета, для комментария должен быть 0x3
|
0x1 | int32 | vendor_vector_size |
0x5 | byte[] | название софта, создавшего файл |
??? | int32 | user_comment_list_length, количество комментариев |
После заголовка следуют сами комментари.
Каждый комментарий состоит из 4байтного поля размера строки и самой строки.
Внутри каждая строка выглядит примерно так:
field_name=field_value
При этом название field_name регистронезависимое, и состоит только из ASCII7.
Точнее (цитирую спецификацию):
The field_name is case-insensitive and may consist of ASCII 0x20 through
0x7D
, 0x3D
('=') excluded. ASCII 0x41 through 0x5A
inclusive (characters
A-Z) is to be considered equivalent to ASCII 0x61 through 0x7A
inclusive
(characters a-z).
Значение поля (field_value) представяет из себя UTF-8 строку без
заключительного 0
.
В случае, если комментарии отсутствуют (user_comment_list_length=0
),
то, соответствено, списка комментариев нет.
В конце после списка комментариев (или даже в случае отсутствия такого
списка) следует байт 0x1
(framing_bit), его отстутствие — признак
повреждённой страницы.
Названия полей могут быть произвольные, могут повторяться (т.е. например, три строки вида AUTHOR=...).
Примерный список возможных названий полей
(список не полный)- LANGUAGE
- TITLE
- VERSION
- ALBUM
- TRACKNUMBER
- ARTIST
- PERFORMER
- DESCRIPTION
- GENRE
- DATE
- LOCATION
11. Пример кода, работающего с OGM файлом
Код находится в файле ogmnav.c
Замечания к коду:
- Код не является оптимальным по скорости, и написан так, чтобы в максимально-удобной форме показать принципы навигации между пакетами. Две простейшие оптимизации, которые можно применить - это буфферизация чтения и более аккуратный пропуск ненужных страниц. (при помощи fseek, а не чтения "в никуда").
- Относительно использования массива unsigned char вместо структуры, описывающей заголовок страницы. Причины использования очевидны. Во-первых, компилятор далеко не всегда распологает структуру в памяти так, как она записана в декларации. Так, вполне может быть, что компилятор, с целью оптимизации, выравняет все переменные на границы машинных слов. (т.е. внутри структуры появятся "дырки" и fread этой структуры из памяти даст некорректный результат). Второе - код с массивом будет одинаково хорошо работать как на машине с litte-endian архитектурой (Intel), так и на big-endian (Mac, Alpha).
- В программе не проверяется CRC каждой страницы.
- Более "живой" пример находится в модуле ogm.c из состава AVInfo (/soft/avinfo/), специфика модуля такова, что ему передаются два парметра - хэндл уже открытого на чтение файла и численный параметр, отвечающий за степень детализации собираемой информации.
12. Ссылки на ресурсы
- http://vorbis.org/ — авторы спецификации контейнера
- http://tobias.everwicked.com/ — автор DirectShow фильтра для воспроизведения, собственно, автор идеи засунуть видео в OGM. (Документация содержит ошибки!)
- http://mplayerhq.hu/ — довольно популярный плеер, не использует VfW, в частности, играет OGM. Идёт под лицензией GPL, и, соответственно, имеет открытые исходные тексты
13. Заключение, копирайты
Использование статьи для разработчиков, в т.ч. для написания коммерческого софта абсолютно свободное. Публикация на web-сайтах и в бумажных изданиях возможна при сохранении явно указанного авторства и ссылок, приведённых в статье (при условии их действительности на момент публикации).
Если вы заметили в статье грамматическую, синтаксическую, логическую ошибку, опечатку, неточность, имеете уточнённые данные по значению полей, отмеченных вопросительными знаками, хотите добавить линк в список ресурсов, пишите на указанный адрес.
© George Shuklin,2004 gs@shounen.ru, shounen.ru .