Автор: Валентин Вовк
То же самое своими руками
IGraphBuilder
Программно граф фильтра строится с помощью интерфеса IGraphBuilder. Для этого нужно получить интерфейс самомого IGraphBuilder'а, интерфейсы всех составлящих его фильтров и интерфейсы контактов каждого фильтра. После этого нужно соединить фильтры посредством их контактов, и можно запускать граф. Как это сделать, мы посмотрим в следующих пунктах, сейчас только замечу, что стандартные DirectShow фильтры, поставляемые в составе DirectShow, описаны в соответствующем разделе справки.
Проигывание видеофайла и Intelligent connect
Сейчас мы построим программным путем граф фильтров, описанный в Проигрывании видеофайла.
Для простого проигрывания нет необходимости вручную строить весь граф фильтров. Графу фильтров нужно подсунуть фильтр источника, а все остальное сделает IntelligentConnect.
Давайте проведем две лабораторных работы. В первой добьемся проигрывания какого-нибудь avi-файла "как-нибудь", а во второй попытаемся добиться большего контроля.
1. Создаем новый проект, в секцию "uses" добавляем модули ActiveX и DirectShow, бросаем на форму TMainMenu, в котором создаем пункт "Open...", который отвечает за выбор видеофайла через диалог выбора файлов, и TOpenDialog. Поскольку мы будем работать с КОМ-объектами, то давайте сразу, при создании формы, добавим инициализацию: CoInitialize(nil), а при разрушении - CoUnitialize. Далее, для приложения нам понадобятся три интерфейса: IGraphBuilder, IMediaControl и IMediaEvent.
IGraphBuilder - это интерфейс графа фильтров. IMediaControl используется для управления запуском графом. IMediaEvent - для контроля и управлением поведения графа.
Я буду приводить упрощенный, схематический код, без проверок успешности выполнения тех или иных операций, но он обязательно должен быть полным в реальных приложениях.
// Объявляем переменные:var pGraphBuilder); |
Полный проект находится здесь.
Теперь попробуем отобразить видео не где-попало, а в указанном нами месте. Предварительные шаги - добавление в "uses", инициализация и деинициализация - те же. Только добавим еще к форме панельку (TPanel), на которой и будем отображать видео. Но нам понадобятся дополнительные интерфейсы: ICaptureGraphBuilder2 - используется для построения графа захвата, но нам он нужен будет для рендеринга файла так, как нам нужно; pSourceFile - фильтр файла-источника; и IVideoWindow - интерфейс окна для отображения в нем видео.
// Объявляем переменные:var pGraphBuilder); IID_ICaptureGraphBuilder2, pCaptureGraphBuilder2); // граф фильтров для использования: pSourceFile); |
Все, с этой задачей мы справились. Полный код проекта можно взять здесь.
Конвертирование WAV<->MP3
Следующие наши эксперименты будем проводить без использования Intelligent Connect, поскольку одна из наших целей - понимание системы работы DirectShow, поэтому будем пытаться побольше работы выполнять руками. Поскольку, как мы помним, у меня были проблемы с конвертированием MP3 в WAV, будем заниматься только конвертацией WAV-MP3.
Что для этого будет нужно? Во-первых, граф фильтров - ни один проект без него не обойдется, во вторых интерфейс WaveParser'а - его CLSID узнать просто: запускаем GraphEdit, находим этот фильтр, и смотрим в дереве его DisplayName - оттуда и вычисляем, что CLSID равен D51BD5A1-7548-11CF-A520-0080C77EF58A; в-третьих - LAME MPEG Layer III Audio Encoder - тем же способом получаем, что его CLSID равен B8D27088-DF5F-4B7C-98DC-0E91A1696286; для фильтра Dump CLSID = 36A5F770-FE4C-11CE-A8ED-00AA002FEAB5. Собственно, это все, что нужно для успешного построения проекта.
// Итак, объявляем константы и переменные:const |
Дальше, давайте подумаем о том, как мы будем получать входящие и исходящие контакты фильтров. Для интерфейса IBaseFilter существует метод EnumPins, который заполняет соответствующую структуру, и позволянт перебрать все контакты и узнать кое-какую информацию о них. Нам нужно будет получать для каждого фильтра входящие и исходящие контакты, поэтому неплохо бы написать функцию, которая по переданному интерфейсу и типу контакта (входящий или исходящий) будет выдавать контакт. Я вовсе не такой умный, чтобы писать такие функции, я просто посмотрел в MSDN, и нашел там приблизительно следующий варинт:
function GetPin(pFilter: IBaseFilter; pinDir: PIN_DIRECTION): IPin; |
Я настолько свыкся с этим кодом, что уже даже не помню, вызывал он у меня какие-то вопросы или нет. Думаю, он более-менее ясен. Поэтому, если они у вас возникнут, рекомендую обращаться с DirectShow - справке. Нужно только заметить, что код будет правильео работать только в том случае, если у фильтров, попадающих на вход этой функции, будет не больше одного контакта соответствующего типа - иначе ничего не гарантировано. В нашем случае это именно так, поскольку мы используем конкретные фильтры, о которых все известно.
Что же, создаем КОМ-объекты и добавляем их к графу фильтров:
CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IID_IGraphBuilder, pGraphBuilder); pSourceFile); pWaveParser); pLameEncoder); |
Теперь нужно соединить фильтры между собой, а для этого нужно получить интерфейсы исходящего контакта для одного фильтра и входящего для другого, а затем соединить их с помощью соответствующего метода графа фильтров. Будем полагать, что у нас уже объявлены и проинициализированы (= nil) следующий переменные:
pSourceOut : IPin; // исходящий контакт фильтра источника |
Все, мы полностью собрали вручную граф фильтров. Это, все-таки, довольно утомительная и однообразная работа, но ничего, зато мы теперь знаем, как это происходит. Остается установить имя mp3-файла, в который будет произведена конвертация. Для этого используем наш Dump, который, как и положено наследнику IFileSinkFilter, поддерживает метод установки имени файла:
(pDump as IFileSinkFilter).SetFileName(StringToOleStr(SaveDialog1.FileName), nil); // и начать конвертацию: |
Замечу, что, поскольку граф у нас именно конкретный, то не всякий файл можно подавать на вход. Но во всяком случай, файл 'Вход в Windows.wav' подходит. Вот и проверьте, насколько mp3 компактнее несжатого wav'а.
Готовый проект можно взять здесь.