Автор: Валентин Вовк
Несколько примеров
Исследуя гороизонты DirectShow приходим к изучению фильтров. Эти компоненты применяются при считывании данных с диска (фильтры источников/Sources) и при преобразования их для отрисовки (трансформационные фильтры/Transform). Процесс отрисовки называется рендерингом (Render). Сами фильтры обычно имеют расширение .ax и содержатся в dll-файлах.
Майкрософт с DirectShow поставляют 20 примеров фильтров (которые возможно найти в каталоге ...\DX90SDK\Samples\C++\DirectShow\Filters). Авторы пакета DSPack часть из них перевели в Delphi и дают возможность познакомиться с исходниками. Это будет хорошей иллюстрацией для нашего подробного рассмотрения.
NullNull
Простейшую структуру фильтра можно увидеть на примере NullNull (DSPack\Demos\D6-D7\Filters\NullNull) Так как основную функциональность несут на себе базовые классы этот фильтр просто пропускает через себя данные ничего с ними не делая.
Рассмотрим его реализацию. Базовый класс для такого фильтра есть TBCTransInPlaceFilter:
Рис. 1. Иерархия классов для фильтра CTransInPlaceFilter
Для базового класса нам понадобится переопределить только два метода: Transfom - в нашем случае он виртуальный, предназначен лишь для возвращения значения типа HRESULT. Возвращаем (NOERROR) и делать в нем ничего не будем; CheckInputType - проверяет, подходит ли переданный в параметре указатель на видеотип для входа - всегда отвечаем что подходит. Таким образом, реализация фильтра будет иметь следующий вид:
... |
Далее регистрируем фильтр в системе. Соответствующий CLSID в исходниках есть, пишем в секции initialization:
initialization CLSID_LegacyAmFilterCategory, MERIT_DO_NOT_USE, 2, @MyPins); |
Фнкция CreateFilter принимает следующие параметры:
constructor TBCClassFactory.CreateFilter(ComClass: TBCUnknownClass; Name: string; (ссылку на него поставить здесь) в фильтре. oFilter: nil; strConnectsToPin: nil; nMediaTypes: 1; lpMediaType: @MyPinTypes), oFilter: nil; strConnectsToPin: nil; nMediaTypes: 1; lpMediaType: @MyPinTypes)); |
Подробнее узнать о значениях элементов структуры TRegFilterPins можно в справке.
После удачной компиляции проекта мы должны получить файл с именем NullNull.ax, регистрируем его из командной строки: regsvr32 NullNull.ax. Затем запускаем GraphEdit и строим граф. Например такой:
Рис. 2. Граф фильтров, включающий фильтр Null-Null
Dump
Следующий интересующий нас фильтр называется Dump (DSPack\Demos\D6-D7\Filters\Dump). Он предназначен для записи на диск данных, поступивших от предыдущего фильтра. Dump хорошо подходит для проверки работы трансформационного фильтра. Это так же можно сделать дебагированием самих трансформационных фильтров.
Dump дожен иметь один входящий контакт и не иметь исходящих (приходящую ему информацию он будет записывать в указанный нами файл). Входящий контакт будет наследником интерфейса контакта, т.е. IPin, а еще точнее TBCRendererInputPin:
Рис. 3. Иерархия классов для контакта CRenderInputPin
Связка контакт необходима для того что бы фильтр функционировал как единое целое. Разрабатывая фильтры о контактах забывать нельзя. Наш фильтр является наследником базового класса TBCBaseFilter и интерфейса IFileSinkFilter. IFileSinkFilter разработан специально для фильтров записывающих медиапоток в файлы (он так же заменим на более поздний IFileSinkFilter2) и происходит от IUnknown. Ко всем известным уже методам он объявляет свои: SetFileName и GetCurFile, о предназначении которых можно догадаться из названия. Нам предстоит их реализовать.
Начнем разбираться с классом TDump не забывая о том, что нам потребуются и некоторые методы класса TMyPin (это будет наш входящий контакт), но их разбор мы пока отложим.
Итак, смотрим файл ..Dump\Main.pas:
TDump = class (TBCBaseFilter,IFileSinkFilter) |
Смотрим на приватные члены:
xxx и yyy используются для внутренних нужд и играют роль флагов. |
Рассмотрим реализацию функций:
function TDump.GetPinCount: integer; |
В конструкторе вызываем конструктор базового класса:
constructor TDump.Create(Name: string; // Object description Unk : IUnKnown; // IUnknown of delegating object Lock: TBCCritSec; // Object who maintains lock const clsid: TGUID // The clsid to be used to serialize this filter |
Если контакта нет, то создаем его. Далее возвращаем FPin.
function TDump.GetPin(n: Integer): TBCBasePin; |
Реализация SetFileName и GetCuFile очень простая. Остается Run, в которой вызывем функцию OpenFile входящего контакта, а затем метод Run предка:
function TDump.Run(tStart: TReferenceTime): HRESULT; |
И Stop, в котором вызываем метод CloseFile входного контакта и вызываем метод Stop предка:
function TDump.Stop: HRESULT; |
Коротко просмотрим что происходит. В конструкторе вызываем конструктор предка, по требованию выдаем/создаем контакт, установленное имя и инициализационно-деинициализационные действия сообщаем контаку, на запуск и остановку вызываем соответствующие свойства контакта, а затем - метод предка. Все более-менее прозрачно, переходим к рассмотрению нашего контакта. Вот его объявление:
TMyPin = Class (TBCRenderedInputPin) //TBCBasePin private |
Как уже было сказано, он есть потомком класса TBCRenderedInputPin.
В приватной секции важен только член:
FFile: integer; - хэндл файла, в который будет вестись запись
Все остальные члены - просто глупость.
В public-секции есть один член:
mF: String; - задает имя файла
И следующие функции:
constructor Create(ObjectName: string; pUnk: IUnKnown; Filter: TBCBaseFilter; |
Разберем их более подробно. Методы OpenFile и CloseFile служат для создания и закрытия файла, в который ведется запись. В конструкторе вызывается конструктор предка и открывается файл для записи. Метод BreakConnect, объявленный в CBaseInputPin, освобождаем контакт от соединения, его реализация вызывает одноименный метод предка. Функция CheckMediaType всегда возвращает S_OK, что значит, что контакт согласен со всем, что ему ни предложат. EndOfStream вызывается в случае, когда данные больше не ожидаются, реализация состоит в вызове метода предка. Реализация оставшегося метода - Receive выглядит следующим образом:
function TMyPin.Receive(pSample: IMediaSample): HRESULT; |
Этот метод вызывается, когда контакту приходят данные и он, недолго думая, просто пишет их в файл. Входной параметр имеет тип IMediaSample, информацию о котором смотрим в помощи и исходниках. Нам достаточно знать, что с помощью этого параметра можно получить входные данные и информацию о них.
В общем, все выглядит следующим образом: создается контакт, открывается файл для записи, при получении данных они пишутся в этот самый файл, а когда данные заканчиваются или получена команда об остановке, файл закрывается.
Т.е. фильтр создает входящий контакт, который, на самом деле, и выполняет всю работу.
Теперь можно и проверить фильтр, но занимайтесь этим самостоятельно.
NullIP
Следующий рассматриваемый фильтр будет минимально полезным. Он будет перечислять поддерживаемые предыдущим и последующим фильтром в графе фильтров мультимедиа -форматы. Вещь почти бесполезная в жизни, но подходящая для тренировки. Еще одно нововведение, которое мы рассмотрим - страницы свойств фильтра.
Итак, переход к директории NUllIP и смотрим файл main.pas. Для начала разберемся с простыми деталями. Это - входящий и исходящий контакты. Вот их объявление:
TNullInPlaceInputPin = class(TBCTransInPlaceInputPin) TBCTransInPlaceFilter; out hr: HRESULT; Name: WideString); |
Т.е. контакты - наследники соответственно TBCTransInPlaceInputPin и TBCTransInPlaceOutputPin:
Рис. 4. Иерархия классов для контакта CTransInPlaceInputPin
Рис. 5. Иерархия классов для контакта CtransInPlaceOutputPin
В их конструкторах всего лишь вызываются конструкторы предков. Рассмотрим функцию
CheckMediaType(mt: PAMMediaType). Этот метод используется для проверки, подходит ли соответтсвующему контакту указанный в параметре тип медиа. Для входящего контакта принцип алгоритма следующий: (ПЕРЕВЕСТИ)
// If we have been given a preferred media type from the property sheet
// then only accept a type that is exactly that.
// else if there is nothing downstream, then accept anything
// else if there is a downstream connection then first check to see if
// the subtype (and implicitly the major type) are different from the downstream
// connection and if they are different, fail them
// else ask the downstream input pin if the type (i.e. all details of it)
// are acceptable and take that as our answer.
Для исходящего контакта проверяем соответствие переданного видеотипа
PushSource
Модифицируем PushSource
…
InfTee
Следующий пример, хотя и присутствует в майкрософтовской поставке DirectShow, не адаптирован к применению в Дельфи. Вот и хорошо, у нас будет возможность сделать грамотное переложение, а заодно и лучше понять некоторые тонкие моменты функционирования фильтров.
Итак, начнем. Нашей задачей будет написание (написание - сильно сказано, ведь мы будем постоянно оглядываться на исходники примера из SDK) некоего фильтра, имеющего один входной контакт и динамически изменяемое количество исходящих контактов. Все данные, которые попадают через входящий контакт в фильтр, будут пересланы по всем нижележащим путям далее вниз, для чего будет использовано соответствующее количество исходящих тредов, связанных с исходящими контактами.
Таким образом, в процессе установления связи между фильтрами, нам придется учесть тот факт, что, если меняется медиатип для одного соединения, то синхронно должны быть изменены и медиатипы для остальных соединений. Если же какое-то из соединений не сможет принять этот медиатип, то, в таком случае, фильтр (контакт) должен отвергнуть его.




