Создание специализированного фильтра

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

Создать собственный специализированный фильтр не так уж и сложно. Вся тонкость заключается в наследовании базового класса фильтра DirectShow, присвоении ему уникального идентификационного номера и перегрузке соответствующих функций, обрабатывающих поступающие видео данные. Звучит не очень сложно, не так ли? В следующем разделе рассмотрены шаги, необходимые для создания специализированного фильтра.

Наследование класса фильтра

Первым шагом к созданию собственного специализированного фильтра DirectShow является наследование класса фильтра от базовых классов DirectShow. Мы собираемся унаследовать класс, обрабатывающий видео образцы из входного потока, от класса CbaseVideoRenderer. Унаследуйте класс (названный cTextureFilter), который будет использоваться для фильтра.

class cTextureFilter : public CBaseVideoRenderer {

He беря в расчет используемые функции (о которых вы прочитаете чуть ниже), необходимо объявить несколько переменных-членов, используемых для хранения указателя на 3D устройство (IDirect3DDevice9), объект текстуры (IDirect3DTexture9), формат текстуры (D3DFORMAT) и исходную ширину, высоту и шаг видео картинки. Вы можете объявить эти шесть переменных в классе cTextureFilter.

public:

IDirect3DDevice9 *m_pD3DDevice; // интерфейс 3D устройства IDirect3DTexture9 *m pTexture; // Объект текстуры D3DFORMAT m_Format; // Формат текстуры

LONG m_lVideoWidth; // Ширина видео, в пикселях LONG m_lVideoHeight; // Высота видео, в пикселях LONG m_lVideoPitch; // Шаг поверхности видео

Первые три переменные m_pD3DDevice, m_pTexture и mFormat являются обычными для хранения текстуры. Среди них интерфейс 3D устройства, используемый в проекте, объект текстуры, содержащий анимированную текстуру, и описание цветового формата текстуры.

Последние три переменные, mlVideoWidth, mlVideoHeight и m_lVideoPitch описывают размеры источника видео. DirectShow импортирует видео данные, создавая в памяти растровую поверхность и копируя эти данные на нее. Поверхность имеет собственную высоту (mlVideoHeight), ширину ((mlVideoWidth) и шаг поверхности (m_lVideoPitch), определяющий размер строки видео данных в байтах.

Пока хватит о переменных. (Мы вернемся к ним позднее в разделе "Работа со специализированным фильтром".) А сейчас давайте рассмотрим, как присвоить уникальной идентификационный номер фильтру текстур.

Определение уникального идентификационного номера

Чтобы DirectShow мог отличить ваш фильтр от всех остальных, необходимо присвоить ему уникальный идентификационный номер. Этот уникальный номер (номер идентификации класса, если быть точным) представлен в виде UUID, которыйвы можете определить с помощью следующего кода. (Обычно необходимо включать этот код в начале исходного файла специализированного фильтра.)

Замечание. UUID фильтра может быть любым; запустив программу guidgen.exe корпорации Microsoft, вы получите число GUID, которое может быть использовано. Я взял на себя смелость использовать показанный UUID во всех демонстрационных программах этой главы. Если вы хотите посмотреть, как создавать собственные GUID, обратитесь к главе 3.

struct_declspec( \

uuid("(61DA4980-0487-11d6-9089-0040053 6B95F}" ) \ ) CLSID_AnimatedTexture;

После того как вы определили UUID, необходимо просто использовать его внутри конструктора специализированного фильтра. Необходимо зарегистрировать UUID в системе фильтров DirectShow, чтобы она знала где искать ваш фильтр. Интересно заметить, что как только вы установите фильтр и зарегистрируете его в системе (о чем мы поговорим немного позднее), он все время будет находиться в памяти. Как только загружается медиа файл, фильтр декодирует его. Чтобы ваш фильтр мог декодировать данные медиа, необходимо перегрузить несколько основных функций.

Перегрузка основных функций

После того как вы объявили класс специализированного фильтра и присвоили ему уникальный идентификационный номер, пришло время определить функции, используемые фильтром. Не беря во внимание стандартный конструктор класса, необходимо перегрузить три основные функции DirectShow, которые одинаковы для всех фильтров.

Первая функция, CheckMediaType: определяет, поддерживает ли фильтр заданный тип данных медиа. Т. к. DirectShow может обрабатывать почти любой медиа файл, фильтр будет использовать CheckMediaType для проверки: являются ли входные данные видео данными, сохраненными в используемом фильтром формате. Посмотрите на прототип функции CheckMediaType.

virtualHRESULTCBaseRenderer::CheckMediaType( const CMediaType *pmt) PURE;

У функции CheckMediaType только один параметр - интерфейс CmediaType, определяющий входные данные. Используя функцию FormatType интерфейса CmediaType, вы можете получить тип медиа данных, содержащихся в интерфейсе (видео, звук или что-нибудь еще).

Т. к. нас интересуют только видео файлы, мы будем искать только значение FORMATVideoInfo, возвращаемое CMediaType::FormatType. Если данные медиа действительно являются видео, то необходимо выполнить еще одну проверку для определения их формата. Эта проверка заключается в сравнении значения GUID с типом и подтипом видео.

Снова, нам необходим видео медиа тип (представленный MEDIATYPE_Video макросом GUID) а необходимый подтип - макрос GUID цветовой глубины MEDIASUBTYPERGB24 (означающий, что используется 24-битная глубина цвета, составленная из красной, зеленой и синей компоненты цвета.)

После того как вы проверили видео поток, вы можете вернуть значение S_OK из вашей функции CheckMediaType, чтобы сообщить DirectShow, что поток может использоваться. Если заданные данные медиа не поддерживаются, вы можете вернуть EFAIL.

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

HRESULT cTextureFile::CheckMediaType( \ const CMediaType *pMediaType)

{

// Принимать только видео тип медиа if(*pMediaType->FormatType() != FORMAT_VideoInfo) return E_INVALIDARG;

// Убедиться, что данные используют 2 4-битный RGB формат цвета if(IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video) && \ IsEqualGUID(*pMediaType->Subtype(),MEDIASUBTYPE_RGB2 4)) return S_OK;

return E_FAIL;

}

Второй перегружаемой функцией является SetMediaType, которая используется фильтром для настройки внутренних переменных и подготовки к обработке внешних данных. Эта функция также может отвергнуть внешние данные, в зависимости от их формата.

Функция SetMediaType имеет только один параметр const CMediaType, также как и CheckMediaType. Внутри функции SetMediaType необходимо получить разрешение видео данных (ширину, высоту и шаг поверхности) и сохранить его в переменных класса.

Также необходимо создать 32-битную поверхность текстуры (которая по умолчанию может быть в 16-битном формате, если 24-битные режимы недоступны) для хранения данных видео по мере их обработки. После того как вы создали текстуру, необходимо проверить ее формат, который должен быть либо 32-либо 16-битным, и сохранить цветовую информацию для дальнейшего использования.

Значение S_OK, возвращаемое функцией SetMediaType, указывает, что фильтр готов к использованию данных видео; в противном случае необходимо вернуть значение ошибки (такое как E_FAIL).

Вы можете создать функцию SetMediaType, получающую разрешение видео и создающую поверхность текстуры, используя следующий код. (Объяснение всех содержащихся операций дано с помощью комментариев.)

HRESULT cTextureFilter::SetMediaType(const CMediaType *pMediaType) (

HRESULT hr;

VIDEOINFO *pVideoInfo; D3DSURFACE_DESC ddsd;

// Получить размер этого типа медиа pVideoInfo = (VIDEOINFO *)pMediaType->Format(); m_lVideoWidth = pVideoInfo->bmiHeader.biWidth; m_lVideoHeight = abs(pVideoInfo->bmiHeader.biHeight); m_lVideoPitch = (m_lVideoWidth * 3 + 3) & ~(3);

// Создать текстуру, которая соответствует этому типу медиа if(FAILED(hr = D3DXCreateTexture(m_pD3DDevice, \ m_lVideoWidth, m_lVideoHeight, \ 1, 0, D3DFMT_A8R8G8B8, \ D3DPOOL_MANAGED, &m_pTexture))) return hr;

// Получить описание текстуры и сверить установки if(FAILED(hr = m_pTexture->GetLevelDesc(0, &ddsd)))

return hr; m_Format = ddsd.Format;

if(m_Format != D3DFMT_A8R8G8B8 && m_Format != D3DFMT_A1R5G5B5) return VFW_E_TYPE_NOT_ACCEPTED;

return S_OK;

}

Последней перегружаемой и наиболее важной из всех является функция DoRenderSample. DoRenderSample вызывается фильтром при обработке данных каждого кадра, содержащегося в медиа файле. В данном случае Данными является видео клип, вставляемый на поверхность текстуры.

Прототип перегружаемой функции DoRenderSample выглядит так:

virtual HRESULT CBaseRenderer::DoRenderSample( IMediaSample *pMediaSample);

DoRenderSample имеет только один параметр, интерфейс ImediaSample, который содержит информацию об одном медиа экземпляре (растровом изображении, представляющем один кадр видео). Вам необходимо взять этот указатель на данные и вставить их на поверхность текстуры.

Начнем с объявления перегруженной функции DoRenderSample и получения указателя на данные медиа с помощью функции IMediaSample::GetPointer.

HRESULT cTextureFilter::DoRenderSample( \ IMediaSample *pMediaSample)

{

// Получить указатель на буфер видео образца BYTE *pSamplePtr;

pMediaSample->GetPointer(&pSamplePtr);

После этого необходимо заблокировать поверхность текстуры и скопировать данные пикселей из данных видео. Для этого вы можете использовать функцию LockRect объекта текстуры.

// Заблокировать поверхность текстуры D3DLOCKED_RECT d3dlr;

if(FAILED(m_pTexture->LockRect(0, &d3dlr, 0, 0))) return E_FAIL;

// Получить шаг текстуры и указатель на данные текстуры BYTE *pTexturePtr = (BYTE*)d3dlr.pBits; LONG lTexturePitch = d3dlr.Pitch;

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

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

pTexturePtr += (lTexturePitch * (m_lVideoHeight-1));

Замечательно, теперь мы готовы скопировать данные видео. Помните, при создании поверхности текстуры цветовой формат текстуры мог быть либо 32- либо 16-битным? Теперь необходимо учитывать глубину цвета при копировании данных видео, потому что за один раз мы можем копировать либо 32 либо 16 бит.

В начале следующего кода стоит оператор switch, который содержит блок, копирующий видео данные, по 32 бита информации за один раз (конечно, если используется 32-битный цветовой формат).

// Скопировать информацию, используя заданный видео формат int х, у, SrcPos, DestPos; switch(m_Format) {

case D3DFMT_A8R8G8B8: // 32 бита

Следующий кусочек кода просматривает все строки видео образца и копирует каждый пиксель на поверхность текстуры.

// Просмотреть каждую строку, копируя информацию for(y=0;y<m_lVideoHeight;y++) {

// Скопировать каждый столбец

SrcPos = DestPos = 0;

for(x=0;x<m_lVideoWidth;x++) {

pTexturePtr[DestPos + +] = pSamplePtr[SrcPos + + ] ; pTexturePtr[DestPos++] = pSamplePtr[SrcPos++]; pTexturePtr[DestPos + +] = pSamplePtr[SrcPos + + ] ; pTexturePtr[DestPos++] = Oxff;

}

// Переместить указатель на следующую строку pSamplePtr += m_lVideoPitch; pTexturePtr -= lTexturePitch;

}

break;

Второй оператор switch копирует данные видео, использующие 16-битную глубину цвета. Этот код в целом похож на код копирования 32-битных пикселей, за исключением того, что копируются 16-битные пиксели.

case D3DFMT_A1R5G5B5: // 16-бит

// Просмотреть все строчки, копируя информацию for(y=0;y<m_lVideoHeight;y++) {

// Скопировать каждый столбец

SrcPos = DestPos = 0;

for(x=0;x<m_lVideoWidth;x++) { *(WORD*)pTexturePtr[DestPos++] = 0x8000 +

((pSamplePtr[SrcPos+2] & 0xF8) << 7) +

((pSamplePtr[SrcPos+1] & 0xF8) << 2) +

(pSamplePtr[SrcPos] >> 3) ; SrcPos += 3;

}

// Переместить указатель на следующую строку pSamplePtr += m_lVideoPitch; pTexturePtr -= lTexturePitch;

}

break;

}

