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

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

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Финал: свой видеотелефон 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).
В теле функции производится поиск установленных в системе драйверов устройств видеоввода, который отображается в информационном поле. При наличии нескольких драйверов можно выбрать нужный и нажать кнопку ÎÊ.