使用OpenH323开发入门

 使用OpenH323开发 bricks 原创 
[email protected] 
必备软件包可以从http://www.openh323.org下载. 
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_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER) 




OpenAm::~OpenAm() 




void OpenAm::Main() 

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

// 分析命令行了 
PConfigArgs args(GetArguments()); 

args.Parse( 
"D-disable:" 
"d-directory:" 
"g-gatekeeper:" "n-no-gatekeeper." 
"-g711-ulaw." "-no-g711-ulaw." 
"-g711-alaw." "-no-g711-alaw." 
"-g711message:" "-no-g711message." 
"-g7231." "-no-g7231." 
"-g7231message:" "-no-g7231message." 
"-gsm." "-no-gsm." 
"-gsmmessage:" "-no-gsmmessage." 
"h-help." 
"H-hangup." "-no-hangup." 
"i-interface:" "-no-interface." 
"k-kill." "-no-kill." 
"l-limit:" "-no-limit." 
"-listenport:" "-no-listenport." 
"-lpc10message:" "-no-lpc10message." 
"-speexmessage:" "-no-speexmessage." 
"m-message:" "-no-message." 
"-no-recordg7231." 
#if PTRACING 
"o-output:" 
#endif 
"P-prefer:" 
"-pcm." "-no-pcm." 
"-pcmmessage:" "-no-pcmmessage." 
"-port:" 
"q-quicknet:" "-no-quicknet:" 
"r-run:" "-no-run." 
"-recordraw." 
"-require-gatekeeper." "-no-require-gatekeeper." 
"-save." 
#if PMEMORY_CHECK 
"-setallocationbreakpoint:" 
#endif 
#if PTRACING 
"t-trace." 
#endif 
"u-username:" "-no-username." 
, FALSE); 

#if PMEMORY_CHECK 
if (args.HasOption("setallocationbreakpoint")) 
PMemoryHeap::SetAllocationBreakpoint(args.GetOptionString("setallocationbreakpoint").AsInteger()); 
#endif 

#if PTRACING 
PTrace::Initialise(args.GetOptionCount( ), 
args.HasOption(o) ? (const char *)args.GetOptionString(o) : NULL); 
#endif 

