Глава 3(фрагмент) Построение графа DirectShow до фильтра сбора
Принципы построения графа сбора
Определение конфигурации устройства сбора
Поиск фильтров с помощью метода FindInterface [переход...]
Принципы построения графа сбора
Построение графа сбора производится вокруг предварительно установленного в граф фильтра сбора, который является центральным фильтром графа. Сначала построение графа ведется от фильтра сбора вверх по потоку, при этом выявляются и устанавливаются все фильтры, которые входят в состав устройства. Затем производится определение возможностей фильтра сбора записывать и отображать данные. Для этого производится обнаружение выходных контактов фильтра сбора и определение их функциональных возможностей. Фильтры сбора могут иметь различный набор выходных контактов. В минимальном варианте фильтр сбора может иметь только один выходной контакт записи. После обнаружения выходных контактов фильтра сбора начинается построение графа вниз по потоку. Каждый выходной контакт фильтра сбора является началом ветви графа определенного назначения. На завершающей стадии построения графа сбора, может быть построено несколько ветвей графа в зависимости от решаемой задачи и наличия выходных контактов. Например, строится одна ветвь графа для предварительного просмотра, вторая ветвь для записи сжатых или не сжатых файлов и может быть третья ветвь графа для просмотра телетекста и дополнительной информации.
Для решения задачи видеозаписи и просмотра существует несколько интерфейсов, для устройств, поддерживающих модель драйвера WDM. В таблице 1 приводится список этих интерфейсов и их краткая характеристика. При построении графа будет рассмотрено применение практически всех указанных интерфейсов, за исключением интерфейса IAMBufferNegotiation, который используется в особых случаях.
Интерфейсы |
Описание |
IAMAnalogVideoDecoder |
Управление аналого-цифровым преобразованием в фильтре сбора WDM. |
IAMBufferNegotiation |
Управление распределением буфера на контакте. |
IAMCopyCaptureFileProgress |
Интерфейс обратного вызова (callback) для приема сообщений при операции копирования файлов. |
IAMCrossbar |
Интерфейс, управляющий переключением источников видео сигнала. |
IAMDroppedFrames |
Определение производительности фильтра сбора в процессе работы (run-time). |
IAMStreamControl |
Управление временем пуска и останова на индивидуальном потоке данных. |
IAMStreamConfig |
Управление выходным форматом видео изображения и звука. |
IAMTVTuner |
Управление настройкой приемника. |
IAMVfwCaptureDialogs |
Отображение диалога для фильтра сбора VFW. Может предоставляться и другими фильтрами для управления их свойствами. |
IAMVideoControl |
Управление конечным изображением, переключает изображение зеркально, вертикально, горизонтально. Управляет внешним переключателем потока данных. Устройство должно поддерживать, указанные возможности. |
IAMVideoProcAmp |
Регулировки видеосигнала, такие как яркость, контрастность и другие. |
ICaptureGraphBuilder2 |
Построение и настройка фильтрового графа. |
IFileSinkFilter2 |
Установка имени и атрибутов записываемого файла. |
Таблица 1 Интерфейсы сбора видео данных
Определение конфигурации устройства сбора
На начальном этапе построения графа сбора следует определить конфигурацию устройства, то есть определить какие фильтры входят в его состав и отыскать их интерфейсы. Как уже упоминалось ранее, типичное устройство аналогового телевидения содержит фильтр сбора, фильтр настройки ТВ приемника, фильтр переключателя видео входов. Многофункциональный телеприемник может дополнительно включать фильтр приемника звуковых программ .
Отправной точкой поиска является фильтр сбора, который должен быть к этому моменту уже установленным в граф. Поиск может производиться двумя способами:
-
Первый способ состоит в том, чтобы, используя метод FindInterface построителя графа сбора (ICaptureGraphBuilder2), найти интерфейсы необходимые для реализации задач стоящих перед приложением. Например, для построения графа аналогового телевидения следует искать интерфейсы IAMTVTuner, IAMCrossbar, IAMTVAudio. Если найдены первые два интерфейса, то это свидетельствует о том, что существуют фильтры устройств, которые позволяют реализовать граф аналогового телевидения. Последний интерфейс позволит реализовать приемник звуковых программ . Поиск производится вверх по потоку. В процессе поиска метод стартует с указанного фильтра, находит, устанавливает фильтры в граф и соединяет их. Если требуемый интерфейс найден, то легко находится и указатель на сам фильтр. Для этого достаточно запросить указатель на интерфейс IBaseFilter из найденного интерфейса. Любой фильтр DirectShow предоставляет этот интерфейс, который считается указателем на фильтр.
Метод работает автоматически и выполняет практически всю работу по построению графа. Недостатком такого подхода к построению графа можно считать достаточно большое время построения, заметное для пользователя. Положительной стороной использования данного метода является то, что он позволяет реализовать приложение без конкретизации устройства. Во многих случаях можно осуществить поддержку нескольких устройств различных производителей, которые имеют требуемые интерфейсы. Этот способ скорее предназначается для приложений, которые не предполагают поддержки всех свойств устройства конкретного типа (производителя). Например, для программы, которая производит запись файлов важно получить данные от любого источника, обеспечив типовую настройку устройства. В то же время остается сомнение, что с помощью данного способа, не имея дополнительных данных для устройства, можно правильно построить граф для некоторых устройств. Например, существуют карты телевизионных приемников, которые имеют два переключателя видеосигналов (IAMCrossbar). Переключатели соединяются в определенном порядке, и конфигурация их соединения может быть различной. Как отреагирует метод FindInterface на такую ситуацию, и каким образом произведет соединение, неизвестно, подробный алгоритм работы метода в документации не описан. В примерах и документации DirectShow предлагается только этот способ построения графа для аналогового телевидения.
-
Второй способ состоит в использовании системного перечислителя. В этом случае перечисляются устройства в своих категориях. Выбор и установка фильтра устройства в граф может производится по его имени. После установки всех фильтров в граф производится их соединение. Достоинством этого способа является скорость работы. Недостатком можно считать то, что необходимо заранее знать имена фильтров, чтобы осуществить их поиск. Данный способ хорош для приложений ориентированных на поддержку одного или небольшого числа определенных устройств, когда заведомо известны имена установленных устройств, для которых пишется приложение. Этот способ построения графа следует предпочесть другим, когда программа пишется для поддержки конкретного устройства и предполагается использовать все возможности предоставляемые устройством.
Поиск фильтров с помощью метода FindInterface
Данный метод осуществляет поиск интерфейса на фильтре. Поиск можно производить для каждого фильтра в графе, начиная с указанного фильтра. Поиск можно ограничить по направлению вверх или вниз по потоку, категорией контакта или категорией медиа типа. Используя FindInterface можно значительно упростить поиск в графе, поскольку отпадает необходимость в перечислении фильтров и поиске на самом фильтре.
HRESULT FindInterface(
const GUID *pCategory,
const GUID *pType,
IBaseFilter *pf,
REFIID riid,
void **ppint
);
Параметры.
pCategory
(in) Указатель на GUID критерия поиска. Может иметь четыре значения.
&LOOK_UPSTREAM_ONLY - поиск вверх по потоку.
&LOOK_DOWNSTREAM_ONLY - поиск вниз по потоку.
Член структуры AMPROPERTY_PIN_CATEGORY -указывает категорию (GUID) контакта для поиска. Например, категория контакта &PIN_CATEGORY_CAPTURE. Категории контактов указаны в разделе справки.
NULL. В этом случае поиск производится в таком порядке:
· На фильтре.
· На контактах.
· Вниз по потоку.
· Вверх по потоку.
pType
(in) Указатель на GUID главного медиа типа выходного контакта или NULL. Список главных типов приводится в разделе справки.
pf
(in) Указатель на фильтр, с которого начинается поиск.
riid
(in) Идентификатор интерфейса (IID), который требуется найти.
ppint
(out) Адрес переменной для приема указателя на найденный интерфейс. После использования интерфейса его следует освободить.
Коды возврата
Возвращается величина типа HRESULT. Возможные значения показаны ниже.
Возвращаемая величина |
Описание |
S_OK |
Завершение без ошибок. |
E_FAIL |
Ошибка. |
E_NOINTERFACE |
Интерфейс не поддерживается. |
E_POINTER |
Указатель на интерфейс NULL. |
Примечание.
Данный метод нельзя использовать для поиска интерфейса IVideoWindow, управляющего окном отображения. Этот интерфейс возвращается менеджером графа по запросу. В противном случае менеджер графа будет некорректно реагировать на события связанные с отображением.
В процессе поиска происходит опрос объектов в графе в следующем порядке:
-
Фильтр.
-
Контакты фильтра.
Поиск можно ограничить установкой параметров pCategory и pType.
Если параметр pCategory имеет значение LOOK_DOWNSTREAM_ONLY или LOOK_UPSTREAM_ONLY, то поиск происходит вверх или вниз по потоку. Параметр pType игнорируется.
Если параметр pCategory указывает категорию контакта, то тогда учитывается параметр pType и он также должен быть указан. В этом случае метод ищет интерфейс на контактах фильтров, что находятся выше по потоку, начиная с указанного фильтра. Поиск ниже по потоку ограничен выходными контактами фильтра. Категория контакта указывается при поиске интерфейса на контактах фильтра сбора. Следует учесть, что фильтр сбора может иметь раздельные контакты для сбора и предварительного просмотра. Некоторые фильтры имеют контакт видео порта (PIN_CATEGORY_VIDEOPORT) вместо контакта предварительного просмотра. В этом случае, если производить поиск только для категории контакта предварительного просмотра (PIN_CATEGORY_PREVIEW) и медиа типа MEDIATYPE_Video, метод определит видео порт как контакт предварительного просмотра и приложение не сможет различить видео порт.
Если устройство сбора использует WDM драйвер, то при поиске интерфейсов для фильтра настройки (TV Tuner) и (или) фильтра переключателя видео входов (Analog Video Crossbar) они автоматически устанавливаются в граф. Метод также производит соединение установленных фильтров.
Для дальнейшего построения графа вокруг уже установленного фильтра сбора создадим метод CCapture::InitCaptureVideo(HWND hwndView), где параметр hwndView является дескриптором окна отображения приложения, в котором будет отображаться видео. Он понадобится нам в дальнейшем для инициализации фильтра отображения. В методе напишем код для поиска интерфейсов IAMTVTuner для фильтра настройки телеприемника и IAMCrossbar для фильтра переключателя видео входов. На первом этапе закомментируем часть кода, которая создает объекты классов переключателя и настройки приемника. Сейчас важно определить, как метод будет производить поиск и установку фильтров в граф. Если метод найдет интерфейс фильтра настройки IAMTVTuner , то он должен будет найти и установить в граф фильтр переключателя видео входов, поскольку переключатель входов всегда находится ниже по потоку. Возможен вариант существования специального устройства сбора без фильтра настройки или без переключателя. Для нестандартного варианта устройства, метод все равно должен найти все фильтры выше по потоку и установить их. Так предполагается из описания метода, но как в действительности он работает в этом случае, можно определить только экспериментально, документация не посвящает нас в такие тонкости. Поэтому для страховки, следует поискать интерфейс переключателя IAMCrossbar, после неудачного поиска интерфейса фильтра настройки. Можно поступить и другим способом. После окончания поиска фильтра настройки перечислить фильтры в графе и найти фильтр переключателя. Это несколько ускорит поиск нужных фильтров.
HRESULT CCapture::InitCaptureVideo(HWND hwndView)
{
//Нет фильтра сбора в графе
if(!m_pCapV)
return E_FAIL;
HRESULT hr=S_OK;
string sErr;
//Указатель на первый переключатель видео входов
CComPtr<IBaseFilter> pCross1;
//Указатель на фильтр настройки приемника видео
CComPtr<IBaseFilter> pTunerV;
//Указатель на фильтр звуковых программ приемника
CComPtr<IBaseFilter> pTunerA;
try
{
//Построения графа сбора через поиск интерфейса IAMTVTuner
CComPtr<IAMTVTuner> pTuner;
hr = m_pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY,&MEDIATYPE_Video,m_pCapV,IID_IAMTVTuner ,reinterpret_cast<void **>(&pTuner));
//Если интерфейс найден, реализуем класс для фильтра настройки приемника
if(hr == S_OK && pTuner)
{
//Создаем объект класса CTuner
//m_pAMTunerV = CTuner::MakeTuner(pAMTVTuner);
//Перечисление фильтров в графе для поиска фильтра переключателя
CComPtr<IEnumFilters> pEnum;
hr = m_pGraph2->EnumFilters(&pEnum);
if (S_OK == hr)
{
//запрашиваем интерфейс на каждом фильтре
CComPtr<IBaseFilter> pFilter;
while (S_OK == pEnum->Next(1, &pFilter, NULL))
{
CComQIPtr<IAMCrossbar> pCross(pFilter);
if(pCross)
{
//Фильтр переключателя был установлен
//создать объект переключателя
// m_pCrossBar1 =
//CCross::MakeCrossbar(pCross);
break;
}
pFilter = NULL;
}
pEnum = NULL;
}
if(!m_pCrossBar1)
return FALSE;
}
else
{
//поиск фильтра переключателя, если не найден фильтр настройки
CComPtr<IAMTVTuner> pCross;
//hr = m_pBuilder->FindInterface(
//&LOOK_UPSTREAM_ONLY
//,&MEDIATYPE_Video,m_pCapV,IID_IAMCrossbar
//,reinterpret_cast<void **>(&pCross));
//если интерфейс найден, реализуем класс для фильтра настройки
if(hr == S_OK && pCross)
{
//m_pCrossBar1 = CCross::MakeCrossbar(pCross);
}
}
}
catch(HRESULT)
{
if(hr != S_OK)
{
DestroyGraph();
}
}
return hr;
}
Проверим работу метода в режиме поиска только фильтра настройки приемника. При удачном завершении поиска построитель графа должен автоматически установить фильтр настройки приемника и соединить его контакты с другими фильтрами. Для вызова метода в обработчике CTVshowView::OnNMClickListVideo добавим следующие строки.
if(S_OK != pCap->InitCaptureVideo(hwnd))
{
string sErr = "Ошибка инициализации фильтра сбора";
CTVshowApp::myMessageError(E_FAIL,sErr);
return;
}
Полностью код обработчика можно посмотреть в папке проекта, версия ProjectState3.
Запускаем программу в режиме отладки и с помощью утилиты GraphEdit проверим результат. В окне утилиты видим картинку, показанную на рисунке 3.1.
Рис.3.1 Результат поиска интерфейса фильтра настройки приемника
Как видно на рисунке метод установил все фильтры и соединил их, в том числе и фильтр приемника звуковых программ , но не полностью. Нет подключения контакта Analog Video фильтра TvTuner к входному контакту переключателя Video Tuner In. Это ошибка, суть которой попытаемся выяснить с помощью утилиты GraphEdit . Как упоминалось ранее, для фильтров WDM потоки данных передаются в режиме ядра, а соединение фильтров отображает только управление фильтром со стороны DirectShow. Проверить работу графа, когда он подключен к приложению невозможно, но его можно сохранить на диске, закрыть GraphEdit, затем опять открыть в GraphEdit и произвести необходимые проверки. Проделав необходимые манипуляции, достроим граф автоматически с контакта фильтра сбора Preview. Для этого следует сделать правый щелчок на контакте Preview и в открывшемся меню выбрать пункт "Render Pin". В граф будет установлен отображающий фильтр и появляется возможность проверить его функционирование в режиме предварительного просмотра. Для управления фильтрами в графе следует на фильтре сделать правый щелчок открывшемся меню выбрать пункт "Filter Properties". Откроется диалог со свойствами фильтра, позволяющий произвести его настройку. Диалог свойств фильтра настройки и фильтра сбора показаны на рисунке ниже. Первый позволяет произвести настройку на каналы, а второй выбрать стандарт телевидения. После настройки фильтров можно запустить граф и увидеть телевизионную картинку. Для ответа, почему не происходит соединения, и в тоже время граф работает, немного поэкспериментируем с графом.
Учтите, что описанные результаты могут отличаться от результатов ваших экспериментов. На результат эксперимента влияет много факторов, от модели устройства до версии его драйвера и того насколько разработчики устройства следовали условиям DirectShow.
Проверим, действительно ли контакт остался неподключенным или GraphEdit нам не показал его подключение. Маловероятно, что утилита GraphEdit виновата в этом, но проверка позволит исключить ее влияние. Сначала сохраним граф в файле и закроем GraphEdit. Напишем вспомогательный метод, определяющий неподключенные контакты на фильтре и возвращающий их имена. Он поможет в процессе экспериментов вывести сообщение с именами неподключенных контактов на фильтре.
HRESULT CCapture::GetUnconnectedPin(IBaseFilter *pFilter ,PIN_DIRECTION pinDir )
{
CComPtr<IEnumPins> pEnum ;
CComPtr<IPin> pPinl,pTmp ;
PIN_INFO info ={0};
vector<string> namePin;
//Создать перечислитель контактов
HRESULT hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
return hr;
while (pEnum->Next(1, &pPinl, NULL) == S_OK)
{
PIN_DIRECTION pinDirl;
//Проверяем направление контакта
pPinl->QueryDirection(&pinDirl);
if (pinDirl == pinDir)
{
if(pPinl->ConnectedTo(&pTmp) == VFW_E_NOT_CONNECTED)
{
//Контакт не подключен,получить информацию о контакте
pPinl->QueryPinInfo(&info);
//Преобразование в простую строку
string stmp = COLE2T(info.achName);
//Запись строк в вектор
namePin.push_back(stmp);
}
pTmp = NULL;
}
pPinl = NULL;
}
//Вывод
string str;
size_t count = namePin.size();
for(size_t i=0; i< count; ++i)
{
str += namePin[i] + "\n\r";
}
str.insert(0,"Неподключенные контакты\n\r");
AfxMessageBox(str.c_str());
return hr;
}
[в начало]