DSPack - мультимедиа фреймворк для Delphi

Автор: Валентин Вовк

Единственной известной мне дельфи-оберткой над DirectShow есть DSPack. Скачать ее можно со страницы http://www.progdigy.com или последнюю версию с этого сайта. Об оберточных классах дает представление следующая иллюстрация, взятая из файла справки по DSPack:

Рис. 13. Структура классов пакета DSPack

Конечно, без хорошего знания DirectShow API трудно оценить качество и полезность классовой обертки. Но мы все же попробуем разобраться с этими классами.

TFilterGraph

Этот компонент - обертка над менеджером графа фильтров DirectShow и занимает исключительное место как в самом DirectShow, так и в DSPack'е. Любое приложение, имеющее дело с DirectShow, обязательно будет иметь дело и менеджером графа фильтров. Он используется для построения и управления графом, а также для управления синхронизацией, уведомлениями о событиях и других аспектах контроля графа.

При использовании API DirectShow для создания менеджера графа фильтров нужно пользоваться вызовом функции CoCreateInstance, а в качестве CLSID передавать либо CLSID_FilterGraph, либо CLSID_FilterGraphNoThread. CLSID_FilterGraph отвечает за создание менеджера графа фильтров (МГФ) на совместно используемом рабочем потоке (видимо, нужно все же дать представление об использовании потоков в DirectShow ), а CLSID_FilterGraphNoThread - за создание МГФ на потоке приложения.

Обычно приложения используют CLSID_FilterGraph. Но оба CLSID_ используются для создания того же объекта, но с использованием разных потоковых моделей:

  • CLSID_FilterGraph служит для создания МГФ на рабочем потоке, который совместно используется всеми экземплярами CLSID_FilterGraph в одном процессе. Поток диспетчерезирует сообщения, которые посылают фильтры и управляет жизненным циклом любых окон, созданных фильтрами.
  • CLSID_FilterGraphNoThread служит для создания МГФ на потоке приложения. Если вы используете этот CLSID, то поток, вызывающий CoCreateInstance, должен иметь цикл обработки сообщений. В противном случае могут происходить разного рода блокировки (deadlock). Перед завершением работы этого потока нужно также освободить (release) МГФ и все объекты графа (такие, как фильтры, контакты, ссылочные часы и т.д.).

Исходя из вышеизложеного, вроде бы нет особых причин использовать при создании МГФ CLSID_FilterGraphNoThread.

Впрочем, компонент TFilterGraph не использует CLSID_FilterGraphNoThread, поэтому больше не будем о нем упоминать.

МГФ предоставляет следующие, кратко описанные, интерфейсы. Несмотря на то, что их достаточно много, я не поленюсь их все выписать, а вам советую не полениться и прочитать эту табличку. После этого вам станут намного понятнее права и обязаности менеджера графа фильтров.

