Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Финал: свой видеотелефон 221
Здесь будет отображено видеоокно. В данном случае показана ситуация, когда корреспондент запретил
передачу изображения, вследствие чего мы видим
только заставку. Если же передача видео разрешена,
то в видеоокне вы будете видеть изображение, «живое» настолько, насколько позволяет производительность сети. Кроме того, во время сеанса имеется возможность регулирования уровня громкости (ГОВОРИТЕЛЬ) и микрофона (МИКРОФОН). Ну и в любой
момент сеанс может быть прекращен кнопкой STOP с
одновременным выходом из программы.
Переходим к разработке программы. Наше приложение будет передавать и принимать аудиоинформацию посредством протокола UDP, как это и требуется
для транспортировки звука. Изображение же будет
Ðèñ. 6.6
транслироваться протоколом ТСР, что, как мы знаем, допускает сколь угодно
большие интервалы между сменами кадров. Для обеспечения оптимальной
оперативности передачи информации мы воспользуемся технологией нитей
(thread) так, что для каждого из входящих и исходящих потоков как видео, так
и аудио будет создана собственная нить. Все это должно быть понятно читателю из изучения предыдущих разделов. Единственным принципиально новым
моментом в данной программе является реализация регулировки уровня звука.
Вообще говоря, такая регулировка выполняется довольно сложно. В Win32 API
существует целая подсистема соответствующих инструментов под общим названием система микширования звука. Регулировка уровней является далеко
не единственной задачей, решаемой микшированием. К сожалению, по крайней мере по мнению автора, эта подсистема организована весьма «замысловато» и не достаточно четко документирована. В этой связи мы не видим возможности подробно описывать данный момент. В соответствующем месте
программы мы только прокомментируем основные особенности, порекомендовав интересующимся обратиться к справке и литературе.
Итак, на форме нового проекта необходимо, в соответствии с рис. 6.7,
разместить:
1. Кнопки (Button) ВЫБОР, ÎÊ, YES, GO è STOP.
2. Метки (Label) с наименованиями, представленными на рисунке.
3. Поле редактирования (Edit) для ввода IP-адреса.
4. Радиогруппу (RadioGroup) для выбора режима.
5. Четыре компонента CheckBox для разрешения приема и передачи звука
и изображения.
6. Компонент организации главного меню окна (MainMenu).
7. Информационное поле Memo.
8. Поверх информационного поля располагаются два ползунковых регуля-
тора TrackBar для управления уровнями звука и микрофона.
9. Под информационным полем «спрятаны» еще две метки ГОВОРИ-
ТЕЛЬ и МИКРОФОН.

222 Глава 6. IP-телефония своими руками
Ðèñ. 6.7
Главное меню состоит из двух пунктов PORTS и DRIVERS. Проектирование меню, хотя и является для нас новой процедурой, особого труда не представляет. Читатель без сомнения сможет выполнить ее самостоятельно, воспользовавшись справочной системой и нашими дальнейшими пояснениями.
Как уже говорилось, в приложении широко использованы инструменты
многопотоковости (нити) так, что проект состоит из целого ряда соответствующих модулей, которые мы представим по отдельности. Начнем же с основного модуля Unit1.cpp:
#include <vcl.h>
#include <stdio.h>
#include <winsock2.h>
#include "Unit1.h"
#include "Unit2.h"
#include "Unit3.h"
#include "Unit4.h"
#include "Unit5.h"
#include "Unit6.h"
#include "Unit7.h"
#include "Unit8.h"
#include "Unit9.h"
#include <vfw.h>
#include <jpeg.hpp>
#include <vcl\Clipbrd.hpp>
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
BOOL KEY = true;
WSADATA WSA;
int diskret=11025;
short razrad=16, kanal=1,VD1=1,DV1=1,VVDD1=1,DDVV1=1;
long BufAll;
char buf111[16000];
HWAVEIN hWaveIn;
HWAVEOUT hWaveOut;
WAVEFORMATEX m_WaveFormatEx;
LPWAVEHDR lpHdr = new WAVEHDR,lpDhr = new WAVEHDR,

