Visual Basic - мастерская разработчика
Библиотеки

DirectX

Обзоры
DirectDraw
Direct3D
DirectX Audio
DirectPlay
DirectInput
Fido Topics
SourceCode
Tools&Libs

OpenGL

Статьи и учебники
Fido Topics
SourceCode
Tools&Libs

Архив по Glide

Движки

Обзоры
Учебники
SourceCode
Downloads

Создание игр

Ваши игры

Обзорные статьи
Учебники
Fido Topics
SourceCode
Download

Stuff

Программер-Чат

Псевдо-FTP
Disclaimer
Оффтопик

 

Использование DirectShow в Windows

Часть 2: DirectShow из C++

DirectShow For Media Playback In Windows
Part II: DirectShow In C++

By Chris Thompson (aka Lightman)
12 August 2000

Введение

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


Стандартные интерфейсы

Основные лошадки DirectX такие, как DirectDraw, DirectSound и Direct3D по большей части доступны через один интерфейс: IDirectDraw, IDirectSound, и IDirect3D соответственно. DirectShow имеет немного более комплексную структуру. В типичном приложении DirectShow у вас может быть 3 или больше интерфейса, которые необходимы для контролирования графа фильтров. Вот вам список наиболее частых интерфейсов, которые мы будем использовать:

IGraphBuilder - это самый важный интерфейс. Он предоставляет платформу для создания графа для указанного файла, а так же для добавления/выкидывания фильтров по одиночке в/из графа.

IMediaControl - Этот интерфейс дает нам методы для запуска и остановки графа

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

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

Переходим к проигрыванию файла

Сперва нам надо создать те интерфейсы, что мы описали вверху.

IGraphBuilder* g_pGraphBuilder;
IMediaControl* g_pMediaControl;
IMediaEventEx* g_pMediaEvent;
IMediaPosition* g_pMediaPosition;

int InitDirectShow()
{
HRESULT hr;

hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,(void**)&g_pGraphBuilder);

if (FAILED(hr))
return -1;
g_pGraphBuilder->QueryInterface(IID_IMediaControl, (void**)&g_pMediaControl);
g_pGraphBuilder->QueryInterface(IID_IMediaEvent, (void**)&g_pMediaEvent);
g_pGraphBuilder->QueryInterface(IID_IMediaPosition, (void**)g_pMediaPosition);
return 0;
}


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

Вы можете устанавливать сообщения двумя способами. Первый - это использование переключателей событий. Вы можете получить хэндл события, который изменится, когда оно произойдет, и который вы сможете использовать кучей способов. Вы можете так же использовать MsgWaitForMultipleObjects в главном цикле вместо GetMessage, или вы можете создать поток, который будет вызывать WaitForSingleObject - функцию, которая очнется, когда событие случится. Вторым способом, который мы можем устанавливать сообщения - это сообщения окна. Вы можете установать окно как получателя сообщения окна при активации события. Этот способ используется в большинстве случаев и мы будем его использовать в нашем простом медиа-проигрывателе.

#define WM_GRAPHEVENT WM_USER // определить пользовательское сообщение окна для событий графа
HWND g_AppWindow; // главное окно приложения

void SetNotifications()
{
g_pMediaEvent->SetNotifyWindow((OAHWND)g_AppWindow, WM_GRAPHEVENT, 0);
g_pMediaEvent->SetNotifyFlags(0); //включить уведомления
}


В WindowProc мы добавим пару строчек кода для работы с уведомлениями

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_GRAPHEVENT:
OnGraphEvent(); // обработка событий
break;
default:
break;
}
return DefWindowProc(hwnd, uMsg, lParam, wParam);
}

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

void OnGraphEvent()
{
long EventCode, Param1, Param2;
while (g_pMediaEvent->GetEvent(&EventCode, &Param1, &Param2, 0) != E_ABORT)
{
switch (EventCode)
{
// реагировать на особенные события
default:
break;
}
g_pMediaEvent->FreeEventParams(EventCode, Param1, Param2);
}
}

Ну вот теперь, мы готовы к проигрыванию. Когда мы знаем, какой файл будем играть, мы просим IGraphBuilder построить для него подходящий граф