№№ Интерфейс Описание
1 IAMGraphStreams Управление графом фильтров (ГФ), работающим с живым источником
2 IAMStats Позволяет приложению возвращать исполнимые данные от МГФ. Фильтры могут использовать этот интерфейс для записи исполнимых данных.Это, словом, для статистики - ничего не понял
3 IBasicAudio
Позволяет приложению управлять громкостью и балансом аудиопотока.
4 IBasicVideo Позволяет приложению устанавливать видеосвойства, такие, как прямоугольники назначения и источника.
5 IBasicVideo2 Происходит от IBasicVideo и предоставляет дополнительный метод GetPrefferedAspectRatio.
6 IFilterChain Предоставляет методы для старта, остановки или удаления цепочки фильтров в графе фильтров.
7 IFilterGraph Предоставляет методы для построения графа фильтров. Приложения используют его для добавления фильтра в граф, соединения и рассоединения фильтров, удаления фильтров и других базисных операций. Однако, интерфейс IGraphBuilder происходит от этого интерфейса и предоставляет дополнительные методы, которые есть более sophistical. Таким образом, приложению удобнее пользоваться интерфейсом IGraphBuilder, а не IFilterGraph.
8 IFilterGraph2 Расширяет интерфейсы IFilterGraph и IGraphBuilder.
9 IFilterMapper2 Регистрирует и дерегистрирует фильтры, а также локализирует их в регистре. Реализует этот интерфейс вспомогательный объект Filter Mapper.
10 IGraphBuilder Этот интерфейс предоставляет методы, пзволяющие приложению строить граф фильтров. Этот интерфейс реализует МГФ.Этот интерфейс наследуется от IFilterGraph (который предоставляет базовые операции - такие, как добавление фильтра в граф или соединение двух контактов) , и добавляет методы, позволяющие создавать граф по частичной информации.
11 IGraphConfig Используется МГФ для поддержки динамического построения графа. Этот интерфейс позволяет приложениям и фильтрам переконфигурировать граф, находящийся в запущенном состоянии, без его остановки, и без потери данных.
12 IGraphVersion Для получения версии МГФ.
13 IMediaControl Предоставляет методы для управления потоком данных, проходящих через граф фильтров. Включает методы для запуска, приостановки и остановки графа.
14 IMediaEvent Содержит методы для получения уведомлений о событиях и переопределения заданных по умолчанию обработчиков событий МГФ.
15 IMediaEventEx Наследует и расширяет интерфейс IMediaEvent, позволяя окну приложения получать уведомления о происходящих событиях в МГФ.
16 IMediaEventSink Уведомляет менеджер графа фильтров о событиях, происходящих на графе. Фильтры используют этот интерфейс для отчета о событиях. Приложения не используют его.
17 IMediaFilter Управляет потоковым состоянием фильтра. Все фильтры DirectShow реализуют этот интерфейс. Он предоставляет методы переключения состояний (остановка, пауза, запуск), для получения текущего состояния и для установки ссылочных часов. Приложения не вызывают методов IMediaFilter. МГФ также предоставляет этот интерфейс. Приложения могут вызывать методы этого интерфейса SetSyncSource и GetSyncSource для установки и получения ссылочных часов. Приложения не должны вызывать других методов этого интерфейса, а вместо этого использовать методы IMediaControl.Сам IMediaFilter наследует от IPersist, а интерфейс IBaseFilter наследует, в свою очередь, от IMediaFilter.
18 IMediaPosition Содержит методы для поиска позиции в потоке. Интерфейс IMediaSeeking основывается на этом интерфейсе. Приложения, написанные на C/C++, могут использовать интеерфейс IMediaSeeking вместо IMediaPosition. Но IMediaSeeking несовместим с автоматизацией, поэтому приложения, написанные, например, на Visual Basic'е, должны использовать IMediaPosition.Этот интерфейс предоставляется как МГФ, так и отдельными фильтрами. Приложения должны получать указатель на интерфейс IMediaPosition от МГФ, а не от фильтров. МГФ распределяет метод путем вызова всех фильтров рендеринга. Фльтры рендеринга распространяют вызов вверх по потоку к фильтрам источников. Такая последовательность событий гарантирует синхронизацию всех потоков.Если один из распеделенных вызовов вернет ошибку, МГФ вернет первую полученную им ошибку. Некоторые из распределенных вызовов могут быть и успешными. Если, впрочем, хоть один з распределеных вызовов вернет не E_NOTIMPL, то и МГФ не вернет E_NOTIMPL. Только в том случае, если все распределенные вызовы вернут E_NOTIMPL, МГФ вернет E_NOTIMPL.Замечание для разработчиков фильтров. Не нужно реализовывать этот интерфейс. Вместо этого нужно реализовывать IMediaSeeking. Если ваш фильтр поддерживает IMediaSeeking, МГФ автоматически будет управлять интерфейсом IMediaPosition.
19 IQueueCommand Ставит команду в очередь для ее обработки в определенное время. Приложение может использовать его для продвижения вперед управляющих команд для графа(?).Методы этого интерфейса моделируют метод IDispatch::InvokeAt. Приложение указывает интерфейс, метод интерфейса, параметры метода и ссылочное время. МГФ ставит эту информацию в очередь и, затем, вызывает этот метод в указанное время. Требуемй интерфейс должен наследовать IDispatch и должен предоставляться МГФ. Примерами таких интерфейсов есть IMediaControl, IMediaEventEx и IMediaPosition.После постановки команды в очередь МГФ возвращает указатель на интерфейс IDefferedCommand. Приложение может использовать этот интерфейс для отмены или модифкации команды.
20 IRegisterServiceProvider Регистрирует объект как сервис.
21 IResourceManager Этот интерфейс отвечает за разрешение конкуренции за системные ресурсы.Фильтры могут использовать этот интерфейс для запроса ресурсов, которые, возможно, используют другие объекты. Например, аудио-рендереры (фильтры, воспроизводящие аудио) использую этот интерфейс для разрешения конфликтов за устройство аудио-вывода.Приложения, обычно, не используют этот интерфейс.
22 IServiceProvider Этот интерфейс в справке по DirectShow не описан.
23 IVideoFrameStep Этот интерфейс делает шаги через видеопоток. Этот интерфейс позволяет приложениям, использующим DirectShow, включая проигрыватели DVD, делать шаги по видеопотоку. МГФ управляет этим шаговым процессом в связке с фильтром оверлейного микширования или фильтром видеоотображения. Прокрутка назад не поддерживается.
24 IVideoWindow Устанавливает свойства окна видеотображения. Приложения могут использовать этот интерфейс для установки владельца, позиции и размера окна и других его свойств.

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

Нам нужны будут еще два интерфейса: IAMGraphBuilderCallback и IAMFilterGraphCallback. Первый из них предоставляет механизм callback'а при построении графа. Для его использования нужно реализовать его методы в приложении или клиентском объекте. Запрашивайте у МГФ интерфейс IObjectWithSize и вызывайте метод IObjectWithSize::SetSize, передавая указатель на реализацию этого интерфейса. МГФ вызывает методы этого интерфейса при построении графа, который (интерфейс) дает клиент в целях модификации процесса построения графа. Главное использование этого интерфейса - конфигурирование фильтра VMR перед его соединением. Его можно использовать также для отказа использования неких фильтров (например, декодеров). Специфичекие методы этого интерфейса - SelectedFilter (вызывается, когда МГФ находит фильтр-кандидат, но перед созданием фильтра) и CreatedFilter (вызывается после того, как МГФ создает фильтр, но до попытки его соединить). Второй интерфейс - IAMFilterGraphCallback, - тоже предоставляет callback-механизм при построении графа. Если при построении графа МГФ получает ошибку при попытке рендеринга некоторого контакта, он вызывает единственный метод этого интерфейса UnableToRender.

Так вот, класс TFilterGraph есть наследником TComponent, а также интерфейсов IAMGraphBuilderCallback, IAMFilterGraphCallback и IServiceProvider :

TFilterGraph = class(TComponent, IAMGraphBuilderCallback, IAMFilterGraphCallback, IServiceProvider)

МГФ может работать в трех режимах - gmNormal, gmCapture и gmDVD. При использовании режима gmNormal создается COM-объект IGraphBuilder (FFilterGraph), при использовании gmCapture - создаются COM-объекты ICaptureGraphBuilder2 (FCaptureGraph) и IGraphBuilder (FFilterGraph), при использовании gmDVD - COM-объект IDvdGraphBuilder (FDvdGraph). Давайте сначала рассмотрим эти интерфейсы, а затем продолжим исследование класса TFilterGraph.