Финал: свой видеотелефон 223
lpHdr1 = new WAVEHDR,lpHdr2 = new WAVEHDR;
Tout *Sending; //Класс для нити отсылки
Tin *Receiving; //Класс для нити приема
VideoOut *Sendvid;
VideoIn *Recvid;
OutIn *RecVid;
InOut *Svid;
HWND hWnd;
SOCKET my_sock,mysocket,mysocket_outin,sock_out,sock_in,
my_sock_inout,client_socket;
sockaddr_in dest_addrTCP,local_addrTCP_outin,local_addr,dest_addr,
local_addrTCP,client_addrTCP,dest_addrTCP_inout;
char buf11[32],VD,DV,VVDD,DDVV;
int client_addr_sizeTCP=sizeof(client_addrTCP);
int jj=0;
extern TJPEGImage *jpg;
extern TClipboard *pCB;
extern Graphics::TBitmap *pBitmap;
extern TMemoryStream* pms;
BITMAPINFO psVideo;
DWORD VolCon=4294967295;
DWORD m_dwControlID,VolVol;
MIXERCONTROLDETAILS mxcd;
MIXERCONTROLDETAILS_UNSIGNED mxdu;
HMIXER hMixer;
//—————————————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{}
//—————————————————————————————————————
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Memo1->Visible=false;
if(CheckBox1->Checked){ Sending = new Tout(true); Sending->Resume(); }
if(CheckBox3->Checked){ Receiving = new Tin(true); Receiving->Resume(); }
if(RadioGroup1->ItemIndex==1)
{
if(CheckBox2->Checked)
{
capGrabFrame(hWnd); capEditCopy(hWnd);
pBitmap->LoadFromClipboardFormat(CF_BITMAP,pCB->GetAsHandle(CF_BITMAP),0);
jpg->Assign(pBitmap); jpg->CompressionQuality=32;
jpg->Compress(); jpg->SaveToStream(pms);
pms->Position = 0; pms->ReadBuffer(buf111,pms->Size);
pms->Position = 0; send(my_sock,buf111,pms->Size,0);
pms->Clear(); Sendvid = new VideoOut(true); Sendvid->Resume();
}
if(CheckBox4->Checked) { RecVid = new OutIn(true); RecVid->Resume(); }
}
else
{

224 Глава 6. IP-телефония своими руками
if(CheckBox4->Checked) { Recvid = new VideoIn(true); Recvid->Resume();
}
if(CheckBox2->Checked)
{
capGrabFrame(hWnd); capEditCopy(hWnd);
pBitmap->LoadFromClipboardFormat(CF_BITMAP,pCB->GetAsHandle(CF_BITMAP),0);
jpg->Assign(pBitmap); jpg->CompressionQuality=32;
jpg->Compress(); jpg->SaveToStream(pms);
pms->Position = 0; pms->ReadBuffer(buf111,pms->Size);
pms->Position = 0; send(my_sock_inout,buf111,pms->Size,0);
pms->Clear(); Svid = new InOut(true); Svid->Resume(); }
}
Button1->Visible=false;
Button2->Top=210;
Button2->Left=40;
Form1->Width=170;
if(!CheckBox4->Checked) Image1->Visible=true;
waveOutSetVolume(hWaveOut,VolCon);
mixerOpen(&hMixer, 0, 0, 0, 0);
MIXERLINE mxl; MIXERCONTROL mc; MIXERLINECONTROLS mxlc;
mxl.cbStruct = sizeof(mxl);
mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
mixerGetLineInfo((HMIXEROBJ)hMixer,&mxl,MIXER_GETLINEINFOF_COMPONENTTYPE);
DWORD count = mxl.dwSource;
for(UINTi=0;i<count; i++)
{
mxl.dwSource = i;
mixerGetLineInfo((HMIXEROBJ)hMixer,&mxl,MIXER_GETLINEINFOF_SOURCE);
if (mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
{
mc.cbStruct = sizeof(mc); mxlc.cbStruct = sizeof(mxlc);
mxlc.dwLineID = mxl.dwLineID;
mxlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mxlc.cControls = 1;
mxlc.cbmxctrl = sizeof(MIXERCONTROL); mxlc.pamxctrl = &mc;
mixerGetLineControls((HMIXEROBJ)hMixer,&mxlc,
MIXER_GETLINECONTROLSF_ONEBYTYPE);
m_dwControlID = mc.dwControlID;
break;
};
}
mxcd.cMultipleItems = 0;
mxcd.cChannels = 1;
mxcd.cbStruct = sizeof(mxcd);
mxcd.dwControlID = m_dwControlID;
mxcd.cbDetails = sizeof(mxdu);
mxcd.paDetails = &mxdu;
TrackBar1->Visible=true;
Label5->Visible=true;
TrackBar2->Visible=true;

Финал: свой видеотелефон 225
Label6->Visible=true;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button2Click(TObject *Sender)
{
KEY = false; capDriverDisconnect(hWnd);
WSACleanup(); Close();
}
//—————————————————————————————————————-
void __fastcall TForm1::Button3Click(TObject *Sender)
{
if(RadioGroup1->ItemIndex==1)
{
Label1->Visible=true; Label2->Visible=true;
Edit1->Visible=true; Edit1->Enabled=true;
Button4->Visible=true; Button4->Enabled=true;
}
else
{
Button4->Visible=true; Button4->Enabled=true;
}
Button3->Visible=false; Label3->Visible=false; Label4->Visible=false;
CheckBox1->Visible=false; CheckBox2->Visible=false;
CheckBox3->Visible=false; CheckBox4->Visible=false;
Memo1->Clear();
if(RadioGroup1->ItemIndex==1)
Memo1->Lines->Add("Сейчас необходимо задать \
IP - вызываемого компьютера");
Memo1->Lines->Add("Нажмите кнопку YES");
}
//—————————————————————————————————————void __fastcall TForm1::Source1Click(TObject *Sender)
{capDlgVideoSource(hWnd);}
//—————————————————————————————————————void __fastcall TForm1::PORTS1Click(TObject *Sender)
{ Form2->Show();}
//—————————————————————————————————————void __fastcall TForm1::DRIVERS1Click(TObject *Sender)
{
BOOL IsDev;
int wDriverIndex,cbName=256,cbVer=256; char lpszName[255],lpszVer[255];
Form9->Show();
Form9->Memo1->Clear();
for(wDriverIndex = 0; wDriverIndex<10; wDriverIndex++)
{
IsDev =
capGetDriverDescription(wDriverIndex,lpszName,cbName,lpszVer,cbVer);
if(IsDev)
{
Form9->Memo1->Lines->Add("Find drive " +IntToStr(wDriverIndex));

226 Глава 6. IP-телефония своими руками
Form9->Memo1->Lines->Add(lpszName); Form9->Memo1->Lines->Add("
");
}
}
}
//—————————————————————————————————————-
void __fastcall TForm1::Button4Click(TObject *Sender)
{
Memo1->Clear();
if(!CheckBox4->Checked) VD1=0;
if(!CheckBox2->Checked) VVDD1=0;
Button1->Enabled=false; Button1->Visible=false;
PORTS1->Enabled=false; PORTS1->Visible=false;
DRIVERS1->Enabled=false; DRIVERS1->Visible=false;
WSAStartup(0x202, &WSA);
if(RadioGroup1->ItemIndex==1)
{
my_sock=socket(AF_INET,SOCK_STREAM,0);
dest_addrTCP.sin_family=AF_INET;
dest_addrTCP.sin_port=htons(566);
sprintf(buf11,"%s",Edit1->Text);
dest_addrTCP.sin_addr.s_addr=inet_addr(buf11);
while(KEY&&
connect(my_sock,(sockaddr *)&dest_addrTCP, sizeof(dest_addrTCP)))
Application->ProcessMessages();
mysocket_outin=socket(AF_INET,SOCK_STREAM,0);
local_addrTCP_outin.sin_family=AF_INET;
local_addrTCP_outin.sin_port=htons(567);
local_addrTCP_outin.sin_addr.s_addr=INADDR_ANY;
bind(mysocket_outin,(sockaddr *)
&local_addrTCP_outin, sizeof(local_addrTCP_outin));
listen(mysocket_outin, 1);
client_socket=accept(mysocket_outin,(sockaddr *)&client_addrTCP,
&client_addr_sizeTCP);
recv(client_socket,&DV,1,0); DV1=DV;
if(DV1==0)
{
CheckBox2->Checked=false;
Memo1->Lines->Add("Корреспондент запретил прием видео. \
Передача изибражения будет блокирована ");
}
recv(client_socket,&DDVV,1,0); DDVV1=DDVV;
if(DDVV1==0)
{
CheckBox4->Checked=false;
Memo1->Lines->Add("Корреспондент запретил прием видео. \
Передача изибражения будет блокирована");
}
VD=VD1; send(my_sock,&VD,1,0);
VVDD=VVDD1; send(my_sock,&VVDD,1,0);
if(CheckBox1->Checked)
{

Финал: свой видеотелефон 227
sock_out=socket(AF_INET, SOCK_DGRAM, 0);
dest_addr.sin_port=htons(666);
dest_addr.sin_family=AF_INET;
dest_addr.sin_addr.s_addr=dest_addrTCP.sin_addr.s_addr;
}
if(CheckBox3->Checked)
{
sock_in=socket(AF_INET,SOCK_DGRAM,0);
local_addr.sin_family=AF_INET;
local_addr.sin_addr.s_addr=INADDR_ANY;
local_addr.sin_port=htons(668);
bind(sock_in,(sockaddr *) &local_addr,sizeof(local_addr));
}
}
else
{
mysocket=socket(AF_INET,SOCK_STREAM,0);
local_addrTCP.sin_family=AF_INET;
local_addrTCP.sin_port=htons(566);
local_addrTCP.sin_addr.s_addr=0;
bind(mysocket,(sockaddr *) &local_addrTCP, sizeof(local_addrTCP));
listen(mysocket, 1);
client_socket=accept(mysocket,(sockaddr *)&client_addrTCP,
&client_addr_sizeTCP);
my_sock_inout = socket(AF_INET,SOCK_STREAM,0);
dest_addrTCP_inout.sin_family=AF_INET;
dest_addrTCP_inout.sin_port= htons(567);
dest_addrTCP_inout.sin_addr.s_addr=client_addrTCP.sin_addr.s_addr;
while(KEY&&
connect(my_sock_inout,(sockaddr *)&dest_addrTCP_inout,
sizeof(dest_addrTCP_inout)))
Application->ProcessMessages();
VD=VD1; send(my_sock_inout,&VD,1,0);
VVDD=VVDD1; send(my_sock_inout,&VVDD,1,0);
recv(client_socket,&DV,1,0); DV1=DV;
if(DV1==0)
{
CheckBox2->Checked=false;
Memo1->Lines->Add("Корреспондент запретил прием видео. \
Передача изибражения будет блокирована ");
}
recv(client_socket,&DDVV,1,0); DDVV1=DDVV;
if(DDVV1==0)
{
CheckBox4->Checked=false;
Memo1->Lines->Add("Корреспондент запретил прием видео. \
Передача изибражения будет блокирована ");
}
if(CheckBox3->Checked)
{
sock_in=socket(AF_INET,SOCK_DGRAM,0);
local_addr.sin_family=AF_INET;
local_addr.sin_addr.s_addr=INADDR_ANY;

228 Глава 6. IP-телефония своими руками
local_addr.sin_port=htons(666);
bind(sock_in,(sockaddr *) &local_addr,sizeof(local_addr));
}
if(CheckBox1->Checked)
{
sock_out=socket(AF_INET, SOCK_DGRAM, 0);
dest_addr.sin_port=htons(668);
dest_addr.sin_family=AF_INET;
dest_addr.sin_addr.s_addr=client_addrTCP.sin_addr.s_addr;
}
}
if(RadioGroup1->ItemIndex==1)
{
Label1->Visible=True;
Label2->Visible=True;
Edit1->Visible=True;
}
Button1->Visible=True;
BufAll = razrad*diskret*kanal/32;
ZeroMemory(&m_WaveFormatEx,sizeof(m_WaveFormatEx));
m_WaveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
m_WaveFormatEx.nChannels = kanal;
m_WaveFormatEx.wBitsPerSample = razrad;
m_WaveFormatEx.nSamplesPerSec = diskret;
m_WaveFormatEx.nAvgBytesPerSec =
m_WaveFormatEx.nSamplesPerSec*(m_WaveFormatEx.wBitsPerSample/8);
m_WaveFormatEx.nBlockAlign =
(m_WaveFormatEx.wBitsPerSample/8)*m_WaveFormatEx.nChannels;
ZeroMemory(lpHdr, sizeof(WAVEHDR));
char* lpByte = (char*)malloc(m_WaveFormatEx.nBlockAlign*diskret);
lpHdr->lpData = (char *) lpByte; lpHdr->dwBufferLength = BufAll;
ZeroMemory(lpDhr, sizeof(WAVEHDR));
char* plTybe = (char*)malloc(m_WaveFormatEx.nBlockAlign*diskret);
lpDhr->lpData = (char *) plTybe; lpDhr->dwBufferLength = BufAll;
waveInOpen(&hWaveIn, WAVE_MAPPER,&m_WaveFormatEx,0L,0L,0L);
waveInPrepareHeader(hWaveIn,lpHdr,sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn,lpDhr,sizeof(WAVEHDR));
ZeroMemory(lpHdr1, sizeof(WAVEHDR));
char* lpByte1 = (char*)malloc(m_WaveFormatEx.nBlockAlign*diskret);
lpHdr1->lpData = (char *) lpByte1; lpHdr1->dwBufferLength = BufAll;
ZeroMemory(lpHdr2, sizeof(WAVEHDR));
char* lpByte2 = (char*)malloc(m_WaveFormatEx.nBlockAlign*diskret);
lpHdr2->lpData = (char *) lpByte2; lpHdr2->dwBufferLength = BufAll;
waveOutOpen(&hWaveOut, WAVE_MAPPER,&m_WaveFormatEx,0L,0L,0L);
waveOutPrepareHeader(hWaveOut,lpHdr1,sizeof(WAVEHDR));
waveOutPrepareHeader(hWaveOut,lpHdr2,sizeof(WAVEHDR));
if(CheckBox2->Checked)
{
hWnd=capCreateCaptureWindow("Capture Window",WS_CHILD,
0,0,160,120,Handle,0xffff);
if(!capDriverConnect(hWnd,jj))
{ ShowMessage("Can't use video driver"); exit(1); }

Финал: свой видеотелефон 229
Memo1->Lines->Add("Для начала разговора нажмите кнопку Go");
capGetVideoFormat(hWnd,&psVideo,sizeof(BITMAPINFO));
psVideo.bmiHeader.biWidth=160;
psVideo.bmiHeader.biHeight=120;
capSetVideoFormat(hWnd,&psVideo,sizeof(BITMAPINFO));
}
else
Memo1->Lines->Add("Для начала разговора нажмите кнопку Go ");
Button1->Enabled=true; Button1->Visible=true;
Label1->Visible=false; Label2->Visible=false;
Edit1->Visible=false; Button4->Visible=false;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button5Click(TObject *Sender)
{
Memo1->Clear();
Memo1->Lines->Add("Сейчас можно разрешить или запретить \
Передачу и прием изображения и звука \
Еажмите кнопку ОК");
PORTS1->Visible=false; DRIVERS1->Visible=false;
Label3->Visible=true; Label4->Visible=true;
CheckBox1->Visible=true; CheckBox2->Visible=true;
CheckBox3->Visible=true; CheckBox4->Visible=true;
Button3->Visible=true; Button5->Visible=false; RadioGroup1->Enabled=false;
RadioGroup1->Visible=false;
}
//—————————————————————————————————————-
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Memo1->Clear();
Memo1->Lines->Add("Сейчас можно настроить \
коммуникационные порты и выбрать устройство \
Захвата изображения (если их несколько) - \
пункты PORTS и DRIVERS главного меню.\
Для установки режима ВЫЗОВ - ОЖИДАНИЕ ВЫЗОВА \
нажмите кнопку ВЫБОР");
}
//—————————————————————————————————————-
void __fastcall TForm1::TrackBar1Change(TObject *Sender)
{
VolCon=TrackBar1->Position;
waveOutSetVolume(hWaveOut,VolCon*65537);
}
//—————————————————————————————————————void SetVolume(DWORD dwVol)
{
mixerOpen(&hMixer, 0, 0, 0, 0);
mxdu.dwValue = dwVol;
mixerSetControlDetails((HMIXEROBJ)hMixer,
&mxcd, MIXER_SETCONTROLDETAILSF_VALUE);

230 Глава 6. IP-телефония своими руками
mixerClose(hMixer);
}
//—————————————————————————————————————-
void __fastcall TForm1::TrackBar2Change(TObject *Sender)
{
VolVol=TrackBar2->Position;
SetVolume(VolVol);
}
//—————————————————————————————————————-
Большая часть элементов программирования нам уже знакома из предыдущего рассмотрения, поэтому для них наши комментарии будут минимальными. Будем рассматривать функции основного модуля в той последовательности, в которой они представлены в тексте. Обработчик нажатия кнопки
Button1 (GO) в зависимости от режима приема и передачи создает или не создает объекты нитей для трансляции (Sending) и получения (Receiving) звука.
Нити включаются в работу.
Если установлен режим вызова, то проверяется разрешение на передачу
видео, захватывается кадр изображения и после сжатия передается в сокет.
Создаются и запускаются нити приема и передачи видео для вызывающего
компьютера. То же самое делается для вызываемого компьютера. Вся оставшаяся часть тела функции-обработчика кнопки относится к регулированию
уровней звука, которую читатель должен принять «как есть» или освоить самостоятельно.
Обработчик нажатия кнопки Button2 (STOP) разрывает соединение, завершает работу всех сокетов и завершает приложение. Предварительно сигнальная логическая переменная KEY принимает значение FALSE, что используется другими модулями программы для завершения работы.
Нажатие кнопки Button3 (ÎÊ) устанавливает видимость различных меток, необходимых к данному моменту, и доступность соответствующих кнопок. Кроме того, в информационное поле выводится текст подсказок для
пользователя.
Следующая функция PORTS1Click срабатывает при выборе пункта меню
PORTS. Результатом срабатывания является визуализация дополнительной
формы Form2 — окна, в котором можно установить номера портов для сокетов ТСР и UDP. Вид окна показан ниже, а проектирование самой формы не
должно вызвать затруднений (рис. 6.8).
Можно изменить номера входящих и исходящих портов. Нажатие кнопки
ÎÊ (обработчик события описан далее) приведет к вводу соответствующих
значений в программу.
При активации пункта меню DRIVERS (выбор устройства захвата изображения) срабатывает функция DRIVERS1Click. Открывается окно еще одной
дополнительной формы (рис. 6.9).
В теле функции производится поиск установленных в системе драйверов
устройств видеоввода, который отображается в информационном поле. При
наличии нескольких драйверов можно выбрать нужный и нажать кнопку ÎÊ.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
