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_
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_