Как уже было сказано (см. таблицу интерфейсов, предоставляемых МГФ), IGraphBuilder наследуется от IFilterGraph (который предоставляет базовые операции - такие, как добавление фильтра в граф или соединение двух контактов) , и добавляет методы, позволяющие создавать граф по частичной информации (единственный метод - Render - позволяет автоматически достроить граф для указанного исходящего контакта).

COM-объект "Построитель графа захвата" (Capture Graph Builder) реализует единственный интерфейс - ICaptureGraphBuilder2 (для меня так и осталось невыясненным происхождение этого названия - возможно, он заменил и расширил используемый ранее для тех же целей интерфейс ICaptureGraphBuilder), который предоставляет методы для построения графа захвата и других пользовательских графов фильтров. В общем, использование этого вспомагательного объекта имеет свои особенности, но я не считаю, что на них нужно останавливаться (неплохо бы разобраться в том, как это helper-object работает и можно ли обойтись и без него).

Наконец, IDvdGraphBuilder используется для работы с, очевидно, dvd, но это тема отдельного разговора, и я постараюсь в дальнейшем о dvd не упоминать вообще.

Методы класса TFilterGraph позволяют обращаться к методам перечисленных в таблице интерфейсов. Причем эти методы в классе предоставляются очень избирательно, вроде SetVolume (для IBasicAudio), SetState, Play, Run и т.п. (для IMediaControl) или SetRate (для IMediaSeeking). Само собой, внутри этих методов идет обращение к соответствующим методам соответствующих интерфейсов. Поэтому возникает вопрос, стоило ли вообще выписывать эти методы в противовес невыписанным, которых подавляющее большинство. Как по мне, так не стоило. Ну да ладно. По крайней мере у нас есть повод рассматривать не DirectShow API, а дельфийскую библиотеку. TFilterGraph содержит также методы для добавления фильтров в граф и очистки его от фильтров. Но не предоставляет методов для их, фильтров, соединения (IGraphBuilder::Connect) и пр. и пр. Что, во-первых, странно (поскольку требовало от разработчиков минимальных усилий), а, во-вторых, настолько сбивает с толку начинающих (потому как даже не намекает им о том, что такое вообще возможно), что на многих форумах я неоднократно пересекался с людьми, которые достаточно долго (по несколько месяцев) имели дело с программами, работающими с видео (и писали их сами!), но не подозревали даже о фильтрах и о хоть каких бы то ни было принципах работы с ними. Что же, кроме справки по дельфи, нужно иногда читать и MSDN.

Подавляющее большинство прочих методов и свойств класса TFilterGraph имеют отношение к обработке сообщений, получаемых графом фильтров. Рассмотрим подробнее, как это происходит (хотя бы для того, чтобы уметь самим это делать в случае использования не DSPack, а непосредственно DirectShow API). Итак, в private-секции класса TFilterGraph можно найти интерфейс IMediaEventEx (член FMediaEventEx). При активизации менеджера графа фильтров (в реализации метода TFilterGraph.SetActive) происходит вызов метода QueryInterface для получения интерфейса IMediaEventEx. Сейчас самое место более подробно коснуться этого интерфейса, но начнем, пожалуй, с интерфейса IMediaEvent (IMediaEventEx наследует и расширяет его функциональность). Итак, интерфейс IMediaEvent содержит методы для возвращения уведомлений о событиях и для переопределения обработчиков этих уведомлений, предоставляемых по умолчанию МГФ. Как уже было сказано, этот интерфейс предоставляется МГФ. Приложение может использовать его для реакции на события, происходящие в графе фильтров, такие, как конец потока или ошибка при отображении (рендеринге). Фильтры посылают события в граф фильтров, используя интерфейс IMediaEventSink. Более подробно о событиях, возникающих в графе фильтров, стоит посмотреть в соответствующих разделах справки.

Интерфейс IMediaEvent предоставляет следующие методы:

Метод Описание
CancelDefaultHandling Отменяет действие по умолчанию, выполняемое МГФ, для указанного сообщения
RestoreDefaultHandling Восстанавливает поведение, заданное МГФ по умолчанию, для указанного события
FreeEventParams Освобождает ресурсы, связанные с параметрами события. Этот метод следует вызывать после вызова метода GetEvent (см. ниже).
GetEvent Возвращает уведомление о следующем событии из очереди сообщений.
GetEventHandle Возвращает обработчик для события со сбросом вручную (manual-reset event) (если вы забыли или вообще не знаете, что это такое, почитайте в MSDN или у Рихера), который остается занятым (signaled), пока очередь содержит уведомления о событиях. МГФ удерживает событие о сбросом вручную, которое отражает состояние очереди сообщений. Если очередь содержит уведомления о событиях, событие со сбросом вручную пребывает в занятом состоянии. Если очередь пуста, метод IMediaEvent::GetEvent сбрасывает событие.Приложение может использовать это событие для определения состояния очереди. Сначала вызывается метод GetEventHandle для получения хэндла события. Затем нужно дождаться, когда событие перейдет в сигнальное состояние, с помощью функции вроде WaitForSingleObject. Затем нужно получить седующее уведомление о событии из очереди с помощью вызова метода IMediaEvent::GetEvent. МГФ удерживает событие в сигнальном состоянии, пока очередь пуста; затем событие сбрасывается. Не следует закрывать хэндл события, возвращаемого этим методом, т.к. он используется внутри графа фильтров. Не нужно также использовать хэндл после после освобождения МГФ, поскольку в этом случае этот хэндл не будет корректным. (Чтобы избежать этой ошибки, можно дублировать хэндл вызовом DuplicateHandle и использовать этот дубликат вместо исходного хэндла. После окончания работы с дубликатом его нужно закрыть.)Другой способ мониторинга приложением очереди сообщений - вызов метода IMediaEventEx::SetNotifyWindow (он будет рассмотрен ниже).
WaitForCompletion Ждет, когда граф фильтров обработает все доступные данные.

