PJSUA2开发文档--第九章 PJSUA2应用程序示例
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应用程序示例的更多相关文章
- PJSUA2开发文档--第十二章 PJSUA2 API 参考手册
12 PJSUA2 API 参考手册 12.1 endpoint.hpp PJSUA2基本代理操作. namespace pj PJSUA2 API在pj命名空间内. 12.1.1 class En ...
- PJSUA2开发文档--第三章 PJSUA2高级API
3. PJSUA2高级API PJSUA2是PJSUA API以上的面向对象抽象.它为构建会话发起协议(SIP)多媒体用户代理应用程序(也称为IP / VoIP软电话)提供高级API.它将信令,媒体和 ...
- PJSUA2开发文档--第五章 帐户(号)Accounts
第五章 帐户(号) 帐户提供正在使用该应用程序的用户的身份(或身份).一个帐户有一个与之相关的SIP统一资源标识符(URI).在SIP术语中,该URI用作该人的记录地址( Address of Rec ...
- PJSUA2开发文档--第四章 端点ENDPOINT
4.端点ENDPOINT Endpoint类是一个单例类,应用程序必须在此类实例之前创建一个并且最多只能创建一个,然后才能执行任何操作.同样,一旦这个类被销毁,应用程序就不能调用该库的任何API.这个 ...
- PJSUA2开发文档--第六章 媒体 Media类
6. 媒体(Media) 媒体对象是能够产生媒体或接受媒体的对象. Media的重要子类是AudioMedia,它代表音频媒体.PJSUA2支持多种类型的音频媒体对象: 捕获设备的AudioMedia ...
- PJSUA2开发文档--第七章 呼叫 Calls类
7 呼叫Calls 呼叫由Call类处理 7.1 子类化Call类 要使用Call类,应用程序应创建子类,如: class MyCall : public Call { public: MyCal ...
- PJSUA2开发文档--第十一章 网络问题
11 网络问题 11.1 IP地址更改 请参阅wiki 处理IP地址更改.请注意,本指南使用PJSUA API作为参考. 11.2 被阻止/过滤的网络 请参阅维基百科 通过阻止或过滤的VoIP网络
- PJSUA2开发文档--第十章 媒体质量(MEDIA QUALITY)
10 媒体质量(Media Quality) 10.1 音频质量 如果遇到音频质量问题,可尝试以下步骤: 遵循指南:使用pjsystest测试声音设备. 识别声音问题并使用以下步骤进行故障排除:检查声 ...
- PJSUA2开发文档--第八章 好友(Buddy)类
8 好友(存在)Buddy PJSUA2的功能是围绕Buddy类为中心展开的.该类表示一个远端好友(伙伴,一个人或一个SIP端点). 8.1 子类化Buddy类 要使用Buddy类,通常应创建子类, ...
随机推荐
- [Swift]LeetCode414. 第三大的数 | Third Maximum Number
Given a non-empty array of integers, return the third maximum number in this array. If it does not e ...
- [Swift]LeetCode731. 我的日程安排表 II | My Calendar II
Implement a MyCalendarTwoclass to store your events. A new event can be added if adding the event wi ...
- 关于QT下资源使用和资源占用…
原文地址:关于QT下资源使用和资源占用内存过多的问题作者:技术成就梦想 最近研究了一下如何从外部动态调用图片的问题,从而研究了图片资源的使用方法.网上最常见的帖子是这个,感觉总结的还不错. h ...
- JVM垃圾回收
1. 概念理解 1.1. 并行(Parallel)与并发(Concurrent) 并行:指多个垃圾收集线程并行工作,但此时用户线程仍然处于等待状态 并发:指用户线程与垃圾收集线程同时执行 1.2. ...
- Kibana(一张图片胜过千万行日志)
Kibana是一个开源的分析和可视化平台,设计用于和Elasticsearch一起工作. 你用Kibana来搜索,查看,并和存储在Elasticsearch索引中的数据进行交互. 你可以轻松地执行高级 ...
- Solr 03 - Solr的模式设计与优化 - 最详细的schema.xml模式文件解读
目录 1 关于schema.xml文件 2 解读schema.xml文件 2.1 field - 配置域 2.2 fieldType - 配置域类型 2.3 copyField - 配置复制域 2.4 ...
- 带着新人学springboot的应用08(springboot+jpa的整合)
这一节的内容比较简单,是springboot和jpa的简单整合,jpa默认使用hibernate,所以本质就是springboot和hibernate的整合. 说实话,听别人都说spring data ...
- C#2.0 迭代器
迭代器 迭代器模式是和为模式的一种范例,我们访问数据序列中所有的元素,不用关心序列是什么类型.从数据管道中数据经过一系列不同的转换或过滤后从管道的另一端出来. 像数组.集合等已经内置了迭代器,我们可以 ...
- 开发函数计算的正确姿势 —— 使用 Fun Local 本地运行与调试
前言 首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传.函数计算 ...
- [八]基础数据类型之Double详解
Double 基本数据类型double 的包装类 Double 类型的对象包含一个 double 类型的字段 属性简介 用来以二进制补码形式表示 double 值的比特位数 public sta ...