Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Программирование видео средствами Win32 API 191
KEY = false;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button3Click(TObject *Sender)
{
capFileSaveDIB(hWnd,"123.bmp");
}
//—————————————————————————————————————-
void __fastcall TForm1::Button4Click(TObject *Sender)
{
capPreview(hWnd,true);
capCaptureSequence(hWnd);
}
//—————————————————————————————————————-
void __fastcall TForm1::Button5Click(TObject *Sender)
{
Close();
}
//—————————————————————————————————————-
В качестве глобальных объектов объявляются дескриптор окна HWND
hWnd, логическая переменная BOOL KEY, инициализированная значением
ИСТИНА (true), и предопределенная структура CAPTUREPARMS cappar. Эта
структура содержит поля, определяющие различные свойства захвата изображения, главные из которых будут описаны ниже.
Основные операции подготовки к работе поместим в функцию создания
окна FormCreate. Прежде всего, вызывается макрос capCreateCaptureWindow,
назначение которого заключается в создании видеоокна. Параметрами здесь
являются имя окна, комбинация флагов, определяющих стиль окна (дочернее
по отношению к окну формы и видимое), координаты левого верхнего угла
видеоокна, его размеры, выраженные в пикселях, дескриптор родительского
окна и идентификатор окна. Макрос capDriverConnect подключает драйвер
устройства видеоввода к созданному окну. Последний параметр здесь определяет номер устройства. Если таковое имеется в единственном экземпляре, то
его номер есть 0. В следующей строке просто определяется имя файла, в котором будет записан видеоклип. Можно видеть, что файл имеет расширение
avi — стандартный файл видеоклипа без сжатия данных.
Далее производится инициализация полей структуры CAPTUREPARMS.
Остановимся только на некоторых значениях. Полную информацию об этих
параметрах можно получить в справочной системе SDK. Поле dwRequest-
MicroSecPerFrame задает интервал следования кадров, выраженный в микросекундах. Значение 66 667 соответствует частоте кадров 15 Герц. Если поле
fCaptureAudio имеет значение ИСТИНА (true), то в клип вместе с изображением будет записано и звуковое сопровождение. Параметры fAbortLeftMouse è
fAbortRightMouse, установленные в ИСТИНА (true), позволяют остановить запись левой или правой кнопкой мыши, что мы и использовали в нашем при
ложении. Если параметр fLimitEnabled имеет значение ИСТИНА (true), òî òåì
-

192 Глава 5. Программирование работы с видео
самым задается ограничение на длительность записываемого файла. Длительность, выраженная в секундах, задается полем wTimeLimit.
Äëÿ
того чтобы назначенные значения полей вступили в силу, необходимо вызвать макрос capCaptureSetSetup. Необходимо отметить, что заполнение
полей структуры вместе с макросом «ввода их в эксплуатацию» не является
обязательным. Если эти операции не делать, то актуальными будут значения
полей, установленные по умолчанию. В данной программе мы изменили некоторые параметры структуры относительно предопределенных.
В обработчике кнопки Button1 (START VIEW FRAMES), прежде всего, делаются доступными следующие две кнопки и, наоборот, закрывается возможность записи avi-файла. Эмпирически обнаружено, что в режиме отображения
последовательности кадров запись клипа невозможна. Здесь же запускается
цикл, который будет выполняться до тех пор, пока переменная KEY имеет значение ИСТИНА (true). В самом теле цикла вызывается макрос capGrabFrame,
который просто получает очередной кадр с камеры и отображает его в виде окне.
Также в цикле вызывается специальный метод Application->ProcessMessages().
Это — очень важный момент. Как это ни странно, система устроена так, что,
если не принять специальных мер, то цикл не может прерваться, если вы просто
где-то установите переменную KEY в ЛОЖЬ (false). Точнее, без специальных
мер цикл просто не позволит выполнить присвоение нужного значения этой переменной, например, с помощью активации некой кнопки. Специальная мера
заключается в том, чтобы заставить цикл читать системные сообщения, в частности, о том, что была нажата некая кнопка. Вот именно этим и занимается
функция ProcessMessages().
Кнопка Button2 прерывает захват и трансляцию кадров на экран. Понятно, что мы для этого в обработчике присваиваем переменной KEY значение
false. Кроме того, после прерывания захвата кадров мы разрешаем воспользоваться кнопкой Button4 для начала записи видеоклипа.
С кнопкой Button3 (SAVE FRAME) все понятно. Здесь работает единственный макрос capFileSaveDIB, который фиксирует кадр изображения в графическом файле, имя которого (у нас — 123.bmp) задается параметром макроса. Снова понятно, что статическое изображение записывается в простейшем
несжатом графическом формате bmp.
В обработчике кнопки захвата подвижного изображения (и звука, если
требуется) работает макрос предварительного просмотра картинки capPreview,
отображаемого все в том же графическом окне. Включение и выключение
предпросмотра устанавливается последним аргументом макроса true èëè false
соответственно. Сам процесс захвата и записи инициализируется макросом
capCaptureSequence.
Наконец, обработчик последней кнопки вызывает знакомую нам функцию Close(), которая завершает работу программы. В результате в рабочем каталоге проекта образуется файл клипа AVIfile.avi со звуковым сопровождением
или без такового и (или) графический файл 123. bmp. Можно без особых трудностей включить в программу и средства просмотра сформированных файлов,
однако сейчас мы этим заниматься не будем.