int CreateGraph(char* filename)
{
int length; //длина файла
WCHAR* wfilename; // где хранить WCHAR версию имени файла

length = strlen(filename)+1;
wfilename = new WCHAR[length];
MultiByteToWideChar(CP_ACP, 0, filename, -1, wfilename, length);
if (FAILED(g_pGraphBuilder->RenderFile(wfilename, NULL))
return -1;
else
return 0;
}

Заметьте, что RenderFile принимает строки WCHAR, поэтому необходима конвертация, если мы используем ANSI строки. Если GraphBuilder успешно создает граф, мы можем начинать его проигрывать таким способом:

g_pMediaControl->Run();

 

Зачистка

Стандартной практикой является освобождение всех интерфейсов, включая IGraphBuilder, когда вы заканчиваете проигрывание медиа. Мы можем делать это так:

// вспомогательный макрос для освобождения интерфейсов
#define HELPER_RELEASE(x) if (x != NULL) \
{ \
x->Release(); \
x = NULL; \
}

void CleanUpDirectShow()
{
HELPER_RELEASE(g_pMediaPosition);
HELPER_RELEASE(g_pMediaEvent);
HELPER_RELEASE(g_pMediaControl);
HELPER_RELEASE(g_pGraphBuilder);
}


Макрос HELPER_RELEASE - это простой способ проверки на существование некоего интерфейса, поэтому при попытке освободить уже освобожденный (или не созданный) интерфейс мы избежим сообщений об ошибках.

Обычно, вы освобождаете все эти интерфейсы сразу же после окончания проигрывания медиа. Я понимаю, что это лишние затраты времени, если вы собираетесь использовать DirectShow далее в вашем приложении, но это самый лучший способ уничтожить все фильтры и сбросить DirectShow до начального состояния. Есть конечно способы обойти это, но мы модет коснемся этого лишь в последующих уроках. А пока будем перестраивать граф для каждого медиа-файла.

 

Простой медиа-проигрыватель

Ну а теперь нам осталось только свести все это вместе в простом проигрывателе. Вы можете скачать готовый проект маленького приложения, проигрывающего типичные файлы вроде WAV, MIDI, AVI и MPEG

dshowtut2.zip (26k)

Эта программа позволит вам выбрать медиа-файл для открытия, а затем постарается создать граф фильтров для него. В программе есть команды Проигрывания и Остановки, так же как и опция Цикла.

 

Состояния графа

Когда граф находится в состоянии действия, он так в нем и находится, даже если все данные уже прошли через граф. Когда посылается сообщение EC_COMPLETE, все данные прошли через все филтры и теперь они ожидают следующих данных. Если вы посмотрите на код, обрабатывающий события в нашем медиа-плеере, то вы увидите следующее:

switch (EventCode)
{
case EC_COMPLETE:
// вот здесь медиа перестает проигрываться
if (!g_Looping)
g_pMediaControl->Stop();
g_pMediaPosition->put_CurrentPosition(0); // сбросить до начала
break;
default:
break;
}

Заметьте, что нам не надо вызывать IMediaControl::Run() после установки позиции проигрывания обратно в начало. Так же нам надо явно указать IMediaControl::Stop(), когда мы получаем EC_COMPLETE. В прочем, это не обязательно, но позволяет нам лучше держать DirectShow под своим контролем.

 

Пусть GraphBuilder строит граф

В медиа-плеере мы позволили GraphBuilder контролировать построение графа, но хорошо бы знать этот процесс изнутри. Один полезный путь посмотреть как работает GraphBuilder, это вызвать метод IGraphBuilder::SetLogFile(). Это скажет GraphBuilder'у, что надо создать журнал шагов, которые он будет предпринимать для создания графа. Код для этого прост:

HANDLE logfile;
char logfilename[MAX_PATH];

// создать хэндл файла win32
sprintf(logfilename, "%s\\graph.log", g_ExecuteDir);
LogFile = CreateFile(logfilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);

g_pGraphBuilder->SetLogFile(logfile);

Есть правда одна проблема, которая заключается в том, что файл журнала создается в директории, в которой находится и проигрываемый файл (а если мы читаем с сидюка, это вызовет ошибку). Медиа-плеер использует функцию Win32 GetOpenFileName() для выбора файла, который как я думаю, меняет вдобавок директорию программы. Для обхода этого, я просто сохраняю директорию, в которой находится программа при инициализации и смотрю ее позже.

Если вы посмотрите на файл журнала, который создает GraphBuilder, вы увидите кучу деталей о создании фильтров, попытках подконнектить пины и так далее... Последовательность, в которой GraphBuilder строит граф такая:

- Создать исходный фильтр. Это самая простая часть. Есть фильтр "File Source", который создается для локальных файлов

- Попытаться присоединить выходной контакт исходного фильтра к какому-нибудь фильтру уже существующему в графе: GraphBuilder проходит по одному через все фильтры, которые уже есть в существующем графе, высматривая входной контакт и пытаясь подконнектить выход исходного фильтра к нему. Вы заметите, что в логе, созданном нашим плеером, никаких других фильтров кроме исходного нет, поэтому GraphBuilder пытается подключить исходный фильтр сам к себе, что естесственно не получится (у исходного фильтра даже нет входного контакта). Если соединение установлено, GraphBuilder повторяет этот шаг, используяуже выходной контакт свежеподсоединенного фильтра, в противном случае, он переходит к следующему шагу.

- Если соединение установлено не было, создается новый фильтр и GraphBuilder начинает пытать его. Как он решает какой фильтр создать я не имею представления. Возможно он основывается на типах медиа, но я не уверен. Если соединение установлено, то следует повторение прошлой ступени: попытка подсоединения очередного фильтра ко всем существующим.

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

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

 

Итого

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

И еще одна вещь, которую я забыл упомянуть. Для того, чтобы запустить программу из этой части, у вас должны быть установлены runtime-библиотеки DirectShow. Если вы имеете установленный SDK, то все уже стоит, но если нет, то вам надо скачать runtime с сайта Microsoft (если только другая программа: такая как Microsoft Media Player уже не установила их)

Chris Thompson (aka Lightman)
cdthompson@home.net

На русский язык статью перевел Anti

 


Проект
Создание Народного Учебника по OpenGL

Участвовать!
Поиск
Найдите статью или файл:


Рассылка
Новости сайта
La Vision в вашем почтовом ящике








Программирование на С++ Delphi и Паскаль
Центр демо-искусства в России