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