В завершение функции DoRenderSample необходимо просто разблокировать поверхность текстуры и вернуть код успешного окончания или ошибки.

// Разблокировать текстуру if (FAILED(m_pTexture->UnlockRect(0))) return E_FAIL;

return S_OK;

}

Каждая из трех только что написанных перегруженных функций (CheckMedi-aТуре, SetMediaType и DoRenderSample) вызывается непосредственно DirectShow. Другими словами, вам не надо непосредственно вызывать эти функции: вместо этого позвольте DirectShow вызывать их по мере необходимости. Именно по этой причине все фильтры DirectShow все время находятся в памяти. Это означает, что после создания фильтра, вы никогда не должны самостоятельно удалять его из памяти. DirectShow самостоятельно выполнит это.

Завершение класса фильтра

После того как фильтр принимает внешние данные медиа, для его завершения необходимо добавить всего лишь еще несколько функций. Этими функциями являются конструктор и функция, возвращающая указатель на объект поверхности текстуры. Каждая функция объявляется так (обе функции объявлены открытыми в классе cTextureFilter):

class cTextureFilter (

// ... объявление предыдущих членов

public:

cTextureFilter(IDirect3DDevice9 *pD3DDevice, \ LPUNKNOWN pUnk = NULL, \ HRESULT *phr = NULL) ;

IDirect3DTexture9 *GetTexture();

Конструктор класса cTextureFilter регистрирует фильтр в системе DirectShow и объявляет интерфейс объекта 3D устройства. Пусть прототип конструктора вас не пугает. Наряду с указателем на объект 3D устройства, в качестве параметров конструктор принимает указатель на объект Iunknown (который можно установить в NULL) и указатель на переменную типа HRESULT, используемый для возвращения результата выполнения функции. Вот код конструктора класса cTextureFilter:

cTextureFilter::cTextureFilter(IDirect3DDevice9 *pD3DDevice, \ LPUNKNOWN pUnk, HRESULT *phr) :CBaseVideoRenderer( uuidof(CLSID AnimatedTexture) ,

NAMEC'ANIMATEDTEXTURE"), pUnk, phr)

{

// Сохранить указатель на устройство m_pD3DDevice = pD3DDevice;

// Возвратить успешное окончание выполнения функции *phr = S_OK;

}

Чтобы зарегистрировать фильтр, необходимо вызвать конструктор CbaseVideo-Renderer, как показано в вызове конструктора вашего фильтра. В качестве параметров конструктор CbaseVideoRenderer принимает UUID созданного фильтра и имя, присваиваемое фильтру (в данном случае ANIMATEDTEXTURE). Другие две переменные просто передаются из параметров конструктора фильтра.

Внутри конструктора необходимо просто сохранить указатель объект 3D указателя в переменной класса (m_pD3DDevice) и код успешного завершения в указателе на HRESULT. Вот и весь конструктор класса фильтра!

Вторая (и последняя) добавляемая к классу фильтра функция возвращает указатель на объект поверхность текстуры. Вспомните, объект текстуры был объявлен в классе как m_Texture, поэтому вернуть указатель на него можно при помощи следующего кода:

IDirect3DTexture9 *cTextureFilter::GetTexture() {

return m_Texture;

}

После создания последней функции класс фильтра готов к использованию!

Импорт видео при помощи DirectShow || Оглавление || Работа со специализированным фильтром