pwlib 是一套跨平台的 C++ 的开发库 , 使基于 pwlib 上开发的应用能够很少量的移植就可以跑在 windows unix 的平台上
Open323
是澳洲的一家公司驱动的 open source 的是视频会议 h323 协议族实现 还不够十分的完整 但是已经是非常的难得了
windows 上和 linux 下都能编译使用 我已经试过了 . Windows 上编译他们比较麻烦 注意的是一定要用 batch building.  VC7 上编译 openh323 的动态连接库的时候 , VS.net 会崩溃 注意避开 不过也可以试试看看现象 如果能够解决 请告诉我一下
linux 上编译就没有什么好说的了 设好两个环境变量 (PWLIBDIR, OPENH323DIR),  就可以在展开的目录下编译了 先编译 PWLIB,  再编译 OPENH323,  别忘了将相应 xx/lib 写到 /etc/ld.so.conf 我这里可能对安装讲的不够详细 , openh323 讲的非常详细 大家可以去看

linux 平台为例
使用 pwlib,  在成功编译之后 $(PWLIBDIR)/SAMPLES/ 
这里是一些例子 , hello_world  是个非常简单的工程 从这里我们可以看到如何写使用 pwlib Makefile: 
# Simple makefile for the hello world program 
PROG = hello 
SOURCES = hello.cxx 
ifndef PWLIBDIR 
PWLIBDIR=$(HOME)/pwlib 
endif 
include $(PWLIBDIR)/make/ptlib.mak 
关键是包含了一个 ptlib.mak 

hello.cxx 
#include 
class Hello : public PProcess 

PCLASSINFO(Hello, PProcess) 
public: 
void Main(); 
}; 

PCREATE_PROCESS(Hello) 
void Hello::Main() 

cout << "Hello world!\n"; 

非常有代表性 . Include $(PWLIBDIR)/make/ptlib.mak  这样就可以 make all, make debug 的之类的进行编译 需要的头文件库都会替你安排好 编译的结果就会放在 obj_linux_x86_xx, xx  表示你用的是 debug 编译还是其他 如果是 debug, xx 就是 d. 

使用 pwlib 的程序 必然要有一个 PProcess 的子类 作为整个进程 这是指在 console 模式下 , gui 模式的用 PApplication 这个我没有用过 . Pwlib 里面的类大多都是 P 开头 , ( 可能是取其兼容的意思 跨平台的特性 我瞎猜的 ),  在进程中如果想创建新的线程就创建 PThread 子类的对象 对于这种关于过程的类 , 都有 Main 函数等待子类去实现
在使用所有的 P 类的时候 注意使用两个宏 声明类的时候 PCLASSINFO(Hello, PProcess);  分号可以加 也可不加 . PProcess 的子类的实现的时候要用 PCREATE_PROCESS(Hello); 这个东西把 main() 之类的系统入口封装了 由他来调用 Main() 成员函数 在使用线程的时候 如果想让线程从线程的对象一创建就运行 就应该在 PThread 子类中的构造函数中调用父类的 Resume().  关于 pwlib 先说这些 在使用 Openh323 的时候到处都会用到 pwlib 的东西和概念

