Пример не совсем удачный, хотя объясняет суть организации вложенности наборов поведений. Но ведь вложенность может быть и с возвратом, когда один набор ссылается на другой набор, но за своей ссылкой содержит еще продолжение поведений (ссылка внутри набора, или несколько ссылок в наборе). Вот это и не позволит использовать указанное содержимое структуры ColorPlane, так как в ней не предусмотрен контроль вложенности наборов. А теперь возвратимся к работе декодера, и позже объясню, почему поля cpCurrent и cpExtent в структуре ColorPlane заданы типами с плавающей точкой.
Первым делом декодер читает заголовок видеопотока. Получив ширину и высоту кадра видеофильма (поля Width и Height структуры PBCvideoHeader), декодер устанавливает размер массива опорного кадра по формуле Width * Height * 3. Значения полей StartRCr, StartGY и StartBCb заносятся в поля cpCurrent соответствующих элементов массива опорного кадра. Декодеру нужно сразу заполнить цветовые плоскости опорного кадра соответствующими начальными значениями. Первые три элемента массива принадлежат первому пикселю (его координаты = 0,0), следующие три элемента - второму пикселю (0,1), следующие - третьему, и так далее. Поэтому в поле cpCurrent первого элемента массива заносится значение поля StartRCr, во второй элемент массива - значение поля StartGY, в третий элемент - значение поля StartBCb, в четвертый элемент - снова поле StartRCr, в пятый - поле StartGY, и так далее. Все остальные поля элементов массива обнуляются. Замечу, что поле cpIndex обнуляется значением 0FFFFh. Декодер использует это поле, чтобы выяснить, обслуживается ли в настоящий момент для данной цветовой плоскости пикселя поведение из видеопотока или оно берется из массива поведений. Индекс набора поведений может лежать только в пределах от 0 до 8191 (всего получается 8192 набора поведений), а значение 0FFFFh находится за пределами массива, поэтому декодер легко определяет, что текущее поведение взято не из массива поведений, а прямо из видеопотока. Для наглядности приведу фрагмент программы.
// глобальные переменные
var
Header: PBCvideoHeader; // заголовок видеопотока
Behaviours: PBCvideoBehaviours; // массив поведений
Frame: array of ColorPlane; // опорный кадр
FrameNum: DWord; // номер текущего кадра
procedure InitFrame;
var
W: Word; // ширина
H: Word; // высота
I: DWord; // индекс плоскости в массиве опорного кадра
begin
// читаем заголовок видеопотока из некоторого файла
BlockRead(F1, Header, SizeOf(Header));
// устанавливаем размер массива опорного кадра
I := Header.Width * Header.Height * 3;
SetLength(Frame, I);
// сначала обнулим остальные поля элементов массива опорного кадра
// (поле cpIndex обнуляем значением 0FFFFh)
repeat
Dec(I);
Frame[I].cpIndex := $FFFF;
Frame[I].cpBehaviour := 0;
Frame[I].cpCode := 0;
Frame[I].cpRepeat := 0;
Frame[I].cpExtent := 0;
until I = 0;
// теперь занесем значения полей StartRCr, StartGY и StartBCb
// в соответствующие цветовые плоскости опорного кадра
for H := 1 to Header.Height do begin
for W := 1 to Header.Width do begin
// вычисляем в переменной I индекс элемента массива
// опорного кадра, с которого расположены подряд
// три цветовых плоскости пикселя с координатами W и H
// (где W = X, H = Y)
I := (H-1) * Header.Width * 3 + (W-1) * 3;
// а теперь заносим в три плоскости пикселя
// начальные цветовые значения
Frame[I].cpCurrent := Header.StartRCr;
Frame[I+1].cpCurrent := Header.StartGY;
Frame[I+2].cpCurrent := Header.StartBCb;
end;
end;
// читаем массив поведений из файла в переменную Behaviours
ReadBehavioursData(F1, Behaviours);
// сбрасываем внутренний счетчик декодированных кадров,
// а по нему будем определять, что видеопоток закончился
FrameNum := 0;
end;
За заголовком декодер должен прочитать из видеопотока массив поведений. Это обычный массив и декодер должен "сложить" его где-нибудь у себя в памяти, чтобы иметь к нему быстрый доступ в случае кодирования поведения цветовой плоскости пикселя набором поведений из массива. В конце приведенного фрагмента кода указан вызов якобы уже написанной процедуры ReadBehavioursData, которая загружает массив поведений из файла в переменную с именем Behaviours. Эта переменная содержит в себе количество элементов в массиве (поле ItemCount) и массив Behaviours с элементами, представляющими собой наборы поведений. У каждого набора есть поле Count (количество поведений в наборе) и поле Behaviour с набором этих поведений. Отмечу, что в наборе поведений всегда есть хотя бы одно поведение, поэтому поле Count со значение 0 обозначает одно поведение, со значением 1 - два поведения, и так далее до 255, что обозначает 256 поведений.
За массивом поведений следует набор сведений о поведении конкретных цветовых плоскостей пикселей изображения - общий видеопоток. Декодер извлекает эти сведения, опираясь на данные элементов массива опорного кадра. Если в некотором элементе массива поле cpIndex равно 0FFFFh (поведение цветовой плоскости пикселя было взято прямо из видеопотока) и поле cpRepeat равно нулю (повторы поведения закончились), значит, сейчас в видеопотоке находятся данные о следующем поведении текущей цветовой плоскости пикселя, и эти данные нужно извлечь и занести в текущий элемент массива. Если же это не так, и поле cpRepeat не равно нулю (еще остались повторы поведения), тогда нужно в текущем элементе массива уменьшить на 1 поле cpRepeat и, в зависимости от поля cpCode (00, 01 или 10), либо оставить без изменения, либо увеличить или уменьшить поле cpCurrent на величину поля cpExtent.
Образно говоря, декодирование каждого кадра начинается с того, что декодер проходит последовательно все элементы массива опорного кадра - от первого до последнего элемента (фактически проходит по всем цветовым плоскостям пикселей видеокадра). Если в поле cpRepeat остались повторы, тогда уменьшить поле cpRepeat на 1, а к полю cpCurrent прибавить или отнять (в зависимости от значения поля cpCode) значение поля cpExtent. Новое значение поля cpCurrent и будет тем, что заносится в соответствующую цветовую плоскость пикселя реального кадра на экране. Но перед этим значение поля должно быть еще обработано, о чем будет рассказано ниже.
// глобальные переменные
var
Header: PBCvideoHeader; // заголовок видеопотока
Behaviours: PBCvideoBehaviours; // массив поведений
Frame: array of ColorPlane; // опорный кадр
procedure CreateNextFrame;
var
I: DWord; // индекс плоскости в массиве опорного кадра
L: DWord; // количество элементов в массиве опорного кадра
C: Byte; // код только что прочитанного поведения
R: Word; // количество повторов поведения
E: Byte; // величина изменения плоскости за весь период поведения
begin
L := Length(Frame);
I := 0;
while I < L do begin
// если еще остались повторы для текущей цветовой плоскости,
// тогда вычислить значение цветовой плоскости для
// текущего кадра
if Frame[I].cpRepeat <> 0 then begin
Dec(Frame[I].cpRepeat);
// при поведении 00 цветовая плоскость не изменяется,
// поэтому в структуре CASE не используется проверка на 00,
// а код 11 всегда заменяется реальным поведением из набора поведений,
// поэтому его проверка тоже не нужна
case Frame[I].cpCode of
01: Frame[I].cpCurrent := Frame[I].cpCurrent + Frame[I].cpExtent;
10: Frame[I].cpCurrent := Frame[I].cpCurrent - Frame[I].cpExtent;
end;
end else begin
// этот фрагмент смотрите ниже, где рассматривается, что происходит,
// если в поле cpRepeat не осталось повторов
end;
Inc(I);
end;
end;
Когда же в поле cpRepeat не осталось повторов, декодер должен проанализировать поле cpIndex. Если там не указан индекс обслуживаемого набора поведений цветовой плоскости пикселя (cpIndex = 0FFFFh), тогда нужно взять следующее поведение прямо из видеопотока и занести его данные в текущий элемент массива опорного кадра. Как уже было сказано ранее, декодер должен уметь обслуживать специальный код внедрения других данных в видеопоток, и продолжать декодирование видео после внедренного блока данных. Заметьте, что в элементах опорного кадра хранятся коды реальных поведений, чтобы знать, что в действительности происходит с цветовой плоскостью пикселя. Поэтому при встрече кода "закодирован в массиве поведений" (cpCode = 11), декодер должен извлечь первое поведение из указанного набора и использовать код этого поведения. Описанный мной фрагмент кода поддерживает безвозвратную вложенность наборов поведений, но с возвратной не справится.
Итак, допишем недостающий фрагмент процедуры CreateNextFrame. Для простоты считаем, что у нас уже написана процедура ReadBehaviour, читающая из потока одно поведение по описанным выше правилам (смотрите описание содержимого структур PBCvideoIdentical, PBCvideoChanged и PBCvideoEncoded). Также считаем, что у нас уже есть процедура чтения N-го поведения из указанного набора в массиве поведений - GetBehaviour.
if Frame[I].cpIndex = $FFFF then begin
// читаем из видеопотока поведение для текущей
// цветовой плоскости пикселя
// в переменную C возвращается код поведения
// в R - количество повторов поведения или индекс,
// если поведение задано в массиве поведений
// (в этом случае переменная C = 11)
// в E - величина изменения плоскости за весь период поведения
// или 0, если C = 00 или C = 11
ReadBehaviour(C, R, E);
// с помощью цикла делаем обслуживание возможных
// внедренных в видеопоток блоков данных
while (C = 00) and (R = 8192) do begin
// здесь должен быть вызов вашей функции обработки
// внедренного в видеопоток блока данных
// а затем продолжаем читать из потока поведение
// для текущей цветовой плоскости пикселя
ReadBehaviour(C, R, E);
end;
// Если поведение закодировано в массиве набором поведений,
// тогда необходимо прочитать первое поведение из набора
// и использовать его данные, так как в опорном кадре
// используются только коды реальных поведений.
// Следующим циклом обеспечивается выбор первого поведения
// при кодировании с помощью набора, а также он обеспечивает
// поддержку безвозратной вложенности наборов поведений.
while C = 11 do begin
Frame[I].cpIndex := R;
Frame[I].cpBehaviour := 0;
// читаем поведение с индексом Frame[I].cpBehaviour = 0
// из набора поведений с индексом Frame[I].cpIndex,
// а результат чтения возвращается в переменные C, R и E
GetBehaviour(Frame[I].cpIndex, Frame[I].cpBehaviour, C, R, E);
end;
// в поле cpExtent заносится величина изменения цветовой
// плоскости за один кадр повтора, а не за весь его период
Frame[I].cpCode := C;
Frame[I].cpRepeat := R;
Frame[I].cpExtent := E / R;