Сжатие видеоинформации 193
Сжатие видеоинформации
На очереди вопрос о компрессии визуальной информации. Сразу отметим, что арсенал Win32 API содержит все необходимые инструменты для сжатия видео и графических файлов, содержащиеся в подсистеме VCM (Video
Compression Manager). Однако использование этих средств достаточно сложно — вспомним, что и сжатие звука не было простой задачей программирования. В этой связи здесь мы ограничимся рассмотрением только частной задачи преобразования картинок из несжатого формата bpm в популярный формат
файлов изображений jpg. А эта конкретная задача с успехом решается средствами самого Builder'à.
Прежде всего, заметим, что преобразование bmp — jpg является преобразованием с изменения качества изображения. Пользователь может регулировать степень сжатия за счет изменения качества результирующего изображения по сравнению с исходным. Опыт показывает, что сжатие файла примерно
в 10 раз обеспечивает приемлемое сохранение качества. Видимо, это и объясняет высокую популярность формата jpg.
Программа преобразования, которую мы представляем, чрезвычайно проста и компактна. Листинг приложения показан ниже, а интерфейс программы — в следующим за ним рисунке.
При проектировании формы на нее следует разместить две кнопки и два
компонента Image со вкладки Additional палитры компонентов. Последние
служат для отображения содержимого графических файлов.
//—————————————————————————————————————#include <vcl.h>
#include <jpeg.hpp>
#pragma hdrstop
#include "compre.h"
//—————————————————————————————————————#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//—————————————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————————————————void __fastcall TForm1::Button1Click(TObject *Sender)
{
TJPEGImage *jpg;
jpg = new TJPEGImage();
Image1->Picture->LoadFromFile("111.bmp");
jpg->Assign(Image1->Picture->Graphic);
jpg->CompressionQuality=12;
jpg->Compress();
jpg->SaveToFile("222.jpg");

194 Глава 5. Программирование работы с видео
Image2->Picture->LoadFromFile("222.jpg");
}
//—————————————————————————————————————void __fastcall TForm1::Button2Click(TObject *Sender)
{
Close();
}
//—————————————————————————————————————-
Для работы с файлами формата jpg необходимо подключение заголовка
jpeg.hpp, что и сделано в начале текста. Обработчик нажатия первой кнопки
выполняет все действия по преобразованию. В первой строке TJPEGImage
*jpg; декларируется класс, необходимый для работы функций преобразования,
а во второй строке создается новый объект этого класса. Далее в рабочую область Image1 загружается заранее подготовленный файл изображения в формате bmp. Затем с помощью функции Assign èç Image1 забирается копия графики для обработки. Свойство jpg->CompressionQuality задает качество результирующего изображения, определяемое целым числом в диапазоне от 1 до
100. Само преобразование выполняется функцией Compress, а следующая инструкция программы сохраняет созданный сжатый файл. И последнее, что
здесь делается, это отображение результирующей картинки во второй объект
Image для возможности сравнения начальной и конечной картинки. Вторая
кнопка просто завершает работу приложения (рис. 5.16).
Ðèñ. 5.16
Выбранное нами качество преобразования весьма низкое, и это заметно
на глаз. При этом степень сжатия составляет примерно 1 к 20. Уже при значении свойства CompressionQuality, например, 32 качество сжатого изображения
становится очень высоким, в то время как степень сжатия остается также высокой — порядка 1 к 10. Что же касается скорости выполнения компрессии,
то здесь также все обстоит очень неплохо — пользователь не заметит задержки