Для мониторинга сообщений нас, впрочем, больше будет интересовать интерфейс IMediaEventEx, который наследует интерфейс IMediaEvent и, кроме того, имеет методы для для возвращения уведомлений о событиях и для переопределения обработчиков сообщений, предоставляемых МГФ по умолчанию. IMediaEventEx расширяет интерфейс IMediaEvent методами, позволяющими окну приложения получат сообщения о происходящих событиях в МГФ. Полный список определенных системой сообщений можно посмотреть здесь. Мы же обратимся к новым методам IMediaEventEx. Их всего три:

Метод Описание
SetNotifyWindow Регистрирует окно, в которое будут посылаться уведомления о событиях.
SetNotifyFlags Разрешает или запрещает уведомлять о событиях
GetNotifyFlags Позволяет определить, разрешено ли уведомлять о событиях

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

Вернемся теперь к вопросу о реализации в классе TFilterGraph возможности обработки сообщений. Итак, в методе SetActive получаем интерфейс IMediaEventEx, разрешаем мониторинг событий (IMediaEventEx::SetNotifyFlags(0)) и устанавливаем окно, в которое МГФ будет посылать уведомления о происходящих событиях (IMediaEventEx::SetNotifyWindow(хэндл нашего окна, WM_GRAPHNOTIFY (некая заранее объявленная константа), ...)). Оконная процедура имеет следующий вид:

procedure TFilterGraph.WndProc(var Msg: TMessage);
beginwith Msg doif Msg = WM_GRAPHNOTIFY thentry
HandleEvents;
except
...
end;

А вот реализация метода HandleEvents:

procedure TFilterGraph.HandleEvents;
var
hr: HRESULT;
Event, Param1, Param2: Integer;
beginif assigned(FMediaEventEx) thenbegin
hr := FMediaEventEx.GetEvent(Event, Param1, Param2);
while (hr = S_OK) dobegin
DoEvent(Event, Param1, Param2); // внутри этого вызова и вызывается
                 // соответствующий обработчик
FMediaEventEx.FreeEventParams(Event, Param1, Param2);
hr := FMediaEventEx.GetEvent(Event, Param1, Param2);
end;
end;
end;

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

TFilter

Следующий интересующий нас класс - TFilter. Не нужно быть особо догадливым, чтобы понять, что он есть оболочкой над интерфейсом, представляющим фильтр - IBaseFilter. Этот интерфейс предоставляет методы для управления фильтром. Его реализуют все фильтры DirectShow. МГФ использует этот интерфейс для управления фильтрами. Приложения же могут его использовать для перечисления контактов или для запроса информации о фильтре, но не для изменения состояния фильтра (каждый фильтр может находиться в одном из трех состояний: запущенном, остановленном или приостановленном (пауза)). Для этой цели нужно использовать интерфейс IMediaControl, предоставляемый МГФ. Поскольку этот интерфейс уже неоднократно упоминался, займемся им более плотно. Он предоставляет методы для управления потоком данных через граф фильтров. Вдобавок к методам, наследуемым от IDispatch, интерфейс IMediaControl предоставляет следующие:

Метод Описание
Run Запускает все фильтры в графе фильтров
Pause Приостанавливает все фильтры в графе фильтров
Stop Останавливает все фильтры в графе фильтров
StopWhenReady Приостанавливает граф фильтров, позволяет фильтрам поставить приходящие им данные в свои очереди, а затем останавливает граф фильтров
GetState Возвращает текущее состояние графа фильтров

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

Сделав это отступление, вернемся к нашему интерфейсу IBaseFilter. Но, поскольку он наследует другой интерфейс - IMediaFilter, рассмотрим сначала методы последнего. Именно он предоставляет методы для управления потоковым состоянием фильтра. Как уже было сказано, его предоставляют все фильтры DirectShow (поскольку они предоставляют интерфейс IBaseFilter, наследующий IMediaFilter; нужно, кстати, сказать, что сам IMediaFilter наследует от интерфейса IPersist). Итак, интерфейс IMediaFilter предоставляет методы для изменения состояния фильтра (остановка, запуск, пауза(приостановка)); для возвращения текущего состояния фильтра; для установки ссылочных часов. Приложения же, однако, не должны использовать интерфейса IMediaFilter, предоставляемых фильтрами. Вместо этого они могут использовать только тот же интерфейс - IMediaFilter, предоставляемый МГФ. Впрочем, они могут использовать только два метода этого интерфейса - SetSyncSource (для установки ссылочных часов для графа) и GetSyncSource (для получения этих часов). Остальные методы они вызывать не должны. Вместо этого рекомендуется использовать соответствующие методы интерфейса IMediaControl. Несмотря на это, для справки приведу таблицу методов IMediaFilter (кроме тех, что наследуются от IPersist):

Метод Описание
Stop Останавливает фильтр
Pause Приостанавливает фильтр
Run Запускает фильтр
GetState Возвращает состояие фильтра (запущен, остановлен, приостановлен)
SetSyncSource Устанавливает ссылочные часы для фильтра или графа фильтров
GetSyncSource Возвращает текущие ссылочные часы

Теперь пришел черед вернуться к методам интерфейса IBaseFilter. Кроме методов IMediaFilter, он предоставляет еще и следующие:

Метод Описание
EnumPins Перечисляет контакты фильтра
FindPin Возвращает контакт с указанным идентификатором
JoinFilterGraph Уведомляет фильтр, что он связан или left(?) графом фильтров.Когда МГФ добавляет фильтр в граф фильтров, он вызывает этот метод с указателем на себя. Имя экземпляра фильтра связывается посредством второго параметра. Когда МГФ удаляет фильтр из графа, он вызывает этот метод, передавая в качестве указателя на граф значение nil. Приложениям этот метод вызывать не следует. Для добавления фильтра в граф нужно вызвать метод AddFilter интерфейса IFilterGraph, полученного у МГФ
QueryFilterInfo Возвращает информацию о фильтре
QueryVendorInfo Возвращает строку, содержащую информацию о производителе

