9. PJSUA2示例应用程序
9.1 示例应用程序
9.1.1 C++
pjsip-apps/src/samples/pjsua2_demo.cpp 是一个非常简单可用的C++示例应用程序。
/* $Id: pjsua2_demo.cpp 5467 2016-10-21 07:55:41Z nanang $ */
/*
* Copyright (C) 2008-2013 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua2.hpp>
#include <iostream>
#include <memory>
#include <pj/file_access.h> #define THIS_FILE "pjsua2_demo.cpp" using namespace pj; class MyAccount; class MyCall : public Call
{
private:
MyAccount *myAcc; public:
MyCall(Account &acc, int call_id = PJSUA_INVALID_ID)
: Call(acc, call_id)
{
myAcc = (MyAccount *)&acc;
} virtual void onCallState(OnCallStateParam &prm);
}; class MyAccount : public Account
{
public:
std::vector<Call *> calls; public:
MyAccount()
{} ~MyAccount()
{
std::cout << "*** Account is being deleted: No of calls="
<< calls.size() << std::endl;
} void removeCall(Call *call)
{
for (std::vector<Call *>::iterator it = calls.begin();
it != calls.end(); ++it)
{
if (*it == call) {
calls.erase(it);
break;
}
}
} virtual void onRegState(OnRegStateParam &prm)
{
AccountInfo ai = getInfo();
std::cout << (ai.regIsActive? "*** Register: code=" : "*** Unregister: code=")
<< prm.code << std::endl;
} virtual void onIncomingCall(OnIncomingCallParam &iprm)
{
Call *call = new MyCall(*this, iprm.callId);
CallInfo ci = call->getInfo();
CallOpParam prm; std::cout << "*** Incoming Call: " << ci.remoteUri << " ["
<< ci.stateText << "]" << std::endl; calls.push_back(call);
prm.statusCode = (pjsip_status_code);
call->answer(prm);
}
}; void MyCall::onCallState(OnCallStateParam &prm)
{
PJ_UNUSED_ARG(prm); CallInfo ci = getInfo();
std::cout << "*** Call: " << ci.remoteUri << " [" << ci.stateText
<< "]" << std::endl; if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
myAcc->removeCall(this);
/* Delete the call */
delete this;
}
} static void mainProg1(Endpoint &ep) throw(Error)
{
// Init library
EpConfig ep_cfg;
ep_cfg.logConfig.level = ;
ep.libInit( ep_cfg ); // Transport
TransportConfig tcfg;
tcfg.port = ;
ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg); // Start library
ep.libStart();
std::cout << "*** PJSUA2 STARTED ***" << std::endl; // Add account
AccountConfig acc_cfg;
acc_cfg.idUri = "sip:test1@pjsip.org";
acc_cfg.regConfig.registrarUri = "sip:sip.pjsip.org";
acc_cfg.sipConfig.authCreds.push_back( AuthCredInfo("digest", "*",
"test1", , "test1") );
std::auto_ptr<MyAccount> acc(new MyAccount);
acc->create(acc_cfg); pj_thread_sleep(); // Make outgoing call
Call *call = new MyCall(*acc);
acc->calls.push_back(call);
CallOpParam prm(true);
prm.opt.audioCount = ;
prm.opt.videoCount = ;
call->makeCall("sip:test1@pjsip.org", prm); // Hangup all calls
pj_thread_sleep();
ep.hangupAllCalls();
pj_thread_sleep(); // Destroy library
std::cout << "*** PJSUA2 SHUTTING DOWN ***" << std::endl;
} static void mainProg2() throw(Error)
{
string json_str;
{
EpConfig epCfg;
JsonDocument jDoc; epCfg.uaConfig.maxCalls = ;
epCfg.uaConfig.userAgent = "Just JSON Test";
epCfg.uaConfig.stunServer.push_back("stun1.pjsip.org");
epCfg.uaConfig.stunServer.push_back("stun2.pjsip.org");
epCfg.logConfig.filename = "THE.LOG"; jDoc.writeObject(epCfg);
json_str = jDoc.saveString();
std::cout << json_str << std::endl << std::endl;
} {
EpConfig epCfg;
JsonDocument rDoc;
string output; rDoc.loadString(json_str);
rDoc.readObject(epCfg); JsonDocument wDoc; wDoc.writeObject(epCfg);
json_str = wDoc.saveString();
std::cout << json_str << std::endl << std::endl; wDoc.saveFile("jsontest.js");
} {
EpConfig epCfg;
JsonDocument rDoc; rDoc.loadFile("jsontest.js");
rDoc.readObject(epCfg);
pj_file_delete("jsontest.js");
}
} static void mainProg3(Endpoint &ep) throw(Error)
{
const char *paths[] = { "../../../../tests/pjsua/wavs/input.16.wav",
"../../tests/pjsua/wavs/input.16.wav",
"input.16.wav"};
unsigned i;
const char *filename = NULL; // Init library
EpConfig ep_cfg;
ep.libInit( ep_cfg ); for (i=; i<PJ_ARRAY_SIZE(paths); ++i) {
if (pj_file_exists(paths[i])) {
filename = paths[i];
break;
}
} if (!filename) {
PJSUA2_RAISE_ERROR3(PJ_ENOTFOUND, "mainProg3()",
"Could not locate input.16.wav");
} // Start library
ep.libStart();
std::cout << "*** PJSUA2 STARTED ***" << std::endl; // Create player and recorder
{
AudioMediaPlayer amp;
amp.createPlayer(filename); AudioMediaRecorder amr;
amr.createRecorder("recorder_test_output.wav"); amp.startTransmit(ep.audDevManager().getPlaybackDevMedia());
amp.startTransmit(amr); pj_thread_sleep();
}
} static void mainProg() throw(Error)
{
string json_str; {
JsonDocument jdoc;
AccountConfig accCfg; accCfg.idUri = "\"Just Test\" <sip:test@pjsip.org>";
accCfg.regConfig.registrarUri = "sip:sip.pjsip.org";
SipHeader h;
h.hName = "X-Header";
h.hValue = "User header";
accCfg.regConfig.headers.push_back(h); accCfg.sipConfig.proxies.push_back("<sip:sip.pjsip.org;transport=tcp>");
accCfg.sipConfig.proxies.push_back("<sip:sip.pjsip.org;transport=tls>"); accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back();
accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back();
accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(); AuthCredInfo aci;
aci.scheme = "digest";
aci.username = "test";
aci.data = "passwd";
aci.realm = "*";
accCfg.sipConfig.authCreds.push_back(aci); jdoc.writeObject(accCfg);
json_str = jdoc.saveString();
std::cout << "Original:" << std::endl;
std::cout << json_str << std::endl << std::endl;
} {
JsonDocument rdoc; rdoc.loadString(json_str);
AccountConfig accCfg;
rdoc.readObject(accCfg); JsonDocument wdoc;
wdoc.writeObject(accCfg);
json_str = wdoc.saveString(); std::cout << "Parsed:" << std::endl;
std::cout << json_str << std::endl << std::endl;
}
} static void mainProg4(Endpoint &ep) throw(Error)
{
// Init library
EpConfig ep_cfg;
ep.libInit( ep_cfg ); // Create transport
TransportConfig tcfg;
tcfg.port = ;
ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
ep.transportCreate(PJSIP_TRANSPORT_TCP, tcfg); // Add account
AccountConfig acc_cfg;
acc_cfg.idUri = "sip:localhost";
std::auto_ptr<MyAccount> acc(new MyAccount);
acc->create(acc_cfg); // Start library
ep.libStart();
std::cout << "*** PJSUA2 STARTED ***" << std::endl; // Just wait for ENTER key
std::cout << "Press ENTER to quit..." << std::endl;
std::cin.get();
} int main()
{
int ret = ;
Endpoint ep; try {
ep.libCreate(); mainProg4(ep);
ret = PJ_SUCCESS;
} catch (Error & err) {
std::cout << "Exception: " << err.info() << std::endl;
ret = ;
} try {
ep.libDestroy();
} catch(Error &err) {
std::cout << "Exception: " << err.info() << std::endl;
ret = ;
} if (ret == PJ_SUCCESS) {
std::cout << "Success" << std::endl;
} else {
std::cout << "Error Found" << std::endl;
} return ret;
}
二进制文件位于 pjsip-apps/bin/samples 目录下
9.1.2 Python GUI
有一个相当完整的Python GUI示例程序,位于 pjsip-apps/src/pygui目录
# $Id: application.py 4798 2014-03-19 21:20:17Z bennylp $
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk import pjsua2 as pj
import log
import accountsetting
import account
import buddy
import endpoint
import settings import os
import traceback # You may try to enable pjsua worker thread by setting USE_THREADS below to True *and*
# recreate the swig module with adding -threads option to swig (uncomment USE_THREADS
# in swig/python/Makefile). In my experiment this would crash Python as reported in:
# http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2014-March/017223.html
USE_THREADS = False class Application(ttk.Frame):
"""
The Application main frame.
"""
def __init__(self):
global USE_THREADS
ttk.Frame.__init__(self, name='application', width=300, height=500)
self.pack(expand='yes', fill='both')
self.master.title('pjsua2 Demo')
self.master.geometry('500x500+100+100') # Logger
self.logger = log.Logger() # Accounts
self.accList = [] # GUI variables
self.showLogWindow = tk.IntVar(value=0)
self.quitting = False # Construct GUI
self._createWidgets() # Log window
self.logWindow = log.LogWindow(self)
self._onMenuShowHideLogWindow() # Instantiate endpoint
self.ep = endpoint.Endpoint()
self.ep.libCreate() # Default config
self.appConfig = settings.AppConfig()
if USE_THREADS:
self.appConfig.epConfig.uaConfig.threadCnt = 1
self.appConfig.epConfig.uaConfig.mainThreadOnly = False
else:
self.appConfig.epConfig.uaConfig.threadCnt = 0
self.appConfig.epConfig.uaConfig.mainThreadOnly = True
self.appConfig.epConfig.logConfig.writer = self.logger
self.appConfig.epConfig.logConfig.filename = "pygui.log"
self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND
self.appConfig.epConfig.logConfig.level = 5
self.appConfig.epConfig.logConfig.consoleLevel = 5 def saveConfig(self, filename='pygui.js'):
# Save disabled accounts since they are not listed in self.accList
disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled]
self.appConfig.accounts = [] # Get account configs from active accounts
for acc in self.accList:
acfg = settings.AccConfig()
acfg.enabled = True
acfg.config = acc.cfg
for bud in acc.buddyList:
acfg.buddyConfigs.append(bud.cfg)
self.appConfig.accounts.append(acfg) # Put back disabled accounts
self.appConfig.accounts.extend(disabled_accs)
# Save
self.appConfig.saveFile(filename) def start(self, cfg_file='pygui.js'):
global USE_THREADS
# Load config
if cfg_file and os.path.exists(cfg_file):
self.appConfig.loadFile(cfg_file) if USE_THREADS:
self.appConfig.epConfig.uaConfig.threadCnt = 1
self.appConfig.epConfig.uaConfig.mainThreadOnly = False
else:
self.appConfig.epConfig.uaConfig.threadCnt = 0
self.appConfig.epConfig.uaConfig.mainThreadOnly = True
self.appConfig.epConfig.uaConfig.threadCnt = 0
self.appConfig.epConfig.uaConfig.mainThreadOnly = True
self.appConfig.epConfig.logConfig.writer = self.logger
self.appConfig.epConfig.logConfig.level = 5
self.appConfig.epConfig.logConfig.consoleLevel = 5 # Initialize library
self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full;
self.ep.libInit(self.appConfig.epConfig)
self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) # Create transports
if self.appConfig.udp.enabled:
self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config)
if self.appConfig.tcp.enabled:
self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config)
if self.appConfig.tls.enabled:
self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) # Add accounts
for cfg in self.appConfig.accounts:
if cfg.enabled:
self._createAcc(cfg.config)
acc = self.accList[-1]
for buddy_cfg in cfg.buddyConfigs:
self._createBuddy(acc, buddy_cfg) # Start library
self.ep.libStart() # Start polling
if not USE_THREADS:
self._onTimer() def updateAccount(self, acc):
if acc.deleting:
return # ignore
iid = str(acc.randId)
text = acc.cfg.idUri
status = acc.statusText() values = (status,)
if self.tv.exists(iid):
self.tv.item(iid, text=text, values=values)
else:
self.tv.insert('', 'end', iid, open=True, text=text, values=values) def updateBuddy(self, bud):
iid = 'buddy' + str(bud.randId)
text = bud.cfg.uri
status = bud.statusText() values = (status,)
if self.tv.exists(iid):
self.tv.item(iid, text=text, values=values)
else:
self.tv.insert(str(bud.account.randId), 'end', iid, open=True, text=text, values=values) def _createAcc(self, acc_cfg):
acc = account.Account(self)
acc.cfg = acc_cfg
self.accList.append(acc)
self.updateAccount(acc)
acc.create(acc.cfg)
acc.cfgChanged = False
self.updateAccount(acc) def _createBuddy(self, acc, buddy_cfg):
bud = buddy.Buddy(self)
bud.cfg = buddy_cfg
bud.account = acc
bud.create(acc, bud.cfg)
self.updateBuddy(bud)
acc.buddyList.append(bud) def _createWidgets(self):
self._createAppMenu() # Main pane, a Treeview
self.tv = ttk.Treeview(self, columns=('Status'), show='tree')
self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) self._createContextMenu() # Handle close event
self.master.protocol("WM_DELETE_WINDOW", self._onClose) def _createAppMenu(self):
# Main menu bar
top = self.winfo_toplevel()
self.menubar = tk.Menu()
top.configure(menu=self.menubar) # File menu
file_menu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Add account..", command=self._onMenuAddAccount)
file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow)
file_menu.add_separator()
file_menu.add_command(label="Settings...", command=self._onMenuSettings)
file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings)
file_menu.add_separator()
file_menu.add_command(label="Quit", command=self._onMenuQuit) # Window menu
self.window_menu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="Window", menu=self.window_menu) # Help menu
help_menu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) def _showChatWindow(self, chat_inst):
chat_inst.showWindow() def updateWindowMenu(self):
# Chat windows
self.window_menu.delete(0, tk.END)
for acc in self.accList:
for c in acc.chatList:
cmd = lambda arg=c: self._showChatWindow(arg)
self.window_menu.add_command(label=c.title, command=cmd) def _createContextMenu(self):
top = self.winfo_toplevel() # Create Account context menu
self.accMenu = tk.Menu(top, tearoff=False)
# Labels, must match with _onAccContextMenu()
labels = ['Unregister', 'Reregister', 'Add buddy...', '-',
'Online', 'Invisible', 'Away', 'Busy', '-',
'Settings...', '-',
'Delete...']
for label in labels:
if label=='-':
self.accMenu.add_separator()
else:
cmd = lambda arg=label: self._onAccContextMenu(arg)
self.accMenu.add_command(label=label, command=cmd) # Create Buddy context menu
# Labels, must match with _onBuddyContextMenu()
self.buddyMenu = tk.Menu(top, tearoff=False)
labels = ['Audio call', 'Send instant message', '-',
'Subscribe', 'Unsubscribe', '-',
'Settings...', '-',
'Delete...'] for label in labels:
if label=='-':
self.buddyMenu.add_separator()
else:
cmd = lambda arg=label: self._onBuddyContextMenu(arg)
self.buddyMenu.add_command(label=label, command=cmd) if (top.tk.call('tk', 'windowingsystem')=='aqua'):
self.tv.bind('<2>', self._onTvRightClick)
self.tv.bind('<Control-1>', self._onTvRightClick)
else:
self.tv.bind('<3>', self._onTvRightClick)
self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) def _getSelectedAccount(self):
items = self.tv.selection()
if not items:
return None
try:
iid = int(items[0])
except:
return None
accs = [acc for acc in self.accList if acc.randId==iid]
if not accs:
return None
return accs[0] def _getSelectedBuddy(self):
items = self.tv.selection()
if not items:
return None
try:
iid = int(items[0][5:])
iid_parent = int(self.tv.parent(items[0]))
except:
return None accs = [acc for acc in self.accList if acc.randId==iid_parent]
if not accs:
return None buds = [b for b in accs[0].buddyList if b.randId==iid]
if not buds:
return None return buds[0] def _onTvRightClick(self, event):
iid = self.tv.identify_row(event.y)
#iid = self.tv.identify('item', event.x, event.y)
if iid:
self.tv.selection_set( (iid,) )
acc = self._getSelectedAccount()
if acc:
self.accMenu.post(event.x_root, event.y_root)
else:
# A buddy is selected
self.buddyMenu.post(event.x_root, event.y_root) def _onTvDoubleClick(self, event):
iid = self.tv.identify_row(event.y)
if iid:
self.tv.selection_set( (iid,) )
acc = self._getSelectedAccount()
if acc:
self.cfgChanged = False
dlg = accountsetting.Dialog(self.master, acc.cfg)
if dlg.doModal():
self.updateAccount(acc)
acc.modify(acc.cfg)
else:
bud = self._getSelectedBuddy()
acc = bud.account
chat = acc.findChat(bud.cfg.uri)
if not chat:
chat = acc.newChat(bud.cfg.uri)
chat.showWindow() def _onAccContextMenu(self, label):
acc = self._getSelectedAccount()
if not acc:
return if label=='Unregister':
acc.setRegistration(False)
elif label=='Reregister':
acc.setRegistration(True)
elif label=='Online':
ps = pj.PresenceStatus()
ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
acc.setOnlineStatus(ps)
elif label=='Invisible':
ps = pj.PresenceStatus()
ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE
acc.setOnlineStatus(ps)
elif label=='Away':
ps = pj.PresenceStatus()
ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
ps.activity = pj.PJRPID_ACTIVITY_AWAY
ps.note = "Away"
acc.setOnlineStatus(ps)
elif label=='Busy':
ps = pj.PresenceStatus()
ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
ps.activity = pj.PJRPID_ACTIVITY_BUSY
ps.note = "Busy"
acc.setOnlineStatus(ps)
elif label=='Settings...':
self.cfgChanged = False
dlg = accountsetting.Dialog(self.master, acc.cfg)
if dlg.doModal():
self.updateAccount(acc)
acc.modify(acc.cfg)
elif label=='Delete...':
msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri
if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
return
iid = str(acc.randId)
self.accList.remove(acc)
acc.setRegistration(False)
acc.deleting = True
del acc
self.tv.delete( (iid,) )
elif label=='Add buddy...':
cfg = pj.BuddyConfig()
dlg = buddy.SettingDialog(self.master, cfg)
if dlg.doModal():
self._createBuddy(acc, cfg)
else:
assert not ("Unknown menu " + label) def _onBuddyContextMenu(self, label):
bud = self._getSelectedBuddy()
if not bud:
return
acc = bud.account if label=='Audio call':
chat = acc.findChat(bud.cfg.uri)
if not chat: chat = acc.newChat(bud.cfg.uri)
chat.showWindow()
chat.startCall()
elif label=='Send instant message':
chat = acc.findChat(bud.cfg.uri)
if not chat: chat = acc.newChat(bud.cfg.uri)
chat.showWindow(True)
elif label=='Subscribe':
bud.subscribePresence(True)
elif label=='Unsubscribe':
bud.subscribePresence(False)
elif label=='Settings...':
subs = bud.cfg.subscribe
uri = bud.cfg.uri
dlg = buddy.SettingDialog(self.master, bud.cfg)
if dlg.doModal():
self.updateBuddy(bud)
# URI updated?
if uri != bud.cfg.uri:
cfg = bud.cfg
# del old
iid = 'buddy' + str(bud.randId)
acc.buddyList.remove(bud)
del bud
self.tv.delete( (iid,) )
# add new
self._createBuddy(acc, cfg)
# presence subscribe setting updated
elif subs != bud.cfg.subscribe:
bud.subscribePresence(bud.cfg.subscribe)
elif label=='Delete...':
msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri
if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes':
return
iid = 'buddy' + str(bud.randId)
acc.buddyList.remove(bud)
del bud
self.tv.delete( (iid,) )
else:
assert not ("Unknown menu " + label) def _onTimer(self):
if not self.quitting:
self.ep.libHandleEvents(10)
if not self.quitting:
self.master.after(50, self._onTimer) def _onClose(self):
self.saveConfig()
self.quitting = True
self.ep.libDestroy()
self.ep = None
self.update()
self.quit() def _onMenuAddAccount(self):
cfg = pj.AccountConfig()
dlg = accountsetting.Dialog(self.master, cfg)
if dlg.doModal():
self._createAcc(cfg) def _onMenuShowHideLogWindow(self):
if self.showLogWindow.get():
self.logWindow.deiconify()
else:
self.logWindow.withdraw() def _onMenuSettings(self):
dlg = settings.Dialog(self, self.appConfig)
if dlg.doModal():
msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') def _onMenuSaveSettings(self):
self.saveConfig() def _onMenuQuit(self):
self._onClose() def _onMenuAbout(self):
msgbox.showinfo(self.master.title(), 'About') class ExceptionCatcher:
"""Custom Tk exception catcher, mainly to display more information
from pj.Error exception
"""
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
try:
if self.subst:
args = apply(self.subst, args)
return apply(self.func, args)
except pj.Error, error:
print 'Exception:'
print ' ', error.info()
print 'Traceback:'
print traceback.print_stack()
log.writeLog2(1, 'Exception: ' + error.info() + '\n')
except Exception, error:
print 'Exception:'
print ' ', str(error)
print 'Traceback:'
print traceback.print_stack()
log.writeLog2(1, 'Exception: ' + str(error) + '\n') def main():
#tk.CallWrapper = ExceptionCatcher
app = Application()
app.start()
app.mainloop() if __name__ == '__main__':
main()
需要Python 2.7及以上版本,以及Python SWIG模块。要使用应用程序,只需运行:
python application.py
9.1.3 安卓
请参考 https://trac.pjsip.org/repos/wiki/Getting-Started/Android#pjsua2 的示例应用程序。
9.1.4 Java
在目录 pjsip-apps/src/swig/java 下
有一个Hello World类型的应用程序。
需要Java SWIG模块。构建SWIG模块后,从该目录运行该应用程序。
make test
9.1.5 iOS
可拷贝 pjsip-apps/src/samples/pjsua2_demo.cpp 的代码(即本页第一个代码段中的代码)到
.mm
文件中,然后加入到 iOS XCode项目中。
注意:必须使用Obj-C ++(.mm
)文件,而不是默认的Obj-C(.m
)文件