Работа с видео в системе DirectX 195
на обработку. Последнее обстоятельство весьма важно для работы систем передачи мультимедиа реального времени.
Работа с видео в системе DirectX
В настоящее время разработка приложений, связанных с видео, на основе
Win 32 API считается несколько устаревшей. На смену ей приходит инструментарий системы DirectX. Ïî ñóòè äåëà DirectX является набором драйверов
для современных мультимедийных устройств. В новых операционных системах от Microsoft эти драйверы инсталлируются по умолчанию так, что специальная их установка нам не потребуется. Однако для реализации программирования наших задач на основе DirectX нужно иметь два заголовочных файла
dshow.h è qedit.h, которые можно взять из свободно распространяемых дистрибутивов. В частности, мы использовали дистрибутив DirectX 8. Кроме того,
нам потребуется обновленная версия библиотеки strmiids.lib äëÿ C++ Builder,
которую можно получить по адресу: http://slydiman.compass.com.ru/download/
lib/dshow_borland_lib.zip.
Заголовки и библиотеку следует разместить в соответствующие каталоги
Builder'а или непосредственно в рабочую папку создаваемого проекта.
Работа в DirectX основывается на использовании так называемой технологии СОМ, в которой создаются СОМ-объекты, являющиеся результатом дальнейшего развития концепции объектно-ориентированного программирования. Упомянуть об этом следует, так как в данном разделе нам придется оперировать с новой терминологией. Впрочем, вникать в суть технологии СОМ
на данном этапе вовсе нет необходимости. Тем не менее, следует указать, что
работа с объектами СОМ производится с помощью СОМ-интерфейсов, представляющих собой наборы функций управления объектами.
Система DirectX подразделяется на ряд подсистем в соответствии с областями задач, ими выполняемыми. Для работы с видеоинформацией (и в
определенной степени со звуком) используется подсистема DirectShow. Основным строительным кирпичиком здесь являются фильтры. Фильтры бывают трех типов: фильтры-источники (например, файл или видеопоток с цифровой камеры), преобразующие фильтры (это всевозможные кодеки, искажающие фильтры эффектов и т. п.), а также фильтры-рендеры (выводящие
видео/аудио на экран/колонки или пишущие в файл). Все фильтры соединяются в единую сеть, которая называется Граф Фильтров. Построение графа и
работа с ним возлагается на Менеджер Графа. В основе написания любого
DirectShow-приложения лежат три простых действия:
1. Создание Менеджера Графа с помощью функции CoCreateInstance.
2. Использование Менеджера Графа для построения графа фильтров.
3. Управление фильтрами и обработка сообщений от DirectShow.
Теперь, после этого краткого введения в DirectX, приступим к разработке
программы. Поскольку рассматриваемая здесь технология является вещью довольно серьезной, мы не сможем описывать ее детально. Конспективное изложение структуры программы может служить только стартовой площадкой

196 Глава 5. Программирование работы с видео
для более глубокого самостоятельного изучения. Тем не менее, программа
вполне функциональна и содержит все основные элементы для работы в
DirectX.
Поставим перед собой следующую задачу. Создаваемое приложение
должно транслировать в реальном времени на экран монитора подвижное
изображение, получаемое с подключенной web-камеры, и в нужный момент
времени фиксирует картинку в оперативной памяти и воспроизводит ее также
на экран. Интерфейс программы может выглядеть так, как это показано на
рис. 5.17.
Ðèñ. 5.17
После запуска программы необходимо выбрать источник видео в поле
выбора в левой верхней части окна. При нажатии стрелки выпадает список
выбора, который при подключении нескольких камер может состоять из нескольких строк. Подчеркнем, что необходимо именно сделать выбор из списка, а не просто оставить значение по умолчанию. В этот момент программа
начнет отображать подвижную картинку в правой части окна. При нажатии
же кнопки Button1 в левой части окна отобразится зафиксированный кадр.
При создании проекта на форму следует поместить поле выбора — визуальный компонент VDMBox, кнопку Button1 и компонент ScrollBox, в котором будет отображаться текущее видео.
Переходя к тексту программы, представим основной модуль Unit1.cpp:
#include <dshow.h>
#include <qedit.h>
#include <jpeg.hpp>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
HRESULT hr;
HRESULT DevEnum(TStrings *SL);
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin);