Это, в общем, и все, что я хотел сказать об интерфейсе IBaseFilter и, соответственно, классе TFilter, поскольку последний реализует простейшие и очевиднейшие вещи, необходимые для того, чтобы называться враппером этого интерфейса. Если вас заинтересуют подробности упомянутых выше интерфейсов или их методов, смотрите их в MSDN.

TVideoWindow

Мы же продолжим знакомство с классами-обертками над COM-объектами DirectShow. Следующим будет TVideoWindow - компонент, используемый для отображения (рендеринга - rendering) проходящих через граф фильтров данных. Он есть оберточным классом над, в первую очередь, интерфейсом IVideoWindow и, во вторую (в зависимости от режима отображения), над IVMRWindowlessControl9. О последнем интерфейсе поговорим чуть позже, а сейчас займемся IVideoWindow, - понимание работы с ним и задач, им выполняемых, чрезвычайно важно, в первую очередь, для понимания множества нюансов собственно видеовывода в DirectShow.

IVideoWindow

Однако, если граф фильтров содержит более одного видеорендерера, МГФ взаимодействует только с одним из них (указанным отдельно). Таким образом, работая с несколькими видеоокнами, приложение должно использовать интерфейс IVideoWindow на соответствующем фильтре напрямую. В этом случае нужно пересылать оконные сообщения каждому видеорендереру, используя метод IVideoWindow::NotifyOwnerMessage.

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

Если видеорендерер не соединен с другими фильтрами, все методы на этом его интерфейсе возвращают код ошибки VFW_E_NOT_CONNECTED. Множество свойств видеорендерера постоянно между моментами успешного соединения и отсоединения. Поскольку этот интерфейс совместим с автоматизацией, булевы значения могут быть OAFALSE(0)или OATRUE(-1).

Кроме методов, наследуемых от IDispatch, интерфейс IVideoWindow предоставляет методы, описанные в далееидущей табличке:

Метод Описание
get_AutoShow Всегда ли видеорендерер автоматически показывает видеоокно, когда он получает видеоданные
get_BackgroundPalette Всегда ли видеоокно освобождает свою палитру в бэкграунде
get_BorderColor Возвращает цвет, который появляется около краев прямоугольника назначения
get_Caption Возвращает надпись видеоокна
get_FullScreenMode Всегда ли видеорендерер работает в полнооконном режиме
get_Height Возвращает высоту видеоокна
get_Left Возвращает x-коордианту окна
get_MessageDrain Возвращает окно, которое получает сообщения от мышки и клавиатуры
get_Owner Возвращает родительское окно видеоокна
get_Top Возвращает y-координату окна
get_Visible Видимо ли видеоокно
get_Width Возвращает ширину видеоокна
get_WindowState Запрашивает состояние видеоокна (видимое, скрытое, минимизированное, максимизированное)
get_WindowStyle Возвращает стиль видеоокна
get_WindowStyleEx Возвращает расширенный стиль видеоокна
GetMaxIdealImageSize Возвращает максимальный идеальный размер изображения для видеоокна
GetMinIdealImageSize Возвращает минимальный идеальный размер изображения для видеоокна
GetRestorePosition Возвращает положение восстановленного видеоокна (есть смысл вызывать, если оно находится в минимизированном или максимизированном состоянии)
GetWindowPosition Возвращает позицию видеоокна
HideCursor Показывает или скрывает курсор, когда мышка находится над окном
IsCursorHidden Скрыт ли курсор
NotifyOwnerMessage Пересылает сообщения видеоокну. МГФ вызывает этот метод для пересылки различных сообщений рендереру
put_AutoShow Устанавливает, должен ли видеорендерер автоматически показывать видеоокно при получении видеоданных (по умолчанию, при изменении графом фильтров состояния с приостановленного к запущенному, видеорендерер показывает видеоокно и устанавливает его на переднем плане; если пользователь зарывает окно, оно не будет автоматически открыто снова)
put_BackgroundPalette Устанавливает, должно ли видеоокно освобождать свою палитру в бэкграунде. Если должно и видеоизображение требует палитру, видеорендерер должен освободить палитру в бэкграунде. Все цвета, используемые палитрой, должны быть заменены их близким соответствием в палитре дисплея для рисования. Это гарантирует то, приложение не нарушит палитру. Но это скажется на быстродействии
put_BorderColor Устанавливает цвет, который появляется около краев прямоугольника назначения (эта операция имеет смысл, если прямоугольник, в котором происходит отображение видео, меньше клиенского простанства видеоокна)
put_Caption Устанавливает надпись видеоокна
put_FullScreenMode Устанавливает или отменяет полноэкранный режим
put_Height Устанавливает высоту видеоокна
put_Left Устанавливает x-координату видеоокна
put_MessageDrain Устанавливает окно, которое должно получать сообщения от клавиатуры и мыши от видеоокна
put_Owner Устанавливает родительское окно для видеоокна
put_Top Устанавливает y-координату видеоокна
put_Visible Показывает или скрывает видеоокно
put_Width Устанавливает ширину видеоокна
put_WindowState Показывает, скрывает, минимизирует или максимизирует видеоокно
put_WindowStyle Устанавливает оконный стиль для видеоокна
put_WindowStyleEx Устанавливает расширенный оконный стиль для видеоокна
SetWindowForeground Устанавливает положение видеоокна на самом верху Z-порядка
SetWindowPosition Устанавливает положение видеоокна

