Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Программирование видео средствами 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)