type PBCvideoIdentical = record
Code: 2_бита;
Repeat: код_повторов;
end;
type PBCvideoChanged = record
Code: 2_бита;
Repeat: код_повторов;
Extent: Byte;
end;
type PBCvideoEncoded = record
Code: 2_бита;
Index: код_индекса;
end;
Поле Code содержит двухбитный код поведения цветовой плоскости пикселя. Всего можно задать четыре кода для поведения: 00 - не изменяется, 01 - увеличивается, 10 - уменьшается и 11 - закодирован в массиве поведений. Поле кода поведения идентично для всех трех указанных структур поведения.
Поле Repeat содержит в себе количество кадров, которые повторяется заданное поведение цветовой плоскости пикселя. Поведение повторяется хотя бы один кадр, но для эффективности использования поля Repeat количество кадров повторения перед сохранением в этом поле переводится в диапазон от 0 до N-1, а не от 1 до N. Это достигается вычитанием 1 из количества кадров, где 0 теперь будет означать 1 кадр, 1 будет означать 2 кадра и так далее. Поле Repeat может иметь длину 6 бит или 14 бит. Это компромиссное решение позволяет удерживать биты структуры в пределах байта или слова (чтобы не усложнять программирование, когда байт может содержать биты разных структур) и расходовать минимум бит как для небольших значений повторов, так и для больших. Фактически поля Code и Repeat занимают совместно один или два байта, где старшие два бита всегда принадлежат полю Code. Я опишу механизм извлечения значения из поля Repeat, и вы поймете, как оно там хранится.
1. Взять текущий байт
2. Старшие два бита принадлежат полю Code
3. Если 6-ой бит байта равен 0, тогда младшие 5 бит байта содержат значение
4. Иначе взять следующий байт, который является младшим байтом для текущего байта
5. Младшие 13 бит полученного слова содержат значение
К извлеченному значению необходимо добавить 1, чтобы получить реальное количество повторений. При повторах от 1 до 32 используются всего 6 бит (совместно с полем Code - один байт), а свыше 32 и до 8192 - 14 бит (с полем Code - два байта). Если количество повторов превышает 8192, тогда в одном поведении кодируется максимально возможное количество повторов, а в следующем поведении - оставшаяся часть повторов.
В поле Extent хранится величина изменения цветовой плоскости пикселя, которая в зависимости от бита RatioType поля Properties задана либо прямым значением, либо процентным отношением. В итоге мы получаем, что структура PBCvideoIdentical может занимать в байтовом представлении один или два байта, а структура PBCvideoChanged - два или три байта.
Прямое значение - это число, вычисленное как разница между текущим и предыдущим значениями цветовой плоскости пикселя. Прямое значение всегда положительное число и вычисляется как разница по модулю. Нужно заметить, что разница вычисляется между крайними точками диапазона изменения, когда значение цветовой плоскости пикселя закончило увеличиваться или уменьшаться. Ниже я привел рисунок в виде графика поведения цветовой плоскости пикселя. Красными черточками выделены места, где один код поведения сменяет другой, а в поведении кодируется количество кадров повторения и разница изменения, или только количество кадров повторения при отсутствии изменений. Для наглядности светло-зеленым цветом выделены поведения, когда цветовая плоскость пикселя остается неизменной.
Процентное отношение - это та же самая разница между значениями цветовой плоскости пикселя, но выраженная в диапазоне от 1 до 100 процентов. Если прямые значения приводят к минимуму потерь в цвете, то процентные отношения в обмен на небольшие потери хорошо справляются с так называемым цветовым шумом, когда цвет пикселя нестабильный и "скачет" с мизерными изменениями вокруг реального цвета.
И последнее поле Index по способу хранения информации сходно с 6/14-битным полем Repeat. В поле Index хранится индекс закодированного в массиве поведений целого набора поведений для данной цветовой плоскости пикселя. Способ хранения информации в поле Index указывает, что в массиве поведений может быть не более 8192 наборов поведений. Кодирование с помощью закодированных наборов поведений значительно увеличивает степень сжатия, так как многие участки поведений пикселей имеют сходные модели поведения. Кодировщик может и не использовать кодирование поведений через массив поведений, но тогда он не должен использовать поведения с кодом 11 (когда поле Code = 11) и массив поведения оставить пустым. Замечу, что структура PBCvideoEncoded в точности повторяет структуру PBCvideoIdentical, только вместо поля Repeat используется поле Index.
Внедрение данных в видеопоток
Чтобы вы могли внедрять в видеопоток собственные данные, предусмотрен специальный код. Возможно, вы хотите параллельно в видеопотоке кодировать звук или внедрить на каких-то участках потока другие методы кодирования. Может, вам понадобится внедрить в поток собственные метки, текст или еще что-нибудь. Для этих целей используется код на основе структуры PBCvideoIdentical, где поле Repeat содержит максимальное количество повторов. Получается, что при поведении цветовой плоскости пикселя, когда она не изменяется на протяжении нескольких кадров (поле Code = 00), максимально можно указать 8191 повтор, а не 8192 как в других структурах. Если же в структуре PBCvideoIdentical количество повторов равно 8192, тогда эта структура обозначает специальный код внедрения других данных в видеопоток.
За таким кодом всегда должно следовать DWord-поле размера блока внедренных данных. Декодер, прочитав код внедрения данных (содержимое PBCvideoIdentical = 3FFFh), читает следующее DWord-поле, по которому узнает, через сколько байт после DWord-поля следует продолжение видеопотока. Декодер знает размер блока внедренных данных, знает адрес (или смещение) этого блока, и может вызывать некоторую специальную функцию, обрабатывающую ваши внедренные данные. Затем декодер на основе размера блока внедренных данных вычисляет адрес (смещение) продолжения данных видеопотока.
Обратите внимание, что в массиве поведений специальный код внедрения данных не используется, так как в массив не внедряются никакие другие данные. Декодер должен просто игнорировать этот код, если он каким-то образом встретится в массиве поведений.
Работа декодера
Чтобы вам было понятнее, рассмотрим шаг за шагом работу декодера. Не стоит считать описанные мной методы реализации декодера и его структуры истинно верными. Я лишь стремился проще объяснить принцип декодирования, не особо вдаваясь в вопросы качества реализации декодера. Вы сами в состоянии определить, как максимально качественно написать его начинку. Если есть желание, можете скачать исходники примера PBC Player'а здесь (205 Кбайт). Исходники написаны в Delphi, а программный код (стиль программирования) не гарантирует высокую производительность и безошибочность реализации.
Нам понадобится опорный кадр. Спроектируем его структуру так, чтобы хранить в нем сведения не только для декодирования первого кадра, но и каждого следующего. Опорный кадр будет представлять собой динамический массив с данными о поведении всех пикселей изображения. В каждом пикселе имеется по три цветовых плоскости, поэтому количество элементов в этом массиве будет равно ШИРИНА * ВЫСОТА * 3. Теперь опишем массив, элементы которого будут представлены структурой ColorPlane. Я намеренно добавил префикс "cp" к именам полей этой структуры, чтобы вы не путали их со сходными полями структур PBCvideoIdentical, PBCvideoChanged и PBCvideoEncoded.
type ColorPlane = record
cpIndex: Word; // индекс закодированного набора поведений
cpBehaviour: Byte; // индекс текущего поведения, выполняемого из набора
cpCode: Byte; // код выполняемого поведения
cpRepeat: Word; // количество кадров повторения поведения
cpCurrent: Double; // текущее значение цветовой плоскости пикселя
cpExtent: Double; // величина изменения плоскости за один кадр
end;
// глобальные переменные
var
Frame: array of ColorPlane; // опорный кадр
Отмечу, что структура ColorPlane в таком виде не позволит использовать кодирование наборов поведений через другие наборы, когда один набор на каком-то своем участке может ссылаться на содержимое другого набора. В наборах поведений допустимо использовать поведения с кодом 11 (закодирован в массиве поведений), а значит, можно реализовать высокую вложенность наборов поведений. Это может помочь сильнее сжимать видео. Приведу пример, где зацикленные друг на друга наборы приводят к эффективному сжатию.
Допустим, у нас есть видеофрагмент в 1000 кадров. Пусть он состоит из одной точки. В каждом нечетном кадре точка имеет черный цвет, а в каждом четном - белый. И вот так 1000 кадров она постоянно "моргает". В закодированном потоке окажется всего два элемента (набора поведений) в массиве поведений, причем каждый набор поведений будет состоять всего из двух поведений, где второе поведение всегда будет ссылаться на другой набор поведений (второе поведение первого набора - на второй набор, второе поведение второго набора - на первый набор). И будет еще одно поведение в общем видеопотоке (поток после массива поведений), которое ссылается на один из наборов поведений. Декодер, считав поведение из общего видеопотока, по ссылке попадает на один из наборов и начинает обслуживать его поведения. В первом поведении набора указано, что точка имеет белый цвет один кадр, а второе поведение набора отсылает декодер на второй набор. Во втором наборе первое поведение указывает, что точка имеет черный цвет один кадр, а второе поведение отсылает декодер на первый набор. И так в цикле декодер отрисовывает 1000 кадров видеофрагмента, больше ничего не читая из видеопотока.