Смекни!
smekni.com

Алгоритм сжатия видео 'pixel behaviour check' (стр. 3 из 6)

Пример не совсем удачный, хотя объясняет суть организации вложенности наборов поведений. Но ведь вложенность может быть и с возвратом, когда один набор ссылается на другой набор, но за своей ссылкой содержит еще продолжение поведений (ссылка внутри набора, или несколько ссылок в наборе). Вот это и не позволит использовать указанное содержимое структуры 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;