使用openh323开发学习资料
- 格式:doc
- 大小:110.50 KB
- 文档页数:72
1 简介欢迎来此学习H323Plus/OpenH323的简单教程。
本教程的目标,是想通过本章节的基本介绍以及后继的高级话题,来描述和解释一些基于PWLib与H323Plus的编程技术。
本教程中,程序示例是基于最新的H323 Plus库进行编译的,但仍能与老版本的OpenH323兼容。
在本教程术语中,名称H323 Plus与OpenH323可以认为是等价的。
本教程中所使用的示例应用,在能够正确展示功能的前提力,力求简单明了,因此这些应用程序是非常容易学习的,因此,这些示例程序把主要精力放在了核心功能的展示上,而不过多介绍那些高级的扩展属性。
此外,这些应用程序必需包含一些可实用的功能。
结果,我们写出了一个简单应用程序,并将其命名为oh323tut。
它看起来像一个WAV的播放器,只是音视频数据是基于OpenH323来传输的:你通过H.323电话来拨打这个程序,它从WAV文件中读取数据,然后通过H.323连接传输给你,最终播放出来。
本教程将向您展示如何使用PWLib与H323Plus/OpenH323去做如下事情:▪派生一个应用程序类(application class);▪解析命令行参数;▪派生出一个H.323终端类(endpoint class);▪初始化终端(endpoint);▪使用虚函数定制终端(endpoint)的功能;▪从WAV文件中读取音频数据并正确处理定时本教程假使读者已经掌握一些基本的H.323知识,如终端(endpoints),别名(aliases),基本呼叫信号(basic call signaling)等;同时,读者需要熟悉C++,以及良好的算法理解能力。
本教程的示例程序基于Linux开发,它可以在绝大多数类Unix操作系统中运行,同时稍作修改,也比较容易移值到Win32环境中。
第二章,我们将简单介绍如此在Linux/Unix系统中编译PWLib、H323Plus/OpenH323,以及我们的示例程序(oh323tut).第三章,将简单介绍oh323tut的用法。
H.323基础培训资料一.基本概念:1. H323描述了在不保证服务质量(QoS)的分组网络(PBN)上提供多媒体的技术。
H.323实体可以提供实时语音,视频,和数据通讯。
其中语音是必须支持的,其余两项是可选的。
为了支持某种媒体类型的所有终端能互通,必须使用通用的操作模式。
2. H.323的系统组件:包括终端,网关(GW), GateKeepers,多点控制(MC),多点处理(MP),多点处理单元(MCU)等。
控制消息和过程定义了这些组件如何通讯。
Scope ofNote: A gateway may support one or more of the GSTN,N-ISDN and/or B-ISDN connections.4. H323是一个框架性的协议。
主要依靠H225、H245协议的应用来实现H323实体之间的交互问题。
具体信令由H225、H245协议规定。
因此可以说是一个总括性的协议。
H.323的范围不包括网络界面,物理网络,及网络上的传输协议。
在H.323系统中UDP和TCP使用在什么协议或控制信号中?TCP(语音和视频信号)、RTCP、RAS协议均采用UDP协议;H.225.0呼叫信令、H.245媒体控制信道采用TCP协议。
5.H.225信令的功能1) 呼叫控制信令2)规定了如何利用RTP对音视信号进行封装:H.225.0层将audio,video,data流格式化成消息,输出至网络界面,以及从网络界面接收他们。
另外,它实施与相应媒体流一致的逻辑流的顺序排列,差错控制和纠错等。
3)定义了登记、接纳和状态(RAS)协议。
RAS协议的 功能就是为网守提供确定端点地址和状态、施行呼叫接纳控制等功能6.H.245信令的作用控制(主从决定、能力交互)打开通道、会议控制,并包含四种消息类型 1) request 请求一个动作、要求相应2) response 应答3) command 命令一个动作,不需要响应4) indication 指示,指示消息只是提供信息,不要求行动和响应H.245控制功能使用H.245控制信道,交换端到端控制信息,保证H.323实体的运作。
H.323介绍背景IP多媒体通信标准,基于包交换的多媒体通信标准,不是一个单独的协议ITU-T发布。
1996, version 1, Visual Telephone Systems and Equipment for LANs which provide a non-guaranteed Quality of Service1998, version 2, Packet-Based Multimedia Communications Systems2000, version 4范围●为了建立基于包的多媒体通信系统,定义了呼叫信令(call signaling),呼叫控制(call control),媒体流协议(media stream protocols)的交互操作。
●进一步描述了用于建立该系统的网络组件。
●集合了在基于包的网络上的多媒体会议的标准。
(IP网)组件1,Terminal终端――video, audio, data client2,MCU――conference control, content mixing3,Gateway网关――protocol translation4,Gatekeeper网守――address resolution, admission control其中,Terminals,MCUs和Gateways称为H.323 Endpoints,一个endpoint是可以呼叫的。
终端Terminal1,网络上的一个endpoint提供和其他H.323terminal,GW,MCU的实时双向通信。
2,通信类别包括:1)仅语音2)语音和数据3)语音和视频4)语音,数据和视频多点控制单元(Multipoint Control Unit , MCU)1,一个MCU包含一个必须的多点控制器(Multipoint Controller, MC)和一个可选的多点处理器(Multipoint Processor, MP):1)MC: 呼叫控制,会议控制2)MP: 媒体的交互,多画面合成,编码格式变换,速率匹配等2,一些MPs可以对接收到的audio/video流做实时的转码:1)每个参与的terminal可以选择其喜好的audio/video codec。
pwlib是一套跨平台的C++的开发库,使基于pwlib上开发的应用能够很少量的移植就可以跑在windows和unix的平台上.Open323是澳洲的一家公司驱动的open source的h323协议族实现, 还不够十分的完整, 但是已经是非常的难得了.在windows上和linux下都能编译使用, 我已经试过了. Windows上编译他们比较麻烦, 注意的是一定要用batch building. 在VC7上编译openh323的动态连接库的时候, 会崩溃, 注意避开, 不过也可以试试看看现象, 如果能够解决, 请告诉我一下.在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 programPROG = helloSOURCES = hello.cxxifndef PWLIBDIRPWLIBDIR=$(HOME)/pwlibendifinclude $(PWLIBDIR)/make/ptlib.mak关键是包含了一个ptlib.makhello.cxx#includeclass 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//避免头文件重复包含#includeclass 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&);//当连接被Forwardvirtual 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->currentCallToke n);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的usernameSetLocalUserName(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 infoif (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::AnswerCallResponseSimpleH323EndPoint::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();elsecerr << 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 H323EndPointvirtual H323Connection * CreateConnection(unsigned callReference); BOOL OnIncomingCall(H323Connection &, const H323SignalPDU &,H323SignalPDU &);// new functionsBOOL 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_CODECPString GetSPEEXOGM() const { return speexOgm; }void SetSPEEXOGM(const PString & s) { speexOgm = s; }#endifPString 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_CODECPString speexOgm;#endifPDirectory 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 H323ConnectionBOOL OpenAudioChannel(BOOL, unsigned, H323AudioCodec & codec); AnswerCallResponse OnAnswerCall(const PString &, const H323SignalPDU &, H323SignalPDU &);BOOL OnStartLogicalChannel(H323Channel & channel);void OnUserInputString(const PString & value);// new functionsvoid 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 PIndirectChannelPCLASSINFO(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);};。
pwlib是一套跨平台的C++的开发库,使基于pwlib上开发的应用能够很少量的移植就可以跑在windows和unix的平台上.Open323是澳洲的一家公司驱动的open source的h323协议族实现, 还不够十分的完整, 但是已经是非常的难得了.在windows上和linux下都能编译使用, 我已经试过了. Windows上编译他们比较麻烦, 注意的是一定要用batch building. 在VC7上编译openh323的动态连接库的时候, 会崩溃, 注意避开, 不过也可以试试看看现象, 如果能够解决, 请告诉我一下.在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 programPROG = helloSOURCES = hello.cxxifndef PWLIBDIRPWLIBDIR=$(HOME)/pwlibendifinclude $(PWLIBDIR)/make/ptlib.mak关键是包含了一个ptlib.makhello.cxx#includeclass 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//避免头文件重复包含#includeclass 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&);//当连接被Forwardvirtual 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->currentCallToke n);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的usernameSetLocalUserName(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 infoif (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::AnswerCallResponseSimpleH323EndPoint::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();elsecerr << 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 H323EndPointvirtual H323Connection * CreateConnection(unsigned callReference); BOOL OnIncomingCall(H323Connection &, const H323SignalPDU &,H323SignalPDU &);// new functionsBOOL 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_CODECPString GetSPEEXOGM() const { return speexOgm; }void SetSPEEXOGM(const PString & s) { speexOgm = s; }#endifPString 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_CODECPString speexOgm;#endifPDirectory 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 H323ConnectionBOOL OpenAudioChannel(BOOL, unsigned, H323AudioCodec & codec); AnswerCallResponse OnAnswerCall(const PString &, const H323SignalPDU &, H323SignalPDU &);BOOL OnStartLogicalChannel(H323Channel & channel);void OnUserInputString(const PString & value);// new functionsvoid 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);。
OPENH323源码分析-/techOPENH323源码分析LI Chun-lin<li_chunlin@> URL: /tech2004/12V ersion: 0.1摘要本文主要从系统架构和运行过程两个方面对OPENH323源码进行了分析,可以作为OPENH323应用开发人员的一点辅助资料,亦可为网络协议开发人员提供参考。
文中大部分内容根据作者开发过程中的笔记整理而成。
目录1.体系结构分析 (1)1.1传输层 (1)1.1.1概述 (1)1.1.2传输对象的创建和打开 (2)1.1.3接口 (3)1.1.4应用 (4)1.2H.225.0 RAS协议 (6)1.2.1协议数据单元抽象 (6)1.2.2协议处理机实现 (8)1.3H.225.0呼叫信令协议 (16)1.3.1协议数据单元抽象 (16)1.3.2呼叫信令协议实现和呼叫管理 (18)1.3.3H.225.0传输信道 (25)1.3.4呼叫信令协议线程 (26)1.4H.245传输控制协议 (27)1.4.1协议数据单元抽象 (27)1.4.2控制协议的实现 (28)1.4.3协议状态机的简单描述 (32)1.4.4协议数据传输信道 (34)1.4.5H.245控制协议线程 (35)1.5实时传输协议 (35)1.5.1数据报抽象 (35)1.5.2RTP信道 (35)1.5.3发送和接收过程 (36)1.5.4RTP会话的控制 (38)1.6应用程序接口 (40)1.6.1能力及能力集 (40)1.6.2编解码器 (43)1.6.3逻辑信道 (45)1.6.4连接 (47)1.6.5端点 (64)1.6.6类之间的相互关系 (80)2运行期分析 (81)2.1侦听 (81)2.2发现网守并向网守注册 (82)2.3呼叫 (83)2.4逻辑信道的打开及实时数据传输 (85)2.5挂断 (88)附录A 专门词定义 (90)附录B 参考资料 (91)附录C H.323网络结构 (92)1. 体系结构分析1.1 传输层1.1.1 概述传输层主要负责上层协议数据单元的发送和接收,在OPENH323中,它由H323Transport抽象类来描述,其实现分别依赖于子类H323TransportTCP和H323TransportUDP。
OPENH323介绍目录前言(一)、综述1.H.323协议简介2.OPENH323协议栈总体框架3、OPENH323协议栈主要类之间的关系图(二)、重要协议过程和体系结构分析1、RAS2、H.245控制信令3、H.225呼叫信令4、Q.9315、控制协议的实现:6、编解码器H323Codec7、逻辑信道:(三)、运行期分析1、侦听2、发现网守并向网守注册:3、呼叫:4、逻辑信道的打开及实时数据传输:5、挂断:前言H.323协议群标准描述了在分组网上由端点EP和其它网元(网守GK、网关GW和多点控制单元MCU)组成的多媒体网络的系统架构,它是指一组多媒体的协议群,包括负责呼叫控制信令H.225和负责媒体控制信令H.245。
媒体控制信令是负责能力交换,媒体通道建立/释放和会议控制协议。
H.323系统架构还包括数据会议T.120协议和传真T.38协议,以及实时媒体流协议RTP和其控制协议RTCP。
H.323支持语音编解码G.711、G.723、G.729和视频编解码H.261、H.263等。
H.323不仅系统地规范了分组语音会议标准而且也包括了完整的数据和视频会议标准。
尽管H.323-v1原本是为分组局域网LAN上设计的多媒体会议系统,但人们从一开始就将H.323用于广域互联网,结果H.323取得了成功并马上受到广泛重视。
H.323标准从一开始就重视与传统公共电话PSTN网完整互联互通,其中H.225中呼叫控制部分和H.245是借用了已有的ITU的窄带视频H.320、H.324协议群H.221和H.242。
但H.225中的注册Registration、准许Admission、状态Status和地址解析机制(RAS)则不得不完全从头开始设计。
基本呼叫程序由RAS信令(端点注册、准许控制和地址解析)和呼叫信令(连接建立、能力交换、打开逻辑通道)组成。
H.323会议是通过多点控制单元MCU来进行的。
H.323的实时媒体流协议和其分别采用了已有的IETF标准RTP和RTCP。
H.323视频会议系统音视频同步原理针对H.323 视频会议系统设计了一种基于RTP 的音视频同步方法,该方法在严格遵守RTP 协议的前提下,将音视频数据联系起来通过同一个媒体通道传输,从而达到唇音同步的目的。
实验表明:该方法在对图像质量影响很小的情况下,很好地实现了音视频的同步播放,并且具有实现简便,不增加系统负担等优点,具有广泛的实用性。
H.323 视频会议系统中,发送端同时采集到的音视频数据能在接收端同时播放,则认为唇音同步。
终端采集到的音视频数据肯定是同步的,要保证同时播放,就要保证音视频在采集和播放处理过程中消耗的时间相同。
IP 网络的特点决定了通过不同通道的音视频数据传输所消耗的时间不可能完全相同,唇音同步是视频会议系统中的一大难题。
如果同时采样的音视频数据播放时间偏差在[-80ms,+80ms]以内,用户基本上感觉不到不同步,一旦超出[-160ms,+160ms],用户就可以明显感觉到,中间部分是临界范围。
1 引言1.1 文章安排本文第2 节分析了现有的音视频同步方案的缺点。
第3 节详细描述了本文所设计方案的实现过程。
第4 节给出实验数据以及分析结果。
第5 节给出结论。
1.2 基本介绍H.323 视频会议系统中,音视频不同步现象产生的原因除了网络环境外,还有一个是音视频的分开传输。
虽然H.323 建议音视频通过不同道道传输,但是实际传输数据的RTP[2,3]协议和其底层的UDP 协议都没有规定一对连接只能传输音频或者视频中的一种,通过同一个通道传输音视频完全可能,而且这样可以最大程度的减少网络原因引起的音视频不同步,本文给出了这一设想的实现方案,并做了验证。
2 现有解决方案目前最常用的唇音同步方法从思路上可以分为以下两类:思路一,发送端给每个要发送的RTP 包打上时戳,记录它们的采样时间。
接收端通过增加延时等方式,保证同时采样的数据同时播放。
这类方法的实现需要一个中立的第三方参考时钟,需要有RTCP 协议的SR[2,3]的参与,如果这两个条件不具备,同步就失去了依据。
6 音频通道在本节,我们将来讲解示例程序使用音频通道的方法。
OpenH323协议栈默认的行为是从声卡读取或者向声卡写入音频数据。
我们的程序需要重写该默认行为,以达到定义的们自己的音频通道的目的。
oh323tut应用程序使用了两种通常类,他们都是由PIndirectChannel类派生而来,这两个类是:WavChannel (文件wavchan.h与wavchan.cxx) 和NullChannel (文件nullchan.h与nullchan.cxx)。
在学习程序源码的时候,你可能意识到这两个类的功能可能合并到同一个类中。
我们之所以分成两个独立的类,是让大家更易于去理解我们的代码。
同时,这有也助于我们强调传入与传出的音频是各自独立的。
在我们定义了这两个新通道类后,我们需要重写四个虚函数:Close()、IsOpen()、Read()、以及Write()。
你可以在文件wavchan.h(行号46–49) 与nullchan.h(行号45–48)中找到它们的原型。
这四个虚函数的角色比较易于理解。
一旦通道实例被创建,它被期望是已打开的。
IsOpen()方法被用于验证在创建通道的过程中所有事情(如设备初始化,打开文件等)都运行正常;当通道不再使用时,调用Close()关闭;Read()接口用于从通道实例中读取数据,Write()接口则相反,用于向通道实例写入数据。
6.1 WavChannelWavChannel类的任务是从WAV文件中读取音频数据。
WavChannel的声明从文件tcz_tt1(`wavchan.h')的第36行开始。
它需要四个数据成员,其中,myConnection是一个H323Connection类的引用,我们需要用它来做一些事情,如当读到WAV文件结尾的时候关闭连接。
PWAVFile wavFile对像允许我们从WAV文件读取音频数据。
另两个数据成员:writeDelay与readDelay都是PAdaptiveDelay类型,我们将在下边详细介绍延时自适应的角色。
基于OpenH323的IPv6多媒体教学系统
胡屹峰;李智展;孙子晨;赵泽宇
【期刊名称】《中国教育网络》
【年(卷),期】2008(000)002
【摘要】作为下一代互联网协议,IPv6相比IPv4具有海量地址、快速传输、高度安全、对移动设备支持等特性,在该环境下的多媒体教学系统能够提供更高的稳定性和方便性.本文对开源的H.323协议栈OpenH323进行了全面的IPv6移植,并以此为基础进行系统的设计和开发,充分利用了IPv6的特点,在对下一代互联网技术多媒体教学的方法和应用方面进行了研究和尝试.
【总页数】3页(P126-128)
【作者】胡屹峰;李智展;孙子晨;赵泽宇
【作者单位】复旦大学信息化办公室,上海,200433;复旦大学信息化办公室,上海,200433;复旦大学信息化办公室,上海,200433;复旦大学信息化办公室,上
海,200433
【正文语种】中文
【中图分类】TP3
【相关文献】
1.IPv6下远程多媒体教学系统网络服务质量研究 [J], 陈活
2.基于OpenH323的IPv6多媒体教学系统 [J], 胡屹峰;李智展;孙子晨;赵泽宇
3.IPv6下远程多媒体教学系统网络服务质量研究 [J], 陈活
4.多媒体网络教学系统的应用研究——关于多媒体网络教学系统的黑屏的原因分析及解决方案 [J], 刘金广; 孔银昌
5.多媒体网络教学系统的应用研究——关于多媒体网络教学系统的黑屏的原因分析及解决方案 [J], 刘金广; 孔银昌
因版权原因,仅展示原文概要,查看原文内容请购买。
基于Openh323协议栈开发视频会议终端李小超,马跃北京邮电大学计算机科学与技术学院,北京(100876)E-mail:shirleychao@摘要:本文首先对H.323协议、Openh323项目以及视频会议终端进行了简要介绍,然后着重说明运用Openh323协议栈开发视频会议终端的方法,以及如何在应用程序中加载音视频编解码器关键词:H.323协议,Openh323协议栈,视频会议终端,音频编解码器,视频编解码器中图分类号:TP3931.引言基于IP网络的视频会议系统,可以支持人们远距离进行实时音视频信息交流、开展协同工作,还可以帮助使用者对工作中各种信息进行处理,如共享数据、共享应用程序等。
H.323协议支持在局域网、城域网以及广域网上进行实时视频、音频及数据传输,该协议已在包括视频会议系统的多方面得到越来越广泛的应用。
但是H.323协议栈非常复杂,Openh323协议栈正是为开发使用H.323协议在IP网上进行多媒体通信的应用程序而专门设计的全功能协议栈,它使得人们能够快捷、有效地进行H.323协议的相关开发。
2.H.323协议概述H.323协议是国际电信联盟电信标准部(ITU-T)制定的在不保证服务质量的分组交换网络上传递信息的多媒体通信协议,它建立在通用的、开放的计算机网络通信技术基础之上,用来在基于包交换的网络上传输音频、视频和数据,该协议描述了呼叫信令和控制、多媒体传输和控制、带宽控制以及多点会议等业务,它可运行在通用的网络体系平台上,因而具有网络独立性,并实现了不同厂商的多媒体产品和应用的互操作性。
对于基于IP网络的多媒体通信应用来说,H.323标准是非常重要的构件。
H.323标准协议不是单纯的一个协议,而是包含网守RAS协议、呼叫信令H.225协议、媒体控制H.245协议、实时媒体传输RTP/RTCP协议以及音视频编解码协议和数据会议的T.120协议族等一系列相关协议的集合[1]。
视频会议系统音视频同步原理针对视频会议系统设计了一种基于RTP 的音视频同步方式,该方式在严格遵守RTP 协议的前提下,将音视频数据联系起来通过同一个媒体通道传输,从而达到唇音同步的目的。
实验说明:该方式在对图像质量阻碍很小的情形下,专门好地实现了音视频的同步播放,而且具有实现简便,不增加系统负担等优势,具有普遍的有效性。
视频会议系统中,发送端同时搜集到的音视频数据能在接收端同时播放,那么认为唇音同步。
终端搜集到的音视频数据确信是同步的,要保证同时播放,就要保证音视频在搜集和播放处置进程中消耗的时刻相同。
IP 网络的特点决定了通过不同通道的音视频数据传输所消耗的时刻不可能完全相同,唇音同步是视频会议系统中的一大难题。
若是同时采样的音视频数据播放时刻误差在[-80ms,+80ms]之内,用户大体上感觉不到不同步,一旦超出[-160ms,+160ms],用户就能够够明显感觉到,中间部份是临界范围。
1 引言文章安排本文第2 节分析了现有的音视频同步方案的缺点。
第3 节详细描述了本文所设计方案的实现进程。
第4 节给出实验数据和分析结果。
第5 节给出结论。
大体介绍视频会议系统中,音视频不同步现象产生的缘故除网络环境外,还有一个是音视频的分开传输。
尽管建议音视频通过不同道道传输,可是实际传输数据的RTP[2,3]协议和其底层的UDP 协议都没有规定一对连接只能传输音频或视频中的一种,通过同一个通道传输音视频完全可能,而且如此能够最大程度的减少网络缘故引发的音视频不同步,本文给出了这一假想的实现方案,并做了验证。
2 现有解决方案目前最经常使用的唇音同步方式从思路上能够分为以下两类:思路一,发送端给每一个要发送的RTP 包打上时戳,记录它们的采样时刻。
接收端通过增加延时等方式,保证同时采样的数据同时播放。
这种方式的实现需要一个中立的第三方参考时钟,需要有RTCP 协议的SR[2,3]的参与,若是这两个条件不具有,同步就失去了依据。
使用o p e n h323开发pwlib是一套跨平台的C++的开发库,使基于pwlib上开发的应用能够很少量的移植就可以跑在windows和unix的平台上.Open323是澳洲的一家公司驱动的open source的h323协议族实现, 还不够十分的完整, 但是已经是非常的难得了.在windows上和linux下都能编译使用, 我已经试过了. Windows上编译他们比较麻烦, 注意的是一定要用batch building. 在VC7上编译openh323的动态连接库的时候, 会崩溃, 注意避开, 不过也可以试试看看现象, 如果能够解决, 请告诉我一下.在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 programPROG = helloSOURCES = hello.cxxifndef PWLIBDIRPWLIBDIR=$(HOME)/pwlibendifinclude $(PWLIBDIR)/make/ptlib.mak关键是包含了一个ptlib.makhello.cxx#includeclass 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也会创建一个H323ConnectionConnection 就会进入一个状态机, 在各个状态中, 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//避免头文件重复包含#includeclass 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::AnswerCallResponseOnAnswerCall(H323Connection &, const PString &, const H323SignalPDU &, H323SignalPDU&);//当连接被Forwardvirtual 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 aliasesif (args.HasOption(u)) {PStringArray aliases = args.GetOptionString(u).Lines();// 设定改Endpoint的usernameSetLocalUserName(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 infoif (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::AnswerCallResponseSimpleH323EndPoint::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();elsecerr << 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 H323EndPointvirtual H323Connection * CreateConnection(unsigned callReference);BOOL OnIncomingCall(H323Connection &, const H323SignalPDU &,H323SignalPDU &);// new functionsBOOL 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_CODECPString GetSPEEXOGM() const { return speexOgm; }void SetSPEEXOGM(const PString & s) { speexOgm = s; }#endifPString 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_CODECPString speexOgm;#endifPDirectory 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 H323ConnectionBOOL OpenAudioChannel(BOOL, unsigned, H323AudioCodec & codec);AnswerCallResponse OnAnswerCall(const PString &, const H323SignalPDU &, H323SignalPDU &);BOOL OnStartLogicalChannel(H323Channel & channel);void OnUserInputString(const PString & value);// new functionsvoid 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);。