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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Работа с видео в системе DirectX 201
{
// Перечисление имен IMoniker *pMoniker = NULL; ULONG cFetched; while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,(void **)
&pPropBag);
if (SUCCEEDED(hr))
{
// To retrieve the filter's friendly name, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr))
SL->Add(varName.bstrVal); VariantClear(&varName); pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
} pSysDevEnum->Release(); return S_OK;
}
//—————————————————————————————————————­HRESULT DDrawSurf() {
long cbBuffer = 0; hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL); if(hr!=S_OK)
MessageBox(NULL,"Не могу выполнить GetCurrentBuffer1","Ошибка",MB_OK); char *pBuffer = new char[cbBuffer]; if (!pBuffer)
MessageBox(NULL,"Out of memory","Ошибка",MB_OK); hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer); if(hr!=S_OK)
MessageBox(NULL,"Не могу выполнить GetCurrentBuffer2","Ошибка",MB_OK); AM_MEDIA_TYPE mt; hr = pGrabber->GetConnectedMediaType(&mt); if (FAILED(hr))
MessageBox(NULL,"Не могу определить аидео тип","Ошибка",MB_OK); // Examine the format block. VIDEOINFOHEADER *pVih; if ((mt.formattype == FORMAT_VideoInfo)&&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER))&&(mt.pbFormat != NULL) )
pVih =reinterpret_cast<VIDEOINFOHEADER*>(mt.pbFormat); // pVih->bmiHeader is the BITMAPINFOHEADER for the frame. else
202 Глава 5. Программирование работы с видео
MessageBox(NULL," Wrong format. Free the format block and return an
error.","Ioeaea",MB_OK);
// You can use the media type to access the BITMAPINFOHEADRE information. // For example, the following code draws the bitmap using GDI: pBitmap->Height=pVih->bmiHeader.biHeight; pBitmap->Width=pVih->bmiHeader.biWidth; HBITMAP hBmp=pBitmap->Handle; HDC hdc=Form1->Canvas->Handle; BITMAPINFO pBi; pBi.bmiHeader=pVih->bmiHeader; try {
SetDIBits(hdc,hBmp,0,pVih->bmiHeader.biHeight,pBuffer,&pBi,DIB_RGB_COLORS);
} catch (...)
{
} jpg->Assign(pBitmap); jpg->CompressionQuality=32; jpg->Compress(); return S_OK;
}
void __fastcall TForm1::Button1Click(TObject *Sender) {
hr = DDrawSurf(); jpg->Assign(pBitmap); jpg->CompressionQuality=32; jpg->Compress(); pBitmap->Assign(jpg); Canvas->Draw(0,24,pBitmap);
}
После препроцессорных директив в программе следует описание пере­менных и функций. HRESULT hr — переменная, в которую записывается со­общение об ошибке или успешности операции. HRESULT — стандартный тип значений, возвращаемый функциями DirectX. Возвращаемое значение со­держит либо константу DD_OK, если функция успешно выполнилась, либо код ошибки. Многие из используемых здесь функций имеют именно тип HRESULT.
При создании формы (функция CreateForm) происходит следующее. Функция CoInitialize инициализирует СОМ-интерфейс, в случае неудачи вы­скакивает сообщение об ошибке. Затем вызывается функция DevEnum c аргу­ментом ComboBox->Items. На этапе выполнения этой функции выполняется поиск всех устройств видеоввода.
Интерфейс ICreateDevEnum создает перечислитель для устройств в преде­лах специфической категории, например, устройств оцифровки видеоизобра­жений, аудиоустройств сбора данных, видеокомпрессоров и т. д. Далее следу­ет функция CoCreateInstance, которая будет не раз использоваться в дальней­шем, поэтому опишем ее более подробно здесь, чтобы не описывать ее далее. Она создает отдельный неинициализированный объект вида, связанного с
Работа с видео в системе DirectX 203
указанным CLSID (идентификатором класса), и возвращает любой требуемый указатель интерфейса. Вызовите CoCreateInstance, когда хотите создать от­дельный объект, основанный на CLSID.
Далее следует интерфейс IEnumMoniker, который используется для того, чтобы перечислить компоненты имени или имена в таблице èìåí. CreateClassEnumerator-метод создает перечислитель для указанной категории устройства. Здесь:
clsidDeviceClass определяет идентификатор класса (CLSID) категории уст­ройства. В нашем случае CLSID_VideoInputDeviceCategory — класс устройств видеоввода;
ppEnumMoniker является адресом переменной, которая получает указатель интерфейса EnumMoniker;
dwFlags — поразрядная комбинация нуля или большего количества фла­гов. Если нуль, метод перечисляет каждый фильтр в категории. Если установ­лены какие-либо флаги, перечисление включает только фильтры, которые со­ответствуют указанным флагам.
Дальше происходит перечисление имен и их запись. Интерфейс IMoniker используется для того, чтобы назвать СОМ-объекты. Объект, который осуще­ствляет IMoniker, называют «прозвищем», являющимся названием, которое уникально идентифицирует СОМ-объект. Используется IEnumMoniker::Next — метод для перечисления каждого прозвища. Этот метод возвращает указатель интерфейса IMoniker. Когда Next-метод достигает конца перечисления, воз­вращается S_FALSE, иначе S_OK.
IpropertyBag — определяет набор названных свойств. pMoniker-> BindToStorage отыскивает указатель интерфейса на память объекта, иденти-
фицированного прозвищем. pPropBag->Read — читает свойства прозвища. В нашем случае удобочитаемое имя. Записывается оно в WDMBox->Items.
Дальше идет функция WDMBoxChange, т. е. при изменении WDMBox вы­полняются следующие функции: GetCaptureFilter, BuildCaptureGraph. Рассмот­рим их содержание. При выполнении WDMBoxChange происходит создание фильтра, выполняющего захват, — pCaptureFilter. Создание фильтра происхо­дит так. При изменении WDMBox-а считывается номер (N) пункта меню, в котором находится нужное устройство ввода, выполняется то, что происходи­ло в функции DevEnum. При совпадении N с Index при перечислении списка устройств (где Index — номер устройства) вызывается pMoniker->BindToObjec. pMoniker->BindToObject связывает
прозвище, то есть возвращает указатель на объект, идентифицированный прозвищем. После этого мы имеем фильтр pCaptureFilter.
Следующая функция BuildCaptureGraph — самая главная. На этапе выпол­нения этой функции происходит создание всех остальных дополнительных фильтров и связывание их между собой. В начале создается граф фильтров pGraphBuilder, с помощью описанной выше функции CoReateInstance. Затем создаем граббер pGrabberF, т. е. фильтр, который будет захватывать кадры. Да­лее создаются интерфейсы для управления потоком, сбором и видеоокном: pGrabber, pMediaControl, pVideoWindow, pMediaEvent. Интерфейсы создаются с
204 Глава 5. Программирование работы с видео
помощью метода QueryInterface. Он определяет, поддерживает ли объект спе­цифический СОМ-интерфейс. Если да, то система увеличивает счет ссылки объекта, и приложение может использовать этот интерфейс немедленно. Те­перь следует добавление фильтров, созданных ранее к графу фильтров, — ме­тод AddFilter.
Дальше для соединения фильтров нужно указать тип медиаданных. Для этого существует структура AM_MEDIA_TYPE, в которой мы в качестве глав­ного типа указываем mt.majortype=MEDIATYPE_Video, т. е. видеоданные, а в качестве подтипа один из видов несжатого RGB-формата, например: mt.subtype=MEDIASUBTYPE_RGB555. В тексте программы в качестве ком­ментария указаны все возможные значения подтипов форматов. Выбор наи­более оптимального из них зависит от многих факторов — типа видеокарты, видеопамяти и прочего. При разработке и тестировании программы целесооб­разно поэкспериментировать с этим альтернативами.
Таким образом, ìû устанавливаем медиатип: pGrabber->SetMedia- Type(&mt). Медиатип мы указали, теперь нам нужно соединить фильтры в графе. Для этого используется функция ConnectFilters. Ее аргументы: Igraph- Builder *pGraph — указатель на граф фильтров, в котором надо соединять фильтры, IBaseFilter *pFirst, IBaseFilter *pSecond — соответственно первый и второй фильтры, которые нужно соединить.
На следующем этапе с помощью еще одной вспомогательной функции GetPin найдем так называемые штырьки (pin), по которым соединяются фильтры, по порядку: выходной у первого и входной у второго. Аргументами являются фильтр, направление, указатель на пин. В фильтрах данные переда­ются через пины. Пины бывают входными (через них данные попадают в фильтр) и выходными (через них данные передаются в следующие фильтры).
IenumPins — перечисляет пины на фильтре. Происходит это таким обра­зом: в цикле перечисляются все пины в фильтре, и когда направление переби­раемого пина совпадает с заданным, возвращается указатель на пин. Пины найдены, теперь все очень просто. С помощью метода Connect соединяем фильтры.
Фильтры соединены. Теперь создаем обработчик (рендер) для того, чтобы показывать поток видео в окне (если это не нужно, создаем нуль-рендер). Для присоединения рендера найдем выходной пин у последнего фильтра в графе (в нашем случае pGrabberF) с помощью все той же функции GetPin и придела­ем рендер: pGraphBuilder->Render(pin). У рендера искать пин не надо, он у него один — входной.
Теперь зададим параметры грабберу: pGrabber->SetOneShot(FALSE), pGrabber->SetBufferSamples(TRUE). Первое означает, что граббер не будет ос­танавливаться после первого кадра, второй означает то, что кадры будут запи­сываться в буфер. После этого создается окно для вывода потока видео.
Глава 6
IP-ТЕЛЕФОНИЯ СВОИМИ РУКАМИ
Очень краткое вступление
Вот мы и добрались до заключительной главы книги! Ради этого события стоит сделать небольшое вступление. Все, что мы рассказывали ранее, не­смотря на большой самостоятельный интерес, было подготовкой к завершаю­щему аккорду — применению приобретенных навыков для создания системы передачи мультимедиа реального времени.
Начнем с того, что в системе междугородной и международной телефонной связи в последнее десятилетие произошли коренные, принципиальные измене­ния. Если раньше, звоня, например, из Москвы в Иркутск, вы использовали обычные аналоговые телефонные каналы связи, то теперь ваш голос передается абоненту, как правило, в цифровом виде. То, что аудиоинформация может быть преобразована в цифровое представление, мы теперь прекрасно знаем и, более того, умеем это делать своими силами. То, что цифровые пакеты данных могут быть переданы по локальным и глобальным сетям, в частности, с исполь­зованием протокола IP, мы также знаем уже не понаслышке. Что же получится при объединении оцифровки звука с транспортировкой информации IP-паке­тами? Получится как раз то, что вынесено в название этой главы — IP-телефо­ния (интернет-телефония, web-телефония). Проницательный читатель совер­шенно справедливо задаст в этот момент вопрос: «А как с передачей по IP-се­тям видеоинформации?» Ответ утвердительный. В понятие IP-телефонии входит и передача «живого» видео. В этой связи автор предложил бы называть тему данной главы МУЛЬТИМЕДИА РЕАЛЬНОГО ВРЕМЕНИ. Итак, мы на­чинаем разговор о том, что писатели-фантасты прошлого века называли в сво­их романах видеотелефоном. К счастью, мы теперь можем констатировать тот факт, что благодаря высоким технологиям в сфере телекоммуникаций видеоте­лефон из категории фантастики превратился в реальность.
Транспортировка цифровой информации в локальных и глобальных се­тях — дело в настоящее время вполне обыденное и широко используется как минимум два десятилетия. Для рядового пользователя компьютера, подклю­ченного к Интернету, хорошо знакомы такие сервисы, как передача файлов и html-документов (страниц гипертекста). Интенсивно используются такие ин­струменты общения, как ЧАТы и Интернет-пейджеры (ICQ, ODIGO и дру­гие). Эти инструменты уже можно назвать коммуникациями реального време ни, в отличие, например, от электронной почты. Отметим, что программной
-
206 Глава 6. IP-телефония своими руками
основой передачи данных здесь является протокол IP. Однако придача в ре­альном времени изображения и, в наибольшей степени, звука имеет принци­пиальные особенности.
Мы уже упоминали ранее, что звук (речь, музыка) не допускает длитель­ных задержек при своей передаче. Задержки в передаче подвижного изобра­жения превращают видеоклип в набор отдельных кадров, что явно нежела­тельно. В этой связи при передаче мультимедиа реального времени высокие требования предъявляются к скорости передачи именно в каналах связи. В коммерческих системах IP-телефонии используются специализированные выделенные высокоскоростные каналы. При этом связь становится довольно дорогостоящей, но проблемы качества связи в основном оказываются решен­ными. Существует большое количество коммуникационных компаний, пре­доставляющих услуги голосового общения с использованием IP-сетей. Реали­зованы различные варианты такой связи: КОМПЬЮТЕР — КОМПЬЮТЕР (оба, разумеется, мультимедиа-оснащенные), КОМПЬЮТЕР — ОБЫЧНЫЙ ТЕЛЕФОН (к которому при вызове подключается провайдер) и ТЕЛЕФОН — ТЕЛЕФОН. В последнем варианте — наиболее интересном, телефонные аппа­раты через специальные процессоры подключаются прямо на месте. Что каса­ется передачи живого видео, то так называемые видеоконференции становят­ся все более популярными, но пока еще не вполне доступны рядовому поль­зователю.
Обычный Интернет, конечно, тоже может являться средой передачи муль­тимедийной информации в реальном времени. Здесь, однако, скорость транс­портировки информации, мягко говоря, оставляет желать лучшего. По этой причине качество IP-телефонии по интернетовским каналам следует оцени­вать как находящееся в диапазоне от «более или менее» до «скорее менее, чем более». С другой стороны, имеется ряд автономно работающих программ, обеспечивающих мультимедиа реального времени практически бесплатно — пользователем оплачивается только обычный сетевой трафик. Имеются также клиент-серверные системы бесплатной IP-телефонии в Интернете. Имеется в виду то, что клиентские программы на компьютерах пользователей подключа­ются к серверам-коммутаторам аналогично Интернет-пейджеру ICQ. При этом появляется возможность выбора конкретного абонента или группы або­нентов по каким-либо признакам.
Для нас в этой связи важным является то, что, как мы можем почувство­вать после освоения предыдущих глав, задачи создания собственных систем IP-телефонии нам уже «по плечу». Вот этим мы теперь и займемся. Разумеет­ся, мы не собираемся на данном этапе (а далее — как знать...) конкурировать с профессиональными продуктами. Мы не делаем «настоящий» IP-телефон. Наша цель значительно скромнее — научиться разбираться с программирова­нием передачи звука и изображения по сети в той степени, которая позволит читателю совершенствовать себя в этом интересном деле уже самостоятельно. В конце концов, мы сделаем систему, которую, как надеется автор, все же бу­дет не стыдно показать «на любительском уровне». Но подходить к этому мы будем постепенно в соответствии с учебной направленностью всей этой кни­ги. Итак, за дело.
Передача аудио в реальном времени 207
Передача аудио в реальном времени
Разработаем приложение, которое может быть использовано на обоих концах соединения, то есть одновременно выполнять роль и клиента и серве­ра телефонии. Наша программа будет работать в полудуплексном режиме. Это означает, что в любой момент один из корреспондентов говорит, держа нажа­той соответствующую кнопку в окне программы, а другой слушает. Затем пользователи могут поменяться ролями. Рабочее окно программы представле­но на рисунке.
Для установления голосовой связи на двух компьютерах должна быть за­пущена программа. Один из корреспондентов вводит IP-адрес второго и но­мер порта, который используется приложением. Второй делает то же самое. Нажатие кнопки ÎÊ производит установку введенных значений в программу. В результате становится доступной кнопка ГОВОРИ, которую нужно прижать мышкой, когда передается звук. Освободив кнопку, вы тем самым переходите в режим приема речи. Кнопка EXIT закрывает приложение (рис. 6.1).
Ðèñ. 6.1
Разработка проекта начинается с размещения на форме представленных на рисунке элементов и визуального компонента NMUDP со вкладки FastNet палитры компонентов. Напомним, что при передаче аудиоинформации ис­пользуется протокол негарантированной доставки данных UDP, а не стандарт­ный для коммуникаций TCP. На этапе проектирования целесообразно устано­вить свойство Host компонента NMUDP равным 127.0.0.0. Тем самым мы смо­жем сразу опробовать работу программы на одной машине, устанавливая связь сами с собой. Выбор порта достаточно произволен. Необходимо только про­контролировать, чтобы выбранный порт не был уже задействован другой про­граммой. Кроме того, следует отключить локальный firewall, который в опера­ционных системах Windows XP и Windows 2000 задействован по умолчанию. Отключение можно выполнить в настройках свойств сетевого соединения.
Теперь обратимся к листингу программы.
208 Глава 6. IP-телефония своими руками
//—————————————————————————————————————­#include <windows.h> #include <windowsx.h> #include <mmsystem.h> #include <vcl.h> #include "Unit1.h" #pragma resource "*.dfm"
TForm1 *Form1; typedef struct tagWAVEIOCB {
DWORD dwDataSize; PCMWAVEFORMAT FAR *lpFmt; LPWAVEHDR lpWaveHdr; HPSTR lpData;
WORD wBitsPerSample; } WAVEIOCB,FAR *LPWAVEIOCB; int diskret = 8000, //Частота дискретизации
razrad=8, //Разрядность оцифровки
kanal=1; //Количество каналов long buf = 2048; //Размер буфера HWAVEIN hWaveIn; //Указатель устройства ввода WAVEIOCB waveiocb,waveiocb1; //Структуры блоков буферов данных HWAVEOUT hWaveOut; //Указатель устройства вывода BOOL KEY = false; //————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
} //—————————————————————————————————————­void __fastcall TForm1::Button1Click(TObject *Sender) {
KEY = false;
}
//—————————————————————————————————————­void __fastcall TForm1::Button4Click(TObject *Sender) {
waveInStop(hWaveIn); waveInClose(hWaveIn); waveOutClose(&hWaveOut); GlobalFreePtr(waveiocb.lpWaveHdr); GlobalFreePtr(waveiocb.lpData); GlobalFreePtr(waveiocb1.lpWaveHdr); GlobalFreePtr(waveiocb1.lpData); waveInUnprepareHeader(hWaveIn, (&waveiocb)->lpWaveHdr,sizeof(WAVEHDR)); waveOutUnprepareHeader(hWaveOut,(&waveiocb1)->lpWaveHdr,sizeof(WAVEHDR)); Close();
}
Передача аудио в реальном времени 209
//—————————————————————————————————————­void __fastcall TForm1::NMUDP1DataReceived(TComponent *Sender,
int NumberBytes, AnsiString FromIP, int Port)
{
NMUDP1->ReadBuffer((&waveiocb1)->lpWaveHdr->lpData,buf,buf); waveOutWrite(hWaveOut,(&waveiocb1)->lpWaveHdr,sizeof(WAVEHDR));
waveOutReset(&hWaveOut); } //—————————————————————————————————————­void __fastcall TForm1::Button3Click(TObject *Sender) {
NMUDP1->LocalPort = StrToInt(Edit2->Text); NMUDP1->RemotePort = StrToInt(Edit2->Text); NMUDP1->RemoteHost = Edit1->Text;
Button1->Enabled = true; } //—————————————————————————————————————­void __fastcall TForm1::FormCreate(TObject *Sender) {
waveiocb.dwDataSize = buf; waveiocb.lpWaveHdr = (LPWAVEHDR)GlobalAllocPtr (GMEM_MOVEABLE | GMEM_SHARE,sizeof(WAVEHDR)); waveiocb.lpData =(HPSTR)GlobalAllocPtr (GMEM_MOVEABLE | GMEM_SHARE,waveiocb.dwDataSize); waveiocb.lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR,
sizeof(WAVEFORMAT));
(&waveiocb)->lpFmt->wf.wFormatTag = WAVE_FORMAT_PCM; int nC = (&waveiocb)->lpFmt->wf.nChannels = kanal; int BPS = (&waveiocb)->lpFmt->wBitsPerSample = razrad; int nSPS = (&waveiocb)->lpFmt->wf.nSamplesPerSec = diskret; int nBA = (&waveiocb)->lpFmt->wf.nBlockAlign = nC*BPS/8; (&waveiocb)->lpFmt->wf.nAvgBytesPerSec = nSPS*nBA; (&waveiocb)->lpWaveHdr->lpData = (LPSTR)(&waveiocb)->lpData; (&waveiocb)->lpWaveHdr->dwBufferLength = (&waveiocb)->dwDataSize; (&waveiocb)->lpWaveHdr->dwFlags = 0L; (&waveiocb)->lpWaveHdr->dwLoops = 0L; (&waveiocb)->lpWaveHdr->dwUser = 0L; waveiocb1.dwDataSize = buf; waveiocb1.lpWaveHdr = (LPWAVEHDR)GlobalAllocPtr (GMEM_MOVEABLE | GMEM_SHARE,sizeof(WAVEHDR)); waveiocb1.lpData =(HPSTR)GlobalAllocPtr (GMEM_MOVEABLE | GMEM_SHARE,waveiocb1.dwDataSize); waveiocb1.lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR,
sizeof(WAVEFORMAT));
(&waveiocb1)->lpFmt->wf.wFormatTag = WAVE_FORMAT_PCM;
nC = (&waveiocb1)->lpFmt->wf.nChannels = kanal;
BPS = (&waveiocb1)->lpFmt->wBitsPerSample = razrad;
nSPS = (&waveiocb1)->lpFmt->wf.nSamplesPerSec = diskret;
nBA = (&waveiocb1)->lpFmt->wf.nBlockAlign = nC*BPS/8; (&waveiocb1)->lpFmt->wf.nAvgBytesPerSec = nSPS*nBA; (&waveiocb1)->lpWaveHdr->lpData = (LPSTR)(&waveiocb1)->lpData; (&waveiocb1)->lpWaveHdr->dwBufferLength = (&waveiocb1)->dwDataSize; (&waveiocb1)->lpWaveHdr->dwFlags = 0L;
210 Глава 6. IP-телефония своими руками
(&waveiocb1)->lpWaveHdr->dwLoops = 0L; (&waveiocb1)->lpWaveHdr->dwUser = 0L; waveInOpen(&hWaveIn, WAVE_MAPPER,
(WAVEFORMATEX FAR *)(&waveiocb)->lpFmt,0L, 0L, 0L); waveInPrepareHeader(hWaveIn, (&waveiocb)->lpWaveHdr,
sizeof(WAVEHDR)); waveOutOpen(&hWaveOut, WAVE_MAPPER,(WAVEFORMATEX FAR *)
(&waveiocb1)->lpFmt,0L,0L,0L); waveOutPrepareHeader(hWaveOut,(&waveiocb1)->lpWaveHdr,sizeof(WAVEHDR));
} //—————————————————————————————————————­void __fastcall TForm1::Button1MouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
KEY = true; while(KEY){ waveInAddBuffer(hWaveIn, (&waveiocb)->lpWaveHdr, sizeof(WAVEHDR)); waveInStart(hWaveIn); while(!(1&waveiocb.lpWaveHdr->dwFlags)); waveiocb.lpWaveHdr->dwBufferLength = waveiocb.lpWaveHdr->dwBytesRecorded; NMUDP1->SendBuffer((&waveiocb)->lpWaveHdr->lpData,buf,buf); waveInReset(hWaveIn); Application->ProcessMessages();
}
} //—————————————————————————————————————-
Все «кирпичики», из которых построена программа, нам уже знакомы. Остается расположить эти «кирпичики» в нужном порядке и соединить их нужными связями. В части объявления глобальных объектов необходимо об­ратить внимание на то, что размер буфера для хранения данных задан равным 2048 байтам. Мы уже упоминали о том, что это — максимально допустимый объем, передаваемый протоколом UDP в одной посылке, заданный Builder'ом. Объявлены два указателя на устройства ввода и вывода. На самом деле одно физическое устройство (звуковая карта) фигурирует здесь в двух ипостасях. Для формирования блоков записи и воспроизведения звука описаны две пре­допределенные структуры.
Функционирование программы следует начать с функции создания фор­мы FormCreate, выполняемой при старте приложения. Здесь, как было описа­но ранее, подготавливаются блоки для входной и выходной информации. Подготовка сводится к инициализации полей соответствующих структур. По­следние четыре оператора в этой функции открывают устройства ввода и вы­вода и подготавливают блоки к использованию.
Нажатие кнопки Button3 (ÎÊ) задает значения свойствам LocalPort, RemotePort è RemoteHost. Поскольку мы можем задать порт одинаковым для обеих участвующих в разговоре программ, первые два свойства считываются из поля редактирования на форме окна. Также считывается из соответствующего поля редактирования и IP-адрес удаленного компьютера. Последнее действие в этой функции — разрешение на использование кнопки Button1 (ГОВОРИ).