Книга: Графика DirectX в Delphi

Глава 6 Работа с AVI-файлами

Глава 6 Работа с AVI-файлами

Модуль VFW

Модуль DirectShow

Запись в видеофайл

Что вы узнали в этой главе

Эта небольшая глава посвящена вопросам, относящимся к воспроизведению и созданию видео. Многие аспекты напрямую не связаны с DirectX и поэтому вынесены в отдельную часть.

Примеры к главе располагаются в каталоге ExamplesChapter06.

Модуль VFW

Существует много способов работы с видео. Предназначенные для этого компоненты Delphi не очень помогут в решении задачи воспроизведения подобных файлов на поверхностях, поэтому нам придется воспользоваться нестандартными методами.

Прежде всего мы познакомимся с модулем vfw.pas, не входящим в набор стандартных модулей Delphi, но работающим со стандартной системной библиотекой avifil32.dll.

Вместе с Delphi поставляется набор справочных файлов системного программиста, корпорации Microsoft. С их помощью вы сможете разобраться во всех тонкостях использования этого модуля. Я же приведу конкретные решения с краткими пояснениями. Для большей части читателей этого объема должно вполне хватать.

В проекте каталога ExOl на знакомом нам фоне воспроизводится AVl-файл, взятый мною из набора файлов, поставляемых в составе DirectX SDK (рис. 6.1).


В списке подключаемых модулей добавлен модуль VFW, а в установках проекта - путь к файлу ole2.dcu, в котором нуждается подключаемый модуль.

Следующие переменные связаны со спецификой примера:

var

TmpBmp : TBitmap; // Вспомогательное изображение

AviStream : PAVISTREAM; // AVI-поток

Frame : PGetFrame; // кадр видео

pbmi : PBITMAPINFOHEADER; // Указатель на заголовок растра

bits : Pointer; // Указатель на картинку растра

CurrFrame DWORD - 0; // Счетчик кадров

AVIClock DWORD; // Эмулятор таймера

AVIDelay DWORD; // Величина паузы между кадрами

AVIWidth DWORD; // Характеристики кадра

AVIHeight DWORD;

AVILength DWORD; // Количество кадров в AVI

Перед инициализацией DirectDraw нам необходимо узнать характеристики отображаемого видео, для этого используем функции рассматриваемого модуля, связанные с работой AVI как с файлом:

var

AVIFile : PAVIFile; // Обрабатываем AVI как файл

AVIlnfo : TAVIFilelnfo; // Заголовок файла, характеристики AVI

begin

TmpBmp := TBitmap.Create; // Создаем вспомогательный растр

AVIFileOpen(AVIFile, AVIName, OF_READ, nil); // Открытие AVI

// Считываем заголовочную информацию, заполняются поля AVIlnfo

AVIFilelnfo(AVIFile, AVIlnfo, SizeOf (AVIlnfo));

AVIWidth := AVIInfo.dwWidth; // Запоминаем размеры кадра

AVIHeight := AVIlnfo.dwHeight;

AVILength := AVIlnfo.dwLength; // Количество кадров

// Вычисляем паузу между очередными кадрами

AVIDelay := 1000 div (AVIInfo.dwRate div AVIInfo.dwScale);

AVIFileRelease(AVIFile); // Освобождаем AVI

Поверхность FDDSImage, на которой будет воспроизводиться видео, создается с размерами, равными размерам кадра видео:

ZeroMemory (@ddsd, SizeOf(ddsd));

with ddsd do begin

dwSize := SizeOf(ddsd);

dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;

ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN;

dwWidth := AviWidth;

dwHeight := AVIHeight;

end;

hRet := FDD.CreateSurface(ddsd, FDDSImage, nil);

if Failed(hRet) then ErrorOut(hRet, 'Create Second Surface');

В общем случае заводить отдельную поверхность для воспроизведения кадра, конечно, не обязательно, можно переносить содержимое вспомогательного растра прямо на первичную поверхность.

После инициализации DirectDraw подготавливаемся к работе с потоковым видео:

procedure TfrmDD.FirstFrame;

var

wrkDC : HDC;

begin

AVIFilelnit; // Инициализация библиотеки

// Открываем AVI-файл для чтения

AVIStreamOpenFromFile(AviStream, AviName, streamtypeVIDEO,

0, OF_READ, nil);

// Загружаем поток

Frame := AVIStreamGetFrameOpen(AviStream, nil);

// Получаем первый кадр видео

pbmi := AVIStreamGetFrame(Frame, CurrFrame);

// Получаем указатель на картинку кадра

bits := Pointer(Integer(pbmi) + SizeOf(TBITMAPINFOHEADER));

// Получаем контекст для воспроизведения кадра на поверхность

if FDDSImage.GetDC (wrkDC) = DD_OK then begin

// Воспроизводим кадр во вспомогательный растр