Работа с видео в системе DirectX 197
HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pFirst,
IBaseFilter *pSecond);
HRESULT GetCaptureFilter(int N,IBaseFilter* *ppCaptureFilter);
HRESULT BuildCaptureGraph();
AM_MEDIA_TYPE mt;
IBaseFilter *pCaptureFilter=NULL;
IBaseFilter *pGrabberF=NULL;
ISampleGrabber *pGrabber=NULL;
IGraphBuilder *pGraphBuilder=NULL;
ICreateDevEnum *pSysDevEnum = NULL;
IEnumMoniker *pEnumCat = NULL;
IVideoWindow *pVideoWindow = NULL;
IMediaControl *pMediaControl = NULL;
IMediaEvent *pMediaEvent = NULL;
Graphics::TBitmap *pBitmap = new Graphics::TBitmap();
TJPEGImage *jpg = new TJPEGImage();
//—————————————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//—————————————————————————————————————-
void __fastcall TForm1::FormCreate(TObject *Sender)
{
hr = CoInitialize(NULL);
if (FAILED(hr))
MessageBox(NULL,"Не могу инициализировать
СОМ-интерфейс","Ошибка",MB_OK);
hr=DevEnum(WDMBox->Items);
if(WDMBox->ItemIndex<0) WDMBox->ItemIndex=0;
}
//—————————————————————————————————————void __fastcall TForm1::WDMBoxChange(TObject *Sender)
{
hr=GetCaptureFilter(Form1->WDMBox->ItemIndex,&pCaptureFilter);
hr=BuildCaptureGraph();
}
//——————————————————————————————————————HRESULT BuildCaptureGraph()
{
//Создание графопострителя
hr = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC,
IID_IGraphBuilder, (void **) &pGraphBuilder);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать графопостроитель","Ошибка",MB_OK);
hr = CoCreateInstance (CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void**)&pGrabberF);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать фильтр сбора","Ошибка",MB_OK);
//Создание интерфейса для управления потоком, сбором и видеоокном
hr = pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber);

198 Глава 5. Программирование работы с видео
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать граббер для фильтра
сбора","Ошибка",MB_OK);
hr = pGraphBuilder->QueryInterface(IID_IMediaControl,(LPVOID *)
&pMediaControl);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать PMediaControl","Ошибка",MB_OK);
hr = pGraphBuilder->QueryInterface(IID_IVideoWindow, (LPVOID *)
&pVideoWindow);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать pVideoWindow","Ошибка",MB_OK);
hr = pGraphBuilder->QueryInterface(IID_IMediaEvent, (LPVOID *)
&pMediaEvent);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать PMediaEvent","Ошибка",MB_OK);
hr = pGraphBuilder->AddFilter(pCaptureFilter, L"Video Capture");
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать pCaptureFilter к графу ",
"Ошибка",MB_OK);
hr = pGraphBuilder->AddFilter(pGrabberF, L"Sample Grabber");
if (hr!=S_OK)
MessageBox(NULL,"Не могу добавить pGrabberF к графу ","Ошибка",MB_OK);
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype=MEDIATYPE_Video;
mt.subtype=MEDIASUBTYPE_RGB555;
/*The following subtypes define uncompressed RGB formats.
GUID Description
MEDIASUBTYPE_RGB1 RGB, 1 bit per pixel (bpp), palettized
MEDIASUBTYPE_RGB4 RGB, 4 bpp, palettized
MEDIASUBTYPE_RGB8 RGB, 8 bpp
MEDIASUBTYPE_RGB565 RGB 555, 16 bpp
MEDIASUBTYPE_RGB555 RGB 565, 16 bpp
MEDIASUBTYPE_RGB24 RGB, 24 bpp
MEDIASUBTYPE_RGB32 RGB, 32 bpp, no alpha channel
MEDIASUBTYPE_ARGB32 RGB, 32 bpp, alpha channel*/
hr = pGrabber->SetMediaType(&mt);
if (hr!=S_OK)
MessageBox(NULL,"Не могу установить медиа тип","Ошибка",MB_OK);
hr = ConnectFilters(pGraphBuilder,pCaptureFilter,pGrabberF);
if (hr!=S_OK)
MessageBox(NULL,"Не могу соединить фильтр сбора и граббер ",
"Ошибка",MB_OK);
IPin *pin;
GetPin(pGrabberF,PINDIR_OUTPUT,&pin);
hr = pGraphBuilder->Render(pin);
if (hr!=S_OK)
MessageBox(NULL,"Не могу создать рендер","Ошибка",MB_OK);
hr=pGrabber->SetOneShot(FALSE);
hr=pGrabber->SetBufferSamples(TRUE);
hr=pGraphBuilder->QueryInterface(IID_IVideoWindow,
void **)&pVideoWindow);

