Direct Show.
Опустим исторические введения и перейдем к делу. Direct Show это замечательный и простой, я не побоюсь этого слова, интерфейс для программирования потокового ( и не только ) видео для Windows. Основан он полностью на технологии COM. Тем, кто с ней знаком достаточно глубоко, будет очень просто изучить DirectShow, я бы даже сказал, что в этом случае все изучение сводится к прочтению мануала по интерфейсам. Тем, кто ничего о ней не знает, придется разбираться по ходу, что я представляю себе не самым простым занятием и рекомендую все же обратиться к соответствующей литературе, коей не мало на просторах сети и уделить немного времени на чтение, за то потом, все станет легко и понятно.
К делу. Есть у COM один недостаток, который мне очень не нравится, это громоздкость выделения и освобождения ресурсов. Но тут на помощь приходит методика «выделение ресурса есть инициализация», которую очень хорошо описал в своей книге Бьерн Страуструп, а воплощена она будет в виде умных указателей на интерфейсы COM. А вот и их реализация, не стоит обращать внимание на объем кода, там много рутинных операций, реализация которых тривиальна, но их инкапсуляция в этом классе существенно облегчит нам жизнь.
И так, в этой статье я приведу немного кода, а теоретический груз, поясняющий нижеследующие пертурбации перенесу в следующие статьи. Исходный код я приведу прям тут:
#ifndef IFACEPTR_H
#define IFACEPTR_H
#ifndef QT_NO_DEBIG
#include <QDebug>
#endif
/**
* @file ifaceptr.h
* @class CIFacePtr
*
* Реализует класс smart указателя на интерфейс COM.Инкапсулирует в себе
* рутинные операции подсчета ссылок,освобождения интерфейсов и т.д.
*
* @param T - интерфейс на который указывает класс
* @param IID - указатель на идентификатор интерфейса
*/
template <class T, const IID* piid = 0> class CIFacePtr
{
public:
/**
* Консртуктор по умолчанию
*/
CIFacePtr()
:m_pointer(0)
{
}
/**
* Конструктор копирования
* Создает объект из указателя на T
* @param ptr - указатель на T
*/
CIFacePtr(T* ptr)
:m_pointer(ptr)
{
if (m_pointer)
m_pointer->AddRef();
}
/**
* Конструктор копирования
* Создает объект из указателя на IUnknown
* @param iu - указатель на IUnknown
*/
CIFacePtr(IUnknown* iu)
:m_pointer(iu)
{
if (iu) {
#ifdef QT_NO_DEBUG
iu->
QueryInterface(*piid,reinterpret_cast<void**>(&m_pointer));
#else
HRESULT hr = iu->
QueryInterface(*piid,reinterpret_cast<void**>(&m_pointer));
if (FAILED(hr))
qDebug()<<"CIFacePtr(IUnknown* iu): failed";
#endif
}
}
/**
* @return указатель пуст?
*/
bool isNull() const
{
return m_pointer == 0 ? true : false;
}
/**
* Освобождает интерфейс
*/
void release()
{
if (m_pointer) {
m_pointer->Release();
m_pointer = 0;
}
}
/**
* Деструктор освобождает интерфейс
*/
~CIFacePtr()
{
release();
}
/**
* Оператор взятия адреса,позволяет использовать стандартную
* семантику.
* @return &указатель
*/
T** operator&()
{
return &m_pointer;
}
/**
* Операция стрелка
* @return T*
*/
T* operator->()
{
Q_ASSERT(m_pointer != 0);
return m_pointer;
}
/**
* Оператор преобразования к bool.
* @return true если указатель не нулевой,false иначе
*/
operator bool() const
{
return (m_pointer != 0) ? true : false;
}
/**
* Оператор логического не.
* @return true если указатель нулевой, false иначе
*
* Позволяет использовать семантику на подобие:
* CIFacePtr<IGraphBuilder> psGraph;
* //......
* if (!psGraph) //...
*/
bool operator!()
{
return (m_pointer == 0) ? true : false;
}
/**
* @return идентификатор иньтерфейса
*/
const IID& iid()
{
Q_ASSERT(piid != 0);
return *piid;
}
/**
* Оператор присваивания.
* @param ptr - указатель на T.
*
* Присваиваие нулевого указателя
* (в прочем, как и любое изменение значения)
* освобождает инкапсулируемый интерфейс
*/
T* operator=(T* ptr)
{
IUnknown* temp = m_pointer;
m_pointer = ptr;
if (m_pointer)
m_pointer->AddRef();
if (temp)
temp->Release();
return m_pointer;
}
/**
* Оператор присваивания.
* @param ptr - указатель на IUnknown
*
* За исключением типов аргументов аналогичен
* оператору выше.
*/
T* operator=(IUnknown* ptr)
{
IUnknown* temp = m_pointer;
m_pointer = ptr;
if (m_pointer) {
#ifdef QT_NO_DEBUG
m_pointer->
QueryInterface(*piid,reinterpret_cast<void**>(&m_pointer));
#else
HRESULT hr = QueryInterface(*piid,reinterpret_cast<void**>(&m_pointer));
if (FAILED(hr))
qDebug()<<"T* operator=(IUnknown* ptr)"
#endif
}
if (temp)
temp->Release();
return m_pointer;
}
/**
* Оператор сравнения.
* @param ptr - указатель на T
* @return true если указатель на инкапсулируемый интерфейс
* равен ptr
*/
bool operator == (T* ptr) const
{
return m_pointer == ptr;
}
/**
* Оператор сравнения.
* @param other - другой экземпляр CIFacePtr
* @return true если классы равны, false иначе
*/
bool operator == (CIFacePtr<T>& other) const
{
return m_pointer == other.m_pointer;
}
/**
* Проверка на нерванество.
* @param ptr - указатель на T.
* @return true если ptr не равен указателю
* на инкапсулируемый интерфейс
*/
bool operator != (T* ptr) const
{
return m_pointer != ptr;
}
/**
* Проверка на нерванество.
* @param other - другой экземпляр CIFacePtr
* @return true если классы не равны, false иначе
*/
bool operator != (CIFacePtr<T>& other) const
{
return m_pointer != other.m_pointer;
}
/**
* @return указатель на инкапсулируемый интерфейс
*/
T* get()
{
Q_ASSERT(m_pointer != 0);
return m_pointer;
}
/**
* Обертка для глобальной CoCreateInstance призвана
* избавить от жаншлирования аргументами
*
* @param clsid - идентификатор классаs
* @param i - указатель на агрегирующий интерфейс
* @param clsctx - контекст
* @return признак усешности
*/
HRESULT createInstance(const CLSID& clsid,IUnknown* i,DWORD clsctx)
{
release();
return
CoCreateInstance(clsid,i,clsctx,*piid,
reinterpret_cast<void**>(&m_pointer));
}
/**
* @return инкапсулируемый интерфейс в виде void**
*/
void** toVoidVoid()
{
return reinterpret_cast<void**>(&m_pointer);
}
private:
/**
* @var m_pointer - внутренний указатель на интерфейс
*/
T* m_pointer;
};
#endif /* !IFACEPTR_H */
Копируем код прямо отсюда, и вставляем в файл ifaceptr.h. Думаю по комментариям можно разобраться, что к чему.
Макрос QT_NO_DEBUG используется что бы контролировать отладку при помощи конфигураций сборки в IDE или в pro файле.
Что касается GCC, он не поддерживает программирование Direct Show ( по этой же причине в нем не компилится phonon на windows). Эту реализацию нельзя использовать для IUnknown, но когда это понадобится, мы решим проблему при помощи специализации.
Протестируем наш код не отходя от кассы, подробности и описания интерфейсов я приведу в следующей статье, а сейчас я приведу ( тривиальный ) код воспроизведения AVI файла.
#include <QtGui/QApplication>
#include "ds.h"
#include <QDebug>
#include <DShow.h>
#include "ifaceptr.h"
#include <cstdlib>
#pragma comment( lib, "strmiids" )
using std::exit;
void die(const char* message = 0)
{
if (message)
qDebug()<<message;
::CoUninitialize();
exit(1);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ds w;
HRESULT hr = ::CoInitializeEx(0,COINIT_APARTMENTTHREADED);
w.show();
if (SUCCEEDED(hr)) {
CIFacePtr<IGraphBuilder,&IID_IGraphBuilder> psGraph;
CIFacePtr<IMediaControl,&IID_IMediaControl> psControl;
CIFacePtr<IMediaEvent,&IID_IMediaEvent> psEvent;
if (FAILED(
psGraph.createInstance
(CLSID_FilterGraph,0,CLSCTX_INPROC_SERVER)))
die("Can't create graph builder");
if (FAILED(
psGraph->
QueryInterface(IID_IMediaControl,psControl.toVoidVoid())))
die("Can't create media control");
if (FAILED
(psGraph->QueryInterface(IID_IMediaEvent,psEvent.toVoidVoid())))
die("Can't create media event");
if (FAILED
(psGraph->RenderFile
//Это путь к файлу, разумеется при компиляции вам нужно указать
//сдесь свой путь
(L"E:/кино/Rekviem po Mechte. Rus. Dvd-Rip. Xvid. by. Friends- Forum.com.avi",0)))
die("Render file failed");
if (SUCCEEDED(psControl->Run())) {
long evCode;
psEvent->WaitForCompletion(INFINITE,&evCode);
}
psControl->Stop();
}
::CoUninitialize();
return a.exec();
}
Хочу лишь добавить, что стиль изложения не всегда будет таким и что следующая пара тройка статей, наверняка такого же объема будут содержать всего несколько строк кода, все остальное теория. Я надеюсь, что народ успеет разобраться с указателями, мы будем широко их применять при программировании DirectShow.