Opal 教程之二 SipIM 例子源码分析

在互联网上.极少有关于Opal的中文教程.因此敝人决定把学习Opal的过程记录下来
勉励自己.抑或给后来者留下一些入门的资料.
最近感觉学习Opal已经慢慢摸出了门道.前几个星期.摸索在 VS2005编译Opal 花了我好多天的时间.
把我的耐心都磨得差不多快要放弃研究Opal.作为初学者.学习的难度被一些小问题无限放大.
很少能找到关于Opal 的资料.
呵呵.所以我决定要为Opal 在中国程序员 普及做出贡献(是不是大言不惭呢?欢迎拍砖哦.).


上面的文字.做为我Opal学习之路的前奏。


先从 SiPIM这个例子开始吧

刚刚开始的时候.我对这个名字有点疑惑.究竟是Simple IM 的缩写
还是SIP 协议的IM 呢.(IM 是即时通讯的意思).答案是后者
这个例子.演示了两个SipIM 程序 如何通讯.主要是文字信息的传递
非常合适初学者学习.


还是分析源码吧.程序员就喜欢比较直接一点

在ptlib框架开发的控制台程序一定要有继承PProcess的子类.控制台程序
从该子类的Main函数入口.

main.h 文件
class SipIM : public PProcess
{
  //这是规范.在框架Ptlib下面的类都这样声明
  PCLASSINFO(SipIM, PProcess)

  public:
    SipIM();
    ~SipIM();

    //定义了三者传输协议,
    //1.MSRP 协议
    //2.SIP 协议
    //3.T140 协议
    enum Mode {
      Use_MSRP,
      Use_SIPIM,
      Use_T140
    };
   
    //控制台程序入口
    virtual void Main();

  private:
    //SimIM 程序的核心管理类
    MyManager * m_manager;
};


main.CXX 文件

//声明控制台程序从SimIM 入口.这是为了确保 ptlib 的初始化类的顺序不出现问题
PCREATE_PROCESS(SipIM);


//构造函数.并把成员函数m_manager指向NULL
//OPAL_MAJOR, OPAL_MINOR, ReleaseCode, OPAL_BUILD 这些都是Opal 版本号标示
SipIM::SipIM()
  : PProcess("OPAL SIP IM", "SipIM", OPAL_MAJOR, OPAL_MINOR, ReleaseCode, OPAL_BUILD)
  , m_manager(NULL)
{
}


SipIM::~SipIM()
{
  delete m_manager;
}


//该例子的主要初始化和处理逻辑都放在Main函数里面

