Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Работа с видео в системе 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 (ГОВОРИ).
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