if (args.HasOption(h)) { 
cout << "Usage : " << GetName() << " [options]\n" 
"Options:\n" 
" -d --directory dir : Put recorded mesages into dir\n" 
" -l --limit secs : Limit recorded messages to secs duration (default " << DEFAULT_MSG_LIMIT << ")\n" 
" -m --pcmmessage fn : Set outgoing message for PCM derived codecs (G.711/GSM) to fn\n" 
" --g7231message fn : Set outgoing message for G723.1 codec to fn\n" 
" --g711message fn : Set outgoing message for G711 codec to fn\n" 
" --gsmmessage fn : Set outgoing message for GSM codec to fn\n" 
" --lpc10message fn : Set outgoing message for LPC10 codec to fn\n" 
#ifdef SPEEX_CODEC 
" --speexmessage fn : Set outgoing message for Speex codec to fn\n" 
#endif 

" --recordraw : Record PCM audo in raw files (.sw) instead of .wav\n" 
" -r --run cmd : Run this command after each recorded message\n" 
" -k --kill : Kill recorded files after user command\n" 
" -H --hangup : hangup after playing message\n" 
" -u --username str : Set the local endpoint name to str\n" 
" -i --interface ip : Bind to a specific interface\n" 
" --listenport port : Listen on a specific port\n" 
" -g --gatekeeper host: Specify gatekeeper host.\n" 
" -n --no-gatekeeper : Disable gatekeeper discovery.\n" 
" --require-gatekeeper: Exit if gatekeeper discovery fails.\n" 
" -D --disable codec : Disable the specified codec (may be used multiple times)\n" 
" -P --prefer codec : Prefer the specified codec (may be used multiple times)\n" 
#if PTRACING 
" -t --trace : Enable trace, use multiple times for more detail\n" 
" -o --output : File for trace output, default is stderr\n" 
#endif 
" --save : Save arguments in configuration file\n" 
" -h --help : Display this help message\n"; 
return; 


args.Save("save"); 

//是指有IXJ卡吗? 我想你肯定没有, 那是电话卡 不用管他 
#if HAS_IXJ 
if (args.GetCount() > 0) { 
if (args[0] *= "record") 
RecordFile(args); 
else if (args[0] *= "play") 
PlayFile(args); 
else 
cerr << "unknown command \"" << args[0] << "\"" << endl; 
return; 

#endif 

unsigned callLimit = DEFAULT_MSG_LIMIT; 
if (args.HasOption(l)) { 
callLimit = args.GetOptionString(l).AsInteger(); 
if (callLimit > 3600) { 
cout << "warning: maximum call length " << callLimit << " is out of range. Using " << DEFAULT_MSG_LIMIT << " instead\n"; 
callLimit = DEFAULT_MSG_LIMIT; 
} else if (callLimit == 0) 
cout << "warning: recorded message call limit disabled\n"; 

cout << "Recorded messages limited to " << callLimit << " seconds\n"; 

PString runCmd; 
if (args.HasOption( )) { 
runCmd = args.GetOptionString( ); 
cout << "Executing \"" << runCmd << "\" after each message" << endl; 


PDirectory dir; 
if (args.HasOption(d)) 
dir = args.GetOptionString(d); 

int flags = 0; 

if (args.HasOption("no-recordg7231")) { 
cout << "Supressing recording of G723.1 messages" << endl; 
flags |= MyH323EndPoint::NoRecordG7231; 

if (args.HasOption(k)) { 
cout << "Deleting recorded files after processing" << endl; 
if (runCmd.IsEmpty()) 
cout << "WARNING: recorded files will be deleted even though no run command is present" << endl; 
flags |= MyH323EndPoint::DeleteAfterRecord; 


if (args.HasOption(H)) 
flags |= MyH323EndPoint::HangupAfterPlay; 

//创建H323 EndPoint 
MyH323EndPoint endpoint(callLimit, runCmd, dir, flags); 

PString userName = "OpenH323 Answering Machine v" + GetVersion(); 
if (args.HasOption(u)) 
userName = args.GetOptionString(u); 
endpoint.SetLocalUserName(userName); 

if (!endpoint.Initialise(args)) 
return; 

// start the H.323 listener 
// 开始监听 H323有个默认的端口应该是1572 之类的 DefaultSignalPort, 我忘了 
H323ListenerTCP * listener; 
PIPSocket::Address interfaceAddress(INADDR_ANY); 
WORD listenPort = H323ListenerTCP::DefaultSignalPort; 

if (args.HasOption("listenport")) 
listenPort = (WORD)args.GetOptionString("listenport").AsInteger(); 

if (args.HasOption(i)) 
interfaceAddress = PIPSocket::Address(args.GetOptionString(i)); 

listener = new H323ListenerTCP(endpoint, interfaceAddress, listenPort); 

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


//gatekeeper 相关的东西我们也不必考虑 用-n这个参数这个端点就不会 
//去找gatekeeper 

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


else if (!args.HasOption( )) { 
cout << "Searching for gatekeeper..." << flush; 
if (endpoint.DiscoverGatekeeper(new H323TransportUDP(endpoint))) 
cout << "\nGatekeeper found: " << *endpoint.GetGatekeeper() << endl; 
else { 
cout << "\nNo gatekeeper found." << endl; 
if (args.HasOption("require-gatekeeper")) 
return; 



cout << "Waiting for incoming calls for \"" << endpoint.GetLocalUserName() << \" << endl; 

//瞧瞧, 这就是主进程干的这点事, Sleep的是毫秒 
for (;;) 
PThread::Current()->Sleep(5000); 



/// 


MyH323EndPoint::MyH323EndPoint(unsigned _callLimit, 
const PString & _runCmd, 
const PDirectory & _dir, 
int _flags) 
: callLimit(_callLimit), runCmd(_runCmd), dir(_dir), flags(_flags) 



BOOL MyH323EndPoint::OnIncomingCall(H323Connection & _conn, 
const H323SignalPDU & setupPDU, 
H323SignalPDU &) 

//传过来的是引用, 引用这个东西必须马上赋值 
MyH323Connection & conn = (MyH323Connection &)_conn; 

// see if incoming call is to a getway address 
PString number; 
if (setupPDU.GetDestinationE164(number)) 
conn.SetE164Number(number); 

return TRUE; 

//这个没什么异常, 都这样做 
H323Connection * MyH323EndPoint::CreateConnection(unsigned callReference) 

return new MyH323Connection(*this, callReference); 


//分析命令, 看看应该使用什么样的能力去交换, 
// 我们在使用的时候指定—g711message 
//就是说只是用g711 的alaw或者mulaw 
//很多废话就不用看了 
//除了和g711相关的其他都没有用处了 

BOOL MyH323EndPoint::Initialise(PConfigArgs & args) 

// format for record files, raw or wav 
if (args.HasOption("recordraw")) 
SetRecordWav(FALSE); 
else 
SetRecordWav(TRUE); 

// get G723.1 OGM 
if (args.HasOption("g7231message")) 
g7231Ogm = args.GetOptionString("g7231message"); 
else if (args.HasOption(m)) { 
if (PFile::Exists(args.GetOptionString(m) + "_g7231" + WAVExt)) { 
g7231Ogm = args.GetOptionString(m) + "_g7231" + WAVExt; 

else if (PFile::Exists(args.GetOptionString(m) + PCMExt)) { 
g7231Ogm = args.GetOptionString(m) + G7231Ext; 



if (!g7231Ogm.IsEmpty()) { 
if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(g7231Ogm)) { 
cout << "warning: cannot open G723.1 OGM file \"" << g7231Ogm << "\"" << endl; 
g7231Ogm = ""; 



if (g7231Ogm.IsEmpty()) 
cout << "No G.723.1 outgoing message set\n"; 
else { 
cout << "Using \"" << g7231Ogm << "\" as G.723.1 outgoing message\n"; 



// Get the OGM message for the PCM codecs 
// Check if the file specified exists. If it does, use it. 
// If it does not exist, try with .wav and .sw extensions. 
if (args.HasOption("pcmmessage")) { 
pcmOgm = args.GetOptionString("pcmmessage"); 

else if (args.HasOption(m)) { 
if (g7231Ogm.Find("%s") == P_MAX_INDEX) { 
pcmOgm = args.GetOptionString(m); 
} else { 
if (PFile::Exists(args.GetOptionString(m))) { 
pcmOgm = args.GetOptionString(m); 

else if (PFile::Exists(args.GetOptionString(m) + WAVExt)) { 
pcmOgm = args.GetOptionString(m) + WAVExt; 

else if (PFile::Exists(args.GetOptionString(m) + PCMExt)) { 
pcmOgm = args.GetOptionString(m) + PCMExt; 





// By default, use the pcmOgm for all the PCM codecs, but allow the user 
// to override them. 
gsmOgm = pcmOgm; 
g711Ogm = pcmOgm; 
lpc10Ogm = pcmOgm; 
#ifdef SPEEX_CODEC 
speexOgm = pcmOgm; 
#endif 


// We can set the filename for specific codecs. 
if (args.HasOption("gsmmessage")) 
gsmOgm = args.GetOptionString("gsmmessage"); 


//这句话用的着 
if (args.HasOption("g711message")) 
g711Ogm = args.GetOptionString("g711message"); 

if (args.HasOption("lpc10message")) 
lpc10Ogm = args.GetOptionString("lpc10message"); 
//这是一个codec设备, 你没有! 
#ifdef SPEEX_CODEC 
if (args.HasOption("speexmessage")) 
speexOgm = args.GetOptionString("speexmessage"); 
#endif 

// Check GSM OGM message 
if (!gsmOgm.IsEmpty()) { 
if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(gsmOgm)) { 
cout << "warning: cannot open GSM OGM file \"" << gsmOgm << "\"" << endl; 
gsmOgm = ""; 


if (gsmOgm.IsEmpty()) 
cout << "No GSM outgoing message set\n"; 
else { 
cout << "Using \"" << gsmOgm << "\" as GSM outgoing message\n"; 


// Check G.711 OGM message 
if (!g711Ogm.IsEmpty()) { 
if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(g711Ogm)) { 
cout << "warning: cannot open G711 OGM file \"" << g711Ogm << "\"" << endl; 
g711Ogm = ""; 


if (g711Ogm.IsEmpty()) 
cout << "No G711 outgoing message set\n"; 
else { 
cout << "Using \"" << g711Ogm << "\" as G.711 outgoing message\n"; 


// Check LPC10 OGM message 
if (!lpc10Ogm.IsEmpty()) { 
if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(lpc10Ogm)) { 
cout << "warning: cannot open LPC10 OGM file \"" << lpc10Ogm << "\"" << endl; 
lpc10Ogm = ""; 


if (lpc10Ogm.IsEmpty()) 
cout << "No LPC10 outgoing message set\n"; 
else { 
cout << "Using \"" << lpc10Ogm << "\" as LPC10 outgoing message\n"; 


#ifdef SPEEX_CODEC 
// Check Speex OGM message 
if (!speexOgm.IsEmpty()) { 
if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(speexOgm)) { 
cout << "warning: cannot open Speex OGM file \"" << speexOgm << "\"" << endl; 
speexOgm = ""; 


if (speexOgm.IsEmpty()) 
cout << "No Speex outgoing message set\n"; 
else { 
cout << "Using \"" << speexOgm << "\" as Speex outgoing message\n"; 

#endif 


if (g7231Ogm.IsEmpty() && gsmOgm.IsEmpty() && g711Ogm.IsEmpty() 
&& lpc10Ogm.IsEmpty() 
#ifdef SPEEX_CODEC 
&& speexOgm.IsEmpty() 
#endif 
) { 
cerr << "Must specify at least one outgoing message" << endl; 
return FALSE; 


if (!g7231Ogm.IsEmpty()) 
SetCapability(0, 0, new G7231_File_Capability); 

if (!gsmOgm.IsEmpty()) 
SetCapability(0, 0, new H323_GSM0610Capability); 

if (!gsmOgm.IsEmpty()) 
SetCapability(0, 0, new MicrosoftGSMAudioCapability); 

//这是 AM Endpoint的能力 支持g711的alaw和ulaw 
if (!g711Ogm.IsEmpty()) 
SetCapability(0, 0, new H323_G711Capability(H323_G711Capability::muLaw, H323_G711Capability::At64k)); 

if (!g711Ogm.IsEmpty()) 
SetCapability(0, 0, new H323_G711Capability(H323_G711Capability::ALaw, H323_G711Capability::At64k)); 

//没有用了 
if (!lpc10Ogm.IsEmpty()) 
SetCapability(0, 0, new H323_LPC10Capability(*this)); 

#ifdef SPEEX_CODEC 
if (!speexOgm.IsEmpty()) 
SetCapability(0, 0, new SpeexNarrow3AudioCapability()); 
#endif 

capabilities.Remove(args.GetOptionString(D).Lines()); 
capabilities.Reorder(args.GetOptionString(P).Lines()); 

cout << "Codecs (in preference order):\n" << setprecision(2) << capabilities << endl; 

return TRUE; 


/// 

//录音通道, 解码对象所附着的通道 
//写文件 
PCM_RecordFile::PCM_RecordFile(MyH323Connection & _conn, const PFilePath & _fn, unsigned _callLimit) 
: conn(_conn), fn(_fn), callLimit(_callLimit) 

recordStarted = FALSE; 
timeLimitExceeded = FALSE; 
closed = FALSE; 
dataWritten = FALSE; 

// If the file name ends in .wav then open the output as a WAV file. 
// Otherwise open it as a raw file. 
if ((_fn.Right(4)).ToLower() == ".wav") 
fileclass = new PWAVFile(_fn, PFile::WriteOnly, 
PFile::ModeDefault,PWAVFile::PCM_WavFile); 
else 
fileclass = new PFile(_fn, PFile::WriteOnly); 


void PCM_RecordFile::StartRecording() 

PWaitAndSignal mutex(pcmrecordMutex); 

if (recordStarted) 
return; 

PTRACE(1, "Starting recording to " << fn); 

PTime now; 
recordStarted = TRUE; 
finishTime = now + (callLimit * 1000); 


BOOL PCM_RecordFile::Close() 

PWaitAndSignal mutex(pcmrecordMutex); 

closed = TRUE; 
return fileclass->Close(); 


BOOL PCM_RecordFile::Write(const void * buf, PINDEX len) 

// Wait for the mutex, and Signal it at the end of this function 
PWaitAndSignal mutex(pcmrecordMutex); 

// If the record file has been closed, or if the time limit has 
// been exceeded, then return immediatly. 
if (closed || timeLimitExceeded) 
return FALSE; 

if (!recordStarted) { 
DelayFrame(len); 
return TRUE; 


PTime now; 
if ((callLimit != 0) && (now >= finishTime)) { 
PTRACE(1, "Terminating call due to timeout"); 
conn.ClearCall(); 
timeLimitExceeded = TRUE; 
return TRUE; 


DelayFrame(len); 

dataWritten = TRUE; 

return WriteFrame(buf, len); 


BOOL PCM_RecordFile::WriteFrame(const void * buf, PINDEX len) 

//cerr << "Writing PCM " << len << endl; 
return fileclass->Write(buf, len); 


void PCM_RecordFile::DelayFrame(PINDEX len) 

delay.Delay(len/16); 


PCM_RecordFile::~PCM_RecordFile() 

PWaitAndSignal mutex(pcmrecordMutex); 

if (!dataWritten) { 
PTRACE(1, "Deleting " << fn << " as no data recorded"); 
fileclass->Remove(fn); 


delete fileclass; 


/// 
// Override some of the PCM_RecordFile functions to write 
// G723.1 data instead of PCM data. 

G7231_RecordFile::G7231_RecordFile(MyH323Connection & _conn, const PFilePath & _fn, unsigned _callLimit) 
: PCM_RecordFile(_conn, _fn, _callLimit) 

// If the record file is a .wav file, we need to close the file 
// that PCM_RecordFile will have opened, and reopen it as a G.723.1 Wav file. 
if ((_fn.Right(4)).ToLower() == ".wav") { 
fileclass->Remove(_fn); 
delete fileclass; 
fileclass = new PWAVFile(_fn, PFile::WriteOnly, 
PFile::ModeDefault,PWAVFile::G7231_WavFile); 



BOOL G7231_RecordFile::WriteFrame(const void * buf, PINDEX /*len*/) 

int frameLen = G7231_File_Codec::GetFrameLen(*(BYTE *)buf); 
// cerr << "Writing G7231 " << frameLen << endl; 
return fileclass->Write(buf, frameLen); 


void G7231_RecordFile::DelayFrame(PINDEX /*len*/) 

// Ignore the len parameter as that is the compressed size. 
// We must delay by the actual sample time. 
delay.Delay((G7231_SAMPLES_PER_BLOCK*2)/16); 


/// 

static BOOL MatchString(const PString & str1, const PString str2) 

if (str1.GetLength() != str2.GetLength()) 
return FALSE; 

PINDEX len = str1.GetLength(); 

PINDEX i; 
for (i = 0; i < len; i++) 
if ((str1[i] != ?) && (str2[i] != ?) && (str1[i] != str2[i])) 
return FALSE; 

return TRUE; 


static PINDEX FindMatch(const PStringList & list, const PString & key) 

PINDEX maxKeyLen = 0; 
PINDEX i; 

PINDEX keyLen = key.GetLength(); 
PINDEX listLen = list.GetSize(); 

for (i = 0; i < listLen; i++) 
maxKeyLen = PMAX(maxKeyLen, list[i].GetLength()); 

if (keyLen == 0 || maxKeyLen == 0) 
return P_MAX_INDEX; 

if (keyLen > maxKeyLen) 
return P_MAX_INDEX; 

PINDEX len = 1; 
while (len <= keyLen) { 
PString subStr = key.Left(len); 

PINDEX matches = 0; 
PINDEX lastMatch = P_MAX_INDEX; 
PINDEX i; 

// look for a match to the substring 
for (i = 0; i < list.GetSize(); i++) { 
if ((list[i].GetLength() >= keyLen) && MatchString(list[i].Left(len), subStr)) { 
matches++; 
lastMatch = i; 



// if we got ONE match, we have a winner 
if (matches == 1) 
return lastMatch+1; 

// if we have no matches, then there is no point continuing 
if (matches == 0) 
return P_MAX_INDEX; 

// if we have more than one match, try the next char 
len++; 


// too many matches 
return 0; 



MyH323Connection::MyH323Connection(MyH323EndPoint & _ep, unsigned callReference) 
: H323Connection(_ep, callReference), ep(_ep) 

basename = psprintf("%04i%02i%02i_%02i%02i%02i", callStartTime.GetYear(), callStartTime.GetMonth(), callStartTime.GetDay(), 
callStartTime.GetHour(), callStartTime.GetMinute(), callStartTime.GetSecond()); 
recordFile = NULL; 
ogmChannel = NULL; 

receiveCodecName = transmitCodecName = "none"; 

cout << "Opening connection" << endl; 

currentMenu = 0; 
digits = ""; 

PConfig config; 
PStringList sections = config.GetSections(); 
PINDEX i; 
for (i = 0; i < sections.GetSize(); i++) { 
if (sections[i].Find(MENU_PREFIX) == 0) 
menuNames.AppendString(sections[i]); 




MyH323Connection::~MyH323Connection() 

cout << "Closing connection" << endl; 

PTime now; 
PTimeInterval interval = now - recordStartTime; 
PString addr = GetControlChannel().GetRemoteAddress(); 

PString codecStr = receiveCodecName + "/" + transmitCodecName; 
unsigned duration = (unsigned)((interval.GetMilliSeconds()+999)/1000); 

LogCall(recordFn, addr, GetRemotePartyName(), duration, codecStr, product); 

if ((recordFile!= NULL) && (recordFile->WasRecordStarted()) && !ep.GetRunCmd().IsEmpty()) { 
PString cmdStr = ep.GetRunCmd() & 
recordFn & 
"\" + addr + "\" & 
"\"" + GetRemotePartyName() + "\"" & 
PString(PString::Unsigned, duration) & 
"\"" + codecStr + "\"" & 
"\"" + product + "\""; 
PTRACE(1, "Executing : " << cmdStr); 
system((const char *)cmdStr); 
} else { 
PTRACE(1, "No action to perform at end of record"); 


if (ogmChannel != NULL) 
delete ogmChannel; 

if (recordFile != NULL) 
delete recordFile; 

if (ep.GetDeleteAfterRecord()) { 
PTRACE(1, "Removing " << recordFn << " as requested by option"); 
PFile::Remove(recordFn); 



H323Connection::AnswerCallResponse 
MyH323Connection::OnAnswerCall(const PString & caller, 
const H323SignalPDU & setupPDU, 
H323SignalPDU & /*connectPDU*/) 

product = "Unknown"; 

const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body; 
const H225_EndpointType & epInfo = setup.m_sourceInfo; 

if (epInfo.HasOptionalField(H225_EndpointType::e_vendor)) { 
const H225_VendorIdentifier & vendorInfo = epInfo.m_vendor; 
if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_productId)) 
product = vendorInfo.m_productId.AsString(); 
if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_versionId)) 
product = product + "/" + vendorInfo.m_versionId.AsString(); 


cout << "Accepting call from " << caller << " using " << product << endl; 

return AnswerCallNow; 

// 
//关键的东西都在这里 
// 从传入的codec的的类别来判断H323 Endpoint正在使用什么样的codec进行数据的编码解码 
//显然我们一开始的设定影响了现在的codec, 我们设定H323 Endpoint 的能力是G711, 
// 所以这里应该是IsDescendant from H323_muLawCodec::Class() 或者H323_ALawCodec::Class(). 
// 
BOOL MyH323Connection::OpenAudioChannel(BOOL isEncoding, 
unsigned /* bufferSize */, 
H323AudioCodec & codec) 

codec.SetSilenceDetectionMode(H323AudioCodec::NoSilenceDetection); 
PStringStream codecName; 
codecName << codec; 

PString ogm; 
BOOL isPCM = FALSE; 

if (codec.IsDescendant(G7231_File_Codec::Class())) { 
ogm = ep.GetG7231OGM(); 
isPCM = FALSE; 
} else if (codec.IsDescendant(H323_GSM0610Codec::Class())) { 
ogm = ep.GetGSMOGM(); 
isPCM = TRUE; 
} else if (codec.IsDescendant(MicrosoftGSMCodec::Class())) { 
ogm = ep.GetGSMOGM(); 
isPCM = TRUE; 
} else if (codec.IsDescendant(H323_muLawCodec::Class())) { 
ogm = ep.GetG711OGM(); 
isPCM = TRUE; 
} else if (codec.IsDescendant(H323_ALawCodec::Class())) { 
ogm = ep.GetG711OGM(); 
isPCM = TRUE; 
} else if (codec.IsDescendant(H323_LPC10Codec::Class())) { 
ogm = ep.GetLPC10OGM(); 
isPCM = TRUE; 
#ifdef SPEEX_CODEC 
} else if (codec.IsDescendant(SpeexCodec::Class())) { 
ogm = ep.GetSPEEXOGM(); 
isPCM = TRUE; 
#endif 
} else { 
cerr << "Unknown codec \"" << codecName << endl; 
return FALSE; 



PWaitAndSignal mutex(connMutex); 

if ((recordFile == NULL) && (isEncoding == FALSE)) { 
if (isPCM) { 
if (ep.GetRecordWav() == TRUE) 
recordFn = ep.GetDirectory() + (basename + ".wav"); 
else 
recordFn = ep.GetDirectory() + (basename + ".sw"); 
recordFile = new PCM_RecordFile (*this, recordFn, ep.GetCallLimit()); 
} else { 
if (ep.GetRecordWav() == TRUE) 
recordFn = ep.GetDirectory() + (basename + ".wav"); 
else 
recordFn = ep.GetDirectory() + (basename + ".g723"); 
recordFile = new G7231_RecordFile(*this, recordFn, ep.GetCallLimit()); 



// 这里创建了我们想用的通道 
if ((ogmChannel == NULL) && (isEncoding == TRUE)) { 
if (isPCM) 
ogmChannel = new PCM_OGMChannel(*this); 
else 
ogmChannel = new G7231_OGMChannel(*this); 


if (isEncoding) { 

if (ep.GetHangupAfterPlay()) 
ogmChannel->SetPlayOnce(); 

if (ogm.Find("%s")) 
ogm.Replace("%s", e164Number); 

transmitCodecName = codecName; 
if (!StartMenu(0)) { 
if (!PFile::Exists(ogm)) 
cerr << "error: cannot find OGM \"" << ogm << "\"" << endl; 
else 
ogmChannel->QueueFile(ogm); 
if (!ep.GetNoRecordG7231()) 
ogmChannel->SetRecordTrigger(); 


//这里讲通道附着在codec上, 放音 
codec.AttachChannel(ogmChannel, FALSE); 
} else { 
receiveCodecName = codecName; 
//这里讲通道附着在codec上. 录音 
codec.AttachChannel(recordFile, FALSE); 

return TRUE; 


BOOL MyH323Connection::OnStartLogicalChannel(H323Channel & channel) 

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

cout << "Started logical channel: "; 

switch (channel.GetDirection()) { 
case H323Channel::IsT

你可能感兴趣的:(音视频会议相关)