Как видим, интерфейс IVideoWindow имеет достаточно много методов (об их подробностях можно узнать из того же MSDN'а), большинство из которых имеет совершенно прозрачный смысл, и только для некоторых (как, например, put_FullScreenMode) характерны важные нюансы.

VMR Windowless Mode

Здесь - VMR Windowless Mode

IVMR WindowlessControl9

Теперь обратимся к интерфейсу IVMRWindowlessControl9. Я не хотел пока заострять на нем внимания,- нам вполне хватило бы и IVideoWindow, но упомянуть все же нужно. Этот интерфейс управляет отображением фильтра рендеринга VMR-9 (Video Mixing Renderer Filter 9; почему он так называется и что оно все такое - сейчас не важно) видеопотока без окна-контейнера. Перед использованием методов этого интерфейса приложения должны установить VMR-9 в безоконный (windowless) режим.

IVMRWindowlessControl9 кроме методов, наследуемых от IUnknown, предоставляет также и следующие:

Метод Описание
DisplayModeChange Информирует VMR о том, что приложение получило сообщение WM_DISPLAYCHANGE.Приложение должно вызывать этот метод всегда, когда оно получает оконное сообщение WM_DISPLAYCHANGE, но только если VMR находится в безоконном (windowless) режиме
GetAspectRatioMode Возвращает текущий aspect ratio режима отображения
GetBorderColor Возвращает текущий цвет рамки, используемый VMR'ом
GetCurrentImage Возвращает копию текущего изображения, показываемого VMR'ом. Изображение возвращается в формате пакованного Windows DIB. Этот метод может быть вызван в любое время, независимо от того, в каком состоянии находится фильтр. Вызывающий ответственен за освобождение возвращаемой памяти с помощью вызова coTaskMemFree. Использование этой функции замедляет воспроизведение
GetMaxIdealVideoSize Возвращает максимальный размер видео, который может быть отображен VMR'ом без incurring significant performance или ухудшения качества изображения
GetMinIdealVideoSize Возвращает минимальный размер видео, который может быть отображен VMR'ом без incurring significant performance или ухудшения качества изображения
GetNativeVideoSize Возвращает un-stretched размер видео и aspect ratioвидео
GetVideoPosition Возвращает текущие прямоугольники источника и назначения, используемые для отображения видео
RepaintVideo Перерисовывает текущий видеокадр
SetAspectRatioMode Устанавливает текущий aspect ratio режима отображения
SetBorderColor Устанавливает цвет рамки для использования VMR'ом.Этот цвет используется для заливки любой площади прямоугольника назначения, не содержащего видео. Обычно используется в следующих двух случаях:Когда видео straddles два монитораWhen the VMR is trying to maintain the aspect ratio of the movies by letter-boxing the video to fit within the specified destination rectangle. See SetAspectRatioMode.
SetVideoClippingWindow Указывает содержащему окну, что видео должно быть clipped to.
SetVideoPosition Устанавливает для видео прямоугольники источника и назначения.

Итак, класс TVideoWindow может пребывать в двух режимах - vmNormal и vmVMR, в зависимости от чего (в методе NotifyFilter) создает либо Video Renderer Filter, либо Video Mixing Renderer Filter 9 - фильтры для видеоотображения - и присваивает созданный фильтр рендеринга внутренней переменной FBaseFilter; имеет также внутреннюю переменную, представляющую менеджер графа фильтров. Естественно, что прочие поля и методы TVideoWindow предназначены для вызова методов интерфейсов IVideoWindow или IVMRWindowlessControl9 и фильтров VMR или VMR9. Поэтому сначала совсем коротко рассмотрим фильтры рендеринга, а затем - прочие свойства и методы TVideoWindow.

Краткий экскурс в фильтры видеоотображения

Video Renderer Filter

Этот фильтр - подходящий вариант для надежного видеовоспроизведения.

Нужно заметить, что в Windows XP фильтром видеовоспроизведения по умолчанию есть Video Mixing Renderer (VMR). И VMP, и Video Renderer имеют общее "дружественное имя" - "Video Renderer". На всех других платформах по умолчанию фильтром видеовоспроизведения есть Video Renderer, хотя приложения и могут использовать VMR-9 для использования дополнительных возможностей видеовоспроизведения.

Video Renderer использует DirectDraw и оверлейные поверхности, если их поддерживает видеокарта. Менеджер графа фильтров предоставляет интерфейс IVideoWindow, который позволяет приложению устанавливать и возвращать свойства Video Renderer'а. На новых видеокартах Video Renderer поддерживает полноэкранный режим отображения. В других случаях МГФ автоматически переключается на фильтр полноэкранного отображателя ( Full Screen Renderer ) для полноэкранного режима. См. также IVideoWindow::put_FullScreenMode.

Нужно заметить, что обычно фильтры таких видеоокон обрабатывают сообщения на рабочем потоке, созданном МГФ. Однако, если приложение напрямую создает фильтр с использованием вызова CoCreateInstance, видеокно обрабатывает сообщение на потоке приложения. В этом случае поток приложения должен иметь цикл обработки для диспетчеризации сообщений, предназначенных видеоокну. Этот поток не должен прекращать выполнение до тех пор, пока Video Renderer не вызовет финальный Release, который происходит в том случае, когда прекращает работу МГФ. В противном случае приложение может получить взаимоблокировку.

В следующей таблице описываются свойства фильтра Video Renderer:

Свойство Описание
Интерфейсы фильтра IBaseFilter, IBasicVideo, IBasicVideo2, IDirectDrawVideo, IKsPropertySet, IMediaPosition, IMediaSeeking, IQualityControl, IQualProp, IVideoWindow
Медиатип входящего контакта MEDIATYPE_Video
Интерфейсы входящего контакта IMemInputPin, IOverlay, IPin, IPinConnection, IQualityControl
Медиатипы исходящего контакта -
Интерфейсы исходящего контакта -
CLSID фильтра CLSID_VideoRenderer
CLSID страницы свойств -
Исполнимый файл quartz.dll
Merit Windows 98, Me, NT, 2000: MERIT_PREFERRED Windows XP: MERIT_UNLIKELY
Категория фильтров CLSID_LegacyAmFilterCategory

Другие подробности, связанные с особенностями функционирования фильтра и поддержки отладки приложений quartz.dll, следует смотреть в MSDN.

Video Mixing Renderer Filter 9

Этот фильтр расширяет возможности воспроизведения видео для всех платформ, поддерживающих DirectX. Он полностью интегрирован с 3D свойствами DirectX9. Например, можно легко добавлять видео к играм и другим 3D оболочкам или преобразовывать видеоизображения с использованием теневых пикселей Direct3D (3D pixel shaders) и других эффектов. Этот фильтр не поддерживает видеопорты.

Для обеспечения обратной совместимости VMR-9 не является фильтром отображения по умолчанию ни на какой системе. Для использования этого фильтра нужно добавить его к графу фильтров явно и отконфигурировать его перед соединением с любыми из его входящих контактов. VMR-9 использует собственный набор интерфейсов, структур, перечислителей, которые не всегда идентичны соответствующим типам данных, используемых VMR-7. Более подробно вопрос освещен в статье Фильтры видеоотображения (Video Rendering Filters).

Свойства, методы и интерфейсы для TVideoWindow

Теперь мы можем перейти к рассмотрению класса TVideoWindow. Он предназначен для отображения видео и объявлен в файле DSPack.pas путем

TVideoWindow = class(TCustomControl, IFilter, IEvent)

Класс TCustomContol есть наследником TWinControl, поэтому понятно, что TVideoWindow дает возможность, среди прочего, работать с ним как с окном. Будем считать, что с этим предком все ясно, и сосредоточимся на интерфейсах IFilter и IEvent. Они объявлены как

IFilter = interface ['{887F94DA-29E9-44C6-B48E-1FBF0FB59878}']
{ Return the IBaseFilter Interface (All DirectShow filters expose this interface)}
function GetFilter: IBaseFilter;
{ Return the filter name (generally the component name). }
function GetName: string;
{ Called by the @link(TFilterGraph) component, this method receive notifications
on what the TFilterGraph is doing. if Operation = foGraphEvent then Param is the
event code received by the FilterGraph.}
procedure NotifyFilter(operation: TFilterOperation; Param: integer = 0);
end;

и

IEvent = interface ['{6C0DCD7B-1A98-44EF-A6D5-E23CBC24E620}']
{ FilterGraph events. }
procedure GraphEvent(Event, Param1, Param2: integer);
{ Control Events. }
procedure ControlEvent(Event: TControlEvent; Param: integer = 0);
end;

Эти функции должны быть реализованы классом TVideoWindow. Кроме них, наиболее интересными есть функции SetFullScreen, VMRGetBitmap. Кроме этого, обратим внимание на для два private - члена - FAllocatorClass типа TAbstractAllocatorClass и FCurrentAllocator типа TAbstractAllocator. Их объявления:

TAbstractAllocator = class(TInterfacedObject)
constructor Create(out hr: HResult; wnd: THandle; d3d: IDirect3D9 = nil;
d3dd: IDirect3DDevice9 = nil); virtual; abstract;
end;
TAbstractAllocatorClass = classof TAbstractAllocator;

Рассмотрим также некоторые дополнительные интерфейсы, важные для понимания процесса отображения. Среди них - IVMRSurfaceAllocator, IVMRSurfaceAllocatorNotify, IVMRImagePresenter.

IVMRSurfaceAllocator

Интерфейс IVMRSurfaceAllocator реализуется аллокатором-презентером (allocator-presenter), заданным по умолчанию для фильтра VMR-7. Он должен реализовываться любым plug-in allocator-presenter, который приложение предоставляет фильтру VMR-7. VMR-7 использует методы на этом интерфейсе для выделения, подготовку и освобождение поверхностей DirectDraw. Приложения не используют этот интерфейс. Для VMR-9 используется IVMRSurfaceAllocator9. Вдобавок к методам IUnknown, интерфейс IVMRSurfaceAllocator предоставляет такие методы:

Метод Описание
AdviseNotify Вызывается VMR'ом для предоставления распределителя-презентера с указателем интерфейса для колбэк-уведомлений
AllocateSurface Выделяется поверхность DirectDraw
FreeSurface Освобождается поверхность DirectDraw
PrepareSurface Подготавливается поверхность DirectDraw для декодирования в нее следующего видеофрейма

А интерфейс IVMRSurfaceAllocator9 такие:

Метод Описание
AdviseNotify Вызывается VMR'ом для предоставления распределителя-презентера с указателем интерфейса для колбэк-уведомлений
GetSurface Возвращает DirectDraw-поверхность
InitializeDevice Инициализируется устройство Direct3D
TerminateDevice Освобождается устройство Direct3D

IVMRImagePresenter

Интерфейс IVMRImagePresenter реализован задаваемым по умолчанию аллокатором-презентером для VMR-7. Он должен также быть реализован любым плагином аллокатора-презентера, предоставляемым приложением для VMR-7. VMR-7 использует методы на этом интерфейсе для информирования аллокатора-презентера, что будет представлен видеофрейм, содержащий поддерживаемую поверхность DirectDraw. Соответствующим фильтром для VMR-9 есть интерфейс IVMRImagePresenter9.

Вдобавок к методам, наследуемым от IUnknown, интерфейс IVMRImagePresenter предоставляет следующие методы:

Метод Описание
PresentImage Вызывается в точно тот момент, когда будет видеокадр будет представлен.
StartPresenting Вызывается только перед началом видеотображения
StopPresenting Вызывается только после окончания видеопроигрывания

IVMRSurfaceAllocatorNotify

Интерфейс IVMRSurfaceAllocatorNotify реализован фильтром VMR-7. Приложения используют этот интерфейс для установки собственного аллокатора-презентера и аллокатор-презентер использует этот интерфейс для информирования VMR-7 об изменениях системного окружения, влияющего на поверхности DirectDraw.

Для того, чтобы приложение получило этот интерфейс, VMR должен быть запущен в renderless режиме.

Соответствующим интерфейсом для VMR-9 есть интерфейс IVMRSurfaceAllocatorNotify9.

Вдобавок к интерфейсам, наследуемым от IUnknown, этот интефейс предоставляет следующие методы:

Метод Описание
AdviseSurfaceAllocator Вызывается приложением для информирования VMR об использовании пользовательского аллокатора-презентера
ChangeDDrawDevice Уведомляет VMR, что проигрывающее устройство DirectDraw было изменено
NotifyEvent Вызывается аллокатором-презентером для информирования VMR'а о любых значимых событиях DirectShow на всем протяжении процесса выделения или представления
RestoreDDrawSurface Уведомляет VMR о том, что была обнаружена потеря устройства DirectDraw
SetBorderColor Указывается для VMR'а, какой цвет используется в области экранного прямоугольника, который не будет использован для видео, например, когда видео выводится как "letterbox"
SetDDrawDevice Устанавливает начальное устройство и монитор DirectDraw для использования для видеопроигрывания.

SetAllocator

Этот метод имеет очень простую реализацию:

procedure TVideoWindow.SetAllocator(Allocator: TAbstractAllocatorClass; UserID:
  Cardinal);
begin
FAllocatorClass := Allocator;
FRenderLessUserID := UserID;
end;

Этот метод используется, в частности, для установки класса аллокатора в методах класса TBCTransInPlaceFilter, TBCTransInPlaceOutputPin.

NotifyFilter

Объявление этого метода -

procedure TVideoWindow.NotifyFilter(operation: TFilterOperation; Param: integer);

Передаваемый параметр operation имеет перечислимый тип:

TFilterOperation = (
foAdding, // Перед добавлением фильтра в граф
foAdded, // После добавления фильтра в граф
foRemoving, // Перед удалением фильтра из графа
foRemoved, // После удаления фильтра из графа
foRefresh // Уведомления дизайнера для обновления фильтра
);

И метод выполняет разнообразные вспомагательные операции при уведомлениях с различным значением операции и в зависимости от разных текущих режимов - установка режима отображения, информирование VMR'а об использовании пользовательского аллокатора-презентера, установки пропорций и т.п.

SetFullScreen

Устанавливает полноэкранный режим (путем простой установки размеров окна вывода в полный экран) или отказывается от использования полноэкранного режима.

GraphEvent

Используется для обработки двух типов событий - связанных с изменением палитры (EC_PALETTE_CHANGED) и установкой типа механизма рендеринга (EC_VMR_RENDERDEVICE_SET). В обоих случаях суть сводится к вызову методов интерфейса IVideoWindow - put_Caption (для установки заголовка видеоокна) и put_MessageDrain (для указания окна для пересылки сообщений от мышки и клавиатуры от видеоокна).

ControlEvent

Используется для обработки управляющих событий ceDVDRendered (фильтр был удален) и cePlay (было начато проигрывание).

VMRGetBitmap

Используется для записи в поток текущего изображения.

Класс TDSVideoWindowEx2

Есть альтернативой для обычного способа отображения видео (с помощью TVideoWindow), который дает простой способ для использования оверлейной графики в приложениях.

Наиболее важными private-членами этого класса, необходимыми для достижения декларируемых целей есть:

FVideoWindow - представляющий интерфейс IVideoWindow;
FFilterGraph - типа TFilterGraph;
FBaseFilter - интерфейс IBaseFilter;
FOverlayMixer - интерефйс IBaseFilter;
FVideoRenderer - интерфейс IBaseFilter;
FDDXM - интерфейс IDDrawExclModeVideo

И методы, наиболее важные из которых мы сейчас и рассмотрим.

Метод

procedure NotifyFilter(operation: TFilterOperation; Param: integer = 0)

выполняет задачи, сходные с задачами одноименного метода класса TVideoWindow, но, естественно, с совершенно другой реализацией.

То же самое относится и к следующейму методу -

procedure GraphEvent(Event, Param1, Param2: integer),

но обрабатывается также событие изменения часов синхронизации.

Метод

procedure ControlEvent(Event: TControlEvent; Param: integer = 0),

как и прежде, обрабатывает события ceDVDRendered, cePlay, но вдобавок к ним еще и cePause, ceStop,ceFileRendered. И обработка становится более слоджной, поскольку в данном случае мі имеем бОльшее количество взаимодействующих компонент.

В методе

procedure SetFilterGraph(AFilterGraph: TFilterGraph)

производится установка графа фильтров.

Очень важным методом есть

function UpdateGraph : HResult;

который служит для обновления графа фильтров и вызывается при различных изменениях состояний класса. Он строит ту часть графа фильтров, которая ответственна за видеотображение. Если используется OverlayMixer, то устанавливается эксклюзивный полноэкранный режим с помощью интерфейса IDDrawExclModeVideo. В случе же, когда используется фильтр VMR, производится попытка его вместо OverlayMixer'а. В процессе этого соединения производится проверка, не используется ли фильтр декодера Line21, поскольку Overlay Mixer не может быть соединен с Line21 Decoder2. А затем производится соединения VMR'а c Overlay Mixer'ом (вновь созданным, в случае необходимости).

Описание примеров, входящих в DSPack

тут описание

Как устанавливать DSPack?

тут описание


Comments