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)文件

PJSUA2开发文档--第九章 PJSUA2应用程序示例的更多相关文章

  1. PJSUA2开发文档--第十二章 PJSUA2 API 参考手册

    12 PJSUA2 API 参考手册 12.1 endpoint.hpp PJSUA2基本代理操作.  namespace pj PJSUA2 API在pj命名空间内. 12.1.1 class En ...

  2. PJSUA2开发文档--第三章 PJSUA2高级API

    3. PJSUA2高级API PJSUA2是PJSUA API以上的面向对象抽象.它为构建会话发起协议(SIP)多媒体用户代理应用程序(也称为IP / VoIP软电话)提供高级API.它将信令,媒体和 ...

  3. PJSUA2开发文档--第五章 帐户(号)Accounts

    第五章 帐户(号) 帐户提供正在使用该应用程序的用户的身份(或身份).一个帐户有一个与之相关的SIP统一资源标识符(URI).在SIP术语中,该URI用作该人的记录地址( Address of Rec ...

  4. PJSUA2开发文档--第四章 端点ENDPOINT

    4.端点ENDPOINT Endpoint类是一个单例类,应用程序必须在此类实例之前创建一个并且最多只能创建一个,然后才能执行任何操作.同样,一旦这个类被销毁,应用程序就不能调用该库的任何API.这个 ...

  5. PJSUA2开发文档--第六章 媒体 Media类

    6. 媒体(Media) 媒体对象是能够产生媒体或接受媒体的对象. Media的重要子类是AudioMedia,它代表音频媒体.PJSUA2支持多种类型的音频媒体对象: 捕获设备的AudioMedia ...

  6. PJSUA2开发文档--第七章 呼叫 Calls类

    7   呼叫Calls 呼叫由Call类处理 7.1 子类化Call类 要使用Call类,应用程序应创建子类,如: class MyCall : public Call { public: MyCal ...

  7. PJSUA2开发文档--第十一章 网络问题

    11 网络问题 11.1 IP地址更改 请参阅wiki 处理IP地址更改.请注意,本指南使用PJSUA API作为参考. 11.2 被阻止/过滤的网络 请参阅维基百科 通过阻止或过滤的VoIP网络

  8. PJSUA2开发文档--第十章 媒体质量(MEDIA QUALITY)

    10 媒体质量(Media Quality) 10.1 音频质量 如果遇到音频质量问题,可尝试以下步骤: 遵循指南:使用pjsystest测试声音设备. 识别声音问题并使用以下步骤进行故障排除:检查声 ...

  9. PJSUA2开发文档--第八章 好友(Buddy)类

    8  好友(存在)Buddy PJSUA2的功能是围绕Buddy类为中心展开的.该类表示一个远端好友(伙伴,一个人或一个SIP端点). 8.1 子类化Buddy类 要使用Buddy类,通常应创建子类, ...

随机推荐

  1. [Swift]LeetCode474. 一和零 | Ones and Zeroes

    In the computer world, use restricted resource you have to generate maximum benefit is what we alway ...

  2. [Swift]LeetCode695. 岛屿的最大面积 | Max Area of Island

    Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) conn ...

  3. [Swift]LeetCode824. 山羊拉丁文 | Goat Latin

    A sentence S is given, composed of words separated by spaces. Each word consists of lowercase and up ...

  4. web开发中获取的各种高度和宽度

    前端开发中经常需要获取页面还有屏幕的高度和宽度进行计算,此文即介绍如何用 JavaScript 还有 jQuery 获取这些尺寸. 1.简介 一个页面显示在浏览器内,浏览器又放置在屏幕窗口内,所以由里 ...

  5. 【Spark调优】提交job资源参数调优

    [场景] Spark提交作业job的时候要指定该job可以使用的CPU.内存等资源参数,生产环境中,任务资源分配不足会导致该job执行中断.失败等问题,所以对Spark的job资源参数分配调优非常重要 ...

  6. Java中需要知道的关键字

    Java中有一些或常用,或不常用,但却不得不知关键字,本篇文章将讨论这些关键字的作用. transient transient关键字可能用的不是那么频繁,但却是一个很重要的关键字,它的作用是在对象序列 ...

  7. Linux查看系统、核数、CPU、位数

    查看系统: cat /etc/os-release 结果为 centOS Linux 7 查看核数和CPU: lscpu 40 个核,处理器为 Intel(R) Xeon(R) CPU E7-8891 ...

  8. 【转】ret,retf,iret的区别

    ret RET, and its exact synonym RETN, pop IP or EIP from the stack and transfer control to the new ad ...

  9. 『NOIP2018普及组题解』

    标题统计 题目描述 凯凯刚写了一篇美妙的作文,请问这篇作文的标题中有多少个字符? 注意:标题中可能包含大.小写英文字母.数字字符.空格和换行符.统计标题字 符数时,空格和换行符不计算在内. 输入格式 ...

  10. ppt制作元素采集

    原文链接 https://www.zhihu.com/question/52157612/answer/247501754?utm_source=qq&utm_medium=social 1动 ...