使用OpenH323开发
必备软件包可以从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("iii_iii", 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