、问题提出
作者最近在开发基于Internet网上可视电话过程中碰到了这样个问题在基于Internet网上可视电话系统中同时要进行语音采集、语音编解码、图象采集、图象编解码、语音和图象 码流传输 所有这些事情都要并行处理特别是语音信号如果进行图象编解码时间过长语音信号得不到服务通话就有间断如果图象或语音处理时间过长而不能及时传输码流数据通信同样也会中断这样就要求我们实现种并行编程在只有个CPU机器上也就是要将该CPU时间按照定优先准则分配给各个事件定期处理某事件而不会在某事件处理过长在32位Windows95或WindowsNT下我们可以用多线程编程技术来实现这种并行编程实际上这种并行编程在很多场合下都是必须例如在FileManager拷贝文件时它显示个对话框 列出源文件和目标文件名称并在对话框中包含了个Cancel按钮如果在文件拷贝过程中点中Cancel按钮就会终止拷贝
在16位Windows中实现这类功能需要在FileCopy循环内部周期性地PeekMessage如果正在读个很大数据块则只有当这个块读完以后才能响应这个按钮动作如果从软盘读文件则要花费好几秒时间由于机器反应太迟钝你会频繁地点中这个按钮以为系统不知道你想终止这个操作如果把FileCopy指令放入另外个线程你就不需要在代码中放大堆PeekMessage处理 用户界面线程将和它分开操作这样点中Cancel按钮后会立即得到响应同样道理在应用中创建个单独线程来处理所有打印任务也是很有用这样用户可以在打印处理时继续使用应用
2、线程概念
为了了解线程概念我们必须先讨论下进程概念
个进程通常定义为个例子在Win32中 进程占据4GB地址空间和它们在MS-DOS和16位Windows操作系统中区别 Win32进程是没有活力这就是说个Win32进程并不执行什么指令它只是占据着4GB地址空间此空间中有应用EXE文件 代码和数据EXE需要任意DLL也将它们代码和数据装入到进程地址空间除了地址空间进程还占有某些资源比如文件、动态内存分配和线程当进程终止时在它生命期中创建各种资源将被清除
但是进程是没有活力它只是个静态概念为了让进程完成些工作进程必须至少占有个线程所以线程是描述进程内执行正是线程负责执行包含在进程地址空间中代码实际上单个进程可以包含几个线程 它们可以同时执行进程地址空间中代码为了做到这点每个线程有自己组CPU寄存器和堆栈
每个进程至少有个线程在执行其地址空间中代码如果没有线程执行进程 地址空间中代码 进程也就没有继续存在理由系统将自动清除进程及其地址空间为了运行所有这些线程操作系统为每个独立线程安排些CPU 时间操作系统以轮转方式向线程提供时间片这就给人种假象好象这些线程都在同时运行创建个Win32进程时它第个线程称为主线程它 由系统自动生成然后可由这个主线程生成额外线程这些线程又可生成更多线程
3、线程编程技术
1、编写线程
所有线程必须从个指定函 数开始执行该称为线程它必须具有下列原型:
DWORDWINAPIYourThreadFunc(LPVOIDlpvThreadParm);
该输入个LPVOID型参数可以是个DWORD型整数也可以是个指向个缓冲区指针 返回个DWORD型值象WinMain样这个并不由操作系统 操作系统包含在KERNEL32.DLL中非C运行时个内部如StartOfThread然后由StartOfThread建立起个异常处理框架后我们
2、创建个线程
个进程主线程是由操作系统自动生成如果你要让个主线程创建额外线程你可以来CreateThread完成
HANDLECreateThread(LPSECURITY_ATTRIBUTES lpsa,DWORDcbstack,LPTHREAD_START_ROUTINElpStartAddr,
LPVOID lpvThreadParm,DWORDfdwCreate,LPDWORDlpIDThread);
其中lpsa参数为个指向SECURITY_ATTRIBUTES结构指针如果想让对象为缺省安全属性话可以传个NULL如果想让任个子进程都可继承个该线程对象句柄必须指定个SECURITY_ATTRIBUTES结构其中bInheritHandle成员化为TRUE参数cbstack表示线程为自己所用堆栈分配地址空间大小0表示采用系统缺省值
参数lpStartAddr用来表示新线程开始执行时代码所在地址即为线程lpvThreadParm为传入线程参数fdwCreate参数指定控制线程创建附加标志可以取两种值如果该参数为0线程就会立即开始执行如果该参数为CREATE_SUSPENDED则系统产生线程后化CPU登记CONTEXT结构成员准备好执行该线程中第条指令但并不马上执行而是挂起该线程最后个参数lpIDThread 是个DWORD类型地址返回赋给该新线程ID值
3、终止线程
如果某线程了ExitThread 就可以终止自己
VOIDExitThread(UINTfuExitCode );
这个为该线程设置了退出码fuExitCode后 就终止该线程TerminateThread亦可终止线程
BOOLTerminateThread(HANDLE hThread,DWORDdwExitCode);
该用来结束由hThread参数指定线程 并把dwExitCode设成该线程退出码当某个线程不在响应时我们可以用其他线程该来终止这个不响应线程
4、设定线程相对优先级
当个线程被首次创建时它优先级等同于它所属进程优先级在单个进程内可以通过SetThreadPriority改变线程相对优先级个线程优先级是相对于其所属进程优先级而言
BOOLSetThreadPriority(HANDLE hThread,nPriority);
其中参数hThread是指向待修改 优先级线程句柄nPriority可以是以下值:
THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL,
THREAD_PRIORITY_ABOVE_NORMAL,
THREAD_PRIORITY_HIGHEST
5、挂起及恢复线程
先前我提到过可以创建挂起状态线程(通过传递CREATE_SUSPENDED标志给CreateThread来实现)当你这样做时系统创建指定线程核心对象创建线程栈在CONTEXT结构中化线程CPU注册成员然而线程对象被分配了个挂起计数值1这表明了系统将不再分配CPU去执行线程要开始执行个线程另个线程必须ResumeThread并传递给它CreateThread时返回线程句柄
DWORD ResumeThread(HANDLEhThread);
个线程可以被挂起多次如果个线程被挂起3次 则该线程在它被分配CPU的前必须被恢复3次除了在创建线程时使用CREATE_SUSPENDED标志你还可以用SuspendThread挂起线程
DWORDSuspendThread(HANDLE hThread);
4、多线程编程技术应用
我在前面说过为了实现基于TCP/IP下可视电话就必须“并行”地执行语音采集、语音编解码、图象采集、图象编解码以及码流数据接收和发送语音和图象采集由硬件采集卡进行我们只需化该硬件采集卡然后实时读取采集数据即可但语音和图象数据编解码以及码流数据传输都必须由去协调执行决不能在某件事件上处理过长必须让CPU轮流为各个事件服务Windows95下线程正是满足这种要求编程技术
下面我给出了利用Windows95 环境下多线程编程技术实现基于TCP/IP可视电话部分源码其中包括主窗口过程以及主叫端和被叫端TCP/IP接收线程和语音编解码线程由于图象编解码实时性比语音处理和传输模块实时性 要求要低些所以我以语音编解码为事件去查询图象数据然后进行图象编解码而没有为图象编解码去单独实现个线程
在主窗口化时 我用CREATE_SUSPENDED标志创建了两个线程hThreadG7231和hThreadTCPRev个用于语音编解码它线程为G723Proc 该线程不断查询本地有无编好码语音和图象码流如有则进行H.223打包然后通过TCP端口发送给对方另外个线程用于TCP/IP接收它线程为AcceptThreadProcRev该线程不断侦 测TCP/IP端口有无对方传来码流如有就接收码流进行H.223解码后送入相应缓冲区该缓冲区内容由语音编解码线程G723Proc查询并送入相应解码器由于使用了多线程编程技术使得操作系统定时去服务语 音编解码模块和传输模块从而保证了通信不中断
5、源码
//基于TCP/IP可视电话主窗口窗口过程
LONG APIENTRY MainWndProc(HWND hWnd,UINT message,UINT wParam, LONG lParam)
{
HANDLE hThreadG7231,hThreadTCPListen,hThreadTCPRev;
DWORDThreadIDG7231,ThreadIDTCPListen,ThreadIDTCPRev;
THREADPACK tp;
THREADPACK tp1;
unsigned char Buf[80];
CAPSTATUS capStatus;
switch (message)
{
WM_CREATE:
Init_Wsock(hWnd); //化些数据结构
Init_BS(2,&bs);
vd_tx_pdu.V_S = 0;vd_tx_pdu.N_S = 0;
vd_rx_pdu.V_R = 0;vd_tx_sdu.s = 0;
( dnldProg ( hWnd, "h324g723.exe") )
{
//装入语音编解码DSP核心
MessageBox(hWnd,"Load G.723.1 Kernel Error","Error",MB_OK);
PostQuitMessage(0); }
MessageBox(hWnd,"Load G.723.1 Kernel OK!","Indication",MB_OK);
//创建语音编解码线程
parag7231.hWnd = hWnd;
hThreadG7231=CreateThread (NULL, 0,(LPTHREAD_START_ROUTINE)G723Proc,
(G7231DATA *)?g7231,
CREATE_SUSPENDED,(LPDWORD)&ThreadIDG7231);
(!hThreadG7231)
{
wsprf(Buf, "Error in creating G7231 thread: %d",GetLastError);
MessageBox (hWnd, Buf, "WM_CREATE", MB_OK);}
//创建TCP/IP接收线程
tp1.hWnd = hWnd;
hThreadTCPRev = CreateThread (NULL, 0,(LPTHREAD_START_ROUTINE)AcceptThreadProcRev,
(G7231DATA *)&tp1,CREATE_SUSPENDED,
(LPDWORD)&ThreadIDTCPRev);
(!hThreadTCPRev)
{
wsprf(Buf, "Error in creating TCP Receive thread: %d",GetLastError);
MessageBox (hWnd, Buf, "WM_CREATE", MB_OK);}
//开始侦听网络
SendMessage(hWnd,WM_COMMAND,IDM_LISTEN,NULL);
;
WM_VIDEO_ENCODE: //图象编码
(needencode)EncodeFunction(hWnd);
needencode = SendVideoToBuff(&vd_tx_sdu, buff);
frameMode=TRUE;
capPreview(capWnd,FALSE);
capOverlay(capWnd,FALSE);
capGrabFrameNoStop(capWnd);
;
WM_VIDEO_DECODE: //图象解码
Video_Decod_begin = 1;
play_movie;
Video_Decod_begin = 0;
;
WM_COMMAND:
switch(LOWORD(wParam))
{
IDM_CONNECT: //响应对方呼叫接通可视电话
WskConnect( hWnd );
ResumeThread(hThreadTCPRev); //运行TCP/IP接收线程
ResumeThread(hThreadG7231); //运行语音编解码线程
BeginG7231Codec; //化图象采集卡并开始采集图象
frameMode = FALSE;
capWnd = capCreateCaptureWindow((LPSTR)"Capture Window",
WS_CHILD | WS_VISIBLE,
100, 100, 176,144 ,
(HWND) hWnd, () 0);
capSetCallbackOnError(capWnd, (FARPROC)ErrorCallbackProc) ;
capSetCallbackOnStatus(capWnd, (FARPROC)StatusCallbackProc) ;
capSetCallbackOnFrame(capWnd, (FARPROC)FrameCallbackProc) ;
capDriverConnect(capWnd, 0);
CenterCaptureWindow(hWnd, capWnd);
capDlgVideoSource(capWnd);
capDlgVideoFormat(capWnd);
capDlgVideoCompression(capWnd);
capGetStatus(capWnd,&capStatus,(CAPSTATUS));
StartNewVideoChannel(hWnd, capWnd) ;
image = image_one;
frameMode = TRUE;
capPreview(capWnd,FALSE);
capOverlay(capWnd,FALSE);
capGrabFrameNoStop(capWnd);
;
IDM_LISTEN: //拨对方号码呼叫对方
sock = ( AF_INET, SOCK_STREAM, 0);
(sock INVALID_SOCKET) {
MessageBox(hWnd, " failed", "Error", MB_OK);
close(sock);
;}
(!FillAddr(hWnd, &local_sin, FALSE )) //获取TCP/IP地址和端口号
;
EnableMenuItem(GetMenu( hWnd ), IDM_LISTEN, MF_GRAYED);
SetWindowText( hWnd, "Waiting for connection..");
bind ( sock , (struct sockaddr FAR *)&local_sin,(local_sin);
(listen( sock, MAX_PENDING_CONNECTS ) <0)
{
sprf(szBuff, "%d is the error",
WSAGetLastError); MessageBox(hWnd, szBuff, "listen(sock) failed",
MB_OK);
;}
tp.hWnd="hWnd; //开始本地TCP/IP接收线程"
_beghread(AcceptThreadProc,0,&tp);
ResumeThread(hThreadG7231); // 开始本地语音编解码线程
;
IDM_DISCONNECT: //挂断可视电话
CloseG7231Codec;
SuspendThread(hThreadG7231);
SuspendThread(hThreadTCPRev);
WSACleanup;
Init_Video_Decod_Again;
capSetCallbackOnError(capWnd, NULL);
capSetCallbackOnStatus(capWnd, NULL);
InvalidateRect(hWnd,NULL,1); capSetCallbackOnFrame(capWnd, NULL);
capSetCallbackOnVideoStream(capWnd, NULL);
capDriverDisconnect(capWnd);
Init_Wsock(hWnd);
MessageBox(hWnd, "Now closing the Video telephone","",MB_OK);
SetDisConnectMenus(hWnd);
SendMessage(hWnd, WM_COMMAND,IDM_LISTEN,NULL);
;
IDM_EXIT:
CloseG7231Codec;
SendMessage(hWnd, WM_CLOSE, 0, 0l);
; default:
(DefWindowProc(hWnd, message, wParam, lParam));
}
;
WM_CLOSE:
(IDOK !="MessageBox(" hWnd, "OK to close window?", gszAppName,
MB_ICONQUESTION | MB_OKCANCEL )) ;
WM_DESTROY:
WSACleanup;
CloseG7231Codec;
TerminateThread(hThreadG7231,0);
TerminateThread(hThreadTCPRev,0);
capSetCallbackOnError(capWnd, NULL);
capSetCallbackOnStatus(capWnd, NULL);
capSetCallbackOnFrame(capWnd, NULL);
capSetCallbackOnVideoStream(capWnd, NULL);
capDriverDisconnect(capWnd);
FreeAll;
PostQuitMessage(0);
;
default: /* Passes it _disibledevent=> //阻塞accept,直到远端响应为止
sock="accept(" sock,(struct sockaddr FAR *) &acc_sin,( FAR *) &acc_sin_len );
(sock < 0)
{
sprf(szBuff, "%d is the error", WSAGetLastError);
MessageBox(ptp->hWnd, szBuff, "accept(sock) failed", MB_OK);
(1);
}
SetConnectMenus( ptp->hWnd ); //远端提机可视电话接通
BeginG7231Codec;
while (1)
{
beg1:
status = recv((SOCKET)sock, r_mux_buf,MY_MSG_LENGTH, NO_FLAGS_SET );
(status SOCKET_ERROR) {
status = WSAGetLastError;
( status 10054 ){
MessageBox(ptp->hWnd,"对方挂断电话","Indication", MB_OK);
SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
_endthread;
(1);
}
goto beg1;
}
(status) {
r_mux_buf[ status ] = '\0';
( r_mux_buf_filled 1 )
r_mux_buf_overwrite = 1;
r_mux_buf_filled = 1;
r_mux_buf_length = status;
}
{
MessageBox( hWnd, "Connection broken", "Error", MB_OK);
SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
_endthread;
(2);
}
demux; //线路码流H.223解码
}
(0);
}
//被叫方TCP/IP接收线程
DWORD WINAPI AcceptThreadProcRev( PTHREADPACK ptp )
{
status;
while (1)
{
beg2:
status = recv((SOCKET)sock, r_mux_buf,MY_MSG_LENGTH, NO_FLAGS_SET );
(status SOCKET_ERROR)
{
status =WSAGetLastError;
( status 10054 )
{
MessageBox(ptp->hWnd,"对方挂断电话","Indication", MB_OK);
SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
(1);
}
goto beg2;
}
(status)
{
r_mux_buf[ status ] = '\0';
( r_mux_buf_filled 1 )
r_mux_buf_overwrite = 1;
r_mux_buf_filled = 1;
r_mux_buf_length = status;
}
{
MessageBox( hWnd, "Connection broken", "Error", MB_OK);
SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
(2);
}
demux;
} /* while (forever) */
(0);
}
//语音编解码线程
DWORD WINAPI G723Proc(G7231DATA *data)
{
i,len;
Audio_tx_pduad_tx_pdu;
unsigned char mux[MAX_MUX_PDU_SIZE];
do
{
len = 0;
//检测本地有无语音图象码流要传输
i = DetectAudioVideoData;
switch(i)
{
AUDIO_ONLY: //只有语音码流
AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);
//H.223打包
len = AL2_To_MUX(&ad_tx_pdu, mux);
;
VIDEO_ONLY: //只有图象码流
SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);
tx_AL3_I_PDU(&vd_tx_pdu ,&bs , 1); //H.223打包
len = AL3_To_MUX(&vd_tx_pdu,mux);
;
AUDIO_VIDEO: //语音和图象码流
AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);
SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);
tx_AL3_I_PDU(&vd_tx_pdu ,&bs , 1);
//H.223打包
len = AL2_AL3_To_MUX(&ad_tx_pdu,&vd_tx_pdu,mux);
;
NO_AUDIO_VIDEO: //此刻无码流要传输
;
}
//TCP/IP发送码流
(len != 0)
send((SOCKET)sock,mux,len,0);
//是否接收到待解码码流有就解码器
PutVideoStreamToDecod;
}
while(1);
(0);
}
最新评论