Openh323: 
终于进入正题了 先粗略的讲点概念 ( 多余了 ), H323 是指协议族了 包含了很多规范 它来自 ITU,  应会议的需要而产生 信令相关的东西用 H225 H245, 类似 Q931, ASN1 编码后在 tcp 之上传输 数据相关的就是编码解码的东西了 ( 包括音频视频 ),  音频 g711(alaw, ulaw) 了等等多了 视频 h261,  好像 h263 还没实现
H323 的系统里进行通讯的角色实体就是 Endpoint,  每个 Endpoint 可以有很多的 Connection,  每个 Endpoint 也可以拥有很多的逻辑角色 这个不讨论
Endpoint 
Openh323 中就是类 H323Endpoint 的实例  
Connection 
Openh323 中就是  H323Connection 的实例  
Endpoint 接收了一个远程的连接请求 , Endpoint 就会创建一个 H323Connection; 
Endpoint 发出一个连接的请求 , Endpoint 也会创建一个 H323Connection 
Connection 
就会进入一个状态机 在各个状态中 , Connetcion 会相应的执行相应的方法 这些方法 大多都是 Onxxxxx(),  是虚函数 我们可以自己通过继承 H323Connection 创建其子类 并且在我们想做事的时机去重载相应的虚函数 这是使用 Openh323 的一个基本的思路
现在我们可以看看如何写一个自己 H323 Endpoint,  让它能够和 netmeeting 互操作 . 成功编译 Openh323 后在它的 samples 的目录下面有几个例子 , mfc 是指在 windows 下如何使用 MFC Openh323 一起开发 还有 simple,  这是个简单的 H323 Endpoint 的实现 作为理解 OpenH323 的库如何使用和开发的技巧方法已经足够了
程序运行主线
PWLIB(PCREATE_PROCESS(SimpleH323Process))--?SimpleH323Process:: SimpleH323Process()--?SimpleH323Process::Main(); 
Main()
如果结束 这个程序就结束了 可是 Main() 里面有个死循环 写过图形程序的朋友们都知道 这就是在等消息来呀 VC 中称之为 Interface thread. 
程序注解
main.h 
这个文件包含了程序用到的所有类的声明 一般应该至少有三个类
来自 PProcess 的一个主进程的 或者说作为界面线程的 ;( 只有一个对象
来自 H323Endpoint 标识这个 H323 端点的 ;( 只有一个对象
来自 H323Connection 标识所有和这个 H323 端点相关的连接 ;( 可以有多个

#ifndef _SimpleH323_MAIN_H 
#define _SimpleH323_MAIN_H 
//
避免头文件重复包含  

#include 

class SimpleH323EndPoint : public H323EndPoint 

//
使用 Pwlib 的要求 就像使用 MFC,  n 多的宏 可以看看 pwlib 的源码
//
宏展开都干了什么  
PCLASSINFO(SimpleH323EndPoint, H323EndPoint); 

public: 
SimpleH323EndPoint(); 
~SimpleH323EndPoint(); 

// overrides from H323EndPoint 
// 
重载 H323EndPoint 的函数  

// 
当收到一个远程的呼入和发出呼出的请求的时候  
virtual H323Connection * CreateConnection(unsigned callReference); 
// 
有远程的请求来到 这是在 CreateConnection 之后的  
virtual BOOL OnIncomingCall(H323Connection &, const H323SignalPDU &, H323SignalPDU &); 
//
应答远程的呼入  
virtual H323Connection::AnswerCallResponse OnAnswerCall(H323Connection &, const PString &, const H323SignalPDU &, H323SignalPDU 
&); 
//
当连接被 Forward 
virtual BOOL OnConnectionForwarded(H323Connection &, const PString &, const H323SignalPDU &); 
//
当连接建立  
virtual void OnConnectionEstablished(H323Connection & connection, const PString & token); 
//
当连接撤销  
virtual void OnConnectionCleared(H323Connection & connection, const PString & clearedCallToken); 
//
当连接需要打开声音的通道  
virtual BOOL OpenAudioChannel(H323Connection &, BOOL, unsigned, H323AudioCodec &); 

// New functions 
// 
自己添加的新函数 父类中不存在  
BOOL Initialise(PArgList &); 
BOOL SetSoundDevice(PArgList &, const char *, PSoundChannel::Directions); 
// 
每个连接会有一个 Token 来唯一标识  
PString currentCallToken; 

protected: 
BOOL autoAnswer; 
PString busyForwardParty; 
}; 

class SimpleH323Connection : public H323Connection 

PCLASSINFO(SimpleH323Connection, H323Connection); 

public: 
//
创建连接对象的时候将 Endpoint 的对象以引用传进来  
//
引用的概念就是将整个对象暴露给你的意思 不是复制了一份的意思
//
对象还是原来的对象 所以在 Connection 中修改了 EndPoint 的某些属性后  
//
就是在操作着传进来的对象 这是 C++ 的基本概念 , OpenH323 大量的使用  
//
引用传递对象 对引用的概念要理解  
SimpleH323Connection(SimpleH323EndPoint &, unsigned); 

//
重载了两个父类的函数  

// 
当打开逻辑通道的时候 ( 等于没说
virtual BOOL OnStartLogicalChannel(H323Channel &); 
// 
处理用户输入 这个不是之运行这个程序的用户 , 而是这个连接上的用户输入  
// 
一般应该是拨号了之类的
virtual void OnUserInputString(const PString &); 

protected: 
// 
快速连接 ?? 
BOOL noFastStart; 
}; 
class SimpleH323Process : public PProcess 

//
主进程 类似 VC 的用户界面线程
//
他是整个程序的入口点 和结束点  
//
创建了 EndPoint 对象后会有好几个线程启动  
//
这个就是主线程  
PCLASSINFO(SimpleH323Process, PProcess) 

public: 
SimpleH323Process(); 
~SimpleH323Process(); 
//
这个函数会被自动调用 是我们程序的入口了  
void Main(); 
protected: 

//
这个 H323 端点对象  
SimpleH323EndPoint * endpoint; 
}; 

#endif // _SimpleH323_MAIN_H 

下面是 main.cpp  所有的类的实现了  

#include 

#ifdef __GNUC__ 
#define H323_STATIC_LIB 
#endif 

#include "main.h" 
#include "../../version.h" 


#define new PNEW 

// 
这个东西里边可能封装了标准的 main 函数  
PCREATE_PROCESS(SimpleH323Process); 


/// 

//
几个宏都在 version.h 里面定义  
SimpleH323Process::SimpleH323Process() 
: PProcess("OpenH323 Project", "SimpleH323", 
MAJOR_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER) 

endpoint = NULL; 


SimpleH323Process::~SimpleH323Process() 

delete endpoint; 

void SimpleH323Process::Main() 

cout << GetName() 
<< " Version " << GetVersion(TRUE) 
<< " by " << GetManufacturer() 
<< " on " << GetOSClass() << << GetOSName() 
<< " (" << GetOSVersion() << - << GetOSHardware() << ")\n\n"; 

// Get and parse all of the command line arguments. 
// 
分析命令行参数 略去数行  
PArgList & args = GetArguments(); 
args.Parse( 
"a-auto-answer." 
"b-bandwidth:" 
"B-forward-busy:" 
"D-disable:” FALSE); 
if (args.HasOption(h) || (!args.HasOption(l) && args.GetCount() == 0)) { 
//
如果没有参数或者参数是 h,  就输出如何使用 此处略去数行  

//
这个东西暂时不管  
#if PTRACING 
#endif 

// Create the H.323 endpoint and initialise it 
// H323 EndPoint 
创建了 并且把命令参数传过去初始化 初始化的时候做了一些事  
endpoint = new SimpleH323EndPoint; 
if (!endpoint->Initialise(args)) 
return; 
//
看看命令行里是不是想直接呼叫另一个 H323 endpoint. 有没有 l(listen) option 
//
如果是就 MakeCall, 
// See if making a call or just listening. 
if (args.HasOption(l)) 
cout << "Waiting for incoming calls for \"" << endpoint->GetLocalUserName() << "\"\n"; 
else { 
cout << "Initiating call to \"" << args[0] << "\"\n"; 
endpoint->MakeCall(args[0], endpoint->currentCallToken); 

cout << "Press X to exit." << endl; 

// Simplest possible user interface 
// 
简单的用户界面 会有一个提示
// 
pid 是我加的  
for (;;) { 
pid_t thispid; 
char prom[20]; 

thispid = getpid(); 
sprintf(prom, "H323 %d >", thispid); 

cout << prom << flush; 
PCaselessString cmd; 
cin >> cmd; 
if (cmd == "X") 
break; 

if (cmd.FindOneOf("HYN") != P_MAX_INDEX) { 
H323Connection*connection; 
//
使用 lock 就是怕别的线程把它给删了  
//
因为这里正用着呢  
connection=endpoint->FindConnectionWithLock(endpoint->currentCallToken); 
if (connection != NULL) { 
if (cmd == "H") 
connection->ClearCall(); 
else if (cmd == "Y") 
connection->AnsweringCall(H323Connection::AnswerCallNow); 
else if (cmd == "N") 
connection->AnsweringCall(H323Connection::AnswerCallDenied); 
connection->Unlock(); 




cout << "Exiting " << GetName() << endl; 

// Main 
函数结束  

// 
自己的 Init 函数  
BOOL SimpleH323EndPoint::Initialise(PArgList & args) 

// Get local username, multiple uses of -u indicates additional aliases 
if (args.HasOption(u)) { 
PStringArray aliases = args.GetOptionString(u).Lines(); 
// 
设定改 Endpoint username 
SetLocalUserName(aliases[0]); 
// 
设定 Aliases  就是每个 Endpoint 可以有好多名字的意思  
for (PINDEX i = 1; i < aliases.GetSize(); i++) 
AddAliasName(aliases[i]); 


// Set the various options 
//
设置静音检测否  

SetSilenceDetectionMode(args.HasOption(e) ? H323AudioCodec::NoSilenceDetection 
: H323AudioCodec::AdaptiveSilenceDetection); 
//
快速连接
DisableFastStart(args.HasOption(f)); 
//H245
通道  
DisableH245Tunneling(args.HasOption(T)); 

autoAnswer = args.HasOption(a); 
busyForwardParty = args.GetOptionString(B); 

if (args.HasOption()) { 
initialBandwidth = args.GetOptionString().AsUnsigned()*100; 
if (initialBandwidth == 0) { 
cerr << "Illegal bandwidth specified." << endl; 
return FALSE; 



if (args.HasOption(j)) { 
unsigned jitter = args.GetOptionString(j).AsUnsigned(); 
//
设定音频抖动的 应该影响到接收的缓存  
if (jitter >= 20 && jitter <= 10000) 
SetMaxAudioDelayJitter(jitter); 
else { 
cerr << "Jitter should be between 20 milliseconds and 10 seconds." << endl; 
return FALSE; 



//
设定声音设备  
//
也可以不用声音设备 比如 Openh323 工程的子项目  OpenAM OpenMCU 
//
都使演示了如何不使用声音物理设备的方法 我想那里边的东西会对某些朋友们  
//
的需求比较合适  
if (!SetSoundDevice(args, "sound", PSoundChannel::Recorder)) 
return FALSE; 
if (!SetSoundDevice(args, "sound", PSoundChannel::Player)) 
return FALSE; 
if (!SetSoundDevice(args, "sound-in", PSoundChannel::Recorder)) 
return FALSE; 
if (!SetSoundDevice(args, "sound-out", PSoundChannel::Player)) 
return FALSE; 

// 
设定 decode encode 的能力  
// H323 EndPoint
在真正进行数据通讯之前要进行能力的交换 说明自己能够接收和发送什么标准的数据 , g.711 是必须支持的
// Set the default codecs available on sound cards. 
AddAllCapabilities(0, 0, "GSM*{sw}"); 
AddAllCapabilities(0, 0, "G.711*{sw}"); 
AddAllCapabilities(0, 0, "LPC*{sw}"); 
AddAllUserInputCapabilities(0, 1); 

RemoveCapabilities(args.GetOptionString(D).Lines()); 
ReorderCapabilities(args.GetOptionString(P).Lines()); 

cout << "Local username: " << GetLocalUserName() << "\n" 
<< "Silence compression is " << (GetSilenceDetectionMode() == H323AudioCodec::NoSilenceDetection ? "Dis" : "En") << "abled\n" 
<< "Auto answer is " << autoAnswer << "\n" 
<< "FastConnect is " << (IsFastStartDisabled() ? "Dis" : "En") << "abled\n" 
<< "H245Tunnelling is " << (IsH245TunnelingDisabled() ? "Dis" : "En") << "abled\n" 
<< "Jitter buffer: " << GetMaxAudioDelayJitter() << " ms\n" 
<< "Sound output device: \"" << GetSoundChannelPlayDevice() << "\"\n" 
"Sound input device: \"" << GetSoundChannelRecordDevice() << "\"\n" 
<< "Codecs (in preference order):\n" << setprecision(2) << GetCapabilities() << endl; 

//
启动一个来电的监听  
//
可以使用配置的端口 也可以使用 default 的端口  
// Start the listener thread for incoming calls. 
H323ListenerTCP * listener; 
if (args.GetOptionString(i).IsEmpty()) 
listener = new H323ListenerTCP(*this); 
else { 
PIPSocket::Address interfaceAddress(args.GetOptionString(i)); 
listener = new H323ListenerTCP(*this, interfaceAddress); 

if (!StartListener(listener)) { 
cerr << "Could not open H.323 listener port on " 
<< listener->GetListenerPort() << endl; 
delete listener; 
return FALSE; 


//
这是连接 GateKeeper 相关的东西 先不讨论了  
// Initialise the security info 
if (args.HasOption(p)) { 
SetGatekeeperPassword(args.GetOptionString(p)); 
cout << "Enabling H.235 security access to gatekeeper." << endl; 


// Establish link with gatekeeper if required. 
if (args.HasOption(g) || !args.HasOption( )) { 
H323TransportUDP * rasChannel; 
if (args.GetOptionString(i).IsEmpty()) 
rasChannel = new H323TransportUDP(*this); 
else { 
PIPSocket::Address interfaceAddress(args.GetOptionString(i)); 
rasChannel = new H323TransportUDP(*this, interfaceAddress); 


if (args.HasOption(g)) { 
PString gkName = args.GetOptionString(g); 
if (SetGatekeeper(gkName, rasChannel)) 
cout << "Gatekeeper set: " << *gatekeeper << endl; 
else { 
cerr << "Error registering with gatekeeper at \"" << gkName << \" << endl; 
return FALSE; 


else { 
cout << "Searching for gatekeeper..." << flush; 
if (DiscoverGatekeeper(rasChannel)) 
cout << "\nGatekeeper found: " << *gatekeeper << endl; 
else { 
cerr << "\nNo gatekeeper found." << endl; 
if (args.HasOption( )) 
return FALSE; 




return TRUE; 


//
设定音频设备 没什么可讲的  
BOOL SimpleH323EndPoint::SetSoundDevice(PArgList & args, 
const char * optionName, 
PSoundChannel::Directions dir) 

if (!args.HasOption(optionName)) 
return TRUE; 

PString dev = args.GetOptionString(optionName); 

if (dir == PSoundChannel::Player) { 
if (SetSoundChannelPlayDevice(dev)) 
return TRUE; 

else { 
if (SetSoundChannelRecordDevice(dev)) 
return TRUE; 


cerr << "Device for " << optionName << " (\"" << dev << "\") must be one of:\n"; 

PStringArray names = PSoundChannel::GetDeviceNames(dir); 
for (PINDEX i = 0; i < names.GetSize(); i++) 
cerr << " \"" << names[i] << "\"\n"; 

return FALSE; 


//
这个函数很简单但是非常关键 是从 EndPoint 中重载过来的
//
本来是 return new H323Connection() 现在改成 Simplexxx 
//
自己实现的一个 Connection,  这样当 Endpoint 里面调用  
//Connection
的一些东西的时候 实际上运行的是 Simplexxx 
//
的实现 看到 C++ 的好处了吧 , C 里用函数指针也可以实现 没有  
//C++
这么 native. 
H323Connection * SimpleH323EndPoint::CreateConnection(unsigned callReference) 

return new SimpleH323Connection(*this, callReference); 

//
没什么东西 关键是看看这个东西的调用的时机  
BOOL SimpleH323EndPoint::OnIncomingCall(H323Connection & connection, 
const H323SignalPDU &, 
H323SignalPDU &) 

if (currentCallToken.IsEmpty()) 
return TRUE; 

if (busyForwardParty.IsEmpty()) { 
cout << "Incoming call from \"" << connection.GetRemotePartyName() << "\" rejected, line busy!" << endl; 
return FALSE; 


cout << "Forwarding call to \"" << busyForwardParty << "\"." << endl; 
return !connection.ForwardCall(busyForwardParty); 



//
这个东西 很有用 , H323Connection 的类里也有这个虚函数  
//
返回的值决定告诉远程的连接者是否接收这份连接请求  
H323Connection::AnswerCallResponse 
SimpleH323EndPoint::OnAnswerCall(H323Connection & connection, 
const PString & caller, 
const H323SignalPDU &, 
H323SignalPDU &) 

currentCallToken = connection.GetCallToken(); 

if (autoAnswer) { 
cout << "Automatically accepting call." << endl; 
return H323Connection::AnswerCallNow; 


cout << "Incoming call from \"" 
<< caller 
<< "\", answer call (Y/n)? " 
<< flush; 

return H323Connection::AnswerCallPending; 


BOOL SimpleH323EndPoint::OnConnectionForwarded(H323Connection & /*connection*/, 
const PString & forwardParty, 
const H323SignalPDU & /*pdu*/) 

if (MakeCall(forwardParty, currentCallToken)) { 
cout << "Call is being forwarded to host " << forwardParty << endl; 
return TRUE; 


cout << "Error forwarding call to \"" << forwardParty << \" << endl; 
return FALSE; 



//
连接建立时候  
void SimpleH323EndPoint::OnConnectionEstablished(H323Connection & connection, 
const PString & token) 

currentCallToken = token; 
cout << "In call with " << connection.GetRemotePartyName() << endl; 


//
连接断开时候  
void SimpleH323EndPoint::OnConnectionCleared(H323Connection & connection, 
const PString & clearedCallToken) 

if (currentCallToken == clearedCallToken) 
currentCallToken = PString(); 

PString remoteName = \" + connection.GetRemotePartyName() + \"; 
switch (connection.GetCallEndReason()) { 
case H323Connection::EndedByRemoteUser : 
cout << remoteName << " has cleared the call"; 
break; 
case H323Connection::EndedByCallerAbort : 
cout << remoteName << " has stopped calling"; 
break; 
case H323Connection::EndedByRefusal : 
cout << remoteName << " did not accept your call"; 
break; 
case H323Connection::EndedByNoAnswer : 
cout << remoteName << " did not answer your call"; 
break; 
case H323Connection::EndedByTransportFail : 
cout << "Call with " << remoteName << " ended abnormally"; 
break; 
case H323Connection::EndedByCapabilityExchange : 
cout << "Could not find common codec with " << remoteName; 
break; 
case H323Connection::EndedByNoAccept : 
cout << "Did not accept incoming call from " << remoteName; 
break; 
case H323Connection::EndedByAnswerDenied : 
cout << "Refused incoming call from " << remoteName; 
break; 
case H323Connection::EndedByNoUser : 
cout << "Gatekeeper could find user " << remoteName; 
break; 
case H323Connection::EndedByNoBandwidth : 
cout << "Call to " << remoteName << " aborted, insufficient bandwidth."; 
break; 
case H323Connection::EndedByUnreachable : 
cout << remoteName << " could not be reached."; 
break; 
case H323Connection::EndedByHostOffline : 
cout << remoteName << " is not online."; 
break; 
case H323Connection::EndedByNoEndPoint : 
cout << "No phone running for " << remoteName; 
break; 
case H323Connection::EndedByConnectFail : 
cout << "Transport error calling " << remoteName; 
break; 
default : 
cout << "Call with " << remoteName << " completed"; 

cout << ", duration " 
<< setprecision(0) << setw(5) 
<< (PTime() - connection.GetConnectionStartTime()) 
<< endl; 


//
打开声音设备时候  
//isEncoding 
表示编码吗  
//
编码表示向外发送数据 从声音设备读  
//
解码表示从网络读出数据 写到声音设备上  
//
不同的方向的 codec 是不同的 所以在这里有好多文章可以做  
//
可以给 codec attach 上不同的 channel 根据 isEncoding 的值  
BOOL SimpleH323EndPoint::OpenAudioChannel(H323Connection & connection, 
BOOL isEncoding, 
unsigned bufferSize, 
H323AudioCodec & codec) 

if (H323EndPoint::OpenAudioChannel(connection, isEncoding, bufferSize, codec)) 
return TRUE; 

cerr << "Could not open sound device "; 
if (isEncoding) 
cerr << GetSoundChannelRecordDevice(); 
else 
cerr << GetSoundChannelPlayDevice(); 
cerr << " - Check permissions or full duplex capability." << endl; 

return FALSE; 

// 
EndPoint
的实现分析完毕


H323Connection
的实现 这个 Connection 的实现太简单了 . 可能不足以说明问题  
我也没什么好说的了  
/// 

SimpleH323Connection::SimpleH323Connection(SimpleH323EndPoint & ep, unsigned ref) 
: H323Connection(ep, ref) 




BOOL SimpleH323Connection::OnStartLogicalChannel(H323Channel & channel) 

if (!H323Connection::OnStartLogicalChannel(channel)) 
return FALSE; 

cout << "Started logical channel: "; 

switch (channel.GetDirection()) { 
case H323Channel::IsTransmitter : 
cout << "sending "; 
break; 

case H323Channel::IsReceiver : 
cout << "receiving "; 
break; 

default : 
break; 


cout << channel.GetCapability() << endl; 

return TRUE; 




void SimpleH323Connection::OnUserInputString(const PString & value) 

cout << "User input received: \"" << value << \" << endl; 

// End of File /// 

总结一下基本的过程就是创建一个 H323Endpoint 的对象 endpoint,  创建对象后这个程序就有好多个小的线程被创建了 . 然后 EndPoint 开始监听来电 之后判断是否直接呼叫另一个 h323 Endpoint.  然后就是一个 for 循环 判断标准的输入 并通过当前的 token lock 一个 Connection,  每个连接会有唯一的一个 token, lock 的意思是说 在被 lock 的期间是不能被释放的 根据输入的字符决定对得到的连接做什么

OpenAM: 
是个 answer machine,  自动应答机 或者是留言机 实现的很简单 里面对 OpenH323 使用的思路很有价值
./openam –n –-g711message sample_message.wav 
这样运行 netmeeting  连接一下这个 IP, netmeeting 就会放一段简单的英语 测测你的英语听力 他在讲什么
这个程序是一个支持多连接和并发连接的 Endpoint,  但是他没有使用真正的声音设备 放出的音从一个已有的 wav 文件中读出来 远程用户的留言被录到一个文件里 文件的名字表示了是什么时间录制的
主要的思路是给在连接打开声音通道的时候 根据 isEncoding 的值区别是录音还是放音 , 如果是录音 将读文件的 Channel 附加在 codec 相反写文件的 Channel 附件在 codec , 注意这是两个 codec. 
这个东西给了我们一个方法 如何使用文件 IO 来代替声音设备的 IO 来使用 OpenH323. 


这是 main.h 

#ifndef _Voxilla_MAIN_H 
#define _Voxilla_MAIN_H 

#include 
#include 
#include 
#include 
#include 

#include 
#include 

//
主进程  
class OpenAm : public PProcess 

PCLASSINFO(OpenAm, PProcess) 

public: 
OpenAm(); 
~OpenAm(); 

void Main(); 
void RecordFile(PArgList & args); 
void PlayFile(PArgList & args); 

protected: 
long GetCodec(const PString & codecname); 
OpalLineInterfaceDevice * GetDevice(const PString & device); 
}; 

//H323 
端点  
class MyH323EndPoint : public H323EndPoint 

PCLASSINFO(MyH323EndPoint, H323EndPoint); 

public: 
MyH323EndPoint(unsigned callLimit, 
const PString & runCmd, 
const PDirectory & dir, 
int flags); 

// overrides from H323EndPoint 
virtual H323Connection * CreateConnection(unsigned callReference); 
BOOL OnIncomingCall(H323Connection &, const H323SignalPDU &, H323SignalPDU &); 

// new functions 
BOOL Initialise(PConfigArgs & args); 

PString GetGSMOGM() const { return gsmOgm; } 
void SetGSMOGM(const PString & s) { gsmOgm = s; } 

PString GetG711OGM() const { return g711Ogm; } 
void SetG711OGM(const PString & s) { g711Ogm = s; } 

PString GetLPC10OGM() const { return lpc10Ogm; } 
void SetLPC10OGM(const PString & s) { lpc10Ogm = s; } 

#ifdef SPEEX_CODEC 
PString GetSPEEXOGM() const { return speexOgm; } 
void SetSPEEXOGM(const PString & s) { speexOgm = s; } 
#endif 

PString GetG7231OGM() const { return g7231Ogm; } 
void SetG7231OGM(const PString & s) { g7231Ogm = s; } 

unsigned GetCallLimit() const { return callLimit; } 
PString GetRunCmd() const { return runCmd; } 
PDirectory GetDirectory() const { return dir; } 

void SetRecordWav(const BOOL rec){ recordWav = rec; } 
BOOL GetRecordWav() const { return recordWav; } 

enum { 
DeleteAfterRecord = 0x01, 
NoRecordG7231 = 0x02, 
HangupAfterPlay = 0x04 
}; 

BOOL GetDeleteAfterRecord() const { return flags & DeleteAfterRecord; } 
BOOL GetNoRecordG7231() const { return flags & NoRecordG7231; } 
BOOL GetHangupAfterPlay() const { return flags & HangupAfterPlay; } 

protected: 
unsigned callLimit; 
PString pcmOgm, g711Ogm, gsmOgm, lpc10Ogm, g7231Ogm, runCmd; 
#ifdef SPEEX_CODEC 
PString speexOgm; 
#endif 
PDirectory dir; 
int flags; 
BOOL recordWav; 
}; 

class PCM_RecordFile; 
class MyH323Connection; 
PQUEUE(PStringQueue, PString); 
// Out Going Channel OGM 
//
就是发送语音的通道  
//
即是读文件的通道  
class PCM_OGMChannel : public PIndirectChannel 

PCLASSINFO(PCM_OGMChannel, PIndirectChannel); 

public: 
PCM_OGMChannel(MyH323Connection & conn); 

BOOL Read(void * buffer, PINDEX amount); 
void PlayFile(PFile * chan); 

BOOL Close(); 

void QueueFile(const PString & cmd); 
void FlushQueue(); 

void SetRecordTrigger(); 
void SetHangupTrigger(); 

void SetPlayOnce() { playOnce = TRUE; } 

protected: 
virtual BOOL ReadFrame(PINDEX amount); 
virtual void CreateSilenceFrame(PINDEX amount); 
virtual void Synchronise(PINDEX amount); 
virtual BOOL IsWAVFileValid(PWAVFile *chan); 

BOOL AdjustFrame(void * buffer, PINDEX amount); 

PStringQueue playQueue; 

MyH323Connection & conn; 
PMutex chanMutex; 
int silentCount; 
int totalData; 
BOOL recordTrigger, hangupTrigger; 
BOOL closed; 
BOOL playOnce; 

PAdaptiveDelay ogm_delay; 

PBYTEArray frameBuffer; 
PINDEX frameLen, frameOffs; 
}; 
//
这个是之读的文件是个 g723 编码的文件 暂时不研究这个类相关的一切  
class G7231_OGMChannel : public PCM_OGMChannel 

PCLASSINFO(G7231_OGMChannel, PCM_OGMChannel); 
public: 
G7231_OGMChannel(MyH323Connection & conn); 

protected: 
BOOL ReadFrame(PINDEX amount); 
void CreateSilenceFrame(PINDEX amount); 
void Synchronise(PINDEX amount); 
BOOL IsWAVFileValid(PWAVFile *chan); 
}; 

//
连接 , 都是从这个类实例出来的  
class MyH323Connection : public H323Connection 

PCLASSINFO(MyH323Connection, H323Connection); 

public: 
MyH323Connection(MyH323EndPoint &, unsigned); 
~MyH323Connection(); 

// overrides from H323Connection 
BOOL OpenAudioChannel(BOOL, unsigned, H323AudioCodec & codec); 
AnswerCallResponse OnAnswerCall(const PString &, const H323SignalPDU &, H323SignalPDU &); 
BOOL OnStartLogicalChannel(H323Channel & channel); 
void OnUserInputString(const PString & value); 

// new functions 
void StartRecording(); 
void Hangup(); 

void SetE164Number(const PString & _num) 
{ e164Number = _num; } 

PString GetE164Number() const 
{ return e164Number; } 

protected: 
void OnUserInputChar(char ch); 
BOOL StartMenu(int menuNumber); 
BOOL ProcessMenuCmd(const PString & cmdStr); 

const MyH323EndPoint & ep; 
PString product; 
PTime callStartTime; 
PTime recordStartTime; 
PString basename; 
PFilePath recordFn; 
PString transmitCodecName, receiveCodecName; 
BOOL recordTrigger; 
PMutex connMutex; 

PCM_RecordFile * recordFile; 
PCM_OGMChannel * ogmChannel; 

PString digits, lastDigits; 
int currentMenu; 
PStringList menuNames; 

PString securityToken, e164Number; 
}; 

//
是录音  
class PCM_RecordFile : public PIndirectChannel 

PCLASSINFO(PCM_RecordFile, PIndirectChannel) 

public: 
PCM_RecordFile(MyH323Connection & conn, const PFilePath & fn, unsigned callLimit); 
~PCM_RecordFile(); 

BOOL Write(const void * buf, PINDEX len); 
BOOL Close(); 
void StartRecording(); 

virtual void DelayFrame(PINDEX len); 
virtual BOOL WriteFrame(const void * buf, PINDEX len); 

BOOL WasRecordStarted() const { return recordStarted; } 

protected: 
MyH323Connection & conn; 
PTime finishTime; 
PFilePath fn; 
unsigned callLimit; 
BOOL recordStarted; 
BOOL timeLimitExceeded; 
BOOL closed; 
BOOL isPCM; 
BOOL dataWritten; 
PAdaptiveDelay delay; 
PMutex pcmrecordMutex; 
PFile *fileclass; // will point to a PWAVFile or PFile class 
}; 
//
录的结果是个 g723 文件 我们暂时不考虑这个类相关的一切  
class G7231_RecordFile : public PCM_RecordFile 

PCLASSINFO(G7231_RecordFile, PCM_RecordFile); 

public: 
G7231_RecordFile(MyH323Connection & conn, const PFilePath & fn, unsigned callLimit); 
void DelayFrame(PINDEX len); 
BOOL WriteFrame(const void * buf, PINDEX len); 
}; 


#endif // _Voxilla_MAIN_H 


// End of File /// 


这是 main.cxx 
#include 
#include 

#include "version.h" 
#include "lpc10codec.h" 

#ifdef SPEEX_CODEC 
#include "speexcodec.h" 
#endif 

#include "mscodecs.h" 
#include "opalvxml.h" 
#include "main.h" 

PCREATE_PROCESS(OpenAm); 

#define new PNEW 

//default 
录音时间  
#define DEFAULT_MSG_LIMIT 30 
#define DEFAULT_CALL_LOG "call_log.txt" 

#define G7231_SAMPLES_PER_BLOCK 240 

#define CHECK_PCM 1 
#define CHECK_G7231 2 

#define MENU_PREFIX "UserMenu-" 

static PMutex logMutex; 
static PTextFile logFile; 
static PFilePath logFilename = DEFAULT_CALL_LOG; 

PString G7231Ext = ".g723"; 
PString WAVExt = ".wav"; 
PString PCMExt = ".sw"; 

//
关于 log 的一切先不用看  
static void LogMessage(const PString & str) 

PTime now; 
PString msg = now.AsString("hh:mm:ss dd/MM/yyyy") & str; 
logMutex.Wait(); 

if (!logFile.IsOpen()) { 
logFile.Open(logFilename, PFile::ReadWrite); 
logFile.SetPosition(0, PFile::End); 


logFile.WriteLine(msg); 

logFile.Close(); 

logMutex.Signal(); 


static void LogCall(const PFilePath & fn, 
const PString & from, 
const PString & user, 
unsigned len, 
const PString & codec, 
const PString & product) 

PString addr = from; 
LogMessage(addr & "\"" + user + "\"" & PString(PString::Unsigned, len) & codec & "\"" + product + "\"" & "\"" + fn + "\""); 



/// 

OpenAm::OpenAm() 
: PProcess("OpenH323 Project", "OpenAM", 
MAJOR_