Работа с видео в системе DirectX 199
hr=pVideoWindow->put_Owner((int)Form1->ScrollBox1->Handle);
hr=pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
hr=pVideoWindow->SetWindowPosition(0,0,320,240);
hr=pMediaControl->Run();
return S_OK;
}
//——————————————————————————————————————HRESULT GetCaptureFilter(int N,IBaseFilter* *ppCaptureFilter)
{
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER,IID_ICreateDevEnum, (void **)&pSysDevEnum);
if(hr!=S_OK)
{
MessageBox(NULL,"Не могу создать список системных устройств",
"Ошибка",MB_OK);
return hr;
}
// Получение списка имен устройств видеозахвата
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
&pEnumCat, 0);
if (hr == S_OK)
{// Перечисление имен
IMoniker *pMoniker = NULL;
int Index=0;
while(hr = pEnumCat->Next(1,&pMoniker,NULL),hr == NO_ERROR)
{
if(Index==N)
{
hr = pMoniker->BindToObject(0,0,IID_IBaseFilter,
(void**)&pCaptureFilter);
if (hr!=S_OK)
{
pMoniker->Release(); MessageBox(NULL,"Ia OK","Ioeaea",MB_OK);
continue;
}
}
if(Index++==N) break;
}
//Теперь имеем фильтр BaseFilter_Capture, отвечающий нулевому
//драйверу
}
return S_OK;
}
//——————————————————————————————————————-
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)
//Ищет у фильтра нужный pin
{
IEnumPins *pEnum;
IPin *pPin;
pFilter->EnumPins(&pEnum);
while(pEnum->Next(1, &pPin, 0) == S_OK)

200 Глава 5. Программирование работы с видео
{
PIN_DIRECTION PinDirThis;
pPin->QueryDirection(&PinDirThis);
if (PinDir == PinDirThis)
{
pEnum->Release(); *ppPin = pPin;
return S_OK;
}
pPin->Release();
}
pEnum->Release();
return E_FAIL;
}
//——————————————————————————————————————HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pFirst,
IBaseFilter *pSecond)
//Соединяет два фильтра
{
IPin *pOut = NULL, *pIn = NULL;
HRESULT hr = GetPin(pFirst, PINDIR_OUTPUT, &pOut);
if (FAILED(hr))
return hr;
hr = GetPin(pSecond, PINDIR_INPUT, &pIn);
if (FAILED(hr))
{
pOut->Release();
return E_FAIL;
}
hr = pGraph->Connect(pOut, pIn);
pIn->Release();
pOut->Release();
return hr;
}
//———————————————————————————————————————
HRESULT DevEnum(TStrings *SL)
{
//Создание списка системных устройств
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void **)&pSysDevEnum);
if(hr!=S_OK)
{
MessageBox(NULL,"Не могу создать список системных устройств",
"Ошибка",MB_OK);
return E_FAIL;
}
//Получение списка устройств видеозахвата
IEnumMoniker *pEnumCat = NULL;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
&pEnumCat, 0);
if (hr == S_OK)
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