void SipIM::Main()
{
  //获取控制台程序的输入参数引用,本人不仅仅Opal 是初学者.连C++ 也是半桶水
  //怎么输入控制台一些参数设置呢?呵呵.打开命令控制台.把本程序sipim.exe 拖到控制台
  //在该路径后面空一格. 然后输入参数按Enter键即可. 注意控制台是按空格来区分参数的个数.
  //例如 G:\Source\开源\opal-3.8.0-src\opal\bin\sipim\Debug\sipim.exe -uhsinxubin --sipim
  //在普通的控制台程序中.参数输入应该是 sipim.exe -uhsinxubin --sipim 这三个参数.
  //但是在ptlib 中.则把sipim.exe 这个参数去除掉.变成了两个参数.在上面的例子

  PArgList & args = GetArguments();

  //args[0] == -uhsinxubin
  //args[1] == --sipim
  //但是在VS2005 Debug的模式运行.怎么设置输入参数呢?
  //答案是SetArgs()函数.例如
  //args.SetArgs("-uhsinxubin --sipim");
 
  //args.Parse 函数则是对输入参数进行分析.
  //帮助文档 Parse the standard C program arguments into an argument of options and parameters.
  //把命令行参数变成了Options 和 parameters.我也不知道怎么翻译. 感觉很拗口.
  args.Parse(
             "u-user:"
             "h-help."
             "-sipim."
             "-t140."
             "-msrp."
#if PTRACING
             "o-output:"             "-no-output."
             "t-trace."              "-no-trace."
#endif
             , FALSE);

  // 例如 args.SetArgs("-uhsinxubin --sipim"); 经过Parse 分析后.
  // 则得到这样的结果  
  // args.HasOption('u') == true;
  // args.GetOptionString('u') ==BinBin;
  // args.HasOption("sipim") == PTrue;
  // args.HasOption("msrp") == PFalse;

  //请注意在分析字符串中  
  //          "u-user:"
  //           "h-help."
  //           "-sipim."
  //           "-t140."
  //           "-msrp."
  // 包含":" 和"."  符号和"-" 开头.这个两个符号有不同的含义(本人就被这些含义 困惑好久了).
  // ":" 表示选项后面带有参数. 例如  -uhslinxubin (u是选项,hslinxubin 是参数)
  // "." 表示只有选项没有参数  例如  --sipim      (sipim 是选项.没有参数)
  // "-" 表示要想获取该选项.  则输入参数必须多加"-" 例如 --sipim
 
 
  //这段是测试用的.基本可以忽略
  if (args.HasOption('h')) {
    return;
  }


  //实例化核心控制类.记得之前的构造函数? m_manager被设置为NULL
  MyManager m_manager;
 
  //设置用户名称 例如 args.GetOptionString('u') == hslinxubin (呵呵)
  if (args.HasOption('u'))
    m_manager.SetDefaultUserName(args.GetOptionString('u'));
 
  // 设置信令协议.在本例子中 .以后再介绍本例子中的三种协议T140,sipim,msrp
  //   mode == Use_SIPIM;
  //   m_manager.m_imFormat == OpalSIPIM;

  Mode mode;
  if (args.HasOption("t140")) {
    mode = Use_T140;
    m_manager.m_imFormat = OpalT140;
  }
#if OPAL_HAS_SIPIM
  else if (args.HasOption("sipim")) {
    mode = Use_SIPIM;
    m_manager.m_imFormat = OpalSIPIM;
  }
#endif
#if OPAL_HAS_MSRP
  else if (args.HasOption("msrp")) {
    mode = Use_MSRP;
    m_manager.m_imFormat = OpalMSRP;
  }
#endif
  else {
    cout << "error: must select IM mode" << endl;
    return;
  }

  //媒体格式列表,
  OpalMediaFormatList allMediaFormats;

  //初始化SIPEndPoint终端,并打开SIP 监听
  SIPEndPoint * sip  = new SIPEndPoint(m_manager);
  if (!sip->StartListeners(PStringArray())) {
    cerr << "Could not start SIP listeners." << endl;
    return;
  }
 
  //获取SIP 协议能操作的媒体格式列表.
  //例如 UserInput/RFC2833,G.711-uLaw-64k,G.711-ALaw-64k,RFC4175_YCbCr-4:2:0,RFC4175_RGB,
  //SIP-IM,NamedSignalEvent,YUV420P,MSRP,T.140,H.224/H323AnnexQ,H.224/HDLCTunneling
  allMediaFormats += sip->GetMediaFormats();

  //初始化计算机声卡终端. 并获取声卡能支持的媒体格式列表
  //例如 PCM-16-48kHz,PCM-16-32kHz,PCM-16-16kHz,PCM-16
  MyPCSSEndPoint * pcss = new MyPCSSEndPoint(m_manager);
  allMediaFormats += pcss->GetMediaFormats();
 
  //设置声卡的录音和播放设备
  pcss->SetSoundChannelPlayDevice("NULL");
  pcss->SetSoundChannelRecordDevice("NULL");

  //获取能传输的媒体格式。例如 RGB24
  allMediaFormats = OpalTranscoder::GetPossibleFormats(allMediaFormats); // Add transcoders

  //移除掉不能通过线进行传输的媒体格式
  //最后只剩下 UserInput/RFC2833,G.711-uLaw-64k,G.711-ALaw-64k,RFC4175_YCbCr-4:2:0,
  // RFC4175_RGB,SIP-IM,NamedSignalEvent,MSRP,T.140,H.224/H323AnnexQ

  for (PINDEX i = 0; i < allMediaFormats.GetSize(); i++) {
    if (!allMediaFormats[i].IsTransportable())
      allMediaFormats.RemoveAt(i--); // Don't show media formats that are not used over the wire
  }

  m_manager.AddRouteEntry("sip:.*\t.*=pc:*");
  m_manager.AddRouteEntry("pc:.*\t.*=sip:<da>");
  cout << "Available codecs: " << setfill(',') << allMediaFormats << setfill(' ') << endl;

 
  PString imFormatMask = PString("!") + (const char *)m_manager.m_imFormat;
  //设置核心管理类的 所要屏蔽的媒体格式
  // 例如 imFormatMask == "!SIP-IM " 表示屏蔽所有非SIP-IM的媒体格式
  m_manager.SetMediaFormatMask(imFormatMask);

  //把所有非SIP-IM的媒体格式移除
  allMediaFormats.Remove(imFormatMask);
 
  cout << "Codecs to be used: " << setfill(',') << allMediaFormats << setfill(' ') << endl;

  OpalConnection::StringOptions options;
  options.SetAt(OPAL_OPT_AUTO_START, m_manager.m_imFormat.GetMediaType() + ":exclusive");

  //按照正常的顺序.到了这一步.arg.getCount ==0
  //所以一定是监听的状态.如果想测试SipIM 互相发信息,
  // 则必须编译两次.并且在不同的机器上分别运行这个两个程序
  //假如是监听程序.则不用改
  //假如是主动拨打的话.则必须执行下面的
  //args.SetArgs("192.168.0.109");
 
  if (args.GetCount() == 0)
    cout << "Awaiting incoming call ..." << flush;
  else {
   // 本地呼叫 args[0]
    if (!m_manager.SetUpCall("pc:", args[0], m_manager.m_callToken, NULL, 0, &options)) {
      cerr << "Could not start IM to \"" << args[0] << '"' << endl;
      return;
    }
  }

  //等待对方的响应、
  m_manager.m_connected.Wait();

  // 应该是一种实时协议吧
  RFC4103Context rfc4103(m_manager.m_imFormat);

  int count = 1;
  for (;;) {
    PThread::Sleep(5000);
    const char * textData = "Hello, world";

    if (count > 0) {
      // 获取和远程终端建立连接的Call.
      PSafePtr<OpalCall> call = m_manager.FindCallWithLock(m_manager.m_callToken);
      if (call != NULL) {
      // 从Call中获取 Connection .一个Call可以包含多个Connection
        PSafePtr<OpalPCSSConnection> conn = call->GetConnectionAs<OpalPCSSConnection>();
        if (conn != NULL) {

          RTP_DataFrameList frameList = rfc4103.ConvertToFrames("text/plain", textData);
          OpalMediaFormat fmt(m_manager.m_imFormat);
         //把 "Hello world " 这个字符串发送出去.
          for (PINDEX i = 0; i < frameList.GetSize(); ++i) {
            conn->TransmitInternalIM(fmt, (RTP_IMFrame &)frameList[i]);
          }
        }
      }
      --count;
    }
  }

  // Wait for call to come in and finish
  m_manager.m_completed.Wait();
  cout << " completed.";
}

//上面好多概念。我现在也搞不清楚.再让我研究一下吧.再详细的描述.敬请期待哦


 

posted on 2010-05-06 09:32  PeterBin  阅读(2240)  评论(0编辑  收藏  举报