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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Передача аудио в реальном времени 211
Событие, связанное с кнопкой Button1, обрабатываемое программой, за­ключается в прижатии кнопки мышью — Button1MouseDown. Здесь в цикле, выполняемом до тех пор, пока логическая переменная KEY имеет значение ИСТИНА (true), делается следующее:
1. Входной буфер отдается в распоряжение звуковой карте как устройству
ввода.
2. Функция waveInStart(hWaveIn) начинает запись сигнала со звуковой
карты в буфер.
3. Запускается цикл while(!(1&waveiocb.lpWaveHdr->dwFlags));, продолжаю­щийся до тех пор, пока аргумент имеет значение ИСТИНА (true). Напомним, что конструкция с участием свойства dwFlags изменит свое значение при за­полнении блока данными. Таким образом, цикл «крутится впустую» до запол­нения блока.
4. Запоминается количество записанных байтов.
5. Заполненный буфер отсылается удаленному компьютеру по протоколу UDP.
6. Переустанавливается для начала новой записи устройство ввода.
Функция Application->ProcessMessages(); позволяет анализировать систем­ные сообщения, в частности, для выхода из внешнего цикла по условию «KEY приняло значение ЛОЖЬ (false)». Что это значит, мы скоро узнаем. Итак, пока мы держим прижатой кнопку ГОВОРИ, циклически происходит запись звука в буфер и отправка его по сети. Важно отметить, что пересылаются именно звуковые данные — отсчеты оцифрованного сигнала.
Обработчик события Button1Click (в нашем случае это любое событие с кнопкой 1) делает простую вещь: присваивает переменной KEY значение ЛОЖЬ (false). Очередное событие с нажатой кнопкой заключается в ее отжи­ме. Таким образом, когда кнопка отпущена, прерывается цикл записи и пере­дачи звука на хост — мы выходим из режима передачи.
Что же происходит с отосланными данными? Посмотрим на функ­цию-обработчик NMUDP1DataReceived. Обработчик начинает действовать, когда на компьютер приходит порция данных, переданная по протоколу UDP. При этом происходит:
1. Чтение в приемный буфер пришедших данных.
2. Передача буфера выходному устройству, на воспроизведение звуковой
карте.
3. Переустановка выходного устройства для воспроизведения очередной
порции звука.
И здесь подметим, что передаются сами звуковые данные. Перед воспро­изведением они помещаются в заранее подготовленный блок, снабженный за­головком и всем прочим.
Последняя кнопка Button4 все очищает, все освобождает и все закрывает, включая и саму программу.
Мы говорили, что программа работает в полудуплексном режиме. На са­мом деле это не совсем так. Если звуковая карта вашего и удаленного компь­ютера полнодуплексные, одновременная запись и воспроизведение вполне возможны. Так, если вы работаете с программой на одной машине и передае
-
212 Глава 6. IP-телефония своими руками
те звук сами себе, то, нажав кнопку ГОВОРИ, вы будете сразу слышать свой голос. Ну а когда вы говорите с другим собеседником, наверное, разумно не перебивать друг друга и соответственно держать кнопку ГОВОРИ поочередно нажатой и отжатой.
Не звуком единым...
Представляем пару программ, обеспечивающих передачу «живого» изо­бражения от одного узла к другому. Клиентским будем называть приложение, которое транслирует картинку на удаленный компьютер, а сервером — про­грамму, принимающую изображение, хотя в данном случае названия можно было бы и поменять местами без изменения сути. Рабочие окна приложений показаны на рис. 6.2.
Ðèñ. 6.2
На одном из компьютеров запускается клиент, который ждет подключе­ния второго корреспондента. Как только подключение состоялось, клиент на­чинает передавать изображение, автоматически дублируя его у себя. Сервер выполняет соединение по указанному IP-адресу. Как обычно, мы можем тес­тировать программы на одном и том же компьютере, указав свой локальный адрес 127.0.0.1, как это показано на рисунке.
В представленном варианте коммуникационный процесс происходит сле­дующим образом. Сразу после установления соединения клиент посылает серверу отдельный кадр, который представляет собой копию графического файла в формате jpg. Информация пересылается между узлами по протоколу TCP. Поскольку этот протокол гарантирует доставку, возможно, с весьма за­метной задержкой, после отсылки кадра клиент ожидает подтверждения полу чения картинки от серверной стороны. Получив кадр, сервер отсылает под
-
-
Не звуком единым... 213
тверждение, и клиент отправляет следующий кадр. Если скорость передачи информации достаточно высокая (как, например, в локальной сети), то воз­никает иллюзия непрерывно меняющегося изображения. При передаче дан­ных через Интернет смена кадров производится нерегулярно и довольно ред­ко, в силу чего слова «живое видео» мы поместили в кавычки.
Запись изображения, полученного с web-камеры, производится в несжа­том формате bmp. Типичный объем данных для запоминания одного кадра в этом случае составляет сотни килобайт. Передавать такие объемы по сети, а тем более через Интернет слишком накладно. В этой связи в программе предусмотрено сжатие изображения путем преобразования в формат jpg. При этом без существенной потери качества картинки размер блоков передавае­мых данных может быть уменьшен примерно на порядок. Что же касается са­мой транспортировки информации, то здесь мы применили рассмотренные ранее средства передачи потоков и текстовых сообщений.
//—————————————————————————————————————­#include <fcntl.h> #include <stdio.h> #include <io.h> #include <vcl.h> #pragma hdrstop #include <vfw.h> #include <jpeg.hpp> #include "Unit1.h" #pragma package(smart_init) #pragma resource "*.dfm"
TForm1 *Form1; HWND hWnd; BOOL KEY; FILE *fil; CAPTUREPARMS cappar; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
} //—————————————————————————————————————­void __fastcall TForm1::FormCreate(TObject *Sender) {
hWnd=capCreateCaptureWindow("Capture Window",WS_CHILD,
0,0,128,128,Handle,0xffff); capDriverConnect(hWnd,0); cappar.wPercentDropForError=66; capCaptureSetSetup(hWnd,&cappar,sizeof(cappar));
} //—————————————————————————————————————­void __fastcall TForm1::Button2Click(TObject *Sender) {
Close();
214 Глава 6. IP-телефония своими руками
} //—————————————————————————————————————­void __fastcall TForm1::NMMSGServ1MSG(TComponent *Sender,
const AnsiString sFrom, const AnsiString sMsg)
{
if(sMsg == "Go")
{
TJPEGImage *jpg = new TJPEGImage();
TMemoryStream *MS = new TMemoryStream();
capGrabFrame(hWnd);
Application->ProcessMessages();
capFileSaveDIB(hWnd,"123.bmp");
Image1->Picture->LoadFromFile("123.bmp");
jpg->Assign(Image1->Picture->Graphic);
jpg->CompressionQuality=32;
jpg->Compress();
jpg->SaveToFile("image.jpg");
int handle = open("image.jpg",O_RDONLY);
long fillen=filelength(handle);
close(handle);
char cc[32000];
fil=fopen("image.jpg","rb");
fread(cc,fillen,1,fil);
fclose(fil);
MS->Write(cc,fillen);
NMStrm1->PostIt(MS);
MS->Free();
jpg->Free();
}
else
{ NMStrm1->Host = sMsg;
Image1->Visible=true;
} } //—————————————————————————————————————-
Листинг клиентской программы представлен выше. Здесь, как и в преды­дущем приложении, все составные элементы нам уже знакомы так, что оста­ется только пояснить структуру приложения.
На форме нового проекта необходимо разместить одну кнопку, область отображения графики Image, визуальный компонент NMstrm потоковой пере­дачи данных и визуальный компонент NMMSGServ — сервер приема тексто­вых сообщений. Об установке IP-адресов и номеров портов больше говорить не будем, поскольку с этим читатель уже, наверное, освоился.
В обработчике события создания формы FormCreate производится орга­низация окна захвата видео. Обратим внимание на то, что окно не объявляет­ся видимым. Оно нам не понадобится для визуализации картинки. Из этого окна мы просто будем забирать полученную с камеры картинку, так сказать «втемную».
Не звуком единым... 215
Главные события у клиента разворачиваются при поступлении сообщения на компонент NMMSGServ в функции-обработчике NMMSGServ1MSG. Если поступило сообщение, отличное от строки «Go» (а как мы увидим далее, такое произойдет только один раз), то в свойство компонента NMStrm1->Host запи­сывается само пришедшее сообщение sMsg. Таким образом, мы должны поза­ботиться о том, чтобы сервер в первую очередь передал клиенту свой адрес.
Во всей последующей работе сервер, после получения очередного кадра, подтверждает получение картинки, послав клиенту строку «Go». При этом на клиенте выполняются следующие операции (подробности опускаем, посколь­ку они описаны в предыдущих программах):
1. Создается поток в памяти.
2. Производится захват изображения с камеры и запись его в файл
123.bmp.
3. Файл визуализуется в графическом поле Image.
4. Файл подвергается сжатию путем преобразования в файл image.jpg.
5. Содержимое файла image.jpg копируется в символьный массив ññ.
6. Массив копируется в поток в памяти, и поток посылается на сервер.
7. Все, что необходимо, закрывается и очищается.
Итак, организован процесс циклической пересылки на сервер очередного кадра по мере поступлений от сервера подтверждений на получение предыду­щего кадра. Процесс продолжается до тех пор, пока не будет нажата кнопка, закрывающая программу, или сервер не прекратит работу на своем конце.
Листинг серверной программы передачи видео представлен далее. При организации проекта на форму следует разместить метку, поле редактирова­ния, две кнопки и два компонента со вкладки FatNet — NMMsg è NMStrmServ. Для отображения полученной картинки необходимо установить на форму и компонент Image.
//—————————————————————————————————————­#include <stdio.h> #include <vcl.h> #pragma hdrstop #include "Unit1.h" #pragma package(smart_init) #pragma resource "*.dfm"
TForm1 *Form1; FILE *fil; //—————————————————————————————————————­__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
} //—————————————————————————————————————­void __fastcall TForm1::Button2Click(TObject *Sender) {
NMMsg1->Host=Edit1->Text; NMMsg1->PostIt(NMMsg1->LocalIP);
216 Глава 6. IP-телефония своими руками
NMMsg1->PostIt("Go"); } //—————————————————————————————————————­void __fastcall TForm1::NMStrmServ1MSG(TComponent *Sender,
const AnsiString sFrom, TStream *strm)
{
char ccc[32000];
strm->ReadBuffer(ccc,strm->Size);
fil=fopen("image1.jpg","wb");
fwrite(ccc,strm->Size,1,fil);
fclose(fil);
Image1->Picture->LoadFromFile("Image1.jpg");
NMMsg1->PostIt("Go"); } //—————————————————————————————————————­void __fastcall TForm1::Button1Click(TObject *Sender) {
Close(); } //—————————————————————————————————————-
При нажатии кнопки Button2 компонент NMMsg получает IP-адрес из поля редактирования, который служит для связи с клиентским компьютером. С помощью метода PostIt клиенту передается локальный адрес сервера. Далее на клиента отправляется текстовая строка «Go». Как мы понимаем, при этом у клиента инициируются описанные выше действия по передаче изображе­ния — пока только первый раз.
Далее начинает работать функция-обработчик NMStrmServ, которая реа­гирует на прием информации от клиента. Мы знаем, что информация от кли­ента представляет собой содержимое графического файла. Создается символь­ный массив достаточно большого размера. С помощью метода потока strm->ReadBuffer буфер прочитанных данных помещается в этот массив. Кон­кретный размер буфера определяется свойством strm->Size. Вслед за этим ор­ганизуется файл с именем Imag1.jpg, который открывается для записи как двоичный. Функция fwrite записывает в файл данные из символьного массива, после чего файл закрывается. Следующий оператор загружает в область визуа­лизации Image только что созданный файл, и мы видим принятую картинку на экране. Теперь на клиентский компьютер снова отправляется текстовая строка «Go» так, что организуется циклическая пересылка обновляемой кар­тинки от клиента серверу. Все это будет продолжаться до тех пор, пока не бу­дет нажата кнопка Button1, которая закрывает программу. Понятно, что сеанс может быть прерван и клиентом. Вот, собственно говоря, и вся нехитрая «кух­ня» программирования передачи «живого» видео. Обратим внимание на то, что данная пара программ реализуется существенно проще по сравнению с программами передачи звука.
Что здесь ХОРОШО и что здесь ПЛОХО? ХОРОШО здесь уже то, что представленные программы работают, то есть выполняют то, что им поручено делать — передают по сети (локальной или глобальной) звук и изображение в
Не звуком единым... 217
реальном времени. Прежде всего, хорошо это тем, что сделаны эти програм­мы своими руками, и, если читатель действительно нашел в себе силы и тер­пение разобраться с особенностями программирования работы с мультиме­диа, то он обрел стартовую площадку, с которой можно смело идти дальше. И идти можно очень далеко, поскольку представленные программы слишком «сыры» для того, чтобы называться настоящими приложениями IP-телефо­нии. Это, наверное, и есть самое главное ПЛОХО. Однако мы с самого начала не претендовали на разработку сколь-нибудь полноценных продуктов. Наша цель заключалась в другом: учиться, учиться и, как известно, еще раз учиться.
Что же плохо конкретно? Это обнаруживается при запуске разработанных программ
íà
выполнение. Начнем со звука. Даже работая со звуковой про­граммой на одном компьютере, общаясь голосом с собой любимым, вы обна­руживаете, что собственную речь вы слышите разбитой на короткие фрагмен­ты, разделенные небольшими паузами, в течение которых звук пропадает. Связано это с тем, что запись и пересылка звука производится не непрерыв­но, а, как мы понимаем из всего написанного ранее, поблочно. На организа­цию очередного блока и его отправку тратится определенное время, в течение которого и возникают паузы. Можно ли с этим как-то бороться? Несомненно, выход должен быть хотя бы уже потому, что «профессиональные» приложения этим дефектом не страдают. Для устранения этой неприятности следует орга­низовать запись (и воспроизведение, естественно) звука с использованием не одного, а двух или более блоков. Поскольку программно-аппаратные средства современных компьютеров могут работать параллельно во времени, возникает возможность во время записи очередного блока заниматься пересылкой пре­дыдущего и подготовкой последующего. Вещь вполне реализуемая, но тре­бующая применения некоторых нетривиальных средств программирования (создание параллельных «нитей» вычислительных процессов и прочее).
Другая беда, которая может случаться при работе в очень загруженной ло­кальной сети, а особенно при работе в Интернете, заключается в потере части пакетов. Это может выражаться в случайном возникновении «дырок» в при­нимаемой речи или музыке. Поскольку эти потери происходят не на конеч­ных компьютерах, а по пути следования пакетов по сетям, бороться с этим, совершенствуя свои приложения, практически бесполезно. Все зависит от пропускной способности каналов, а она часто оставляет желать лучшего. К сожалению, и профессиональные программы оказываются не лишенными этого недостатка. В то же время, при использовании коммерческих систем IP-телефонии, где задействованы выделенные скоростные каналы, эти «дыр­ки» практически не наблюдаются.
Мы вкратце упоминали о том, что, путешествуя по сетям, отдельные па­кеты могут приходить к адресату с различными запаздываниями. Надо отда­вать себе отчет в том, что при передаче данных от одного маршрутизатора к другому пакеты могут попадать в очереди и находиться там случайное время. По этой причине на приемном конце блоки данных могут «пересекаться» во времени и даже приходить не в той последовательности, в какой были отправ­лены. Эта неприятность отчасти устранима. На приемном конце организуется буфер временного хранения пакетов — так называемый джиттер-буфер, где
218 Глава 6. IP-телефония своими руками
они программно сортируются и отправляются на воспроизведение через рав­ные промежутки времени. Понятно, что в этом случае необходимо смириться с неизбежным запаздыванием всего потока воспроизводимых звуков. Как по­казывает практика, такое запаздывание, если оно не превышает несколько де­сятков миллисекунд, вполне приемлемо. Современные приложения, в том числе рассмотренные в первой части этой книги, широко используют джит­тер-буфер. Однако программирование работы с джиттером также достаточно хлопотно.
Другой способ решения проблемы неравномерного поступления пакетов заключается в использовании не протокола UDP, а протокола реального вре­мени RTP/RTCP. Здесь уже на уровне сетевой транспортировки гарантирует­ся прохождение блоков данных через фиксированные промежутки времени (либо не прохождение данного пакета вообще). К сожалению, автору пока не удалось найти сколь-нибудь удобного инструментария использования RTP в Windows-приложениях, хотя, возможно, такой инструментарий существует или в скором времени появится.
Следует также отметить, что для передачи и приема звука очень важно ка­чество используемого «железа». Речь идет о звуковых картах. Применение не­дорогих низкокачественных звуковых адаптеров приводит к возникновению в передаваемом звуке фона, щелчков и прочего «мусора», но к вопросам про­граммирования это прямого отношения не имеет. Хотя (это для «гурманов»), можно отметить, что программная фильтрация и очистка звука — это вполне решаемая и очень интересная задача.
При передаче «живого» изображения все обстоит гораздо проще, но и го­раздо менее оптимистично. Здесь изначально констатируется, что видео будет настолько «живым», насколько это позволяет пропускная способность канала. Представленная выше программа в локальной сети работает вполне приемле­мо, но при использовании Интернета смена кадров происходит непредсказуе­мо редко. В оправдание нам можно сказать, что и в профессиональных бес­платных программах дело обстоит ничуть не лучше. Иллюзия «живого» видео практически никогда не реализуется — мы видим всего лишь череду статиче­ских картинок. Между тем надо признать, что наша программа сделана далеко не лучшим образом. Использование в ней визуальных компонентов типа по­токов и сообщений — это отнюдь не оптимальный вариант. Зато, как нам ка­жется, здесь все достаточно понятно, а именно к этому мы и стремились. Что же здесь можно улучшить? Опять же, для тех, кто имеет желание глубже осво­ить увлекательный мир мультимедиа реального времени, можно посоветовать более детально разобраться с аппаратом создания и использования сокетных соединений. Этот аппарат не есть тайна за семью печатями, но требует углуб­ленного изучения. Кроме того, стандартные сокетные средства, представлен­ные в используемой нами среде C++ Builder, недостаточно внятно докумен­тированы и, порой, не слишком надежно работают, на что обращают внима­ние и другие авторы.
Последним существенным недостатком программы передачи видео явля­ется то, что мы «безжалостно» эксплуатируем жесткий диск компьютера, не­прерывно создавая, записывая, читая и перезаписывая графические файлы.
Финал: свой видеотелефон 219
Мало того, что тем самым физически изнашивается магнитный носитель. Важно и то, что операции чтения и записи на диск довольно медленные, что может сказываться и на качестве видеосвязи. В заключительном разделе мы попробуем в какой-то степени устранить перечисленные недостатки.
Финал: свой видеотелефон
Вот мы и подошли к финальной части нашей книги! Достойным фина­лом, как нам кажется, будет представление приложения, которое теперь уже может быть практически использовано в качестве видеотелефона, по крайней мере, в локальной компьютерной сети. Почему мы говорим о локальной сети, а не об Интернете? На это есть две причины. Во-первых, это все же не ком­мерческий продукт, оптимизированный по всем параметрам. Трафик, созда­ваемый программой, оказывается весьма большим, и передача изображения и звука по интернетовским каналам приведет к потере качества и большим за­держкам. Во-вторых, в данном приложении предполагается, что каждый из участвующих в диалоге компьютеров имеет реальный (для данной сети) IP-ад­рес, что, как мы знаем, в Интернете далеко не всегда имеет место. Поэтому будем считать, что программа ориентирована именно на локальную сеть.
Как всегда, начнем с описания интерфейса. После запуска программы на обоих компьютерах открываются стартовые окна в виде, представленном на рисунке. На этом этапе в программе можно сделать некоторые настройки. От­крыв пункт меню PORTS, можно изменить номера портов TCP и UDP для локального и удаленного компьютеров, относительно предлагаемых по умол­чанию. В пункте меню DRIVERS можно выбрать один из нескольких драйве­ров устройств видеоввода, если таковых в системе действительно установлено несколько. Обратите внимание на то, что в информационном поле в левой части окна отображены полезные сведения о текущем состоянии программы и возможных действиях пользователя (рис. 6.3).
Как следует из подсказок, на паре «общающихся» компьютеров нужно ус­тановить режимы вызова — ожидания вызова. На ждущей машине оставляем выбранной радиокнопку Wait, а на вызывающем выбираем режим Call. Под­тверждаем выбор кнопкой с одноименным названием.
Ðèñ. 6.3
220 Глава 6. IP-телефония своими руками
На следующем этапе на компьютерах необходимо установить режимы пе­редачи и приема мультимедийной информации. Как видно из следующего ри­сунка, выбор производится установкой или снятием соответствующих отме­ток в разделах SENDING è RECEIVING. В частности, на представленном ри­сунке установлен режим, в котором компьютер будет передавать только звук (но не изображение), а принимать как аудио, так и видео. Здесь необходимо отметить, что если вы будете тестировать программу на одном компьютере, запустив на нем два экземпляра приложения, то передачу изображения нужно разрешить только из одного экземпляра. Звук же можно и принимать и пере­давать обоими приложениями (рис. 6.4).
Ðèñ. 6.4
Далее в вызывающей программе нужно указать IP-адрес вызываемого корреспондента, как это представлено на следующем рисунке. Напомним, что для тестирования на одном компьютере, не подключенном к сети, можно за­дать адрес 127.0.0.1. Теперь в той и другой программе нужно выполнить под­тверждение — кнопка YES (ðèñ. 6.5)
Ðèñ. 6.5
После этого будет установлена связь, и в последнем перед началом сеанса окне (иллюстрацию не приводим) нужно будет нажать кнопку GO. В режиме сеанса связи, в который мы теперь перешли, рабочее окно существенно изме нится (рис. 6.6).
-