TmpBmp.Handle := CreateDIBitmap(

// Вспомогательным контекстом служит HDC поверхности

wrkDC,

pbmi^, // Адрес размера растра и формата данных

CBM_INIT, // Флаг инициализации

bits, // Данные для инициализации

PBITMAPINFO(pbmi)^, // Данные о формате цвета

DIB RGB_COLORS); // Флаг цветности растра

// Переносим картинку из вспомогательного растра на поверхность

BitBlt (wrkDC, О, О, AVIWidth, AVIHeight,

TinpBmp. Canvas .Handle, 0, 0, SRCCOPY);

FDDSImage.ReleaseDC (wrkDC);

end;

AVIClock := GetTickCount; // Инициализация вспомогательного таймера

end;

Действия по воспроизведению очередного кадра аналогичны, но тратить время на получение адресов теперь не нужно:

procedure ТfrmDD,NextFrame;

var

wrkDC : HDC;

begin

// Настало время воспроизвести следующий кадр AVI

if GetTickCount - AVIClock > AVIDelay then begin

pbmi := AVIStreamGetFrame(Frame, CurrFrame);

if FDDSImage.GetDC (wrkDC) = DD_OK then begin

TmpBmp.Handle := CreateDIBitmap(wrkDC, pbmi^, CBM_INIT,

bits, PBITMAPINFO(pbmi) Л, DIB_RGB__COLORS) ;

BitBlt (wrkDC, 0, 0, AVIWidth, AVIHeight,

TmpBmp.Canvas.Handle, 0, 0, SRCCOPY);

FDDSImage.ReleaseDC (wrkDC);

end;

// Увеличиваем счетчик кадров

CurrFrame := (CurrFrame + 1) mod AVILength;

AVIClock := GetTickCount;

end;

end;

В этом примере AVI-файл воспроизводится бесконечно, вслед за последним кадром все повторяется с начала.

Кадр воспроизведен на поверхности FDDSimage, блиттинг которой осуществляется тривиальным способом.

По завершении работы добавились ожидаемые действия:

AVIStreamRelease(AviStream); // Закрытие потока

AVIFileExit; // Завершение работы с библиотекой

TmpBmp.Free; // Удаление вспомогательного растра

Итак, теперь вы способны делать видео частью ваших игр, но я должен предупредить, что библиотека может корректно работать не с каждым типом сжатия. По этой причине я рекомендую заставки игр воспроизводить стандартным для Delphi способом.

Модуль DirectShow

Поскольку изложенный в предыдущем разделе способ годится не для каждого видео, нам придется бегло рассмотреть еще один способ воспроизведения видео, основанный на использовании модуля DirectShow. Эта библиотека также входит в состав DirectX, включает набор функций для работы с мультимедиа. Подробно рассматривать ее не будем, познакомимся с ее использованием на конкретном примере, проекте каталога Ех02, воспроизводящем AVI-файл на поверхности (рис. 6.2).


Файл видео для этого примера также взят мною из пакета DirectX SDK.

При воспроизведении файла с помощью модуля VFW картинка получается искаженной, поэтому и приходится прибегать к иному способу. Отказаться от первого способа мы также не можем, поскольку DirectShow тоже годится не для любого файла. Другая причина, по которой мы не можем усердствовать в изучении упомянутого модуля, состоит в том, что он может применяться лишь с интерфейсами ранних версий.

Модуль использует СОМ-модель, поэтому здесь мы встретим знакомые понятия главного объекта и дочерних интерфейсов:

var

AMStream : lAMMultiMediaStream; // Главный объект

PrimaryVidStream : IMediaStream; // Дочерний поток, связан с видео

Sample : IDirectDrawStreamSample; // Интерфейс для вывода на поверхность

В процедуру инициализации потока передается имя требуемого файла:

procedure TfrmDD.PlayMedia(const FileName: WideString);

var

hRet : HRESULT;

begin

// Создание главного объекта ('filter graph1)

AMStream:=IAMMultiMediaStream(CreateComObject

(CLSID_AMMultiMediaStream));

// Инициализация потока для чтения

hRet := AMStream.Initialize(STREAMTYPE_READ, 0, nil);

if Failed (hRet) then ErrorOut (hRet, 'Stream Initialize');

// Добавление потока видео к главному объекту

hRet := AMStream.AddMediaStream(FDD, MSPID_PrimaryVideo,

0, IMediaStream(ni!A));

if Failed (hRet) then ErrorOut (hRet, 'Add Video Stream');

// Открытие файла

hRet := AMStream.OpenFile(PWideChar(FileName) , 0);

if Failed (hRet) then ErrorOut (hRet, 'Open AVI File');

// Следующие действия предназначены для связывания потока и поверхности

// Получение дочернего потока

hRet := (AMStream as IMultiMediaStream).

GetMediaStream(MSPID_PrimaryVideo, PrimaryVidStream);

if Failed (hRet) then ErrorOut (hRet, 'GetMediaStream');

// Преобразование интерфейса в тип Isample

//и связывание его с поверхностью

hRet := (PrimaryVidStream as IDirectDrawMediaStream).

CreateSample (FDDS Image, TRect(nil/4), 0, Sample);

if Failed (hRet) then ErrorOut (hRet, 'CreateSample');

// Запуск потока

hRet := (AMStream as IMultiMediaStream).SetState(STREAMSTATE_RUN);

if Failed (hRet) then ErrorOut (hRet, 'SetState');

end;

Обработку возможных ошибок я оставил прежней, но коды ошибок не будут расшифровываться. Модуль DirectDraw не содержит, конечно, пояснения по этим ошибкам.

Перед блиттингом поверхности FDDS image обновляем позицию в видео:

if Sample.Update(0, 0, nil, 0) <> S_OK

then (AMStream as IMultiMediaStream).Seek (0);

Неудача свидетельствует о том, что видео прокручено до конца, в этом случае мы заново запускаем его.

Самостоятельно разберитесь, каким образом останавливается ролик при деактивизации окна приложения, и как заново запускается поток при восстановлении.

Запись в видеофайл

.Познакомившись с различными способами воспроизведения видеоданных, мы научимся создавать AVI-файлы. Пройдет этот процесс для вас очень легко, поскольку воспользуемся мы готовым модулем AviMaker. Он содержит описание класса TAviMaker, выполняющего за нас всю изнурительную работу. Нам остается только подготовить набор растров, составляющих последовательность кадров создаваемого фильма, и вызвать метод записи.

Вот скромный набор свойств и методов класса, необходимых нам для работы:

Bitmaps TList; // Список объектов класса Bitmap, кадры AVI

Height Integer; // Размеры кадров AVI

Width Integer;

FrameTime Integer; // Величина паузы между кадрами

Stretch BOOL; // Признак, надо ли масштабировать кадры

FileName String; // Имя файла результата

PixelFormat : TPixelFormat; // Разрядность AVI

constructor Create;

destructor Destroy; override;

procedure Write; // Запись AVI

Здесь используется модуль VFW, поэтому в опциях проекта указывается путь к файлу ole2.dcu. В проекте каталога Ех03 формируются кадры, на которых вращается спираль (рис. 6.3).


Рис. 6.3. Один из кадров нашего фильма Создается

Для записи фильма используется объект AviMakeri класса TAviMaker:

AviMakerI := TAviMaker.Create;

with AviMakeri do begin

Width := 256;

Height := 256;

Stretch := True; // Кадры будут масштабироваться

PixelFormat := pf24bit; // 24-битный формат кадра

FrameTime := 100;

FileName := 'test.avi';

end;

фильм из 20-ти кадров, продолжительностью 2 секунды:

function TfrmDD.UpdateFrame : HRESULT;

const

step = 2 * Pi / 400;

var

i : Integer;

hRet : HRESULT;

Вitmap : ТВ i tmap; DC : HOC;

begin

ThisTickCount := GetTickCount;

// В этом примере паузы можно было бы и не делать

if ThisTickCount - LastTickCount > 30 then begin

Angle := Angle + 0.25;

if Angle > 2 * Pi then Angle := Angle - 2 * Pi;

LastTickCount := GetTickCount;

// Берем 20 кадров

Inc (FrameCount);

if FrameCount > 20 then begin

FActive := False;

Result := DD_OK;

AviMakerl.Write; // Записываем AVI

Close;

Exit;

end;

// Выводим фон

hRet := FDDSBack. BltFast (0, 0, E'DDSBackGround,

nil, DDBLTFAST_WAIT);

if hRet = DDERR_SURFACELOST then begin

Result := hRet;

if Failed (RestoreAll) then Exit;

end;

// Выводим точки спирали for i := 0 to 800 do

if FDDSBack.BltFast (310 + trunc (cos(Angle -f i * step) * i / 4) ,

230 + trunc (sin(Angle + i * step) *i / 4),

FDDSImage, nil,

DDBLTFAST_WAIT) = DDERR_SURFACELOST then begin

hRet := RestoreAll;

if Failed (hRet) then begin

Result := hRet; Expend;

end;

// Формируем кадр ролика

Bitmap := TBitmap.Create;

with Bitmap do begin Width := ScreenWidth;

Height := ScreenHeight;

PixelFormat := pf24bit; // Важно, иначе устанавливается 8 бит

FDDSBack.GetDC (DC);

BitBlt(Canvas.Handle, 0, 0, // Копируем экран в растр

ScreenWidth, ScreenHeight, DC, 0, 0, SRCCOPY);

FDDSBack.ReleaseDC (DC) ;

AviMakerl.Bitmaps.Add (Bitmap); // Добавляем кадр в фильм

end;

end;

Result := DD_OK;

end;

Кадры масштабируются при записи фильма, в качестве упражнения исправьте код так, чтобы их изменение происходило при формировании списка.

Что вы узнали в этой главе

Для работы с видео имеется много способов, и, как всегда, нам пришлось выбирать из них работающие.

Кроме того, вы получили инструмент для создания собственных фильмов.

Оглавление книги


Генерация: 1.118. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз