Closed #1723: Merging pjsua2 branch into trunk
git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@4704 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
commit
f33813f793
20
build.mak.in
20
build.mak.in
|
@ -172,6 +172,13 @@ export APP_CFLAGS := -DPJ_AUTOCONF=1\
|
|||
-I$(PJDIR)/pjmedia/include\
|
||||
-I$(PJDIR)/pjsip/include
|
||||
export APP_CXXFLAGS := $(APP_CFLAGS)
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
#
|
||||
# FIX THIS
|
||||
#
|
||||
# pjsua2 is c++ library hence maybe needs to be put in separate
|
||||
# variables. it will also require -lstdc++ or -static-libstdc++
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
export APP_LDFLAGS := -L$(PJDIR)/pjlib/lib\
|
||||
-L$(PJDIR)/pjlib-util/lib\
|
||||
-L$(PJDIR)/pjnath/lib\
|
||||
|
@ -179,8 +186,19 @@ export APP_LDFLAGS := -L$(PJDIR)/pjlib/lib\
|
|||
-L$(PJDIR)/pjsip/lib\
|
||||
-L$(PJDIR)/third_party/lib\
|
||||
$(PJ_VIDEO_LDFLAGS) \
|
||||
-static-libstdc++ \
|
||||
@LDFLAGS@
|
||||
export APP_LIB_FILES = $(PJ_DIR)/pjsip/lib/libpjsua-$(LIB_SUFFIX) \
|
||||
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
#
|
||||
# FIX THIS
|
||||
#
|
||||
# pjsua2 is c++ library hence maybe needs to be put in separate
|
||||
# variables. it will also require -lstdc++
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
export APP_LIB_FILES = \
|
||||
$(PJ_DIR)/pjsip/lib/libpjsua2-$(LIB_SUFFIX) \
|
||||
$(PJ_DIR)/pjsip/lib/libpjsua-$(LIB_SUFFIX) \
|
||||
$(PJ_DIR)/pjsip/lib/libpjsip-ua-$(LIB_SUFFIX) \
|
||||
$(PJ_DIR)/pjsip/lib/libpjsip-simple-$(LIB_SUFFIX) \
|
||||
$(PJ_DIR)/pjsip/lib/libpjsip-$(LIB_SUFFIX) \
|
||||
|
|
|
@ -101,8 +101,8 @@ if test "$1" = "--use-ndk-cflags"; then
|
|||
export LDFLAGS="${LDFLAGS} -nostdlib -L${ANDROID_SYSROOT}/usr/lib/"
|
||||
export LIBS="${LIBS} -lc -lgcc"
|
||||
export CFLAGS="${NDK_CFLAGS} ${CFLAGS}"
|
||||
export CPPFLAGS="${CFLAGS}"
|
||||
export CXXFLAGS="${NDK_CXXFLAGS}"
|
||||
export CPPFLAGS="${CFLAGS} -fexceptions -frtti"
|
||||
export CXXFLAGS="${NDK_CXXFLAGS} -fexceptions -frtti"
|
||||
|
||||
else
|
||||
|
||||
|
@ -127,11 +127,29 @@ else
|
|||
export LDFLAGS="${LDFLAGS} -nostdlib -L${ANDROID_SYSROOT}/usr/lib/"
|
||||
export LIBS="${LIBS} -lc -lgcc"
|
||||
export CFLAGS="${CFLAGS} -I${ANDROID_SYSROOT}/usr/include"
|
||||
export CPPFLAGS="${CFLAGS}"
|
||||
export CXXFLAGS="${CXXFLAGS} -shared --sysroot=${ANDROID_SYSROOT}"
|
||||
export CPPFLAGS="${CFLAGS} -fexceptions -frtti"
|
||||
export CXXFLAGS="${CXXFLAGS} -shared --sysroot=${ANDROID_SYSROOT} -fexceptions -frtti"
|
||||
|
||||
fi
|
||||
|
||||
# C++ STL
|
||||
# Note: STL for pjsua2 sample app is specified in pjsip-apps/src/swig/java/android/jni/Application.mk
|
||||
|
||||
# gnustl
|
||||
STDCPP_TC_VER=`ls -d ${ANDROID_NDK_ROOT}/sources/cxx-stl/gnu-libstdc++/[0-9]* | sort -gr | head -1`
|
||||
STDCPP_CFLAGS="-I${STDCPP_TC_VER}/include -I${STDCPP_TC_VER}/libs/armeabi/include"
|
||||
STDCPP_LIBS="${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o -lgnustl_static"
|
||||
STDCPP_LDFLAGS="-L${STDCPP_TC_VER}/libs/armeabi"
|
||||
|
||||
# stlport
|
||||
#STDCPP_CFLAGS="-I${ANDROID_NDK_ROOT}/sources/cxx-stl/stlport/stlport"
|
||||
#STDCPP_LIBS="${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o -lstlport_static -ldl"
|
||||
#STDCPP_LDFLAGS="-L${ANDROID_NDK_ROOT}/sources/cxx-stl/stlport/libs/armeabi"
|
||||
|
||||
export CFLAGS="${CFLAGS} ${STDCPP_CFLAGS}"
|
||||
export LIBS="${STDCPP_LIBS} ${LIBS}"
|
||||
export LDFLAGS="${LDFLAGS} ${STDCPP_LDFLAGS}"
|
||||
|
||||
# Print settings
|
||||
if test "1" = "1"; then
|
||||
echo "$F: calling ./configure with env vars:"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,160 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
all: html
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
-rm -rf xml
|
||||
-rm -rf html
|
||||
|
||||
xml: Doxyfile
|
||||
doxygen
|
||||
|
||||
html: xml
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ThePJSIPBook.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ThePJSIPBook.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/ThePJSIPBook"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ThePJSIPBook"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf: xml
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,132 @@
|
|||
|
||||
|
||||
Accounts
|
||||
=========
|
||||
Accounts provide identity (or identities) of the user who is currently using the application. An account has one SIP Uniform Resource Identifier (URI) associated with it. In SIP terms, the identity is used as the From header in outgoing requests.
|
||||
|
||||
Account may or may not have client registration associated with it. An account is also associated with route set and some authentication credentials, which are used when sending SIP request messages using the account. An account also has presence online status, which will be reported to remote peer when they subscribe to the account's presence, or which is published to a presence server if presence publication is enabled for the account.
|
||||
|
||||
At least one account MUST be created in the application, since any outgoing requests require an account context. If no user association is required, application can create a userless account by calling Account.create(). A userless account identifies local endpoint instead of a particular user, and it corresponds to a particular transport ID.
|
||||
|
||||
Also one account must be set as the default account, which will be used as the account identity when pjsua fails to match the request with any accounts using the stricter matching rules.
|
||||
|
||||
Subclassing the Account class
|
||||
---------------------------------
|
||||
To use the Account class, normally application SHOULD create its own subclass, such as::
|
||||
|
||||
class MyAccount : public Account
|
||||
{
|
||||
public:
|
||||
MyAccount() {}
|
||||
~MyAccount() {}
|
||||
|
||||
virtual void onRegState(OnRegStateParam &prm)
|
||||
{
|
||||
AccountInfo ai = getInfo();
|
||||
cout << (ai.regIsActive? "*** Register: code=" : "*** Unregister: code=")
|
||||
<< prm.code << endl;
|
||||
|
||||
}
|
||||
|
||||
virtual void onIncomingCall(OnIncomingCallParam &iprm)
|
||||
{
|
||||
Call *call = new MyCall(*this, iprm.callId);
|
||||
// Delete the call, which will also hangup the call
|
||||
delete call;
|
||||
}
|
||||
};
|
||||
|
||||
In its subclass, application can implement the account callbacks, which is basically used to process events related to the account, such as:
|
||||
|
||||
- the status of SIP registration
|
||||
- incoming calls
|
||||
- incoming presence subscription requests
|
||||
- incoming instant message not from buddy
|
||||
|
||||
Application needs to override the relevant callback methods in the derived class to handle these particular events.
|
||||
|
||||
If the events are not handled, default actions will be invoked:
|
||||
|
||||
- incoming calls will not be handled
|
||||
- incoming presence subscription requests will be accepted
|
||||
- incoming instant messages from non-buddy will be ignored
|
||||
|
||||
Creating Userless Accounts
|
||||
--------------------------
|
||||
A userless account identifies a particular SIP endpoint rather than a particular user. Some other SIP softphones may call this peer-to-peer mode, which means that we are calling another computer via its address rather than calling a particular user ID.
|
||||
|
||||
So for example, we might identify ourselves as "sip:192.168.0.15" (a userless account) rather than, say, "sip:bennylp@pjsip.org".
|
||||
|
||||
In pjsua, a userless account corresponds to a particular transport. Creating userless account is very simple, all we need is the transport ID which is returned by Endpoint.transportCreate() method as explained in previous chapter.
|
||||
|
||||
Here's a snippet::
|
||||
|
||||
AccountConfig acc_cfg;
|
||||
acc_cfg.sipConfig.transportId = tid;
|
||||
MyAccount *acc = new MyAccount;
|
||||
try {
|
||||
acc->create(acc_cfg);
|
||||
} catch(Error& err) {
|
||||
cout << "Account creation error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
Once the account is created, you can use the instance as a normal account. More will be explained later.
|
||||
|
||||
Accounts created this way will have its URI derived from the transport address. For example, if the transport address is "192.168.0.15:5080", then the account's URI for UDP transport will be "sip:192.168.0.15:5080", or "sip:192.168.0.15:5080;transport=tcp" for TCP transport.
|
||||
|
||||
Creating Account
|
||||
----------------
|
||||
For the "normal" account, we need to configure AccountConfig and call Account.create() to create the account.
|
||||
|
||||
At the very minimum, pjsua only requires the account's ID, which is an URI to identify the account (or in SIP terms, it's called Address of Record/AOR). Here's a snippet::
|
||||
|
||||
AccountConfig acc_cfg;
|
||||
acc_cfg.idUri = "sip:test1@pjsip.org";
|
||||
MyAccount *acc = new MyAccount;
|
||||
try {
|
||||
acc->create(acc_cfg);
|
||||
} catch(Error& err) {
|
||||
cout << "Account creation error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
The account created above doesn't do anything except to provide identity in the "From:" header for outgoing requests. The account will not register to SIP server or anything.
|
||||
|
||||
Typically you will want the account to authenticate and register to your SIP server so that you can receive incoming calls. To do that you will need to configure some more settings in your AccountConfig, something like this::
|
||||
|
||||
AccountConfig acc_cfg;
|
||||
acc_cfg.idUri = "sip:test1@pjsip.org";
|
||||
acc_cfg.regConfig.registrarUri = "sip:pjsip.org";
|
||||
acc_cfg.sipConfig.authCreds.push_back( AuthCredInfo("digest", "*", "test1", 0, "test1") );
|
||||
MyAccount *acc = new MyAccount;
|
||||
try {
|
||||
acc->create(acc_cfg);
|
||||
} catch(Error& err) {
|
||||
cout << "Account creation error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
Account Configurations
|
||||
-----------------------
|
||||
There are many more settings that can be specified in AccountConfig, like:
|
||||
|
||||
- AccountRegConfig, to specify registration settings, such as registrar server and retry interval.
|
||||
- AccountSipConfig, to specify SIP settings, such as credential information and proxy server.
|
||||
- AccountCallConfig, to specify call settings, such as whether reliable provisional response (SIP 100rel) is required.
|
||||
- AccountPresConfig, to specify presence settings, such as whether presence publication (PUBLISH) is enabled.
|
||||
- AccountMwiConfig, to specify MWI (Message Waiting Indication) settings.
|
||||
- AccountNatConfig, to specify NAT settings, such as whether STUN or ICE is used.
|
||||
- AccountMediaConfig, to specify media settings, such as Secure RTP (SRTP) related settings.
|
||||
- AccountVideoConfig, to specify video settings, such as default capture and render device.
|
||||
|
||||
Please see AccountConfig reference documentation for more info.
|
||||
|
||||
Account Operations
|
||||
--------------------------------------
|
||||
Some of the operations to the Account object:
|
||||
|
||||
- add buddy objects
|
||||
- set account's presence online status
|
||||
- stop/start SIP registration
|
||||
|
||||
Please see the reference documentation for Account for more info. Calls, presence, and buddy list will be explained in later sections.
|
||||
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
|
||||
|
||||
Calls
|
||||
=====
|
||||
Calls are represented by Call class.
|
||||
|
||||
Subclassing the Call Class
|
||||
------------------------------------
|
||||
To use the Call class, normally application SHOULD create its own subclass, such as::
|
||||
|
||||
class MyCall : public Call
|
||||
{
|
||||
public:
|
||||
MyCall(Account &acc, int call_id = PJSUA_INVALID_ID)
|
||||
: Call(acc, call_id)
|
||||
{ }
|
||||
|
||||
~MyCall()
|
||||
{ }
|
||||
|
||||
// Notification when call's state has changed.
|
||||
virtual void onCallState(OnCallStateParam &prm);
|
||||
|
||||
// Notification when call's media state has changed.
|
||||
virtual void onCallMediaState(OnCallMediaStateParam &prm);
|
||||
};
|
||||
|
||||
In its subclass, application can implement the call callbacks, which is basically used to process events related to the call, such as call state change or incoming call transfer request.
|
||||
|
||||
Making Outgoing Calls
|
||||
--------------------------------------
|
||||
Making outgoing call is simple, just invoke makeCall() method of the Call object. Assuming you have the Account object as acc variable and destination URI string in dst_uri, you can initiate outgoing call with the snippet below::
|
||||
|
||||
Call *call = new MyCall(*acc);
|
||||
CallOpParam prm(true); // Use default call settings
|
||||
try {
|
||||
call->makeCall(dest_uri, prm);
|
||||
} catch(Error& err) {
|
||||
}
|
||||
|
||||
The snippet above creates a Call object and initiates outgoing call to dst_uri using the default call settings. Subsequent operations to the call can use the method in the call instance, and events to the call will be reported to the callback. More on the callback will be explained a bit later.
|
||||
|
||||
Receiving Incoming Calls
|
||||
--------------------------------------
|
||||
Incoming calls are reported as onIncomingCall() of the Account class. You must derive a class from the Account class to handle incoming calls.
|
||||
|
||||
Below is a sample code of the callback implementation::
|
||||
|
||||
void MyAccount::onIncomingCall(OnIncomingCallParam &iprm)
|
||||
{
|
||||
Call *call = new MyCall(*this, iprm.callId);
|
||||
CallOpParam prm;
|
||||
prm.statusCode = (pjsip_status_code)200;
|
||||
call->answer(prm);
|
||||
}
|
||||
|
||||
For incoming calls, the call instance is created in the callback parameter as shown above. Application should make sure to store the call instance during the lifetime of the call (that is until the call is disconnected).
|
||||
|
||||
Call Properties
|
||||
-------------------
|
||||
All call properties such as state, media state, remote peer information, etc. are stored as CallInfo class, which can be retrieved from the call object with using getInfo() method of the Call.
|
||||
|
||||
Call Disconnection
|
||||
--------------------------------------
|
||||
Call disconnection event is a special event since once the callback that reports this event returns, the call is no longer valid and any operations invoked to the call object will raise error exception. Thus, it is recommended to delete the call object inside the callback.
|
||||
|
||||
The call disconnection is reported in onCallState() method of Call and it can be detected as follows::
|
||||
|
||||
void MyCall::onCallState(OnCallStateParam &prm)
|
||||
{
|
||||
CallInfo ci = getInfo();
|
||||
if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
|
||||
/* Delete the call */
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
Working with Call's Audio Media
|
||||
-------------------------------------------------
|
||||
You can only operate with the call's audio media (e.g. connecting the call to the sound device in the conference bridge) when the call's audio media is ready (or active). The changes to the call's media state is reported in onCallMediaState() callback, and if the call’s audio media is ready (or active) the function getMedia() will return a valid audio media.
|
||||
|
||||
Below is a sample code to connect the call to the sound device when the media is active::
|
||||
|
||||
void MyCall::onCallMediaState(OnCallMediaStateParam &prm)
|
||||
{
|
||||
CallInfo ci = getInfo();
|
||||
// Iterate all medias
|
||||
for (unsigned i = 0; i < ci.media.size(); i++) {
|
||||
if (getMedia(i)) { // Check if the media is valid
|
||||
AudioMedia *aud_med = getMedia(i);
|
||||
// Connect the call audio media to sound device
|
||||
aud_med->startTransmit();
|
||||
->startTransmit(*aud_med);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When the audio media becomes inactive (for example when the call is put on hold), there is no need to stop the audio media's transmission to/from the sound device since the call's audio media will be removed automatically from the conference bridge when it's no longer valid, and this will automatically remove all connections to/from the call.
|
||||
|
||||
Call Operations
|
||||
--------------------------------------
|
||||
Some of the operations to the Call object, such as making outgoing call, answering, holding, sending re-INVITE, etc. Please see the reference documentation of Call for more info.
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The PJSIP Book documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Nov 30 06:36:26 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [ 'breathe', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'The PJSIP Book'
|
||||
copyright = u'2013, Teluu Ltd.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0-beta'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'ThePJSIPBookdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'ThePJSIPBook.tex', u'The PJSIP Book Documentation',
|
||||
u'Sauw Ming Liong, Benny Prijono', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'thepjsipbook', u'The PJSIP Book Documentation',
|
||||
[u'Sauw Ming Liong', u'Benny Prijono'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'ThePJSIPBook', u'The PJSIP Book Documentation',
|
||||
u'Sauw Ming Liong@*Benny Prijono', 'ThePJSIPBook', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
breathe_projects = {
|
||||
"pjsua2": "xml/",
|
||||
}
|
||||
|
||||
breathe_default_project = "pjsua2"
|
||||
|
||||
breathe_projects_source = {
|
||||
"pjsua2":"../../pjsip/include/pjsua2"
|
||||
}
|
||||
|
||||
breathe_domain_by_extension = {
|
||||
"hpp":"cpp"
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
|
||||
|
||||
|
||||
Development Considerations
|
||||
**************************
|
||||
|
||||
Let's review various aspects that you need to consider when developing your application.
|
||||
|
||||
|
||||
Target Platforms
|
||||
================
|
||||
Platform selection will affect all aspects of development, and here we will cover considerations for each platforms that we support.
|
||||
|
||||
Windows Desktop
|
||||
---------------
|
||||
Windows is supported from Windows 2000 up to the recent Windows 8 and beyond. All features are expected to work. 64bit support was added recently. Development is based on Visual Studio. Considerations for this platform include:
|
||||
|
||||
#. because Visual Studio file format keeps changing on every release, we decided to support the lowest denominator, namely Visual Studio 2005. Unfortunately the project upgrade procedure fails on Visual Studio 2010, and we don't have any solution for that. But VS 2008 and VS 20012 work.
|
||||
|
||||
MacOS X
|
||||
-------
|
||||
All features are expected to work. Considerations include:
|
||||
|
||||
#. development with XCode is currently not supported. This is **not** to say that you cannot use XCode, but PJSIP only provides basic Makefiles and if you want to use XCode you'd need to arrange the project yourself.
|
||||
#. Mac systems typically provides very good sound device, so we don't expect any problems with audio on Mac.
|
||||
|
||||
Linux Desktop
|
||||
-------------
|
||||
All features are expected to work. Linux considerations:
|
||||
|
||||
#. use our native ALSA backend instead of PortAudio because ALSA has less jitter than OSS and our backend is more lightweight than PortAudio
|
||||
|
||||
|
||||
iOS for iPhone, iPad, and iPod Touch
|
||||
------------------------------------
|
||||
All features except video are expected to work (video is coming soon!). Considerations for iOS:
|
||||
|
||||
#. you need to use TCP transport for SIP for the background feature to work
|
||||
#. IP change (for example when user is changing access point) is a feature frequently asked by developers and you can find the documentation here: http://trac.pjsip.org/repos/wiki/IPAddressChange
|
||||
#. there are some specific issues for iOS 7 and beyond, please see http://trac.pjsip.org/repos/ticket/1697
|
||||
#. if SSL is needed, you need to compile OpenSSL for iOS
|
||||
|
||||
|
||||
Android
|
||||
-------
|
||||
All features except video are expected to work (video is coming soon!). Considerations for Android:
|
||||
|
||||
#. you can only use pjsua2 Java binding for this target
|
||||
#. it has been reported that Android audio device is not so good in general, so some audio tuning may be needed. Echo cancellation also needs to be checked
|
||||
#. this is also a new platform for us
|
||||
|
||||
|
||||
Symbian
|
||||
-------
|
||||
Symbian has been supported for a long time but it doesn't get too many attention recently. In general all features (excluding video) are expected to work, but we're not going to do Symbian specific development anymore. Other considerations for Symbian:
|
||||
|
||||
#. the MDA audio is not very good (it has high latency), so normally you'd want to use Audio Proxy Server (APS) or VoIP Audio Service (VAS) for the audio device, which we support. Using these audio backends will also provide us with high quality echo cancellation as well as low bitrate codecs such as AMR-NB, G.729, and iLBC. But VAS and APS requires purchase of Nokia development certificate to sign the app, and also since APS and VAS only run on specific device type, you need to package the app carefully and manage the deployment to cover various device types.
|
||||
|
||||
|
||||
Blackberry 10
|
||||
-------------
|
||||
Blackberry 10 is supported since PJSIP version 2.2. As this is a relatively new platform for us, we are currently listening to developer's feedback regarding the port. But so far it seems to be working well. Some considerations for BB10 platform include:
|
||||
|
||||
#. IP change (for example when user is changing access point) is a feature frequently asked by developers and you can find the documentation here: http://trac.pjsip.org/repos/wiki/IPAddressChange
|
||||
|
||||
|
||||
Windows Mobile
|
||||
--------------
|
||||
This is the old Windows Mobile platform that is based on WinCE. This platform has been supported for a long time, but doesn't seem to attract any attentions recently. We expect all features except video to work, but there may be some errors every now and then because this target is not actively maintained. No new development will be done for this platform.
|
||||
|
||||
Other considerations for Windows Mobile platform are:
|
||||
|
||||
#. the quality of audio device on WM varies a lot, and this affects audio latency. Audio latency could go as high as hundreds of millisecond on bad hardware.
|
||||
#. echo cancellation could be a problem. We can only use basic echo suppressor due to hardware limitation, and combined with bad quality of audio device, it may cause ineffective echo cancellation. This could be mitigated by setting the audio level to low.
|
||||
|
||||
|
||||
Windows Phone 8
|
||||
---------------
|
||||
Windows Phone 8 (WP8) support is being added to PJSIP version 2.2 and is still under development. Specific considerations for this platform are:
|
||||
|
||||
#. WP8 governs specific interaction with WP8 GUI and framework that needs to be followed by application in order to make VoIP call work seamlessly on the device. Some lightweight process will be created by WP8 framework in order for background call to work and PJSIP needs to put it's background processing in this process's context. Currently this feature is under development.
|
||||
|
||||
|
||||
|
||||
Embedded Linux
|
||||
--------------
|
||||
In general embedded Linux support is similar to Linux and we find no problems with it. We found some specific considerations for embedded Linux as follows:
|
||||
|
||||
#. the performance of the audio device is probably the one with most issues, as some development boards have not so descent sound device that has high audio jitter (or burst) and latency. This will affect end to end audio latency and also the performance of the echo canceller. Also we found that ALSA generally works better than OSS, so if you can have ALSA up and running that will be better. Use our native ALSA backend audio device instead of PortAudio since it is simpler and lighter.
|
||||
|
||||
|
||||
QNX or Other Posix Embedded OS
|
||||
------------------------------
|
||||
This is not part of our officially supported OSes, but users have run PJSIP on QNX and Blackberry 10 is based on QNX too. Since QNX provides Posix API, and maybe by using the settings found in the configure-bb10 script, PJSIP should be able to run on it, but you need to develop PJMEDIA sound device wrapper for your audio device. Other than this, we don't have enough experience to comment on the platform.
|
||||
|
||||
|
||||
Other Unix Desktop OSes
|
||||
-----------------------
|
||||
Community members, including myself, have occasionally run PJSIP on other Unix OSes such as Solaris, FreeBSD, and OpenBSD. We expect PJSIP to run on these platforms (maybe with a little kick).
|
||||
|
||||
|
||||
Porting to Other Embedded OSes
|
||||
------------------------------
|
||||
It is possible to port PJSIP to other embedded OSes or even directly to device without OS and people have done so. In general, the closer resemblance the new OS to existing supported OS, the easier the porting job will be. The good thing is, PJSIP has been made to be very very portable, and system dependent features are localized in PJLIB and PJMEDIA audio device, so the effort is more quantifiable. Once you are able to successfully run *pjlib-test*, you are more or less there with your porting effort. Other than that, if you really want to port PJSIP to new platform, you probably already know what you're doing.
|
||||
|
||||
|
||||
|
||||
Which API to Use
|
||||
================
|
||||
|
||||
PJSIP, PJMEDIA, and PJNATH Level
|
||||
--------------------------------
|
||||
At the lowest level we have the individual PJSIP **C** libraries, which consist of PJSIP, PJMEDIA, and PJNATH, with PJLIB-UTIL and PJLIB as support libraries. This level provides the most flexibility, but it's also the hardest to use. The only reason you'd want to use this level is if:
|
||||
|
||||
#. you only need the individual library (say, PJNATH)
|
||||
#. you need to be very very tight in footprint (say when things need to be measured in Kilobytes instead of Megabytes)
|
||||
#. you are **not** developing a SIP client
|
||||
|
||||
Use the corresponding PJSIP, PJMEDIA, PJNATH manuals from http://trac.pjsip.org/repos/ for information on how to use the libraries. If you use PJSIP, the PJSIP Developer's Guide (PDF) from that page provides in-depth information about PJSIP library.
|
||||
|
||||
PJSUA-LIB API
|
||||
-------------
|
||||
Next up is PJSUA-LIB API that combines all those libraries into a high level, integrated client user agent library written in C. This is the library that most PJSIP users use, and the highest level abstraction before pjsua2 was created.
|
||||
|
||||
Motivations for using PJSUA-LIB library includes:
|
||||
|
||||
#. developing client application (PJSUA-LIB is optimized for developing client app)
|
||||
#. better efficiency than higher level API
|
||||
|
||||
|
||||
pjsua2 C++ API
|
||||
--------------
|
||||
pjsua2 is a new, objected oriented, C++ API created on top of PJSUA-LIB. The API is different than PJSUA-LIB, but it should be even easier to use and it should have better documentation too (such as this book). The pjsua2 API removes most cruxes typically associated with PJSIP, such as the pool and pj_str_t, and add new features such as object persistence so you can save your configs to a file, for example. All data structures are rewritten for more clarity.
|
||||
|
||||
A C++ application can use pjsua2 natively, while at the same time still has access to the lower level objects if it needs to. This means that the C++ application should not loose any information from using the C++ abstraction, compared to if it is using PJSUA-LIB directly. The C++ application also should not loose the ability to extend the library. It would still be able to register a custom PJSIP module, pjmedia_port, pjmedia_transport, and so on.
|
||||
|
||||
Benefits of using pjsua2 C++ API include:
|
||||
|
||||
#. cleaner object oriented API
|
||||
#. uniform API for higher level language such as Java and Python
|
||||
#. persistence API
|
||||
#. the ability to access PJSUA-LIB and lower level libraries when needed (including the ability to extend the libraries, for example creating custom PJSIP module, pjmedia_port, pjmedia_transport, etc.)
|
||||
|
||||
|
||||
Some considerations on pjsua2 C++ API are:
|
||||
#. instead of returning error, the API uses exception for error reporting
|
||||
#. pjsua2 uses standard C++ library
|
||||
#. the performance penalty due to the API abstraction should be negligible on typical modern device
|
||||
|
||||
|
||||
|
||||
pjsua2 API for Java, Python, and Others
|
||||
---------------------------------------
|
||||
The pjsua2 API is also available for non-native code via SWIG binding. Configurations for Java and Python are provided with the distribution. Thanks to SWIG, other language bindings may be generated relatively easily.
|
||||
|
||||
The pjsua2 API for non-native code is effectively the same as pjsua2 C++ API. However, unlike C++, you cannot access PJSUA-LIB and the underlying C libraries from the scripting language, hence you are limited to what pjsua2 provides.
|
||||
|
||||
You can use this API if native application development is not available in target platform (such as Android), or if you prefer to develop with non-native code instead of C/C++.
|
||||
|
||||
|
||||
|
||||
|
||||
Network and Infrastructure Considerations
|
||||
=========================================
|
||||
|
||||
NAT Issues
|
||||
----------
|
||||
|
||||
|
||||
TCP Requirement
|
||||
---------------
|
||||
If you support iOS devices in your service, you need to use TCP, because only TCP will work on iOS device when it is in background mode. This means your infrastructure needs to support TCP.
|
||||
|
||||
|
||||
Sound Device
|
||||
============
|
||||
|
||||
Latency
|
||||
-------
|
||||
|
||||
|
||||
Echo Cancellation
|
||||
-----------------
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
|
||||
|
||||
Endpoint
|
||||
************
|
||||
The Endpoint class is a singleton class, and application MUST create one and at most one of this class instance before it can do anything else. This class is the core class of PJSUA2, and it provides the following functions:
|
||||
|
||||
- Starting up and shutting down
|
||||
- Customization of configurations, such as core UA (User Agent) SIP configuration, media configuration, and logging configuration
|
||||
|
||||
This section will describe the functions above.
|
||||
|
||||
To use the Endpoint class, normally application does not need to subclass it unless:
|
||||
|
||||
- application wants to implement/override Endpoint’s callback methods to get the events such as transport state change or NAT detection completion, or
|
||||
- application schedules a timer using Endpoint.utilTimerSchedule() API. In this case, application needs to implement the onTimer() callback to get the notification when the timer expires.
|
||||
|
||||
Instantiating the Endpoint
|
||||
--------------------------
|
||||
Before anything else, you must instantiate the Endpoint class::
|
||||
|
||||
Endpoint *ep = new Endpoint;
|
||||
|
||||
Once the endpoint is instantiated, you can retrieve the Endpoint instance using Endpoint.instance() static method.
|
||||
|
||||
Creating the Library
|
||||
----------------------
|
||||
Create the library by calling its libCreate() method::
|
||||
|
||||
try {
|
||||
ep->libCreate();
|
||||
} catch(Error& err) {
|
||||
cout << "Startup error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
The libCreate() method will raise exception if error occurs, so we need to trap the exception using try/catch clause as above.
|
||||
|
||||
Initializing the Library and Configuring the Settings
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
The EpConfig class provides endpoint configuration which allows the customization of the following settings:
|
||||
|
||||
- UAConfig, to specify core SIP user agent settings.
|
||||
- MediaConfig, to specify various media settings, including ICE and TURN.
|
||||
- LogConfig, to customize logging settings.
|
||||
|
||||
To customize the settings, create instance of EpConfig class and specify them during the endpoint initialization (will be explained more later), for example::
|
||||
|
||||
EpConfig ep_cfg;
|
||||
ep_cfg.logConfig.level = 5;
|
||||
ep_cfg.uaConfig.maxCalls = 4;
|
||||
ep_cfg.mediaConfig.sndClockRate = 16000;
|
||||
|
||||
Next, you can initialize the library by calling libInit()::
|
||||
|
||||
try {
|
||||
EpConfig ep_cfg;
|
||||
// Specify customization of settings in ep_cfg
|
||||
ep->libInit(ep_cfg);
|
||||
} catch(Error& err) {
|
||||
cout << "Initialization error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
The snippet above initializes the library with the default settings.
|
||||
|
||||
Creating One or More Transports
|
||||
--------------------------------------------------
|
||||
Application needs to create one or more transports before it can send or receive SIP messages::
|
||||
|
||||
try {
|
||||
TransportConfig tcfg;
|
||||
tcfg.port = 5060;
|
||||
TransportId tid = ep->transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
|
||||
} catch(Error& err) {
|
||||
cout << "Transport creation error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
The transportCreate() method returns the newly created Transport ID and it takes the transport type and TransportConfig object to customize the transport settings like bound address and listening port number. Without this, by default the transport will be bound to INADDR_ANY and any available port.
|
||||
|
||||
There is no real use of the Transport ID, except to create userless account (with Account.create(), as will be explained later), and perhaps to display the list of transports to user if the application wants it.
|
||||
|
||||
Starting the Library
|
||||
--------------------
|
||||
Now we're ready to start the library. We need to start the library to finalize the initialization phase, e.g. to complete the initial STUN address resolution, initialize/start the sound device, etc. To start the library, call libStart() method::
|
||||
|
||||
try {
|
||||
ep->libStart();
|
||||
} catch(Error& err) {
|
||||
cout << "Startup error: " << err.reason << endl;
|
||||
}
|
||||
|
||||
Shutting Down the Library
|
||||
--------------------------------------
|
||||
Once the application exits, the library needs to be shutdown so that resources can be released back to the operating system. This is done by deleting the Endpoint instance, which will internally call libDestroy()::
|
||||
|
||||
delete ep;
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import urllib2
|
||||
import sys
|
||||
|
||||
def fetch_rst(url):
|
||||
print 'Fetching %s..' % url
|
||||
req = urllib2.Request(url)
|
||||
|
||||
fd = urllib2.urlopen(req, timeout=30)
|
||||
body = fd.read()
|
||||
|
||||
pos = body.find("{{{")
|
||||
if pos >= 0:
|
||||
body = body[pos+4:]
|
||||
|
||||
pos = body.find("}}}")
|
||||
if pos >= 0:
|
||||
body = body[:pos]
|
||||
|
||||
pos = body.find("#!rst")
|
||||
if pos >= 0:
|
||||
body = body[pos+6:]
|
||||
|
||||
pos = url.rfind("/")
|
||||
if pos >= 0:
|
||||
filename = url[pos+1:]
|
||||
else:
|
||||
filename = url
|
||||
|
||||
pos = filename.find('?')
|
||||
if pos >= 0:
|
||||
filename = filename[:pos]
|
||||
|
||||
filename += ".rst"
|
||||
f = open(filename, 'w')
|
||||
f.write(body)
|
||||
f.close()
|
||||
|
||||
|
||||
def process_index(index):
|
||||
pages = []
|
||||
|
||||
f = open(index + '.rst', 'r')
|
||||
line = f.readline()
|
||||
while line:
|
||||
if line.find('toctree::') >= 0:
|
||||
break
|
||||
line = f.readline()
|
||||
|
||||
if line.find('toctree::') < 0:
|
||||
return []
|
||||
# Skip directive (or whatever it's called
|
||||
line = f.readline().strip()
|
||||
while line and line[0] == ':':
|
||||
line = f.readline().strip()
|
||||
# Skip empty lines
|
||||
line = f.readline().strip()
|
||||
while not line:
|
||||
line = f.readline().strip()
|
||||
# Parse names
|
||||
while line:
|
||||
pages.append(line)
|
||||
line = f.readline().strip()
|
||||
|
||||
f.close()
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print "** Warning: This will overwrite ALL RST files in current directory. Continue? [n] ",
|
||||
if sys.stdin.readline().strip() != 'y':
|
||||
sys.exit(0)
|
||||
|
||||
url_format = 'http://trac.pjsip.org/repos/wiki/pjsip-doc/%s?format=txt'
|
||||
|
||||
index = url_format % ('index')
|
||||
fetch_rst(index)
|
||||
|
||||
pages = process_index('index')
|
||||
for page in pages:
|
||||
url = url_format % (page)
|
||||
fetch_rst(url)
|
||||
|
||||
print 'Done.'
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
.. The PJSIP Book documentation master file, created by
|
||||
sphinx-quickstart on Sat Nov 30 06:36:26 2013.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to The PJSIP Book's documentation!
|
||||
==========================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
intro
|
||||
consider
|
||||
intro_pjsua2
|
||||
endpoint
|
||||
account
|
||||
media
|
||||
call
|
||||
presence
|
||||
samples
|
||||
optimization
|
||||
media_quality
|
||||
network_problems
|
||||
reference
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
|
||||
Introduction to PJSUA2
|
||||
*******************************
|
||||
This documentation is intended for developers looking to develop Session Initiation Protocol (SIP) based client application. Some knowledge on SIP is definitely required, and of course some programming experience. Prior knowledge of PJSUA C API is not needed, although it will probably help.
|
||||
|
||||
This PJSUA2 module provides very high level API to do SIP calls, presence, and instant messaging, as well as handling media and NAT traversal. Knowledge of SIP protocol details (such as the grammar, transaction, dialog, and whatnot) are not required, however you should know how SIP works in general and particularly how to configure SIP clients to, e.g. register to a SIP provider, specify SIP URI to make call to, and so on, to use the module.
|
||||
|
||||
Getting Started with PJSIP
|
||||
==============================
|
||||
To begin using PJSIP
|
||||
http://trac.pjsip.org/repos/wiki/Getting-Started
|
||||
|
||||
PJSIP Info and Documentation
|
||||
================================
|
||||
PJSIP General Wiki:
|
||||
http://trac.pjsip.org/repos/wiki
|
||||
|
||||
PJSIP FAQ:
|
||||
http://trac.pjsip.org/repos/wiki/FAQ
|
||||
|
||||
PJSIP Reference Manual:
|
||||
http://trac.pjsip.org/repos/wiki - Reference Manual
|
||||
|
||||
Building PJSUA2
|
||||
=================
|
||||
PJSUA2 API declaration can be found in pjproject/pjsip/include/pjsua2 while the source codes are located in pjproject/pjsip/src/pjsua2. It will be automatically built when you compile PJSIP.
|
||||
|
||||
PJSUA2 Language Binding Support
|
||||
===================================
|
||||
Optional if you want to:
|
||||
SWIG (minimum version 2.0.5)
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
|
||||
PJSUA2-High Level API
|
||||
******************************
|
||||
PJSUA2 is an object-oriented abstraction above PJSUA API. It provides high level API for constructing Session Initiation Protocol (SIP) multimedia user agent applications (a.k.a Voice over IP/VoIP softphones). It wraps together the signaling, media, and NAT traversal functionality into easy to use call control API, account management, buddy list management, presence, and instant messaging, along with multimedia features such as local conferencing, file streaming, local playback, and voice recording, and powerful NAT traversal techniques utilizing STUN, TURN, and ICE.
|
||||
|
||||
PJSUA2 is implemented on top of PJSUA-LIB API. The SIP and media features and object modelling follows what PJSUA-LIB provides (for example, we still have accounts, call, buddy, and so on), but the API to access them is different. These features will be described later in this chapter. PJSUA2 is a C++ library, which you can find under ``pjsip`` directory in the PJSIP distribution. The C++ library can be used by native C++ applications directly. But PJSUA2 is not just a C++ library. From the beginning, it has been designed to be accessible from high level non-native languages such as Java and Python. This is achieved by SWIG binding. And thanks to SWIG, binding to other languages can be added relatively easily in the future.
|
||||
|
||||
|
||||
PJSUA2 Main Classes
|
||||
======================
|
||||
Here are the main classes of the PJSUA2:
|
||||
|
||||
Endpoint
|
||||
--------------
|
||||
This is the main class of PJSUA2. You need to instantiate one and exactly one of this class, and from the instance you can then initialize and start the library.
|
||||
|
||||
Account
|
||||
-------------
|
||||
An account specifies the identity of the person (or endpoint) on one side of SIP conversation. At least one account instance needs to be created before anything else, and from the account instance you can start making/receiving calls as well as adding buddies.
|
||||
|
||||
Media
|
||||
----------
|
||||
This class represents a media element which is capable to either produce media or takes media.
|
||||
|
||||
Call
|
||||
------
|
||||
This class is used to manipulate calls, such as to answer the call, hangup the call, put the call on hold, transfer the call, etc.
|
||||
|
||||
Buddy
|
||||
---------
|
||||
This class represents a remote buddy (a person, or a SIP endpoint). You can subscribe to presence status of a buddy to know whether the buddy is online/offline/etc., and you can send and receive instant messages to/from the buddy.
|
||||
|
||||
General Concepts
|
||||
==================
|
||||
Class Usage Patterns
|
||||
---------------------
|
||||
With the methods of the main classes above, you will be able to invoke various operations to the object quite easily. But how can we get events/notifications from these classes? Each of the main classes above (except Media) will get their events in the callback methods. So to handle these events, just derive a class from the corresponding class (Endpoint, Call, Account, or Buddy) and implement/override the relevant method (depending on which event you want to handle). More will be explained in later sections.
|
||||
|
||||
Objects Persistence
|
||||
---------------------
|
||||
PJSUA2 includes PersistentObject class to provide functionality to read/write data from/to a document (string or file). The data can be simple data types such as boolean, number, string, and string arrays, or a user defined object. Currently the implementation supports reading and writing from/to JSON document, but the framework allows application to extend the API to support other document formats.
|
||||
|
||||
As such, classes which inherit from PersistentObject, such as EpConfig (endpoint configuration), AccountConfig (account configuration), and BuddyConfig (buddy configuration) can be loaded/saved from/to a file. Here’s an example to save a config to a file:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
EpConfig epCfg;
|
||||
JsonDocument jDoc;
|
||||
epCfg.uaConfig.maxCalls = 61;
|
||||
epCfg.uaConfig.userAgent = "Just JSON Test";
|
||||
jDoc.writeObject(epCfg);
|
||||
jDoc.saveFile(“jsontest.js”);
|
||||
|
||||
To load from the file:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
EpConfig epCfg;
|
||||
JsonDocument jDoc;
|
||||
jDoc.loadFile("jsontest.js");
|
||||
jDoc.readObject(epCfg);
|
||||
|
||||
Error Handling
|
||||
---------------
|
||||
We use exceptions as means to report error, as this would make the program flows more naturally. Operations which yield error will raise Error exception. If you prefer to display the error in more structured manner, the Error class has several members to explain the error, such as the operation name that raised the error, the error code, and the error message itself.
|
||||
|
||||
Asynchronous Operations
|
||||
-------------------------
|
||||
If you have developed applications with PJSIP, you'll know about this already. In PJSIP, all operations that involve sending and receiving SIP messages are asynchronous, meaning that the function that invokes the operation will complete immediately, and you will be given the completion status as callbacks.
|
||||
|
||||
Take a look for example the makeCall() method of the Call class. This function is used to initiate outgoing call to a destination. When this function returns successfully, it does not mean that the call has been established, but rather it means that the call has been initiated successfully. You will be given the report of the call progress and/or completion in the onCallState() callback method of Call class.
|
||||
|
||||
Threading
|
||||
----------
|
||||
For platforms that require polling, the PJSUA2 module provides its own worker thread to poll PJSIP, so it is not necessary to instantiate own your polling thread. Having said that the application should be prepared to have the callbacks called by different thread than the main thread. The PJSUA2 module should be thread safe.
|
||||
|
||||
|
||||
Using in C++ Application
|
||||
========================
|
||||
As mentioned in previous chapter, A C++ application can use *pjsua2* natively, while at the same time still has access to the lower level objects and the ability to extend the libraries if it needs to. Using the API will be exactly the same as the API reference that is written in this book.
|
||||
|
||||
Here is a sample complete C++ application to give you some idea about the API. The snippet below initializes the library and creates an account that registers to our pjsip.org SIP server.
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#include <pjsua2.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace pj;
|
||||
|
||||
// Subclass to extend the Account and get notifications etc.
|
||||
class MyAccount : public Account {
|
||||
public:
|
||||
virtual void onRegState(OnRegStateParam &prm) {
|
||||
AccountInfo ai = getInfo();
|
||||
std::cout << (ai.regIsActive? "*** Register:" : "*** Unregister:")
|
||||
<< " code=" << prm.code << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
Endpoint ep;
|
||||
|
||||
ep.libCreate();
|
||||
|
||||
// Initialize endpoint
|
||||
EpConfig ep_cfg;
|
||||
ep_cfg.uaConfig.userAgent = "pjsua2-hello";
|
||||
ep.libInit( ep_cfg );
|
||||
|
||||
// Create SIP transport. Error handling sample is shown
|
||||
TransportConfig tcfg;
|
||||
tcfg.port = 5060;
|
||||
try {
|
||||
ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
|
||||
} catch (Error &err) {
|
||||
std::cout << err.info() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Start the library (worker threads etc)
|
||||
ep.libStart();
|
||||
std::cout << "*** PJSUA2 STARTED ***" << std::endl;
|
||||
|
||||
// Configure an AccountConfig
|
||||
AccountConfig acfg;
|
||||
acfg.idUri = "sip:test@pjsip.org";
|
||||
acfg.regConfig.registrarUri = "sip:pjsip.org";
|
||||
AuthCredInfo cred("digest", "*", "test", 0, "secret");
|
||||
acfg.sipConfig.authCreds.push_back( cred );
|
||||
|
||||
// Create the account
|
||||
MyAccount *acc = new MyAccount;
|
||||
acc->create(acfg);
|
||||
|
||||
// Here we don't have anything else to do..
|
||||
pj_thread_sleep(10000);
|
||||
|
||||
// Delete the account. This will unregister from server
|
||||
delete acc;
|
||||
|
||||
// This will implicitly shutdown the library
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Using in Python Application
|
||||
===========================
|
||||
|
||||
|
||||
|
||||
Using in Java Application
|
||||
=========================
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ThePJSIPBook.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ThePJSIPBook.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,113 @@
|
|||
|
||||
|
||||
Media
|
||||
=====
|
||||
Media objects are objects that are capable to either produce media or takes media. In PJMEDIA terms, these objects are implemented as media ports (pjmedia_port).
|
||||
|
||||
An important subclass of Media is AudioMedia which represents audio media. There are several type of audio media objects supported in PJSUA2:
|
||||
|
||||
- CallAudioMedia, to transmit and receive audio to/from remote person.
|
||||
- AudioMediaPlayer, to play WAV file(s).
|
||||
- AudioMediaRecorder, to record audio to a WAV file.
|
||||
|
||||
More media objects may be added in the future.
|
||||
|
||||
The Audio Conference Bridge
|
||||
----------------------------
|
||||
The conference bridge provides a simple but yet powerful concept to manage audio flow between the audio medias. The principle is very simple, that is you connect audio source to audio destination, and the bridge will make the audio flows from the source to destination, and that's it. If more than one sources are transmitting to the same destination, then the audio from the sources will be mixed. If one source is transmitting to more than one destinations, the bridge will take care of duplicating the audio from the source to the multiple destinations.
|
||||
|
||||
In PJSUA2, all audio media objects are plugged-in to the central conference bridge for easier manipulation. A plugged-in audio media will not be connected to anything, so media will not flow from/to any objects. An audio media source can start/stop the transmission to a destination by using the API AudioMedia.startTransmit() / AudioMedia.stopTransmit().
|
||||
|
||||
An audio media object plugged-in to the conference bridge will be given a port ID number that identifies the object in the bridge. Application can use the API AudioMedia.getPortId() to retrieve the port ID. Normally, application should not need to worry about the port ID (as all will be taken care of by the bridge) unless application want to create its own custom audio media.
|
||||
|
||||
Playing a WAV File
|
||||
++++++++++++++++++
|
||||
To playback the WAV file to the speaker, just start the transmission of the WAV playback object to the sound device::
|
||||
|
||||
AudioMediaPlayer player;
|
||||
try {
|
||||
player.createPlayer(“file.wav”);
|
||||
player.startTransmit();
|
||||
} catch(Error& err) {
|
||||
}
|
||||
|
||||
Once you're done with the playback, just stop the transmission n to stop the playback::
|
||||
|
||||
try {
|
||||
player.stopTransmit();
|
||||
} catch(Error& err) {
|
||||
}
|
||||
|
||||
Recording to WAV File
|
||||
+++++++++++++++++++++
|
||||
Or if you want to record the microphone to the WAV file, simply do this::
|
||||
|
||||
AudioMediaRecorder recorder;
|
||||
try {
|
||||
recorder.createRecorder(“file.wav”);
|
||||
.startTransmit(recorder);
|
||||
} catch(Error& err) {
|
||||
}
|
||||
|
||||
And the media will flow from the sound device to the WAV record file. As usual, to stop or pause recording, just stop the transmission::
|
||||
|
||||
try {
|
||||
.stopTransmit(recorder);
|
||||
} catch(Error& err) {
|
||||
}
|
||||
|
||||
(Note that stopping the transmission to the WAV recorder as above does not close the WAV file, and you can resume recording by connecting a source to the WAV recorder again. You cannot playback the recorded WAV file before you close it.)
|
||||
|
||||
Looping Audio
|
||||
+++++++++++++
|
||||
If you want, you can loop the audio of a media object to itself (i.e. the audio received from the object will be transmitted to itself). For example, you can loop the audio of the sound device with::
|
||||
|
||||
.startTransmit();
|
||||
|
||||
With the above connection, audio received from the microphone will be played back to the speaker. This is useful to test whether the microphone and speaker are working properly.
|
||||
|
||||
You can loop-back audio from any objects, as long as the object has bidirectional media. That means you can loop the call's audio media, so that audio received from the remote person will be transmitted back to her/him. But you can't loop the WAV player or recorder since these objects can only play or record and not both.
|
||||
|
||||
Normal Call
|
||||
+++++++++++
|
||||
|
||||
A single call can has several audio medias. Application can retrieve the audio media by using the API Call.getMedia()::
|
||||
|
||||
AudioMedia *aud_med = (AudioMedia *)call.getMedia(0);
|
||||
|
||||
In the above, we assume that the audio media is located in index 0. Of course on a real application, we should iterate the medias to find the correct media index by checking whether the media returned by getMedia() is valid. More on this will be explained later in the Call section. Then for a normal call, we would want to establish bidirectional audio with the remote person, which can be done easily by connecting the sound device and the call audio media and vice versa::
|
||||
|
||||
// This will connect the sound device/mic to the call audio media
|
||||
->startTransmit(*aud_med);
|
||||
|
||||
// And this will connect the call audio media to the sound device/speaker
|
||||
aud_med->startTransmit();
|
||||
|
||||
Second Call
|
||||
+++++++++++
|
||||
Suppose we want to talk with two remote parties at the same time. Since we already have bidirectional media connection with one party, we just need to add bidirectional connection with the other party using the code below::
|
||||
|
||||
AudioMedia *aud_med2 = (AudioMedia *)call2.getMedia(0);
|
||||
->startTransmit(*aud_med2);
|
||||
aud_med2->startTransmit();
|
||||
|
||||
Now we can talk to both parties at the same time, and we will hear audio from either party. But at this stage, the remote parties can't talk or hear each other (i.e. we're not in full conference mode yet).
|
||||
|
||||
Conference Call
|
||||
+++++++++++++++
|
||||
To enable both parties talk to each other, just establish bidirectional media between them::
|
||||
|
||||
aud_med->startTransmit(*aud_med2);
|
||||
aud_med2->startTransmit(*aud_med);
|
||||
|
||||
Now the three parties (us and both remote parties) will be able to talk to each other.
|
||||
|
||||
Recording the Conference
|
||||
++++++++++++++++++++++++
|
||||
|
||||
While doing the conference, it perfectly makes sense to want to record the conference to a WAV file, and all we need to do is to connect the microphone and both calls to the WAV recorder::
|
||||
|
||||
->startTransmit(recorder);
|
||||
aud_med->startTransmit(recorder);
|
||||
aud_med2->startTransmit(recorder);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
Media Quality
|
||||
*************
|
||||
|
||||
Audio Quality
|
||||
=============
|
||||
|
||||
Video Quality
|
||||
=============
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
Network Problems
|
||||
****************
|
||||
|
||||
IP Address Change
|
||||
=================
|
||||
|
||||
Blocked/Filtered Network
|
||||
========================
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
General Configuration Optimization
|
||||
**********************************
|
||||
|
||||
PJSUA2 Settings
|
||||
===============
|
||||
|
||||
CPU Optimization
|
||||
================
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
|
||||
Buddy (Presence)
|
||||
================
|
||||
This class represents a remote buddy (a person, or a SIP endpoint).
|
||||
To use the Buddy class, application DOES NOT need to subclass it unless application wants to get the notifications on buddy state change.
|
||||
|
||||
Subscribe to Buddy's Presence Status
|
||||
---------------------------------------------------------
|
||||
To subscribe to buddy's presence status, you need to add a buddy object, install callback to handle buddy's event, and start subscribing to buddy's presence status. The snippet below shows a sample code to achieve these::
|
||||
|
||||
class MyBuddyCallback(pjsua.BuddyCallback):
|
||||
def __init__(self, buddy=None):
|
||||
pjsua.BuddyCallback.__init__(self, buddy)
|
||||
|
||||
def on_state(self):
|
||||
print "Buddy", self.buddy.info().uri, "is",
|
||||
print self.buddy.info().online_text
|
||||
|
||||
try:
|
||||
uri = '"Alice" <sip:alice@example.com>'
|
||||
buddy = acc.add_buddy(uri, cb=MyBuddyCallback())
|
||||
buddy.subscribe()
|
||||
|
||||
except pjsua.Error, err:
|
||||
print 'Error adding buddy:', err
|
||||
|
||||
For more information please see Buddy class and BuddyCallback class reference documentation.
|
||||
|
||||
Responding to Presence Subscription Request
|
||||
|
||||
By default, incoming presence subscription to an account will be accepted automatically. You will probably want to change this behavior, for example only to automatically accept subscription if it comes from one of the buddy in the buddy list, and for anything else prompt the user if he/she wants to accept the request.
|
||||
|
||||
This can be done by implementing the on_incoming_subscribe() method of the AccountCallback class.
|
||||
|
||||
Changing Account's Presence Status
|
||||
|
||||
The Account class provides two methods to change account's presence status:
|
||||
|
||||
set_basic_status() can be used to set basic account's presence status (i.e. available or not available).
|
||||
set_presence_status() can be used to set both the basic presence status and some extended information (e.g. busy, away, on the phone, etc.).
|
||||
When the presence status is changed, the account will publish the new status to all of its presence subscriber, either with PUBLISH request or SUBSCRIBE request, or both, depending on account configuration.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
|
||||
PJSUA2 API Reference Manuals
|
||||
****************************
|
||||
|
||||
Endpoint
|
||||
========
|
||||
|
||||
Account
|
||||
=======
|
||||
|
||||
Media
|
||||
=====
|
||||
|
||||
Call
|
||||
====
|
||||
|
||||
Buddy
|
||||
=====
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
|
||||
PJSUA2 Sample Applications
|
||||
***********************************
|
||||
|
||||
Sample Apps
|
||||
===========
|
||||
|
||||
Python GUI
|
||||
------------------
|
||||
It requires Python 2.7 and above.
|
||||
|
||||
Android & Java
|
||||
-----------------------------
|
||||
|
||||
C++
|
||||
-----------------------------
|
||||
|
||||
Miscellaneous
|
||||
===================
|
||||
|
||||
How to dump call stats
|
||||
-----------------------------
|
||||
|
||||
How to …
|
||||
-----------------------------
|
||||
|
|
@ -38,7 +38,7 @@ export PJLIB_UTIL_SRCDIR = ../src/pjlib-util
|
|||
export PJLIB_UTIL_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
|
||||
base64.o cli.o cli_console.o cli_telnet.o crc32.o errno.o dns.o \
|
||||
dns_dump.o dns_server.o getopt.o hmac_md5.o hmac_sha1.o \
|
||||
http_client.o md5.o pcap.o resolver.o scanner.o sha1.o \
|
||||
http_client.o json.o md5.o pcap.o resolver.o scanner.o sha1.o \
|
||||
srv_resolver.o string.o stun_simple.o \
|
||||
stun_simple_client.o xml.o
|
||||
export PJLIB_UTIL_CFLAGS += $(_CFLAGS)
|
||||
|
@ -50,7 +50,7 @@ export PJLIB_UTIL_LDFLAGS += $(PJLIB_LDLIB) $(_LDFLAGS)
|
|||
#
|
||||
export UTIL_TEST_SRCDIR = ../src/pjlib-util-test
|
||||
export UTIL_TEST_OBJS += xml.o encryption.o stun.o resolver_test.o test.o \
|
||||
http_client.o
|
||||
json_test.o http_client.o
|
||||
export UTIL_TEST_CFLAGS += $(_CFLAGS)
|
||||
export UTIL_TEST_CXXFLAGS += $(_CXXFLAGS)
|
||||
export UTIL_TEST_LDFLAGS += $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -55,6 +55,9 @@
|
|||
/* XML */
|
||||
#include <pjlib-util/xml.h>
|
||||
|
||||
/* JSON */
|
||||
#include <pjlib-util/json.h>
|
||||
|
||||
/* Old STUN */
|
||||
#include <pjlib-util/stun_simple.h>
|
||||
|
||||
|
|
|
@ -117,6 +117,15 @@
|
|||
#define PJLIB_UTIL_EINXML (PJLIB_UTIL_ERRNO_START+20) /* 320020 */
|
||||
|
||||
|
||||
/************************************************************
|
||||
* JSON ERROR
|
||||
***********************************************************/
|
||||
/**
|
||||
* @hideinitializer
|
||||
* General invalid JSON message.
|
||||
*/
|
||||
#define PJLIB_UTIL_EINJSON (PJLIB_UTIL_ERRNO_START+30) /* 320030 */
|
||||
|
||||
|
||||
/************************************************************
|
||||
* DNS ERROR
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
#ifndef __PJLIB_UTIL_JSON_H__
|
||||
#define __PJLIB_UTIL_JSON_H__
|
||||
|
||||
|
||||
/**
|
||||
* @file json.h
|
||||
* @brief PJLIB JSON Implementation
|
||||
*/
|
||||
|
||||
#include <pj/types.h>
|
||||
#include <pj/list.h>
|
||||
#include <pj/pool.h>
|
||||
|
||||
PJ_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* @defgroup PJ_JSON JSON Writer and Loader
|
||||
* @ingroup PJ_FILE_FMT
|
||||
* @{
|
||||
* This API implements JSON file format according to RFC 4627. It can be used
|
||||
* to parse, write, and manipulate JSON documents.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type of JSON value.
|
||||
*/
|
||||
typedef enum pj_json_val_type
|
||||
{
|
||||
PJ_JSON_VAL_NULL, /**< Null value (null) */
|
||||
PJ_JSON_VAL_BOOL, /**< Boolean value (true, false) */
|
||||
PJ_JSON_VAL_NUMBER, /**< Numeric (float or fixed point) */
|
||||
PJ_JSON_VAL_STRING, /**< Literal string value. */
|
||||
PJ_JSON_VAL_ARRAY, /**< Array */
|
||||
PJ_JSON_VAL_OBJ /**< Object. */
|
||||
} pj_json_val_type;
|
||||
|
||||
/* Forward declaration for JSON element */
|
||||
typedef struct pj_json_elem pj_json_elem;
|
||||
|
||||
/**
|
||||
* JSON list to store child elements.
|
||||
*/
|
||||
typedef struct pj_json_list
|
||||
{
|
||||
PJ_DECL_LIST_MEMBER(pj_json_elem);
|
||||
} pj_json_list;
|
||||
|
||||
/**
|
||||
* This represents JSON element. A JSON element is basically a name/value
|
||||
* pair, where the name is a string and the value can be one of null, boolean
|
||||
* (true and false constants), number, string, array (containing zero or more
|
||||
* elements), or object. An object can be viewed as C struct, that is a
|
||||
* compound element containing other elements, each having name/value pair.
|
||||
*/
|
||||
struct pj_json_elem
|
||||
{
|
||||
PJ_DECL_LIST_MEMBER(pj_json_elem);
|
||||
pj_str_t name; /**< ELement name. */
|
||||
pj_json_val_type type; /**< Element type. */
|
||||
union
|
||||
{
|
||||
pj_bool_t is_true; /**< Boolean value. */
|
||||
float num; /**< Number value. */
|
||||
pj_str_t str; /**< String value. */
|
||||
pj_json_list children; /**< Object and array children */
|
||||
} value; /**< Element value. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure to be specified to pj_json_parse() to be filled with additional
|
||||
* info when parsing failed.
|
||||
*/
|
||||
typedef struct pj_json_err_info
|
||||
{
|
||||
unsigned line; /**< Line location of the error */
|
||||
unsigned col; /**< Column location of the error */
|
||||
int err_char; /**< The offending character. */
|
||||
} pj_json_err_info;
|
||||
|
||||
/**
|
||||
* Type of function callback to write JSON document in pj_json_writef().
|
||||
*
|
||||
* @param s The string to be written to the document.
|
||||
* @param size The length of the string
|
||||
* @param user_data User data that was specified to pj_json_writef()
|
||||
*
|
||||
* @return If the callback returns non-PJ_SUCCESS, it will
|
||||
* stop the pj_json_writef() function and this error
|
||||
* will be returned to caller.
|
||||
*/
|
||||
typedef pj_status_t (*pj_json_writer)(const char *s,
|
||||
unsigned size,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize null element.
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name);
|
||||
|
||||
/**
|
||||
* Initialize boolean element with the specified value.
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
* @param val The value.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name,
|
||||
pj_bool_t val);
|
||||
|
||||
/**
|
||||
* Initialize number element with the specified value.
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
* @param val The value.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name,
|
||||
float val);
|
||||
|
||||
/**
|
||||
* Initialize string element with the specified value.
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
* @param val The value.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_string(pj_json_elem *el, pj_str_t *name,
|
||||
pj_str_t *val);
|
||||
|
||||
/**
|
||||
* Initialize element as an empty array
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name);
|
||||
|
||||
/**
|
||||
* Initialize element as an empty object
|
||||
*
|
||||
* @param el The element.
|
||||
* @param name Name to be given to the element, or NULL.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name);
|
||||
|
||||
/**
|
||||
* Add an element to an object or array.
|
||||
*
|
||||
* @param el The object or array element.
|
||||
* @param child Element to be added to the object or array.
|
||||
*/
|
||||
PJ_DECL(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child);
|
||||
|
||||
/**
|
||||
* Parse a JSON document in the buffer. The buffer MUST be NULL terminated,
|
||||
* or if not then it must have enough size to put the NULL character.
|
||||
*
|
||||
* @param pool The pool to allocate memory for creating elements.
|
||||
* @param buffer String buffer containing JSON document.
|
||||
* @param size Size of the document.
|
||||
* @param err_info Optional structure to be filled with info when
|
||||
* parsing failed.
|
||||
*
|
||||
* @return The root element from the document.
|
||||
*/
|
||||
PJ_DECL(pj_json_elem*) pj_json_parse(pj_pool_t *pool,
|
||||
char *buffer,
|
||||
unsigned *size,
|
||||
pj_json_err_info *err_info);
|
||||
|
||||
/**
|
||||
* Write the specified element to the string buffer.
|
||||
*
|
||||
* @param elem The element to be written.
|
||||
* @param buffer Output buffer.
|
||||
* @param size On input, it must be set to the size of the buffer.
|
||||
* Upon successful return, this will be set to
|
||||
* the length of the written string.
|
||||
*
|
||||
* @return PJ_SUCCESS on success or the appropriate error.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_json_write(const pj_json_elem *elem,
|
||||
char *buffer, unsigned *size);
|
||||
|
||||
/**
|
||||
* Incrementally write the element to arbitrary medium using the specified
|
||||
* callback to write the document chunks.
|
||||
*
|
||||
* @param elem The element to be written.
|
||||
* @param writer Callback function which will be called to write
|
||||
* text chunks.
|
||||
* @param user_data Arbitrary user data which will be given back when
|
||||
* calling the callback.
|
||||
*
|
||||
* @return PJ_SUCCESS on success or the appropriate error.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_json_writef(const pj_json_elem *elem,
|
||||
pj_json_writer writer,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
PJ_END_DECL
|
||||
|
||||
#endif /* __PJLIB_UTIL_JSON_H__ */
|
|
@ -0,0 +1,106 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
#include "test.h"
|
||||
|
||||
#define THIS_FILE "json_test.c"
|
||||
|
||||
#if INCLUDE_JSON_TEST
|
||||
|
||||
#include <pjlib-util/json.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
static char json_doc1[] =
|
||||
"{\
|
||||
\"Object\": {\
|
||||
\"Integer\": 800,\
|
||||
\"Negative\": -12,\
|
||||
\"Float\": -7.2,\
|
||||
\"String\": \"A\\tString with tab\",\
|
||||
\"Object2\": {\
|
||||
\"True\": true,\
|
||||
\"False\": false,\
|
||||
\"Null\": null\
|
||||
},\
|
||||
\"Array1\": [116, false, \"string\", {}],\
|
||||
\"Array2\": [\
|
||||
{\
|
||||
\"Float\": 123.,\
|
||||
},\
|
||||
{\
|
||||
\"Float\": 123.,\
|
||||
}\
|
||||
]\
|
||||
},\
|
||||
\"Integer\": 800,\
|
||||
\"Array1\": [116, false, \"string\"]\
|
||||
}\
|
||||
";
|
||||
|
||||
static int json_verify_1()
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_json_elem *elem;
|
||||
char *out_buf;
|
||||
unsigned size;
|
||||
pj_json_err_info err;
|
||||
|
||||
pool = pj_pool_create(mem, "json", 1000, 1000, NULL);
|
||||
|
||||
size = strlen(json_doc1);
|
||||
elem = pj_json_parse(pool, json_doc1, &size, &err);
|
||||
if (!elem) {
|
||||
PJ_LOG(1, (THIS_FILE, " Error: json_verify_1() parse error"));
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
size = strlen(json_doc1) * 2;
|
||||
out_buf = pj_pool_alloc(pool, size);
|
||||
|
||||
if (pj_json_write(elem, out_buf, &size)) {
|
||||
PJ_LOG(1, (THIS_FILE, " Error: json_verify_1() write error"));
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "Json document:\n%s", out_buf));
|
||||
pj_pool_release(pool);
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
pj_pool_release(pool);
|
||||
return 10;
|
||||
}
|
||||
|
||||
|
||||
int json_test(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = json_verify_1();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else
|
||||
int json_dummy;
|
||||
#endif
|
|
@ -72,6 +72,10 @@ static int test_inner(void)
|
|||
DO_TEST(xml_test());
|
||||
#endif
|
||||
|
||||
#if INCLUDE_JSON_TEST
|
||||
DO_TEST(json_test());
|
||||
#endif
|
||||
|
||||
#if INCLUDE_ENCRYPTION_TEST
|
||||
DO_TEST(encryption_test());
|
||||
DO_TEST(encryption_benchmark());
|
||||
|
|
|
@ -20,12 +20,14 @@
|
|||
#include <pj/types.h>
|
||||
|
||||
#define INCLUDE_XML_TEST 1
|
||||
#define INCLUDE_JSON_TEST 1
|
||||
#define INCLUDE_ENCRYPTION_TEST 1
|
||||
#define INCLUDE_STUN_TEST 1
|
||||
#define INCLUDE_RESOLVER_TEST 1
|
||||
#define INCLUDE_HTTP_CLIENT_TEST 1
|
||||
|
||||
extern int xml_test(void);
|
||||
extern int json_test(void);
|
||||
extern int encryption_test();
|
||||
extern int encryption_benchmark();
|
||||
extern int stun_test();
|
||||
|
|
|
@ -51,6 +51,9 @@ static const struct
|
|||
/* XML errors */
|
||||
PJ_BUILD_ERR( PJLIB_UTIL_EINXML, "Invalid XML message" ),
|
||||
|
||||
/* JSON errors */
|
||||
PJ_BUILD_ERR( PJLIB_UTIL_EINJSON, "Invalid JSON document" ),
|
||||
|
||||
/* DNS errors */
|
||||
PJ_BUILD_ERR( PJLIB_UTIL_EDNSQRYTOOSMALL, "DNS query packet buffer is too small"),
|
||||
PJ_BUILD_ERR( PJLIB_UTIL_EDNSINSIZE, "Invalid DNS packet length"),
|
||||
|
|
|
@ -0,0 +1,621 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
#include <pjlib-util/json.h>
|
||||
#include <pjlib-util/errno.h>
|
||||
#include <pjlib-util/scanner.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/ctype.h>
|
||||
#include <pj/except.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
#define EL_INIT(p_el, nm, typ) do { \
|
||||
if (nm) { \
|
||||
p_el->name = *nm; \
|
||||
} else { \
|
||||
p_el->name.ptr = (char*)""; \
|
||||
p_el->name.slen = 0; \
|
||||
} \
|
||||
p_el->type = typ; \
|
||||
} while (0)
|
||||
|
||||
struct write_state;
|
||||
struct parse_state;
|
||||
|
||||
#define NO_NAME 1
|
||||
|
||||
static pj_status_t elem_write(const pj_json_elem *elem,
|
||||
struct write_state *st,
|
||||
unsigned flags);
|
||||
static pj_json_elem* parse_elem_throw(struct parse_state *st,
|
||||
pj_json_elem *elem);
|
||||
|
||||
|
||||
PJ_DEF(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_NULL);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name,
|
||||
pj_bool_t val)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_BOOL);
|
||||
el->value.is_true = val;
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name,
|
||||
float val)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_NUMBER);
|
||||
el->value.num = val;
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_string( pj_json_elem *el, pj_str_t *name,
|
||||
pj_str_t *value)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_STRING);
|
||||
el->value.str = *value;
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_ARRAY);
|
||||
pj_list_init(&el->value.children);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name)
|
||||
{
|
||||
EL_INIT(el, name, PJ_JSON_VAL_OBJ);
|
||||
pj_list_init(&el->value.children);
|
||||
}
|
||||
|
||||
PJ_DEF(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child)
|
||||
{
|
||||
pj_assert(el->type == PJ_JSON_VAL_OBJ || el->type == PJ_JSON_VAL_ARRAY);
|
||||
pj_list_push_back(&el->value.children, child);
|
||||
}
|
||||
|
||||
struct parse_state
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_scanner scanner;
|
||||
pj_json_err_info *err_info;
|
||||
pj_cis_t float_spec; /* numbers with dot! */
|
||||
};
|
||||
|
||||
static pj_status_t parse_children(struct parse_state *st,
|
||||
pj_json_elem *parent)
|
||||
{
|
||||
char end_quote = (parent->type == PJ_JSON_VAL_ARRAY)? ']' : '}';
|
||||
|
||||
pj_scan_get_char(&st->scanner);
|
||||
|
||||
while (*st->scanner.curptr != end_quote) {
|
||||
pj_json_elem *child;
|
||||
|
||||
while (*st->scanner.curptr == ',')
|
||||
pj_scan_get_char(&st->scanner);
|
||||
|
||||
if (*st->scanner.curptr == end_quote)
|
||||
break;
|
||||
|
||||
child = parse_elem_throw(st, NULL);
|
||||
if (!child)
|
||||
return PJLIB_UTIL_EINJSON;
|
||||
|
||||
pj_json_elem_add(parent, child);
|
||||
}
|
||||
|
||||
pj_scan_get_char(&st->scanner);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/* Return 0 if success or the index of the invalid char in the string */
|
||||
static unsigned parse_quoted_string(struct parse_state *st,
|
||||
pj_str_t *output)
|
||||
{
|
||||
pj_str_t token;
|
||||
char *op, *ip, *iend;
|
||||
|
||||
pj_scan_get_quote(&st->scanner, '"', '"', &token);
|
||||
|
||||
/* Remove the quote characters */
|
||||
token.ptr++;
|
||||
token.slen-=2;
|
||||
|
||||
if (pj_strchr(&token, '\\') == NULL) {
|
||||
*output = token;
|
||||
return 0;
|
||||
}
|
||||
|
||||
output->ptr = op = pj_pool_alloc(st->pool, token.slen);
|
||||
|
||||
ip = token.ptr;
|
||||
iend = token.ptr + token.slen;
|
||||
|
||||
while (ip != iend) {
|
||||
if (*ip == '\\') {
|
||||
++ip;
|
||||
if (ip==iend) {
|
||||
goto on_error;
|
||||
}
|
||||
if (*ip == 'u') {
|
||||
ip++;
|
||||
if (iend - ip < 4) {
|
||||
ip = iend -1;
|
||||
goto on_error;
|
||||
}
|
||||
/* Only use the last two hext digits because we're on
|
||||
* ASCII */
|
||||
*op++ = (char)(pj_hex_digit_to_val(ip[2]) * 16 +
|
||||
pj_hex_digit_to_val(ip[3]));
|
||||
ip += 4;
|
||||
} else if (*ip=='"' || *ip=='\\' || *ip=='/') {
|
||||
*op++ = *ip++;
|
||||
} else if (*ip=='b') {
|
||||
*op++ = '\b';
|
||||
ip++;
|
||||
} else if (*ip=='f') {
|
||||
*op++ = '\f';
|
||||
ip++;
|
||||
} else if (*ip=='n') {
|
||||
*op++ = '\n';
|
||||
ip++;
|
||||
} else if (*ip=='r') {
|
||||
*op++ = '\r';
|
||||
ip++;
|
||||
} else if (*ip=='t') {
|
||||
*op++ = '\t';
|
||||
ip++;
|
||||
} else {
|
||||
goto on_error;
|
||||
}
|
||||
} else {
|
||||
*op++ = *ip++;
|
||||
}
|
||||
}
|
||||
|
||||
output->slen = op - output->ptr;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
output->slen = op - output->ptr;
|
||||
return ip - token.ptr;
|
||||
}
|
||||
|
||||
static pj_json_elem* parse_elem_throw(struct parse_state *st,
|
||||
pj_json_elem *elem)
|
||||
{
|
||||
pj_str_t name = {NULL, 0}, value = {NULL, 0};
|
||||
pj_str_t token;
|
||||
|
||||
if (!elem)
|
||||
elem = pj_pool_alloc(st->pool, sizeof(*elem));
|
||||
|
||||
/* Parse name */
|
||||
if (*st->scanner.curptr == '"') {
|
||||
pj_scan_get_char(&st->scanner);
|
||||
pj_scan_get_until_ch(&st->scanner, '"', &token);
|
||||
pj_scan_get_char(&st->scanner);
|
||||
|
||||
if (*st->scanner.curptr == ':') {
|
||||
pj_scan_get_char(&st->scanner);
|
||||
name = token;
|
||||
} else {
|
||||
value = token;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.slen) {
|
||||
/* Element with string value and no name */
|
||||
pj_json_elem_string(elem, &name, &value);
|
||||
return elem;
|
||||
}
|
||||
|
||||
/* Parse value */
|
||||
if (pj_cis_match(&st->float_spec, *st->scanner.curptr) ||
|
||||
*st->scanner.curptr == '-')
|
||||
{
|
||||
float val;
|
||||
pj_bool_t neg = PJ_FALSE;
|
||||
|
||||
if (*st->scanner.curptr == '-') {
|
||||
pj_scan_get_char(&st->scanner);
|
||||
neg = PJ_TRUE;
|
||||
}
|
||||
|
||||
pj_scan_get(&st->scanner, &st->float_spec, &token);
|
||||
val = pj_strtof(&token);
|
||||
if (neg) val = -val;
|
||||
|
||||
pj_json_elem_number(elem, &name, val);
|
||||
|
||||
} else if (*st->scanner.curptr == '"') {
|
||||
unsigned err;
|
||||
char *start = st->scanner.curptr;
|
||||
|
||||
err = parse_quoted_string(st, &token);
|
||||
if (err) {
|
||||
st->scanner.curptr = start + err;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pj_json_elem_string(elem, &name, &token);
|
||||
|
||||
} else if (pj_isalpha(*st->scanner.curptr)) {
|
||||
|
||||
if (pj_scan_strcmp(&st->scanner, "false", 5)==0) {
|
||||
pj_json_elem_bool(elem, &name, PJ_FALSE);
|
||||
pj_scan_advance_n(&st->scanner, 5, PJ_TRUE);
|
||||
} else if (pj_scan_strcmp(&st->scanner, "true", 4)==0) {
|
||||
pj_json_elem_bool(elem, &name, PJ_TRUE);
|
||||
pj_scan_advance_n(&st->scanner, 4, PJ_TRUE);
|
||||
} else if (pj_scan_strcmp(&st->scanner, "null", 4)==0) {
|
||||
pj_json_elem_null(elem, &name);
|
||||
pj_scan_advance_n(&st->scanner, 4, PJ_TRUE);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} else if (*st->scanner.curptr == '[') {
|
||||
pj_json_elem_array(elem, &name);
|
||||
if (parse_children(st, elem) != PJ_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
} else if (*st->scanner.curptr == '{') {
|
||||
pj_json_elem_obj(elem, &name);
|
||||
if (parse_children(st, elem) != PJ_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
static void on_syntax_error(pj_scanner *scanner)
|
||||
{
|
||||
PJ_UNUSED_ARG(scanner);
|
||||
PJ_THROW(11);
|
||||
}
|
||||
|
||||
PJ_DEF(pj_json_elem*) pj_json_parse(pj_pool_t *pool,
|
||||
char *buffer,
|
||||
unsigned *size,
|
||||
pj_json_err_info *err_info)
|
||||
{
|
||||
pj_cis_buf_t cis_buf;
|
||||
struct parse_state st;
|
||||
pj_json_elem *root;
|
||||
PJ_USE_EXCEPTION;
|
||||
|
||||
PJ_ASSERT_RETURN(pool && buffer && size, NULL);
|
||||
|
||||
if (!*size)
|
||||
return NULL;
|
||||
|
||||
pj_bzero(&st, sizeof(st));
|
||||
st.pool = pool;
|
||||
st.err_info = err_info;
|
||||
pj_scan_init(&st.scanner, buffer, *size,
|
||||
PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE,
|
||||
&on_syntax_error);
|
||||
pj_cis_buf_init(&cis_buf);
|
||||
pj_cis_init(&cis_buf, &st.float_spec);
|
||||
pj_cis_add_str(&st.float_spec, ".0123456789");
|
||||
|
||||
PJ_TRY {
|
||||
root = parse_elem_throw(&st, NULL);
|
||||
}
|
||||
PJ_CATCH_ANY {
|
||||
root = NULL;
|
||||
}
|
||||
PJ_END
|
||||
|
||||
if (!root && err_info) {
|
||||
err_info->line = st.scanner.line;
|
||||
err_info->col = pj_scan_get_col(&st.scanner) + 1;
|
||||
err_info->err_char = *st.scanner.curptr;
|
||||
}
|
||||
|
||||
*size = (buffer + *size) - st.scanner.curptr;
|
||||
|
||||
pj_scan_fini(&st.scanner);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
struct buf_writer_data
|
||||
{
|
||||
char *pos;
|
||||
unsigned size;
|
||||
};
|
||||
|
||||
static pj_status_t buf_writer(const char *s,
|
||||
unsigned size,
|
||||
void *user_data)
|
||||
{
|
||||
struct buf_writer_data *buf_data = (struct buf_writer_data*)user_data;
|
||||
if (size+1 >= buf_data->size)
|
||||
return PJ_ETOOBIG;
|
||||
|
||||
pj_memcpy(buf_data->pos, s, size);
|
||||
buf_data->pos += size;
|
||||
buf_data->size -= size;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_json_write(const pj_json_elem *elem,
|
||||
char *buffer, unsigned *size)
|
||||
{
|
||||
struct buf_writer_data buf_data;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(elem && buffer && size, PJ_EINVAL);
|
||||
|
||||
buf_data.pos = buffer;
|
||||
buf_data.size = *size;
|
||||
|
||||
status = pj_json_writef(elem, &buf_writer, &buf_data);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
*buf_data.pos = '\0';
|
||||
*size = (unsigned)(buf_data.pos - buffer);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
#define MAX_INDENT 100
|
||||
#ifndef PJ_JSON_NAME_MIN_LEN
|
||||
# define PJ_JSON_NAME_MIN_LEN 20
|
||||
#endif
|
||||
#define ESC_BUF_LEN 64
|
||||
#ifndef PJ_JSON_INDENT_SIZE
|
||||
# define PJ_JSON_INDENT_SIZE 3
|
||||
#endif
|
||||
|
||||
struct write_state
|
||||
{
|
||||
pj_json_writer writer;
|
||||
void *user_data;
|
||||
char indent_buf[MAX_INDENT];
|
||||
int indent;
|
||||
char space[PJ_JSON_NAME_MIN_LEN];
|
||||
};
|
||||
|
||||
#define CHECK(expr) do { \
|
||||
status=expr; if (status!=PJ_SUCCESS) return status; } \
|
||||
while (0)
|
||||
|
||||
static pj_status_t write_string_escaped(const pj_str_t *value,
|
||||
struct write_state *st)
|
||||
{
|
||||
const char *ip = value->ptr;
|
||||
const char *iend = value->ptr + value->slen;
|
||||
char buf[ESC_BUF_LEN];
|
||||
char *op = buf;
|
||||
char *oend = buf + ESC_BUF_LEN;
|
||||
pj_status_t status;
|
||||
|
||||
while (ip != iend) {
|
||||
/* Write to buffer to speedup writing instead of calling
|
||||
* the callback one by one for each character.
|
||||
*/
|
||||
while (ip != iend && op != oend) {
|
||||
if (oend - op < 2)
|
||||
break;
|
||||
|
||||
if (*ip == '"') {
|
||||
*op++ = '\\';
|
||||
*op++ = '"';
|
||||
ip++;
|
||||
} else if (*ip == '\\') {
|
||||
*op++ = '\\';
|
||||
*op++ = '\\';
|
||||
ip++;
|
||||
} else if (*ip == '/') {
|
||||
*op++ = '\\';
|
||||
*op++ = '/';
|
||||
ip++;
|
||||
} else if (*ip == '\b') {
|
||||
*op++ = '\\';
|
||||
*op++ = 'b';
|
||||
ip++;
|
||||
} else if (*ip == '\f') {
|
||||
*op++ = '\\';
|
||||
*op++ = 'f';
|
||||
ip++;
|
||||
} else if (*ip == '\n') {
|
||||
*op++ = '\\';
|
||||
*op++ = 'n';
|
||||
ip++;
|
||||
} else if (*ip == '\r') {
|
||||
*op++ = '\\';
|
||||
*op++ = 'r';
|
||||
ip++;
|
||||
} else if (*ip == '\t') {
|
||||
*op++ = '\\';
|
||||
*op++ = 't';
|
||||
ip++;
|
||||
} else if ((*ip >= 32 && *ip < 127)) {
|
||||
/* unescaped */
|
||||
*op++ = *ip++;
|
||||
} else {
|
||||
/* escaped */
|
||||
if (oend - op < 6)
|
||||
break;
|
||||
*op++ = '\\';
|
||||
*op++ = 'u';
|
||||
*op++ = '0';
|
||||
*op++ = '0';
|
||||
pj_val_to_hex_digit(*ip, op);
|
||||
op+=2;
|
||||
ip++;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK( st->writer( buf, op-buf, st->user_data) );
|
||||
op = buf;
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static pj_status_t write_children(const pj_json_list *list,
|
||||
const char quotes[2],
|
||||
struct write_state *st)
|
||||
{
|
||||
unsigned flags = (quotes[0]=='[') ? NO_NAME : 0;
|
||||
pj_status_t status;
|
||||
|
||||
//CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
|
||||
CHECK( st->writer( "es[0], 1, st->user_data) );
|
||||
CHECK( st->writer( " ", 1, st->user_data) );
|
||||
|
||||
if (!pj_list_empty(list)) {
|
||||
pj_bool_t indent_added = PJ_FALSE;
|
||||
pj_json_elem *child = list->next;
|
||||
|
||||
if (child->name.slen == 0) {
|
||||
/* Simple list */
|
||||
while (child != (pj_json_elem*)list) {
|
||||
status = elem_write(child, st, flags);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (child->next != (pj_json_elem*)list)
|
||||
CHECK( st->writer( ", ", 2, st->user_data) );
|
||||
child = child->next;
|
||||
}
|
||||
} else {
|
||||
if (st->indent < sizeof(st->indent_buf)) {
|
||||
st->indent += PJ_JSON_INDENT_SIZE;
|
||||
indent_added = PJ_TRUE;
|
||||
}
|
||||
CHECK( st->writer( "\n", 1, st->user_data) );
|
||||
while (child != (pj_json_elem*)list) {
|
||||
status = elem_write(child, st, flags);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
if (child->next != (pj_json_elem*)list)
|
||||
CHECK( st->writer( ",\n", 2, st->user_data) );
|
||||
else
|
||||
CHECK( st->writer( "\n", 1, st->user_data) );
|
||||
child = child->next;
|
||||
}
|
||||
if (indent_added) {
|
||||
st->indent -= PJ_JSON_INDENT_SIZE;
|
||||
}
|
||||
CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
|
||||
}
|
||||
}
|
||||
CHECK( st->writer( "es[1], 1, st->user_data) );
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static pj_status_t elem_write(const pj_json_elem *elem,
|
||||
struct write_state *st,
|
||||
unsigned flags)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
if (elem->name.slen) {
|
||||
CHECK( st->writer( st->indent_buf, st->indent, st->user_data) );
|
||||
if ((flags & NO_NAME)==0) {
|
||||
CHECK( st->writer( "\"", 1, st->user_data) );
|
||||
CHECK( write_string_escaped(&elem->name, st) );
|
||||
CHECK( st->writer( "\": ", 3, st->user_data) );
|
||||
if (elem->name.slen < PJ_JSON_NAME_MIN_LEN /*&&
|
||||
elem->type != PJ_JSON_VAL_OBJ &&
|
||||
elem->type != PJ_JSON_VAL_ARRAY*/)
|
||||
{
|
||||
CHECK( st->writer( st->space,
|
||||
PJ_JSON_NAME_MIN_LEN - elem->name.slen,
|
||||
st->user_data) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (elem->type) {
|
||||
case PJ_JSON_VAL_NULL:
|
||||
CHECK( st->writer( "null", 4, st->user_data) );
|
||||
break;
|
||||
case PJ_JSON_VAL_BOOL:
|
||||
if (elem->value.is_true)
|
||||
CHECK( st->writer( "true", 4, st->user_data) );
|
||||
else
|
||||
CHECK( st->writer( "false", 5, st->user_data) );
|
||||
break;
|
||||
case PJ_JSON_VAL_NUMBER:
|
||||
{
|
||||
char num_buf[65];
|
||||
int len;
|
||||
|
||||
if (elem->value.num == (int)elem->value.num)
|
||||
len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%d",
|
||||
(int)elem->value.num);
|
||||
else
|
||||
len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%f",
|
||||
elem->value.num);
|
||||
|
||||
if (len < 0 || len >= sizeof(num_buf))
|
||||
return PJ_ETOOBIG;
|
||||
CHECK( st->writer( num_buf, len, st->user_data) );
|
||||
}
|
||||
break;
|
||||
case PJ_JSON_VAL_STRING:
|
||||
CHECK( st->writer( "\"", 1, st->user_data) );
|
||||
CHECK( write_string_escaped( &elem->value.str, st) );
|
||||
CHECK( st->writer( "\"", 1, st->user_data) );
|
||||
break;
|
||||
case PJ_JSON_VAL_ARRAY:
|
||||
CHECK( write_children(&elem->value.children, "[]", st) );
|
||||
break;
|
||||
case PJ_JSON_VAL_OBJ:
|
||||
CHECK( write_children(&elem->value.children, "{}", st) );
|
||||
break;
|
||||
default:
|
||||
pj_assert(!"Unhandled value type");
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
#undef CHECK
|
||||
|
||||
PJ_DEF(pj_status_t) pj_json_writef( const pj_json_elem *elem,
|
||||
pj_json_writer writer,
|
||||
void *user_data)
|
||||
{
|
||||
struct write_state st;
|
||||
|
||||
PJ_ASSERT_RETURN(elem && writer, PJ_EINVAL);
|
||||
|
||||
st.writer = writer;
|
||||
st.user_data = user_data,
|
||||
st.indent = 0;
|
||||
pj_memset(st.indent_buf, ' ', MAX_INDENT);
|
||||
pj_memset(st.space, ' ', PJ_JSON_NAME_MIN_LEN);
|
||||
|
||||
return elem_write(elem, &st, 0);
|
||||
}
|
||||
|
|
@ -23,7 +23,8 @@ export TEST_OBJS += @ac_main_obj@
|
|||
#
|
||||
# Additional LDFLAGS for pjlib-test
|
||||
#
|
||||
export TEST_LDFLAGS += @LDFLAGS@ @LIBS@
|
||||
# Disabled, as this causes duplicated LDFLAGS, which may raise linking errors
|
||||
#export TEST_LDFLAGS += @LDFLAGS@ @LIBS@
|
||||
|
||||
#
|
||||
# TARGETS are make targets in the Makefile, to be executed for this given
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
#endif
|
||||
|
||||
#define PJ_INT64(val) val##LL
|
||||
#define PJ_UINT64(val) val##LLU
|
||||
#define PJ_UINT64(val) val##ULL
|
||||
#define PJ_INT64_FMT "L"
|
||||
|
||||
|
||||
|
|
|
@ -574,6 +574,15 @@ PJ_DECL(unsigned long) pj_strtoul(const pj_str_t *str);
|
|||
PJ_DECL(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr,
|
||||
unsigned base);
|
||||
|
||||
/**
|
||||
* Convert string to float.
|
||||
*
|
||||
* @param str the string.
|
||||
*
|
||||
* @return the value.
|
||||
*/
|
||||
PJ_DECL(float) pj_strtof(const pj_str_t *str);
|
||||
|
||||
/**
|
||||
* Utility to convert unsigned integer to string. Note that the
|
||||
* string will be NULL terminated.
|
||||
|
|
|
@ -86,15 +86,18 @@ typedef int pj_bool_t;
|
|||
# define PJ_T(literal_str) literal_str
|
||||
#endif
|
||||
|
||||
/** Some constants */
|
||||
enum pj_constants_
|
||||
{
|
||||
/** Status is OK. */
|
||||
PJ_SUCCESS=0,
|
||||
|
||||
/** Status is OK. */
|
||||
#define PJ_SUCCESS 0
|
||||
/** True value. */
|
||||
PJ_TRUE=1,
|
||||
|
||||
/** True value. */
|
||||
#define PJ_TRUE 1
|
||||
|
||||
/** False value. */
|
||||
#define PJ_FALSE 0
|
||||
/** False value. */
|
||||
PJ_FALSE=0
|
||||
};
|
||||
|
||||
/**
|
||||
* File offset type.
|
||||
|
|
|
@ -175,6 +175,44 @@ PJ_DEF(unsigned long) pj_strtoul2(const pj_str_t *str, pj_str_t *endptr,
|
|||
return value;
|
||||
}
|
||||
|
||||
PJ_DEF(float) pj_strtof(const pj_str_t *str)
|
||||
{
|
||||
pj_str_t part;
|
||||
char *pdot;
|
||||
float val;
|
||||
|
||||
if (str->slen == 0)
|
||||
return 0;
|
||||
|
||||
pdot = (char*)pj_memchr(str->ptr, '.', str->slen);
|
||||
part.ptr = str->ptr;
|
||||
part.slen = pdot ? pdot - str->ptr : str->slen;
|
||||
|
||||
if (part.slen)
|
||||
val = (float)pj_strtol(&part);
|
||||
else
|
||||
val = 0;
|
||||
|
||||
if (pdot) {
|
||||
part.ptr = pdot + 1;
|
||||
part.slen = (str->ptr + str->slen - pdot - 1);
|
||||
if (part.slen) {
|
||||
pj_str_t endptr;
|
||||
float fpart, fdiv;
|
||||
int i;
|
||||
fpart = (float)pj_strtoul2(&part, &endptr, 10);
|
||||
fdiv = 1.0;
|
||||
for (i=0; i<(part.slen - endptr.slen); ++i)
|
||||
fdiv = fdiv * 10;
|
||||
if (val >= 0)
|
||||
val += (fpart / fdiv);
|
||||
else
|
||||
val -= (fpart / fdiv);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
PJ_DEF(int) pj_utoa(unsigned long val, char *buf)
|
||||
{
|
||||
return pj_utoa_pad(val, buf, 0, 0);
|
||||
|
|
|
@ -131,7 +131,7 @@ typedef enum pjmedia_vid_dev_wnd_flag
|
|||
/**
|
||||
* Device index constants.
|
||||
*/
|
||||
enum
|
||||
enum pjmedia_vid_dev_std_index
|
||||
{
|
||||
/**
|
||||
* Constant to denote default capture device
|
||||
|
|
|
@ -35,6 +35,7 @@ EndProject
|
|||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample_debug", "pjsip-apps\build\sample_debug.vcproj", "{A0F1AA62-0F6F-420D-B09A-AC04B6862821}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{23D7679C-764C-4E02-8B29-BB882CEEEFE2} = {23D7679C-764C-4E02-8B29-BB882CEEEFE2}
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1} = {B82CDD25-6903-430E-BD38-D8129A2015C1}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjnath", "pjnath\build\pjnath.vcproj", "{A5D9AA24-08ED-48B9-BD65-F0A25E96BFC4}"
|
||||
|
@ -100,6 +101,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsip_test", "pjsip\build\p
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpjproject", "pjsip-apps\build\libpjproject.vcproj", "{23D7679C-764C-4E02-8B29-BB882CEEEFE2}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1} = {B82CDD25-6903-430E-BD38-D8129A2015C1}
|
||||
{2BB84911-C1B4-4747-B93D-36AA82CC5031} = {2BB84911-C1B4-4747-B93D-36AA82CC5031}
|
||||
{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA} = {E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}
|
||||
{2A3F241E-682C-47E1-9543-DC28708B406A} = {2A3F241E-682C-47E1-9543-DC28708B406A}
|
||||
|
@ -116,10 +118,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpjproject", "pjsip-apps\
|
|||
{B8719FD5-E8A6-4A36-943C-891D07F5DD21} = {B8719FD5-E8A6-4A36-943C-891D07F5DD21}
|
||||
{DA0E03ED-53A7-4050-8A85-90541C5509F8} = {DA0E03ED-53A7-4050-8A85-90541C5509F8}
|
||||
{B5FE16F8-3EDB-4110-BD80-B4238CC01E8D} = {B5FE16F8-3EDB-4110-BD80-B4238CC01E8D}
|
||||
{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
|
||||
{E53AA5FF-B737-40AA-BD13-387EFA99023D} = {E53AA5FF-B737-40AA-BD13-387EFA99023D}
|
||||
{A1989FF3-9894-40F4-B5A6-6EA364476E45} = {A1989FF3-9894-40F4-B5A6-6EA364476E45}
|
||||
{F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858} = {F0DBAA03-1BA3-4E3B-A2CA-727E3D3AB858}
|
||||
{9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37} = {9CA0FDFB-2172-41FC-B7F1-5CE915EDCB37}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python_pjsua", "pjsip-apps\build\python_pjsua.vcproj", "{0C91838B-3372-40B4-A764-DE075A4BC94B}"
|
||||
|
@ -136,6 +138,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjmedia_videodev", "pjmedia
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbaseclasses", "third_party\build\baseclasses\libbaseclasses.vcproj", "{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pjsua2_lib", "pjsip\build\pjsua2_lib.vcproj", "{B82CDD25-6903-430E-BD38-D8129A2015C1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Pocket PC 2003 (ARMV4) = Debug|Pocket PC 2003 (ARMV4)
|
||||
|
@ -3172,6 +3176,114 @@ Global
|
|||
{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Release-Static|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release|Win32
|
||||
{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Release-Static|x64.ActiveCfg = Release-Static|x64
|
||||
{E8A3F6FA-AE1C-4C8E-A0B6-9C8480324EAA}.Release-Static|x64.Build.0 = Release-Static|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Pocket PC 2003 (ARMV4).ActiveCfg = Debug|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Pocket PC 2003 (ARMV4).Build.0 = Debug|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Pocket PC 2003 (ARMV4).Deploy.0 = Debug|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Smartphone 2003 (ARMV4).ActiveCfg = Debug|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Smartphone 2003 (ARMV4).Build.0 = Debug|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Smartphone 2003 (ARMV4).Deploy.0 = Debug|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Debug|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Debug|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Debug|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Debug|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Debug|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Debug|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Debug|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Debug|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug|x64.Build.0 = Debug|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Pocket PC 2003 (ARMV4).ActiveCfg = Debug-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Pocket PC 2003 (ARMV4).Build.0 = Debug-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Pocket PC 2003 (ARMV4).Deploy.0 = Debug-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Smartphone 2003 (ARMV4).ActiveCfg = Debug-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Smartphone 2003 (ARMV4).Build.0 = Debug-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Smartphone 2003 (ARMV4).Deploy.0 = Debug-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Win32.ActiveCfg = Debug-Dynamic|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Win32.Build.0 = Debug-Dynamic|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|x64.ActiveCfg = Debug-Dynamic|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Dynamic|x64.Build.0 = Debug-Dynamic|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Pocket PC 2003 (ARMV4).ActiveCfg = Debug-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Pocket PC 2003 (ARMV4).Build.0 = Debug-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Pocket PC 2003 (ARMV4).Deploy.0 = Debug-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Smartphone 2003 (ARMV4).ActiveCfg = Debug-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Smartphone 2003 (ARMV4).Build.0 = Debug-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Smartphone 2003 (ARMV4).Deploy.0 = Debug-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Win32.ActiveCfg = Debug-Static|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Win32.Build.0 = Debug-Static|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|x64.ActiveCfg = Debug-Static|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Debug-Static|x64.Build.0 = Debug-Static|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Pocket PC 2003 (ARMV4).ActiveCfg = Release|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Pocket PC 2003 (ARMV4).Build.0 = Release|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Pocket PC 2003 (ARMV4).Deploy.0 = Release|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Smartphone 2003 (ARMV4).ActiveCfg = Release|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Smartphone 2003 (ARMV4).Build.0 = Release|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Smartphone 2003 (ARMV4).Deploy.0 = Release|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Win32.Build.0 = Release|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Release|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Release|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Release|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Release|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Release|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Release|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|x64.ActiveCfg = Release|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release|x64.Build.0 = Release|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Pocket PC 2003 (ARMV4).ActiveCfg = Release-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Pocket PC 2003 (ARMV4).Build.0 = Release-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Pocket PC 2003 (ARMV4).Deploy.0 = Release-Dynamic|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Smartphone 2003 (ARMV4).ActiveCfg = Release-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Smartphone 2003 (ARMV4).Build.0 = Release-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Smartphone 2003 (ARMV4).Deploy.0 = Release-Dynamic|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Win32.ActiveCfg = Release-Dynamic|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Win32.Build.0 = Release-Dynamic|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|x64.ActiveCfg = Release-Dynamic|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Dynamic|x64.Build.0 = Release-Dynamic|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Pocket PC 2003 (ARMV4).ActiveCfg = Release-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Pocket PC 2003 (ARMV4).Build.0 = Release-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Pocket PC 2003 (ARMV4).Deploy.0 = Release-Static|Pocket PC 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Smartphone 2003 (ARMV4).ActiveCfg = Release-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Smartphone 2003 (ARMV4).Build.0 = Release-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Smartphone 2003 (ARMV4).Deploy.0 = Release-Static|Smartphone 2003 (ARMV4)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Win32.ActiveCfg = Release-Static|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Win32.Build.0 = Release-Static|Win32
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I).ActiveCfg = Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I).ActiveCfg = Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Professional SDK (ARMV4I).ActiveCfg = Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Professional SDK (ARMV4I).Build.0 = Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Professional SDK (ARMV4I).Deploy.0 = Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Standard SDK (ARMV4I).ActiveCfg = Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Standard SDK (ARMV4I).Build.0 = Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|Windows Mobile 6 Standard SDK (ARMV4I).Deploy.0 = Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|x64.ActiveCfg = Release-Static|x64
|
||||
{B82CDD25-6903-430E-BD38-D8129A2015C1}.Release-Static|x64.Build.0 = Release-Static|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -62,10 +62,18 @@ export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
|
|||
# Main entry
|
||||
#
|
||||
#
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
#
|
||||
# FIX THIS
|
||||
#
|
||||
# x x x x x x x x x x x x x x x x x x x x x x x x
|
||||
TARGETS := $(BINDIR)/$(PJSUA_EXE) $(BINDIR)/$(PJSYSTEST_EXE) samples
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
swig:
|
||||
$(MAKE) -C ../src/swig
|
||||
|
||||
doc:
|
||||
|
||||
dep: depend
|
||||
|
|
|
@ -48,13 +48,21 @@ SAMPLES := auddemo \
|
|||
|
||||
EXES := $(foreach file, $(SAMPLES), $(file)$(HOST_EXE))
|
||||
|
||||
.PHONY: $(EXES)
|
||||
PJSUA2_SAMPLES := pjsua2_demo
|
||||
|
||||
all: $(EXES)
|
||||
PJSUA2_EXES := $(foreach file, $(PJSUA2_SAMPLES), $(file)$(HOST_EXE))
|
||||
|
||||
.PHONY: $(EXES)
|
||||
.PHONY: $(PJSUA2_EXES)
|
||||
|
||||
all: $(EXES) $(PJSUA2_EXES)
|
||||
|
||||
$(EXES):
|
||||
$(MAKE) --no-print-directory -f $(RULES_MAK) SAMPLE_SRCDIR=$(SRCDIR) SAMPLE_OBJS=$@.o SAMPLE_CFLAGS="$(_CFLAGS)" SAMPLE_LDFLAGS="$(_LDFLAGS)" SAMPLE_EXE=$@ APP=SAMPLE app=sample $(subst /,$(HOST_PSEP),$(BINDIR)/$@)
|
||||
|
||||
$(PJSUA2_EXES):
|
||||
$(MAKE) --no-print-directory -f $(RULES_MAK) SAMPLE_SRCDIR=$(SRCDIR) SAMPLE_OBJS=$@.o SAMPLE_CFLAGS="$(_CFLAGS)" SAMPLE_LDFLAGS="$(_LDFLAGS) -lstdc++" SAMPLE_EXE=$@ APP=SAMPLE app=sample $(subst /,$(HOST_PSEP),$(BINDIR)/$@)
|
||||
|
||||
depend:
|
||||
|
||||
clean:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -121,4 +121,5 @@ int main(int argc, char *argv[])
|
|||
pj_thread_join(sig_thread);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 random
|
||||
import pjsua2 as pj
|
||||
import _pjsua2
|
||||
import accountsetting
|
||||
import application
|
||||
import call
|
||||
import chat as ch
|
||||
|
||||
# Account class
|
||||
class Account(pj.Account):
|
||||
"""
|
||||
High level Python Account object, derived from pjsua2's Account object.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
pj.Account.__init__(self)
|
||||
self.app = app
|
||||
self.randId = random.randint(1, 9999)
|
||||
self.cfg = pj.AccountConfig()
|
||||
self.cfgChanged = False
|
||||
self.buddyList = []
|
||||
self.chatList = []
|
||||
self.deleting = False
|
||||
|
||||
def findChat(self, uri_str):
|
||||
uri = ch.ParseSipUri(uri_str)
|
||||
if not uri: return None
|
||||
|
||||
for chat in self.chatList:
|
||||
if chat.isUriParticipant(uri) and chat.isPrivate():
|
||||
return chat
|
||||
return None
|
||||
|
||||
def newChat(self, uri_str):
|
||||
uri = ch.ParseSipUri(uri_str)
|
||||
if not uri: return None
|
||||
|
||||
chat = ch.Chat(self.app, self, uri)
|
||||
self.chatList.append(chat)
|
||||
self.app.updateWindowMenu()
|
||||
return chat
|
||||
|
||||
def statusText(self):
|
||||
status = '?'
|
||||
if self.isValid():
|
||||
ai = self.getInfo()
|
||||
if ai.regLastErr:
|
||||
status = self.app.ep.utilStrError(ai.regLastErr)
|
||||
elif ai.regIsActive:
|
||||
if ai.onlineStatus:
|
||||
if len(ai.onlineStatusText):
|
||||
status = ai.onlineStatusText
|
||||
else:
|
||||
status = "Online"
|
||||
else:
|
||||
status = "Registered"
|
||||
else:
|
||||
if ai.regIsConfigured:
|
||||
if ai.regStatus/100 == 2:
|
||||
status = "Unregistered"
|
||||
else:
|
||||
status = ai.regStatusText
|
||||
else:
|
||||
status = "Doesn't register"
|
||||
else:
|
||||
status = '- not created -'
|
||||
return status
|
||||
|
||||
def onRegState(self, prm):
|
||||
self.app.updateAccount(self)
|
||||
|
||||
def onIncomingCall(self, prm):
|
||||
c = call.Call(self, call_id=prm.callId)
|
||||
call_prm = pj.CallOpParam()
|
||||
call_prm.statusCode = 180
|
||||
c.answer(call_prm)
|
||||
ci = c.getInfo()
|
||||
msg = "Incoming call for account '%s'" % self.cfg.idUri
|
||||
if msgbox.askquestion(msg, "Accept call from '%s'?" % (ci.remoteUri), default=msgbox.YES) == u'yes':
|
||||
call_prm.statusCode = 200
|
||||
c.answer(call_prm)
|
||||
|
||||
# find/create chat instance
|
||||
chat = self.findChat(ci.remoteUri)
|
||||
if not chat: chat = self.newChat(ci.remoteUri)
|
||||
|
||||
chat.showWindow()
|
||||
chat.registerCall(ci.remoteUri, c)
|
||||
chat.updateCallState(c, ci)
|
||||
else:
|
||||
c.hangup(call_prm)
|
||||
|
||||
def onInstantMessage(self, prm):
|
||||
chat = self.findChat(prm.fromUri)
|
||||
if not chat: chat = self.newChat(prm.fromUri)
|
||||
|
||||
chat.showWindow()
|
||||
chat.addMessage(prm.fromUri, prm.msgBody)
|
||||
|
||||
def onInstantMessageStatus(self, prm):
|
||||
if prm.code/100 == 2: return
|
||||
|
||||
chat = self.findChat(prm.toUri)
|
||||
if not chat:
|
||||
print "=== IM status to '%s' cannot find chat" % prm.toUri
|
||||
return
|
||||
|
||||
chat.addMessage(None, "Failed sending message to '%s': %s" % (prm.toUri, prm.reason))
|
||||
|
||||
def onTypingIndication(self, prm):
|
||||
chat = self.findChat(prm.fromUri)
|
||||
if not chat:
|
||||
print "=== Incoming typing indication from '%s' cannot find chat" % prm.fromUri
|
||||
return
|
||||
|
||||
chat.setTypingIndication(prm.fromUri, prm.isTyping)
|
||||
|
||||
|
||||
# Account frame, to list accounts
|
||||
class AccountListFrame(ttk.Frame):
|
||||
"""
|
||||
This implements a Frame which contains account list and buttons to operate
|
||||
on them (Add, Modify, Delete, etc.).
|
||||
"""
|
||||
def __init__(self, parent, app, acc_list = []):
|
||||
ttk.Frame.__init__(self, parent, name='acclist')
|
||||
self.app = app
|
||||
self.accList = acc_list
|
||||
self.accDeletedList = []
|
||||
self.pack(expand='yes', fill='both')
|
||||
self._createWidgets()
|
||||
for acc in self.accList:
|
||||
self._showAcc(acc)
|
||||
|
||||
def _createWidgets(self):
|
||||
self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse')
|
||||
self.tv.heading('#0', text='Priority')
|
||||
self.tv.heading(0, text='ID')
|
||||
self.tv.heading(1, text='Registrar')
|
||||
self.tv.heading(2, text='Default?')
|
||||
self.tv.column('#0', width=60)
|
||||
self.tv.column(0, width=300)
|
||||
self.tv.column(1, width=200)
|
||||
self.tv.column(2, width=60)
|
||||
self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5)
|
||||
|
||||
ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5)
|
||||
ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1)
|
||||
ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2)
|
||||
ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3)
|
||||
|
||||
def _showAcc(self, acc):
|
||||
is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
|
||||
values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
|
||||
self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values)
|
||||
|
||||
def updateAccount(self, acc):
|
||||
is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
|
||||
values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
|
||||
self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values)
|
||||
|
||||
def _getSelectedAcc(self):
|
||||
items = self.tv.selection()
|
||||
if not items:
|
||||
return None
|
||||
iid = int(items[0])
|
||||
return [acc for acc in self.accList if acc.randId==iid][0]
|
||||
|
||||
def _onBtnAdd(self):
|
||||
cfg = pj.AccountConfig()
|
||||
dlg = accountsetting.Dialog(self.master, cfg)
|
||||
if dlg.doModal():
|
||||
acc = Account(self.app)
|
||||
acc.cfg = cfg
|
||||
self._showAcc(acc)
|
||||
self.accList.append(acc)
|
||||
self.cfgChanged = True
|
||||
|
||||
def _onBtnSettings(self):
|
||||
acc = self._getSelectedAcc()
|
||||
if not acc:
|
||||
return
|
||||
dlg = accountsetting.Dialog(self.master, acc.cfg)
|
||||
if dlg.doModal():
|
||||
self.updateAccount(acc)
|
||||
self.cfgChanged = True
|
||||
|
||||
def _onBtnDelete(self):
|
||||
acc = self._getSelectedAcc()
|
||||
if not acc:
|
||||
return
|
||||
msg = "Do you really want to delete account '%s'" % acc.cfg.idUri
|
||||
if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
|
||||
return
|
||||
self.accList.remove(acc)
|
||||
self.accDeletedList.append(acc)
|
||||
self.tv.delete( (str(acc.randId),) )
|
||||
|
||||
def _onBtnSetDefault(self):
|
||||
acc = self._getSelectedAcc()
|
||||
if not acc:
|
||||
return
|
||||
if acc.isValid():
|
||||
acc.setDefault()
|
||||
for acc in self.accList:
|
||||
self.updateAccount(acc)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,369 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 endpoint
|
||||
import application
|
||||
|
||||
class Dialog(tk.Toplevel):
|
||||
"""
|
||||
This implements account settings dialog to manipulate account settings.
|
||||
"""
|
||||
def __init__(self, parent, cfg):
|
||||
tk.Toplevel.__init__(self, parent)
|
||||
self.transient(parent)
|
||||
self.parent = parent
|
||||
self.geometry("+100+100")
|
||||
self.title('Account settings')
|
||||
|
||||
self.frm = ttk.Frame(self)
|
||||
self.frm.pack(expand='yes', fill='both')
|
||||
|
||||
self.isOk = False
|
||||
self.cfg = cfg
|
||||
|
||||
self.createWidgets()
|
||||
|
||||
def doModal(self):
|
||||
if self.parent:
|
||||
self.parent.wait_window(self)
|
||||
else:
|
||||
self.wait_window(self)
|
||||
return self.isOk
|
||||
|
||||
def createWidgets(self):
|
||||
# The notebook
|
||||
self.frm.rowconfigure(0, weight=1)
|
||||
self.frm.rowconfigure(1, weight=0)
|
||||
self.frm.columnconfigure(0, weight=1)
|
||||
self.frm.columnconfigure(1, weight=1)
|
||||
self.wTab = ttk.Notebook(self.frm)
|
||||
self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E)
|
||||
|
||||
# Main buttons
|
||||
btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
|
||||
btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
|
||||
btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
|
||||
btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
|
||||
|
||||
# Tabs
|
||||
self.createBasicTab()
|
||||
self.createSipTab()
|
||||
self.createMediaTab()
|
||||
self.createMediaNatTab()
|
||||
|
||||
def createBasicTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgPriority = tk.IntVar(value=self.cfg.priority)
|
||||
self.cfgAccId = tk.StringVar(value=self.cfg.idUri)
|
||||
self.cfgRegistrar = tk.StringVar(value=self.cfg.regConfig.registrarUri)
|
||||
self.cfgRegisterOnAdd = tk.IntVar(value=self.cfg.regConfig.registerOnAdd)
|
||||
self.cfgUsername = tk.StringVar()
|
||||
self.cfgPassword = tk.StringVar()
|
||||
if len(self.cfg.sipConfig.authCreds):
|
||||
self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username )
|
||||
self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data )
|
||||
self.cfgProxy = tk.StringVar()
|
||||
if len(self.cfg.sipConfig.proxies):
|
||||
self.cfgProxy.set( self.cfg.sipConfig.proxies[0] )
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgAccId, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgRegistrar, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgProxy, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Auth username:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgUsername, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
|
||||
self.wTab.add(frm, text='Basic Settings')
|
||||
|
||||
|
||||
def createSipTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgPrackUse = tk.IntVar(value=self.cfg.callConfig.prackUse)
|
||||
self.cfgTimerUse = tk.IntVar(value=self.cfg.callConfig.timerUse)
|
||||
self.cfgTimerExpires = tk.IntVar(value=self.cfg.callConfig.timerSessExpiresSec)
|
||||
self.cfgPublish = tk.BooleanVar(value=self.cfg.presConfig.publishEnabled)
|
||||
self.cfgMwiEnabled = tk.BooleanVar(value=self.cfg.mwiConfig.enabled)
|
||||
self.cfgEnableContactRewrite = tk.BooleanVar(value=self.cfg.natConfig.contactRewriteUse != 0)
|
||||
self.cfgEnableViaRewrite = tk.BooleanVar(value=self.cfg.natConfig.viaRewriteUse != 0)
|
||||
self.cfgEnableSdpRewrite = tk.BooleanVar(value=self.cfg.natConfig.sdpNatRewriteUse != 0)
|
||||
self.cfgEnableSipOutbound = tk.BooleanVar(value=self.cfg.natConfig.sipOutboundUse != 0)
|
||||
self.cfgKaInterval = tk.IntVar(value=self.cfg.natConfig.udpKaIntervalSec)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='100rel/PRACK:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Only offer PRACK', value=pj.PJSUA_100REL_NOT_USED, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Offer and use if remote supports', value=pj.PJSUA_100REL_OPTIONAL, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_100REL_MANDATORY, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Session Timer:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Not offered', value=pj.PJSUA_SIP_TIMER_INACTIVE, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Optional', value=pj.PJSUA_SIP_TIMER_OPTIONAL, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_SIP_TIMER_REQUIRED, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text="Always use", value=pj.PJSUA_SIP_TIMER_ALWAYS, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Session Timer Expiration:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
tk.Spinbox(frm, from_=90, to=7200, textvariable=self.cfgTimerExpires, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(seconds)').grid(row=row, column=1, sticky=tk.E)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Presence:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable PUBLISH', variable=self.cfgPublish).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Message Waiting Indication:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable MWI', variable=self.cfgMwiEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='NAT Traversal:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable Contact Rewrite', variable=self.cfgEnableContactRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Enable Via Rewrite', variable=self.cfgEnableViaRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Enable SDP IP Address Rewrite', variable=self.cfgEnableSdpRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Enable SIP Outbound Extension', variable=self.cfgEnableSipOutbound).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='UDP Keep-Alive Interval:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
tk.Spinbox(frm, from_=0, to=3600, textvariable=self.cfgKaInterval, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(seconds) Zero to disable.').grid(row=row, column=1, sticky=tk.E)
|
||||
|
||||
|
||||
self.wTab.add(frm, text='SIP Features')
|
||||
|
||||
def createMediaTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgMedPort = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.port)
|
||||
self.cfgMedPortRange = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.portRange)
|
||||
self.cfgMedLockCodec = tk.BooleanVar(value=self.cfg.mediaConfig.lockCodecEnabled)
|
||||
self.cfgMedSrtp = tk.IntVar(value=self.cfg.mediaConfig.srtpUse)
|
||||
self.cfgMedSrtpSecure = tk.IntVar(value=self.cfg.mediaConfig.srtpSecureSignaling)
|
||||
self.cfgMedIpv6 = tk.BooleanVar(value=self.cfg.mediaConfig.ipv6Use==pj.PJSUA_IPV6_ENABLED)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=21)
|
||||
row = 0
|
||||
ttk.Label(frm, text='Secure RTP (SRTP):').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Disable', value=pj.PJMEDIA_SRTP_DISABLED, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Mandatory', value=pj.PJMEDIA_SRTP_MANDATORY, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Optional (non-standard)', value=pj.PJMEDIA_SRTP_OPTIONAL, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='SRTP signaling:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Does not require secure signaling', value=0, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Require secure next hop (TLS)', value=1, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Require secure end-to-end (SIPS)', value=2, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='RTP transport start port:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0: any)').grid(row=row, column=1, sticky=tk.E, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Port range:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPortRange, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0: not limited)').grid(row=row, column=1, sticky=tk.E, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Lock codec:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable', variable=self.cfgMedLockCodec).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Use IPv6:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Yes', variable=self.cfgMedIpv6).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
|
||||
self.wTab.add(frm, text='Media settings')
|
||||
|
||||
def createMediaNatTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgSipUseStun = tk.IntVar(value = self.cfg.natConfig.sipStunUse)
|
||||
self.cfgMediaUseStun = tk.IntVar(value = self.cfg.natConfig.mediaStunUse)
|
||||
self.cfgIceEnabled = tk.BooleanVar(value = self.cfg.natConfig.iceEnabled)
|
||||
self.cfgIceAggressive = tk.BooleanVar(value = self.cfg.natConfig.iceAggressiveNomination)
|
||||
self.cfgAlwaysUpdate = tk.BooleanVar(value = True if self.cfg.natConfig.iceAlwaysUpdate else False)
|
||||
self.cfgIceNoHostCands = tk.BooleanVar(value = True if self.cfg.natConfig.iceMaxHostCands == 0 else False)
|
||||
self.cfgTurnEnabled = tk.BooleanVar(value = self.cfg.natConfig.turnEnabled)
|
||||
self.cfgTurnServer = tk.StringVar(value = self.cfg.natConfig.turnServer)
|
||||
self.cfgTurnConnType = tk.IntVar(value = self.cfg.natConfig.turnConnType)
|
||||
self.cfgTurnUser = tk.StringVar(value = self.cfg.natConfig.turnUserName)
|
||||
self.cfgTurnPasswd = tk.StringVar(value = self.cfg.natConfig.turnPassword)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='SIP STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Media STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='ICE:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable', variable=self.cfgIceEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Use aggresive nomination', variable=self.cfgIceAggressive).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Always re-INVITE after negotiation', variable=self.cfgAlwaysUpdate).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Disable host candidates', variable=self.cfgIceNoHostCands).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TURN:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Checkbutton(frm, text='Enable', variable=self.cfgTurnEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TURN server:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgTurnServer, width=20).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='host[:port]').grid(row=row, column=1, sticky=tk.E, pady=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TURN connection:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Radiobutton(frm, text='UDP', value=pj.PJ_TURN_TP_UDP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Radiobutton(frm, text='TCP', value=pj.PJ_TURN_TP_TCP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TURN username:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgTurnUser, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TURN password:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgTurnPasswd, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
|
||||
self.wTab.add(frm, text='NAT settings')
|
||||
|
||||
def onOk(self):
|
||||
# Check basic settings
|
||||
errors = "";
|
||||
if not self.cfgAccId.get():
|
||||
errors += "Account ID is required\n"
|
||||
if self.cfgAccId.get():
|
||||
if not endpoint.validateSipUri(self.cfgAccId.get()):
|
||||
errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get())
|
||||
if self.cfgRegistrar.get():
|
||||
if not endpoint.validateSipUri(self.cfgRegistrar.get()):
|
||||
errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get())
|
||||
if self.cfgProxy.get():
|
||||
if not endpoint.validateSipUri(self.cfgProxy.get()):
|
||||
errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get())
|
||||
if self.cfgTurnEnabled.get():
|
||||
if not self.cfgTurnServer.get():
|
||||
errors += "TURN server is required\n"
|
||||
if errors:
|
||||
msgbox.showerror("Error detected:", errors)
|
||||
return
|
||||
|
||||
# Basic settings
|
||||
self.cfg.priority = self.cfgPriority.get()
|
||||
self.cfg.idUri = self.cfgAccId.get()
|
||||
self.cfg.regConfig.registrarUri = self.cfgRegistrar.get()
|
||||
self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get()
|
||||
while len(self.cfg.sipConfig.authCreds):
|
||||
self.cfg.sipConfig.authCreds.pop()
|
||||
if self.cfgUsername.get():
|
||||
cred = pj.AuthCredInfo()
|
||||
cred.scheme = "digest"
|
||||
cred.realm = "*"
|
||||
cred.username = self.cfgUsername.get()
|
||||
cred.data = self.cfgPassword.get()
|
||||
self.cfg.sipConfig.authCreds.append(cred)
|
||||
while len(self.cfg.sipConfig.proxies):
|
||||
self.cfg.sipConfig.proxies.pop()
|
||||
if self.cfgProxy.get():
|
||||
self.cfg.sipConfig.proxies.append(self.cfgProxy.get())
|
||||
|
||||
# SIP features
|
||||
self.cfg.callConfig.prackUse = self.cfgPrackUse.get()
|
||||
self.cfg.callConfig.timerUse = self.cfgTimerUse.get()
|
||||
self.cfg.callConfig.timerSessExpiresSec = self.cfgTimerExpires.get()
|
||||
self.cfg.presConfig.publishEnabled = self.cfgPublish.get()
|
||||
self.cfg.mwiConfig.enabled = self.cfgMwiEnabled.get()
|
||||
self.cfg.natConfig.contactRewriteUse = 1 if self.cfgEnableContactRewrite.get() else 0
|
||||
self.cfg.natConfig.viaRewriteUse = 1 if self.cfgEnableViaRewrite.get() else 0
|
||||
self.cfg.natConfig.sdpNatRewriteUse = 1 if self.cfgEnableSdpRewrite.get() else 0
|
||||
self.cfg.natConfig.sipOutboundUse = 1 if self.cfgEnableSipOutbound.get() else 0
|
||||
self.cfg.natConfig.udpKaIntervalSec = self.cfgKaInterval.get()
|
||||
|
||||
# Media
|
||||
self.cfg.mediaConfig.transportConfig.port = self.cfgMedPort.get()
|
||||
self.cfg.mediaConfig.transportConfig.portRange = self.cfgMedPortRange.get()
|
||||
self.cfg.mediaConfig.lockCodecEnabled = self.cfgMedLockCodec.get()
|
||||
self.cfg.mediaConfig.srtpUse = self.cfgMedSrtp.get()
|
||||
self.cfg.mediaConfig.srtpSecureSignaling = self.cfgMedSrtpSecure.get()
|
||||
self.cfg.mediaConfig.ipv6Use = pj.PJSUA_IPV6_ENABLED if self.cfgMedIpv6.get() else pj.PJSUA_IPV6_DISABLED
|
||||
|
||||
# NAT
|
||||
self.cfg.natConfig.sipStunUse = self.cfgSipUseStun.get()
|
||||
self.cfg.natConfig.mediaStunUse = self.cfgMediaUseStun.get()
|
||||
self.cfg.natConfig.iceEnabled = self.cfgIceEnabled.get()
|
||||
self.cfg.natConfig.iceAggressiveNomination = self.cfgIceAggressive .get()
|
||||
self.cfg.natConfig.iceAlwaysUpdate = self.cfgAlwaysUpdate.get()
|
||||
self.cfg.natConfig.iceMaxHostCands = 0 if self.cfgIceNoHostCands.get() else -1
|
||||
self.cfg.natConfig.turnEnabled = self.cfgTurnEnabled.get()
|
||||
self.cfg.natConfig.turnServer = self.cfgTurnServer.get()
|
||||
self.cfg.natConfig.turnConnType = self.cfgTurnConnType.get()
|
||||
self.cfg.natConfig.turnUserName = self.cfgTurnUser.get()
|
||||
self.cfg.natConfig.turnPasswordType = 0
|
||||
self.cfg.natConfig.turnPassword = self.cfgTurnPasswd.get()
|
||||
|
||||
self.isOk = True
|
||||
self.destroy()
|
||||
|
||||
def onCancel(self):
|
||||
self.destroy()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,510 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class Application(ttk.Frame):
|
||||
"""
|
||||
The Application main frame.
|
||||
"""
|
||||
def __init__(self):
|
||||
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()
|
||||
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'):
|
||||
# Load config
|
||||
if cfg_file and os.path.exists(cfg_file):
|
||||
self.appConfig.loadFile(cfg_file)
|
||||
|
||||
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
|
||||
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()
|
|
@ -0,0 +1,152 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 random
|
||||
import pjsua2 as pj
|
||||
import endpoint
|
||||
import application
|
||||
|
||||
# Buddy class
|
||||
class Buddy(pj.Buddy):
|
||||
"""
|
||||
High level Python Buddy object, derived from pjsua2's Buddy object.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
pj.Buddy.__init__(self)
|
||||
self.app = app
|
||||
self.randId = random.randint(1, 9999)
|
||||
self.cfg = None
|
||||
self.account = None
|
||||
|
||||
def statusText(self):
|
||||
bi = self.getInfo()
|
||||
status = ''
|
||||
if bi.subState == pj.PJSIP_EVSUB_STATE_ACTIVE:
|
||||
if bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE:
|
||||
status = bi.presStatus.statusText
|
||||
if not status:
|
||||
status = 'Online'
|
||||
elif bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_OFFLINE:
|
||||
status = 'Offline'
|
||||
else:
|
||||
status = 'Unknown'
|
||||
return status
|
||||
|
||||
def onBuddyState(self):
|
||||
self.app.updateBuddy(self)
|
||||
|
||||
class SettingDialog(tk.Toplevel):
|
||||
"""
|
||||
This implements buddy settings dialog to manipulate buddy settings.
|
||||
"""
|
||||
def __init__(self, parent, cfg):
|
||||
tk.Toplevel.__init__(self, parent)
|
||||
self.transient(parent)
|
||||
self.parent = parent
|
||||
self.geometry("+100+100")
|
||||
self.title('Buddy settings')
|
||||
|
||||
self.frm = ttk.Frame(self)
|
||||
self.frm.pack(expand='yes', fill='both')
|
||||
|
||||
self.isOk = False
|
||||
self.cfg = cfg
|
||||
|
||||
self.createWidgets()
|
||||
|
||||
def doModal(self):
|
||||
if self.parent:
|
||||
self.parent.wait_window(self)
|
||||
else:
|
||||
self.wait_window(self)
|
||||
return self.isOk
|
||||
|
||||
def createWidgets(self):
|
||||
# The notebook
|
||||
self.frm.rowconfigure(0, weight=1)
|
||||
self.frm.rowconfigure(1, weight=0)
|
||||
self.frm.columnconfigure(0, weight=1)
|
||||
self.frm.columnconfigure(1, weight=1)
|
||||
self.wTab = ttk.Notebook(self.frm)
|
||||
self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E)
|
||||
|
||||
# Main buttons
|
||||
btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
|
||||
btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
|
||||
btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
|
||||
btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
|
||||
|
||||
# Tabs
|
||||
self.createBasicTab()
|
||||
|
||||
def createBasicTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgUri = tk.StringVar()
|
||||
self.cfgUri.set( self.cfg.uri )
|
||||
self.cfgSubscribe = tk.IntVar()
|
||||
self.cfgSubscribe.set(self.cfg.subscribe)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
|
||||
ttk.Entry(frm, textvariable=self.cfgUri, width=40).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Subscribe presence', variable=self.cfgSubscribe).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
|
||||
self.wTab.add(frm, text='Basic Settings')
|
||||
|
||||
|
||||
def onOk(self):
|
||||
# Check basic settings
|
||||
errors = "";
|
||||
if self.cfgUri.get():
|
||||
if not endpoint.validateSipUri(self.cfgUri.get()):
|
||||
errors += "Invalid Buddy URI: '%s'\n" % (self.cfgUri.get())
|
||||
|
||||
if errors:
|
||||
msgbox.showerror("Error detected:", errors)
|
||||
return
|
||||
|
||||
# Basic settings
|
||||
self.cfg.uri = self.cfgUri.get()
|
||||
self.cfg.subscribe = self.cfgSubscribe.get()
|
||||
|
||||
self.isOk = True
|
||||
self.destroy()
|
||||
|
||||
def onCancel(self):
|
||||
self.destroy()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,104 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 random
|
||||
import pjsua2 as pj
|
||||
import application
|
||||
import endpoint as ep
|
||||
|
||||
# Call class
|
||||
class Call(pj.Call):
|
||||
"""
|
||||
High level Python Call object, derived from pjsua2's Call object.
|
||||
"""
|
||||
def __init__(self, acc, peer_uri='', chat=None, call_id = pj.PJSUA_INVALID_ID):
|
||||
pj.Call.__init__(self, acc, call_id)
|
||||
self.acc = acc
|
||||
self.peerUri = peer_uri
|
||||
self.chat = chat
|
||||
self.connected = False
|
||||
self.onhold = False
|
||||
|
||||
def onCallState(self, prm):
|
||||
ci = self.getInfo()
|
||||
self.connected = ci.state == pj.PJSIP_INV_STATE_CONFIRMED
|
||||
if self.chat:
|
||||
self.chat.updateCallState(self, ci)
|
||||
|
||||
def onCallMediaState(self, prm):
|
||||
ci = self.getInfo()
|
||||
for mi in ci.media:
|
||||
if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
|
||||
(mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE or \
|
||||
mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD):
|
||||
m = self.getMedia(mi.index)
|
||||
am = pj.AudioMedia.typecastFromMedia(m)
|
||||
# connect ports
|
||||
ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
|
||||
am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
|
||||
|
||||
if mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD and not self.onhold:
|
||||
self.chat.addMessage(None, "'%s' sets call onhold" % (self.peerUri))
|
||||
self.onhold = True
|
||||
elif mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE and self.onhold:
|
||||
self.chat.addMessage(None, "'%s' sets call active" % (self.peerUri))
|
||||
self.onhold = False
|
||||
|
||||
def onInstantMessage(self, prm):
|
||||
# chat instance should have been initalized
|
||||
if not self.chat: return
|
||||
|
||||
self.chat.addMessage(self.peerUri, prm.msgBody)
|
||||
self.chat.showWindow()
|
||||
|
||||
def onInstantMessageStatus(self, prm):
|
||||
if prm.code/100 == 2: return
|
||||
# chat instance should have been initalized
|
||||
if not self.chat: return
|
||||
|
||||
self.chat.addMessage(None, "Failed sending message to '%s' (%d): %s" % (self.peerUri, prm.code, prm.reason))
|
||||
|
||||
def onTypingIndication(self, prm):
|
||||
# chat instance should have been initalized
|
||||
if not self.chat: return
|
||||
|
||||
self.chat.setTypingIndication(self.peerUri, prm.isTyping)
|
||||
|
||||
def onDtmfDigit(self, prm):
|
||||
#msgbox.showinfo("pygui", 'Got DTMF:' + prm.digit)
|
||||
pass
|
||||
|
||||
def onCallMediaTransportState(self, prm):
|
||||
#msgbox.showinfo("pygui", "Media transport state")
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,489 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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
|
||||
else:
|
||||
import Tkinter as tk
|
||||
import ttk
|
||||
|
||||
import buddy
|
||||
import call
|
||||
import chatgui as gui
|
||||
import endpoint as ep
|
||||
import pjsua2 as pj
|
||||
import re
|
||||
|
||||
SipUriRegex = re.compile('(sip|sips):([^:;>\@]*)@?([^:;>]*):?([^:;>]*)')
|
||||
ConfIdx = 1
|
||||
|
||||
# Simple SIP uri parser, input URI must have been validated
|
||||
def ParseSipUri(sip_uri_str):
|
||||
m = SipUriRegex.search(sip_uri_str)
|
||||
if not m:
|
||||
assert(0)
|
||||
return None
|
||||
|
||||
scheme = m.group(1)
|
||||
user = m.group(2)
|
||||
host = m.group(3)
|
||||
port = m.group(4)
|
||||
if host == '':
|
||||
host = user
|
||||
user = ''
|
||||
|
||||
return SipUri(scheme.lower(), user, host.lower(), port)
|
||||
|
||||
class SipUri:
|
||||
def __init__(self, scheme, user, host, port):
|
||||
self.scheme = scheme
|
||||
self.user = user
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __cmp__(self, sip_uri):
|
||||
if self.scheme == sip_uri.scheme and self.user == sip_uri.user and self.host == sip_uri.host:
|
||||
# don't check port, at least for now
|
||||
return 0
|
||||
return -1
|
||||
|
||||
def __str__(self):
|
||||
s = self.scheme + ':'
|
||||
if self.user: s += self.user + '@'
|
||||
s += self.host
|
||||
if self.port: s+= ':' + self.port
|
||||
return s
|
||||
|
||||
class Chat(gui.ChatObserver):
|
||||
def __init__(self, app, acc, uri, call_inst=None):
|
||||
self._app = app
|
||||
self._acc = acc
|
||||
self.title = ''
|
||||
|
||||
global ConfIdx
|
||||
self.confIdx = ConfIdx
|
||||
ConfIdx += 1
|
||||
|
||||
# each participant call/buddy instances are stored in call list
|
||||
# and buddy list with same index as in particpant list
|
||||
self._participantList = [] # list of SipUri
|
||||
self._callList = [] # list of Call
|
||||
self._buddyList = [] # list of Buddy
|
||||
|
||||
self._gui = gui.ChatFrame(self)
|
||||
self.addParticipant(uri, call_inst)
|
||||
|
||||
def _updateGui(self):
|
||||
if self.isPrivate():
|
||||
self.title = str(self._participantList[0])
|
||||
else:
|
||||
self.title = 'Conference #%d (%d participants)' % (self.confIdx, len(self._participantList))
|
||||
self._gui.title(self.title)
|
||||
self._app.updateWindowMenu()
|
||||
|
||||
def _getCallFromUriStr(self, uri_str, op = ''):
|
||||
uri = ParseSipUri(uri_str)
|
||||
if uri not in self._participantList:
|
||||
print "=== %s cannot find participant with URI '%s'" % (op, uri_str)
|
||||
return None
|
||||
idx = self._participantList.index(uri)
|
||||
if idx < len(self._callList):
|
||||
return self._callList[idx]
|
||||
return None
|
||||
|
||||
def _getActiveMediaIdx(self, thecall):
|
||||
ci = thecall.getInfo()
|
||||
for mi in ci.media:
|
||||
if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
|
||||
(mi.status != pj.PJSUA_CALL_MEDIA_NONE and \
|
||||
mi.status != pj.PJSUA_CALL_MEDIA_ERROR):
|
||||
return mi.index
|
||||
return -1
|
||||
|
||||
def _getAudioMediaFromUriStr(self, uri_str):
|
||||
c = self._getCallFromUriStr(uri_str)
|
||||
if not c: return None
|
||||
|
||||
idx = self._getActiveMediaIdx(c)
|
||||
if idx < 0: return None
|
||||
|
||||
m = c.getMedia(idx)
|
||||
am = pj.AudioMedia.typecastFromMedia(m)
|
||||
return am
|
||||
|
||||
def _sendTypingIndication(self, is_typing, sender_uri_str=''):
|
||||
sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
|
||||
type_ind_param = pj.SendTypingIndicationParam()
|
||||
type_ind_param.isTyping = is_typing
|
||||
for idx, p in enumerate(self._participantList):
|
||||
# don't echo back to the original sender
|
||||
if sender_uri and p == sender_uri:
|
||||
continue
|
||||
|
||||
# send via call, if any, or buddy
|
||||
sender = None
|
||||
if self._callList[idx] and self._callList[idx].connected:
|
||||
sender = self._callList[idx]
|
||||
else:
|
||||
sender = self._buddyList[idx]
|
||||
assert(sender)
|
||||
|
||||
try:
|
||||
sender.sendTypingIndication(type_ind_param)
|
||||
except:
|
||||
pass
|
||||
|
||||
def _sendInstantMessage(self, msg, sender_uri_str=''):
|
||||
sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
|
||||
send_im_param = pj.SendInstantMessageParam()
|
||||
send_im_param.content = str(msg)
|
||||
for idx, p in enumerate(self._participantList):
|
||||
# don't echo back to the original sender
|
||||
if sender_uri and p == sender_uri:
|
||||
continue
|
||||
|
||||
# send via call, if any, or buddy
|
||||
sender = None
|
||||
if self._callList[idx] and self._callList[idx].connected:
|
||||
sender = self._callList[idx]
|
||||
else:
|
||||
sender = self._buddyList[idx]
|
||||
assert(sender)
|
||||
|
||||
try:
|
||||
sender.sendInstantMessage(send_im_param)
|
||||
except:
|
||||
# error will be handled via Account::onInstantMessageStatus()
|
||||
pass
|
||||
|
||||
def isPrivate(self):
|
||||
return len(self._participantList) <= 1
|
||||
|
||||
def isUriParticipant(self, uri):
|
||||
return uri in self._participantList
|
||||
|
||||
def registerCall(self, uri_str, call_inst):
|
||||
uri = ParseSipUri(uri_str)
|
||||
try:
|
||||
idx = self._participantList.index(uri)
|
||||
bud = self._buddyList[idx]
|
||||
self._callList[idx] = call_inst
|
||||
call_inst.chat = self
|
||||
call_inst.peerUri = bud.cfg.uri
|
||||
except:
|
||||
assert(0) # idx must be found!
|
||||
|
||||
def showWindow(self, show_text_chat = False):
|
||||
self._gui.bringToFront()
|
||||
if show_text_chat:
|
||||
self._gui.textShowHide(True)
|
||||
|
||||
def addParticipant(self, uri, call_inst=None):
|
||||
# avoid duplication
|
||||
if self.isUriParticipant(uri): return
|
||||
|
||||
uri_str = str(uri)
|
||||
|
||||
# find buddy, create one if not found (e.g: for IM/typing ind),
|
||||
# it is a temporary one and not really registered to acc
|
||||
bud = None
|
||||
try:
|
||||
bud = self._acc.findBuddy(uri_str)
|
||||
except:
|
||||
bud = buddy.Buddy(None)
|
||||
bud_cfg = pj.BuddyConfig()
|
||||
bud_cfg.uri = uri_str
|
||||
bud_cfg.subscribe = False
|
||||
bud.create(self._acc, bud_cfg)
|
||||
bud.cfg = bud_cfg
|
||||
bud.account = self._acc
|
||||
|
||||
# update URI from buddy URI
|
||||
uri = ParseSipUri(bud.cfg.uri)
|
||||
|
||||
# add it
|
||||
self._participantList.append(uri)
|
||||
self._callList.append(call_inst)
|
||||
self._buddyList.append(bud)
|
||||
self._gui.addParticipant(str(uri))
|
||||
self._updateGui()
|
||||
|
||||
def kickParticipant(self, uri):
|
||||
if (not uri) or (uri not in self._participantList):
|
||||
assert(0)
|
||||
return
|
||||
|
||||
idx = self._participantList.index(uri)
|
||||
del self._participantList[idx]
|
||||
del self._callList[idx]
|
||||
del self._buddyList[idx]
|
||||
self._gui.delParticipant(str(uri))
|
||||
|
||||
if self._participantList:
|
||||
self._updateGui()
|
||||
else:
|
||||
self.onCloseWindow()
|
||||
|
||||
def addMessage(self, from_uri_str, msg):
|
||||
if from_uri_str:
|
||||
# print message on GUI
|
||||
msg = from_uri_str + ': ' + msg
|
||||
self._gui.textAddMessage(msg)
|
||||
# now relay to all participants
|
||||
self._sendInstantMessage(msg, from_uri_str)
|
||||
else:
|
||||
self._gui.textAddMessage(msg, False)
|
||||
|
||||
def setTypingIndication(self, from_uri_str, is_typing):
|
||||
# notify GUI
|
||||
self._gui.textSetTypingIndication(from_uri_str, is_typing)
|
||||
# now relay to all participants
|
||||
self._sendTypingIndication(is_typing, from_uri_str)
|
||||
|
||||
def startCall(self):
|
||||
self._gui.enableAudio()
|
||||
call_param = pj.CallOpParam()
|
||||
call_param.opt.audioCount = 1
|
||||
call_param.opt.videoCount = 0
|
||||
fails = []
|
||||
for idx, p in enumerate(self._participantList):
|
||||
# just skip if call is instantiated
|
||||
if self._callList[idx]:
|
||||
continue
|
||||
|
||||
uri_str = str(p)
|
||||
c = call.Call(self._acc, uri_str, self)
|
||||
self._callList[idx] = c
|
||||
self._gui.audioUpdateState(uri_str, gui.AudioState.INITIALIZING)
|
||||
|
||||
try:
|
||||
c.makeCall(uri_str, call_param)
|
||||
except:
|
||||
self._callList[idx] = None
|
||||
self._gui.audioUpdateState(uri_str, gui.AudioState.FAILED)
|
||||
fails.append(p)
|
||||
|
||||
for p in fails:
|
||||
# kick participants with call failure, but spare the last (avoid zombie chat)
|
||||
if not self.isPrivate():
|
||||
self.kickParticipant(p)
|
||||
|
||||
def stopCall(self):
|
||||
for idx, p in enumerate(self._participantList):
|
||||
self._gui.audioUpdateState(str(p), gui.AudioState.DISCONNECTED)
|
||||
c = self._callList[idx]
|
||||
if c:
|
||||
c.hangup(pj.CallOpParam())
|
||||
|
||||
def updateCallState(self, thecall, info = None):
|
||||
# info is optional here, just to avoid calling getInfo() twice (in the caller and here)
|
||||
if not info: info = thecall.getInfo()
|
||||
|
||||
if info.state < pj.PJSIP_INV_STATE_CONFIRMED:
|
||||
self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.INITIALIZING)
|
||||
elif info.state == pj.PJSIP_INV_STATE_CONFIRMED:
|
||||
self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.CONNECTED)
|
||||
med_idx = self._getActiveMediaIdx(thecall)
|
||||
si = thecall.getStreamInfo(med_idx)
|
||||
stats_str = "Audio codec: %s/%s\n..." % (si.codecName, si.codecClockRate)
|
||||
self._gui.audioSetStatsText(thecall.peerUri, stats_str)
|
||||
elif info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
|
||||
if info.lastStatusCode/100 != 2:
|
||||
self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.FAILED)
|
||||
else:
|
||||
self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.DISCONNECTED)
|
||||
|
||||
# reset entry in the callList
|
||||
try:
|
||||
idx = self._callList.index(thecall)
|
||||
if idx >= 0: self._callList[idx] = None
|
||||
except:
|
||||
pass
|
||||
|
||||
self.addMessage(None, "Call to '%s' disconnected: %s" % (thecall.peerUri, info.lastReason))
|
||||
|
||||
# kick the disconnected participant, but the last (avoid zombie chat)
|
||||
if not self.isPrivate():
|
||||
self.kickParticipant(ParseSipUri(thecall.peerUri))
|
||||
|
||||
|
||||
# ** callbacks from GUI (ChatObserver implementation) **
|
||||
|
||||
# Text
|
||||
def onSendMessage(self, msg):
|
||||
self._sendInstantMessage(msg)
|
||||
|
||||
def onStartTyping(self):
|
||||
self._sendTypingIndication(True)
|
||||
|
||||
def onStopTyping(self):
|
||||
self._sendTypingIndication(False)
|
||||
|
||||
# Audio
|
||||
def onHangup(self, peer_uri_str):
|
||||
c = self._getCallFromUriStr(peer_uri_str, "onHangup()")
|
||||
if not c: return
|
||||
call_param = pj.CallOpParam()
|
||||
c.hangup(call_param)
|
||||
|
||||
def onHold(self, peer_uri_str):
|
||||
c = self._getCallFromUriStr(peer_uri_str, "onHold()")
|
||||
if not c: return
|
||||
call_param = pj.CallOpParam()
|
||||
c.setHold(call_param)
|
||||
|
||||
def onUnhold(self, peer_uri_str):
|
||||
c = self._getCallFromUriStr(peer_uri_str, "onUnhold()")
|
||||
if not c: return
|
||||
|
||||
call_param = pj.CallOpParam()
|
||||
call_param.opt.audioCount = 1
|
||||
call_param.opt.videoCount = 0
|
||||
call_param.opt.flag |= pj.PJSUA_CALL_UNHOLD
|
||||
c.reinvite(call_param)
|
||||
|
||||
def onRxMute(self, peer_uri_str, mute):
|
||||
am = self._getAudioMediaFromUriStr(peer_uri_str)
|
||||
if not am: return
|
||||
if mute:
|
||||
am.stopTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
|
||||
self.addMessage(None, "Muted audio from '%s'" % (peer_uri_str))
|
||||
else:
|
||||
am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
|
||||
self.addMessage(None, "Unmuted audio from '%s'" % (peer_uri_str))
|
||||
|
||||
def onRxVol(self, peer_uri_str, vol_pct):
|
||||
am = self._getAudioMediaFromUriStr(peer_uri_str)
|
||||
if not am: return
|
||||
# pjsua volume range = 0:mute, 1:no adjustment, 2:100% louder
|
||||
am.adjustRxLevel(vol_pct/50.0)
|
||||
self.addMessage(None, "Adjusted volume level audio from '%s'" % (peer_uri_str))
|
||||
|
||||
def onTxMute(self, peer_uri_str, mute):
|
||||
am = self._getAudioMediaFromUriStr(peer_uri_str)
|
||||
if not am: return
|
||||
if mute:
|
||||
ep.Endpoint.instance.audDevManager().getCaptureDevMedia().stopTransmit(am)
|
||||
self.addMessage(None, "Muted audio to '%s'" % (peer_uri_str))
|
||||
else:
|
||||
ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
|
||||
self.addMessage(None, "Unmuted audio to '%s'" % (peer_uri_str))
|
||||
|
||||
# Chat room
|
||||
def onAddParticipant(self):
|
||||
buds = []
|
||||
dlg = AddParticipantDlg(None, self._app, buds)
|
||||
if dlg.doModal():
|
||||
for bud in buds:
|
||||
uri = ParseSipUri(bud.cfg.uri)
|
||||
self.addParticipant(uri)
|
||||
if not self.isPrivate():
|
||||
self.startCall()
|
||||
|
||||
def onStartAudio(self):
|
||||
self.startCall()
|
||||
|
||||
def onStopAudio(self):
|
||||
self.stopCall()
|
||||
|
||||
def onCloseWindow(self):
|
||||
self.stopCall()
|
||||
# will remove entry from list eventually destroy this chat?
|
||||
if self in self._acc.chatList: self._acc.chatList.remove(self)
|
||||
self._app.updateWindowMenu()
|
||||
# destroy GUI
|
||||
self._gui.destroy()
|
||||
|
||||
|
||||
class AddParticipantDlg(tk.Toplevel):
|
||||
"""
|
||||
List of buddies
|
||||
"""
|
||||
def __init__(self, parent, app, bud_list):
|
||||
tk.Toplevel.__init__(self, parent)
|
||||
self.title('Add participants..')
|
||||
self.transient(parent)
|
||||
self.parent = parent
|
||||
self._app = app
|
||||
self.buddyList = bud_list
|
||||
|
||||
self.isOk = False
|
||||
|
||||
self.createWidgets()
|
||||
|
||||
def doModal(self):
|
||||
if self.parent:
|
||||
self.parent.wait_window(self)
|
||||
else:
|
||||
self.wait_window(self)
|
||||
return self.isOk
|
||||
|
||||
def createWidgets(self):
|
||||
# buddy list
|
||||
list_frame = ttk.Frame(self)
|
||||
list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1, padx=20, pady=20)
|
||||
#scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=list_frame.yview)
|
||||
#list_frame.config(yscrollcommand=scrl.set)
|
||||
#scrl.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# draw buddy list
|
||||
self.buddies = []
|
||||
for acc in self._app.accList:
|
||||
self.buddies.append((0, acc.cfg.idUri))
|
||||
for bud in acc.buddyList:
|
||||
self.buddies.append((1, bud))
|
||||
|
||||
self.bud_var = []
|
||||
for idx,(flag,bud) in enumerate(self.buddies):
|
||||
self.bud_var.append(tk.IntVar())
|
||||
if flag==0:
|
||||
s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
|
||||
s.pack(fill=tk.X)
|
||||
l = tk.Label(list_frame, anchor=tk.W, text="Account '%s':" % (bud))
|
||||
l.pack(fill=tk.X)
|
||||
else:
|
||||
c = tk.Checkbutton(list_frame, anchor=tk.W, text=bud.cfg.uri, variable=self.bud_var[idx])
|
||||
c.pack(fill=tk.X)
|
||||
s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
|
||||
s.pack(fill=tk.X)
|
||||
|
||||
# Ok/cancel buttons
|
||||
tail_frame = ttk.Frame(self)
|
||||
tail_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
|
||||
|
||||
btnOk = ttk.Button(tail_frame, text='Ok', default=tk.ACTIVE, command=self.onOk)
|
||||
btnOk.pack(side=tk.LEFT, padx=20, pady=10)
|
||||
btnCancel = ttk.Button(tail_frame, text='Cancel', command=self.onCancel)
|
||||
btnCancel.pack(side=tk.RIGHT, padx=20, pady=10)
|
||||
|
||||
def onOk(self):
|
||||
self.buddyList[:] = []
|
||||
for idx,(flag,bud) in enumerate(self.buddies):
|
||||
if not flag: continue
|
||||
if self.bud_var[idx].get() and not (bud in self.buddyList):
|
||||
self.buddyList.append(bud)
|
||||
|
||||
self.isOk = True
|
||||
self.destroy()
|
||||
|
||||
def onCancel(self):
|
||||
self.destroy()
|
|
@ -0,0 +1,420 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 ttk
|
||||
import tkMessageBox as msgbox
|
||||
|
||||
|
||||
class TextObserver:
|
||||
def onSendMessage(self, msg):
|
||||
pass
|
||||
def onStartTyping(self):
|
||||
pass
|
||||
def onStopTyping(self):
|
||||
pass
|
||||
|
||||
class TextFrame(ttk.Frame):
|
||||
def __init__(self, master, observer):
|
||||
ttk.Frame.__init__(self, master)
|
||||
self._observer = observer
|
||||
self._isTyping = False
|
||||
self._createWidgets()
|
||||
|
||||
def _onSendMessage(self, event):
|
||||
send_text = self._typingBox.get("1.0", tk.END).strip()
|
||||
if send_text == '':
|
||||
return
|
||||
|
||||
self.addMessage('me: ' + send_text)
|
||||
self._typingBox.delete("0.0", tk.END)
|
||||
self._onTyping(None)
|
||||
|
||||
# notify app for sending message
|
||||
self._observer.onSendMessage(send_text)
|
||||
|
||||
def _onTyping(self, event):
|
||||
# notify app for typing indication
|
||||
is_typing = self._typingBox.get("1.0", tk.END).strip() != ''
|
||||
if is_typing != self._isTyping:
|
||||
self._isTyping = is_typing
|
||||
if is_typing:
|
||||
self._observer.onStartTyping()
|
||||
else:
|
||||
self._observer.onStopTyping()
|
||||
|
||||
def _createWidgets(self):
|
||||
self.rowconfigure(0, weight=1)
|
||||
self.rowconfigure(1, weight=0)
|
||||
self.rowconfigure(2, weight=0)
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=0)
|
||||
|
||||
self._text = tk.Text(self, width=50, height=30, font=("Arial", "10"))
|
||||
self._text.grid(row=0, column=0, sticky='nswe')
|
||||
self._text.config(state=tk.DISABLED)
|
||||
self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic"))
|
||||
|
||||
scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview)
|
||||
self._text.config(yscrollcommand=scrl.set)
|
||||
scrl.grid(row=0, column=1, sticky='nsw')
|
||||
|
||||
self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10"))
|
||||
self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0)
|
||||
|
||||
self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic"))
|
||||
self._statusBar.grid(row=2, columnspan=2, sticky='we')
|
||||
|
||||
self._typingBox.bind('<Return>', self._onSendMessage)
|
||||
self._typingBox.bind("<Key>", self._onTyping)
|
||||
self._typingBox.focus_set()
|
||||
|
||||
def addMessage(self, msg, is_chat = True):
|
||||
self._text.config(state=tk.NORMAL)
|
||||
if is_chat:
|
||||
self._text.insert(tk.END, msg+'\r\n')
|
||||
else:
|
||||
self._text.insert(tk.END, msg+'\r\n', 'info')
|
||||
self._text.config(state=tk.DISABLED)
|
||||
self._text.yview(tk.END)
|
||||
|
||||
def setTypingIndication(self, who, is_typing):
|
||||
if is_typing:
|
||||
self._statusBar['text'] = "'%s' is typing.." % (who)
|
||||
else:
|
||||
self._statusBar['text'] = ''
|
||||
|
||||
class AudioState:
|
||||
NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5)
|
||||
|
||||
class AudioObserver:
|
||||
def onHangup(self, peer_uri):
|
||||
pass
|
||||
def onHold(self, peer_uri):
|
||||
pass
|
||||
def onUnhold(self, peer_uri):
|
||||
pass
|
||||
def onRxMute(self, peer_uri, is_muted):
|
||||
pass
|
||||
def onRxVol(self, peer_uri, vol_pct):
|
||||
pass
|
||||
def onTxMute(self, peer_uri, is_muted):
|
||||
pass
|
||||
|
||||
|
||||
class AudioFrame(ttk.Labelframe):
|
||||
def __init__(self, master, peer_uri, observer):
|
||||
ttk.Labelframe.__init__(self, master, text=peer_uri)
|
||||
self.peerUri = peer_uri
|
||||
self._observer = observer
|
||||
self._initFrame = None
|
||||
self._callFrame = None
|
||||
self._rxMute = False
|
||||
self._txMute = False
|
||||
self._state = AudioState.NULL
|
||||
|
||||
self._createInitWidgets()
|
||||
self._createWidgets()
|
||||
|
||||
def updateState(self, state):
|
||||
if self._state == state:
|
||||
return
|
||||
|
||||
if state == AudioState.INITIALIZING:
|
||||
self._callFrame.pack_forget()
|
||||
self._initFrame.pack(fill=tk.BOTH)
|
||||
self._btnCancel.pack(side=tk.TOP)
|
||||
self._lblInitState['text'] = 'Intializing..'
|
||||
|
||||
elif state == AudioState.CONNECTED:
|
||||
self._initFrame.pack_forget()
|
||||
self._callFrame.pack(fill=tk.BOTH)
|
||||
else:
|
||||
self._callFrame.pack_forget()
|
||||
self._initFrame.pack(fill=tk.BOTH)
|
||||
if state == AudioState.FAILED:
|
||||
self._lblInitState['text'] = 'Failed'
|
||||
else:
|
||||
self._lblInitState['text'] = 'Normal cleared'
|
||||
self._btnCancel.pack_forget()
|
||||
|
||||
self._btnHold['text'] = 'Hold'
|
||||
self._btnHold.config(state=tk.NORMAL)
|
||||
self._rxMute = False
|
||||
self._txMute = False
|
||||
self.btnRxMute['text'] = 'Mute'
|
||||
self.btnTxMute['text'] = 'Mute'
|
||||
self.rxVol.set(5.0)
|
||||
|
||||
# save last state
|
||||
self._state = state
|
||||
|
||||
def setStatsText(self, stats_str):
|
||||
self.stat.config(state=tk.NORMAL)
|
||||
self.stat.delete("0.0", tk.END)
|
||||
self.stat.insert(tk.END, stats_str)
|
||||
self.stat.config(state=tk.DISABLED)
|
||||
|
||||
def _onHold(self):
|
||||
self._btnHold.config(state=tk.DISABLED)
|
||||
# notify app
|
||||
if self._btnHold['text'] == 'Hold':
|
||||
self._observer.onHold(self.peerUri)
|
||||
self._btnHold['text'] = 'Unhold'
|
||||
else:
|
||||
self._observer.onUnhold(self.peerUri)
|
||||
self._btnHold['text'] = 'Hold'
|
||||
self._btnHold.config(state=tk.NORMAL)
|
||||
|
||||
def _onHangup(self):
|
||||
# notify app
|
||||
self._observer.onHangup(self.peerUri)
|
||||
|
||||
def _onRxMute(self):
|
||||
# notify app
|
||||
self._rxMute = not self._rxMute
|
||||
self._observer.onRxMute(self.peerUri, self._rxMute)
|
||||
self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute'
|
||||
|
||||
def _onRxVol(self, event):
|
||||
# notify app
|
||||
vol = self.rxVol.get()
|
||||
self._observer.onRxVol(self.peerUri, vol*10.0)
|
||||
|
||||
def _onTxMute(self):
|
||||
# notify app
|
||||
self._txMute = not self._txMute
|
||||
self._observer.onTxMute(self.peerUri, self._txMute)
|
||||
self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute'
|
||||
|
||||
def _createInitWidgets(self):
|
||||
self._initFrame = ttk.Frame(self)
|
||||
#self._initFrame.pack(fill=tk.BOTH)
|
||||
|
||||
|
||||
self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='')
|
||||
self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1)
|
||||
|
||||
# Operation: cancel/kick
|
||||
self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup)
|
||||
self._btnCancel.pack(side=tk.TOP)
|
||||
|
||||
def _createWidgets(self):
|
||||
self._callFrame = ttk.Frame(self)
|
||||
#self._callFrame.pack(fill=tk.BOTH)
|
||||
|
||||
# toolbar
|
||||
toolbar = ttk.Frame(self._callFrame)
|
||||
toolbar.pack(side=tk.TOP, fill=tk.X)
|
||||
self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold)
|
||||
self._btnHold.pack(side=tk.LEFT, fill=tk.Y)
|
||||
#self._btnXfer = ttk.Button(toolbar, text='Transfer..')
|
||||
#self._btnXfer.pack(side=tk.LEFT, fill=tk.Y)
|
||||
self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup)
|
||||
self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
# volume tool
|
||||
vol_frm = ttk.Frame(self._callFrame)
|
||||
vol_frm.pack(side=tk.TOP, fill=tk.X)
|
||||
|
||||
self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume')
|
||||
self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute)
|
||||
self.btnRxMute.pack(side=tk.LEFT)
|
||||
self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1)
|
||||
self.rxVol.set(5.0)
|
||||
self.rxVol.bind("<ButtonRelease-1>", self._onRxVol)
|
||||
self.rxVol.pack(side=tk.LEFT)
|
||||
|
||||
self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume')
|
||||
self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute)
|
||||
self.btnTxMute.pack(side=tk.LEFT)
|
||||
|
||||
# stat
|
||||
self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Arial", "9"))
|
||||
self.stat.insert(tk.END, 'stat here')
|
||||
self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
|
||||
|
||||
|
||||
class ChatObserver(TextObserver, AudioObserver):
|
||||
def onAddParticipant(self):
|
||||
pass
|
||||
def onStartAudio(self):
|
||||
pass
|
||||
def onStopAudio(self):
|
||||
pass
|
||||
def onCloseWindow(self):
|
||||
pass
|
||||
|
||||
class ChatFrame(tk.Toplevel):
|
||||
"""
|
||||
Room
|
||||
"""
|
||||
def __init__(self, observer):
|
||||
tk.Toplevel.__init__(self)
|
||||
self.protocol("WM_DELETE_WINDOW", self._onClose)
|
||||
self._observer = observer
|
||||
|
||||
self._text = None
|
||||
self._text_shown = True
|
||||
|
||||
self._audioEnabled = False
|
||||
self._audioFrames = []
|
||||
self._createWidgets()
|
||||
|
||||
def _createWidgets(self):
|
||||
# toolbar
|
||||
self.toolbar = ttk.Frame(self)
|
||||
self.toolbar.pack(side=tk.TOP, fill=tk.BOTH)
|
||||
|
||||
btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText)
|
||||
btnText.pack(side=tk.LEFT, fill=tk.Y)
|
||||
btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio)
|
||||
btnAudio.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4)
|
||||
|
||||
btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant)
|
||||
btnAdd.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
# media frame
|
||||
self.media = ttk.Frame(self)
|
||||
self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
|
||||
|
||||
# create Text Chat frame
|
||||
self.media_left = ttk.Frame(self.media)
|
||||
self._text = TextFrame(self.media_left, self._observer)
|
||||
self._text.pack(fill=tk.BOTH, expand=1)
|
||||
self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
|
||||
|
||||
# create other media frame
|
||||
self.media_right = ttk.Frame(self.media)
|
||||
|
||||
def _arrangeMediaFrames(self):
|
||||
if len(self._audioFrames) == 0:
|
||||
self.media_right.pack_forget()
|
||||
return
|
||||
|
||||
self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
|
||||
MAX_ROWS = 3
|
||||
row_num = 0
|
||||
col_num = 1
|
||||
for frm in self._audioFrames:
|
||||
frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5)
|
||||
row_num += 1
|
||||
if row_num >= MAX_ROWS:
|
||||
row_num = 0
|
||||
col_num += 1
|
||||
|
||||
def _onShowHideText(self):
|
||||
self.textShowHide(not self._text_shown)
|
||||
|
||||
def _onAddParticipant(self):
|
||||
self._observer.onAddParticipant()
|
||||
|
||||
def _onStartStopAudio(self):
|
||||
self._audioEnabled = not self._audioEnabled
|
||||
if self._audioEnabled:
|
||||
self._observer.onStartAudio()
|
||||
else:
|
||||
self._observer.onStopAudio()
|
||||
self.enableAudio(self._audioEnabled)
|
||||
|
||||
def _onClose(self):
|
||||
self._observer.onCloseWindow()
|
||||
|
||||
# APIs
|
||||
|
||||
def bringToFront(self):
|
||||
self.deiconify()
|
||||
self.lift()
|
||||
self._text._typingBox.focus_set()
|
||||
|
||||
def textAddMessage(self, msg, is_chat = True):
|
||||
self._text.addMessage(msg, is_chat)
|
||||
|
||||
def textSetTypingIndication(self, who, is_typing = True):
|
||||
self._text.setTypingIndication(who, is_typing)
|
||||
|
||||
def addParticipant(self, participant_uri):
|
||||
aud_frm = AudioFrame(self.media_right, participant_uri, self._observer)
|
||||
self._audioFrames.append(aud_frm)
|
||||
|
||||
def delParticipant(self, participant_uri):
|
||||
for aud_frm in self._audioFrames:
|
||||
if participant_uri == aud_frm.peerUri:
|
||||
self._audioFrames.remove(aud_frm)
|
||||
# need to delete aud_frm manually?
|
||||
aud_frm.destroy()
|
||||
return
|
||||
|
||||
def textShowHide(self, show = True):
|
||||
if show:
|
||||
self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
|
||||
self._text._typingBox.focus_set()
|
||||
else:
|
||||
self.media_left.pack_forget()
|
||||
self._text_shown = show
|
||||
|
||||
def enableAudio(self, is_enabled = True):
|
||||
if is_enabled:
|
||||
self._arrangeMediaFrames()
|
||||
else:
|
||||
self.media_right.pack_forget()
|
||||
self._audioEnabled = is_enabled
|
||||
|
||||
def audioUpdateState(self, participant_uri, state):
|
||||
for aud_frm in self._audioFrames:
|
||||
if participant_uri == aud_frm.peerUri:
|
||||
aud_frm.updateState(state)
|
||||
break
|
||||
if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1:
|
||||
self.enableAudio(False)
|
||||
else:
|
||||
self.enableAudio(True)
|
||||
|
||||
def audioSetStatsText(self, participant_uri, stats_str):
|
||||
for aud_frm in self._audioFrames:
|
||||
if participant_uri == aud_frm.peerUri:
|
||||
aud_frm.setStatsText(stats_str)
|
||||
break
|
||||
|
||||
if __name__ == '__main__':
|
||||
root = tk.Tk()
|
||||
root.title("Chat")
|
||||
root.columnconfigure(0, weight=1)
|
||||
root.rowconfigure(0, weight=1)
|
||||
|
||||
obs = ChatObserver()
|
||||
dlg = ChatFrame(obs)
|
||||
#dlg = TextFrame(root)
|
||||
#dlg = AudioFrame(root)
|
||||
|
||||
#dlg.pack(fill=tk.BOTH, expand=1)
|
||||
root.mainloop()
|
|
@ -0,0 +1,53 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 application
|
||||
|
||||
|
||||
class Endpoint(pj.Endpoint):
|
||||
"""
|
||||
This is high level Python object inherited from pj.Endpoint
|
||||
"""
|
||||
instance = None
|
||||
def __init__(self):
|
||||
pj.Endpoint.__init__(self)
|
||||
Endpoint.instance = self
|
||||
|
||||
|
||||
def validateUri(uri):
|
||||
return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS
|
||||
|
||||
def validateSipUri(uri):
|
||||
return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,127 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 application
|
||||
|
||||
|
||||
class LogWindow(tk.Toplevel):
|
||||
"""
|
||||
Log window
|
||||
"""
|
||||
instance = None
|
||||
def __init__(self, app):
|
||||
tk.Toplevel.__init__(self, name='logwnd', width=640, height=480)
|
||||
LogWindow.instance = self
|
||||
self.app = app
|
||||
self.state('withdrawn')
|
||||
self.title('Log')
|
||||
self._createWidgets()
|
||||
self.protocol("WM_DELETE_WINDOW", self._onHide)
|
||||
|
||||
def addLog(self, entry):
|
||||
"""entry fields:
|
||||
int level;
|
||||
string msg;
|
||||
long threadId;
|
||||
string threadName;
|
||||
"""
|
||||
self.addLog2(entry.level, entry.msg)
|
||||
|
||||
def addLog2(self, level, msg):
|
||||
if level==5:
|
||||
tags = ('trace',)
|
||||
elif level==3:
|
||||
tags = ('info',)
|
||||
elif level==2:
|
||||
tags = ('warning',)
|
||||
elif level<=1:
|
||||
tags = ('error',)
|
||||
else:
|
||||
tags = None
|
||||
self.text.insert(tk.END, msg, tags)
|
||||
self.text.see(tk.END)
|
||||
|
||||
def _createWidgets(self):
|
||||
self.rowconfigure(0, weight=1)
|
||||
self.rowconfigure(1, weight=0)
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=0)
|
||||
|
||||
self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5)
|
||||
self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5)
|
||||
|
||||
scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview)
|
||||
self.text.config(yscrollcommand=scrl.set)
|
||||
scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5)
|
||||
|
||||
scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview)
|
||||
self.text.config(xscrollcommand=scrl.set)
|
||||
scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5)
|
||||
|
||||
self.text.bind("<Key>", self._onKey)
|
||||
|
||||
self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black')
|
||||
self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777')
|
||||
self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black')
|
||||
self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan')
|
||||
self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red')
|
||||
|
||||
def _onKey(self, event):
|
||||
# Ignore key event to make text widget read-only
|
||||
return "break"
|
||||
|
||||
def _onHide(self):
|
||||
# Hide when close ('x') button is clicked
|
||||
self.withdraw()
|
||||
self.app.showLogWindow.set(0)
|
||||
|
||||
|
||||
def writeLog2(level, msg):
|
||||
if LogWindow.instance:
|
||||
LogWindow.instance.addLog2(level, msg)
|
||||
|
||||
def writeLog(entry):
|
||||
if LogWindow.instance:
|
||||
LogWindow.instance.addLog(entry)
|
||||
|
||||
class Logger(pj.LogWriter):
|
||||
"""
|
||||
Logger to receive log messages from pjsua2
|
||||
"""
|
||||
def __init__(self):
|
||||
pj.LogWriter.__init__(self)
|
||||
|
||||
def write(self, entry):
|
||||
print entry.msg,
|
||||
writeLog(entry)
|
||||
|
||||
if __name__ == '__main__':
|
||||
application.main()
|
|
@ -0,0 +1,362 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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 application
|
||||
|
||||
# Transport setting
|
||||
class SipTransportConfig:
|
||||
def __init__(self, type, enabled):
|
||||
#pj.PersistentObject.__init__(self)
|
||||
self.type = type
|
||||
self.enabled = enabled
|
||||
self.config = pj.TransportConfig()
|
||||
def readObject(self, node):
|
||||
child_node = node.readContainer("SipTransport")
|
||||
self.type = child_node.readInt("type")
|
||||
self.enabled = child_node.readBool("enabled")
|
||||
self.config.readObject(child_node)
|
||||
def writeObject(self, node):
|
||||
child_node = node.writeNewContainer("SipTransport")
|
||||
child_node.writeInt("type", self.type)
|
||||
child_node.writeBool("enabled", self.enabled)
|
||||
self.config.writeObject(child_node)
|
||||
|
||||
# Account setting with buddy list
|
||||
class AccConfig:
|
||||
def __init__(self):
|
||||
self.enabled = True
|
||||
self.config = pj.AccountConfig()
|
||||
self.buddyConfigs = []
|
||||
def readObject(self, node):
|
||||
acc_node = node.readContainer("Account")
|
||||
self.enabled = acc_node.readBool("enabled")
|
||||
self.config.readObject(acc_node)
|
||||
buddy_node = acc_node.readArray("buddies")
|
||||
while buddy_node.hasUnread():
|
||||
buddy_cfg = pj.BuddyConfig()
|
||||
buddy_cfg.readObject(buddy_node)
|
||||
self.buddyConfigs.append(buddy_cfg)
|
||||
def writeObject(self, node):
|
||||
acc_node = node.writeNewContainer("Account")
|
||||
acc_node.writeBool("enabled", self.enabled)
|
||||
self.config.writeObject(acc_node)
|
||||
buddy_node = acc_node.writeNewArray("buddies")
|
||||
for buddy in self.buddyConfigs:
|
||||
buddy_node.writeObject(buddy)
|
||||
|
||||
|
||||
# Master settings
|
||||
class AppConfig:
|
||||
def __init__(self):
|
||||
self.epConfig = pj.EpConfig() # pj.EpConfig()
|
||||
self.udp = SipTransportConfig(pj.PJSIP_TRANSPORT_UDP, True)
|
||||
self.tcp = SipTransportConfig(pj.PJSIP_TRANSPORT_TCP, True)
|
||||
self.tls = SipTransportConfig(pj.PJSIP_TRANSPORT_TLS, False)
|
||||
self.accounts = [] # Array of AccConfig
|
||||
|
||||
def loadFile(self, file):
|
||||
json = pj.JsonDocument()
|
||||
json.loadFile(file)
|
||||
root = json.getRootContainer()
|
||||
self.epConfig = pj.EpConfig()
|
||||
self.epConfig.readObject(root)
|
||||
|
||||
tp_node = root.readArray("transports")
|
||||
self.udp.readObject(tp_node)
|
||||
self.tcp.readObject(tp_node)
|
||||
if tp_node.hasUnread():
|
||||
self.tls.readObject(tp_node)
|
||||
|
||||
acc_node = root.readArray("accounts")
|
||||
while acc_node.hasUnread():
|
||||
acfg = AccConfig()
|
||||
acfg.readObject(acc_node)
|
||||
self.accounts.append(acfg)
|
||||
|
||||
def saveFile(self,file):
|
||||
json = pj.JsonDocument()
|
||||
|
||||
# Write endpoint config
|
||||
json.writeObject(self.epConfig)
|
||||
|
||||
# Write transport config
|
||||
tp_node = json.writeNewArray("transports")
|
||||
self.udp.writeObject(tp_node)
|
||||
self.tcp.writeObject(tp_node)
|
||||
self.tls.writeObject(tp_node)
|
||||
|
||||
# Write account configs
|
||||
node = json.writeNewArray("accounts")
|
||||
for acc in self.accounts:
|
||||
acc.writeObject(node)
|
||||
|
||||
json.saveFile(file)
|
||||
|
||||
|
||||
# Settings dialog
|
||||
class Dialog(tk.Toplevel):
|
||||
"""
|
||||
This implements account settings dialog to manipulate account settings.
|
||||
"""
|
||||
def __init__(self, parent, cfg):
|
||||
tk.Toplevel.__init__(self, parent)
|
||||
self.transient(parent)
|
||||
self.parent = parent
|
||||
self.title('Settings')
|
||||
|
||||
self.frm = ttk.Frame(self)
|
||||
self.frm.pack(expand='yes', fill='both')
|
||||
|
||||
self.isOk = False
|
||||
self.cfg = cfg
|
||||
|
||||
self.createWidgets()
|
||||
|
||||
def doModal(self):
|
||||
if self.parent:
|
||||
self.parent.wait_window(self)
|
||||
else:
|
||||
self.wait_window(self)
|
||||
return self.isOk
|
||||
|
||||
def createWidgets(self):
|
||||
# The notebook
|
||||
self.frm.rowconfigure(0, weight=1)
|
||||
self.frm.rowconfigure(1, weight=0)
|
||||
self.frm.columnconfigure(0, weight=1)
|
||||
self.frm.columnconfigure(1, weight=1)
|
||||
self.wTab = ttk.Notebook(self.frm)
|
||||
self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E)
|
||||
|
||||
# Main buttons
|
||||
btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
|
||||
btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
|
||||
btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
|
||||
btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
|
||||
|
||||
# Tabs
|
||||
self.createBasicTab()
|
||||
self.createNetworkTab()
|
||||
self.createMediaTab()
|
||||
|
||||
def createBasicTab(self):
|
||||
# Prepare the variables to set/receive values from GUI
|
||||
self.cfgLogFile = tk.StringVar(value=self.cfg.epConfig.logConfig.filename)
|
||||
self.cfgLogAppend = tk.BooleanVar(value=True if (self.cfg.epConfig.logConfig.fileFlags & pj.PJ_O_APPEND) else False)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='User Agent:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Label(frm, text=self.cfg.epConfig.uaConfig.userAgent).grid(row=row, column=1, sticky=tk.W, pady=2, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Max calls:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Label(frm, text=str(self.cfg.epConfig.uaConfig.maxCalls)).grid(row=row, column=1, sticky=tk.W, pady=2, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Log file:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Entry(frm, textvariable=self.cfgLogFile, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Append log file', variable=self.cfgLogAppend).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
|
||||
self.wTab.add(frm, text='Basic')
|
||||
|
||||
def createNetworkTab(self):
|
||||
self.cfgNameserver = tk.StringVar()
|
||||
if len(self.cfg.epConfig.uaConfig.nameserver):
|
||||
self.cfgNameserver.set(self.cfg.epConfig.uaConfig.nameserver[0])
|
||||
self.cfgStunServer = tk.StringVar()
|
||||
if len(self.cfg.epConfig.uaConfig.stunServer):
|
||||
self.cfgStunServer.set(self.cfg.epConfig.uaConfig.stunServer[0])
|
||||
self.cfgStunIgnoreError = tk.BooleanVar(value=self.cfg.epConfig.uaConfig.stunIgnoreFailure)
|
||||
|
||||
self.cfgUdpEnabled = tk.BooleanVar(value=self.cfg.udp.enabled)
|
||||
self.cfgUdpPort = tk.IntVar(value=self.cfg.udp.config.port)
|
||||
self.cfgTcpEnabled = tk.BooleanVar(value=self.cfg.tcp.enabled)
|
||||
self.cfgTcpPort = tk.IntVar(value=self.cfg.tcp.config.port)
|
||||
self.cfgTlsEnabled = tk.BooleanVar(value=self.cfg.tls.enabled)
|
||||
self.cfgTlsPort = tk.IntVar(value=self.cfg.tls.config.port)
|
||||
|
||||
self.cfgTlsCaFile = tk.StringVar(value=self.cfg.tls.config.tlsConfig.CaListFile)
|
||||
self.cfgTlsCertFile = tk.StringVar(value=self.cfg.tls.config.tlsConfig.certFile)
|
||||
self.cfgTlsVerifyClient = tk.BooleanVar(value=self.cfg.tls.config.tlsConfig.verifyClient)
|
||||
self.cfgTlsVerifyServer = tk.BooleanVar(value=self.cfg.tls.config.tlsConfig.verifyServer)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
#ttk.Label(frm, text='UDP transport:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Checkbutton(frm, text='Enable UDP transport', variable=self.cfgUdpEnabled).grid(row=row, column=0, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='UDP port:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgUdpPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0 for any)').grid(row=row, column=1, sticky=tk.E, pady=6, padx=6)
|
||||
row += 1
|
||||
#ttk.Label(frm, text='TCP transport:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Checkbutton(frm, text='Enable TCP transport', variable=self.cfgTcpEnabled).grid(row=row, column=0, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TCP port:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgTcpPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0 for any)').grid(row=row, column=1, sticky=tk.E, pady=6, padx=6)
|
||||
row += 1
|
||||
#ttk.Label(frm, text='TLS transport:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Checkbutton(frm, text='Enable TLS transport', variable=self.cfgTlsEnabled).grid(row=row, column=0, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TLS port:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgTlsPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0 for any)').grid(row=row, column=1, sticky=tk.E, pady=6, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TLS CA file:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Entry(frm, textvariable=self.cfgTlsCaFile, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='TLS cert file:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Entry(frm, textvariable=self.cfgTlsCertFile, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='TLS verify server', variable=self.cfgTlsVerifyServer).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='TLS verify client', variable=self.cfgTlsVerifyClient).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='DNS and STUN:').grid(row=row, column=0, sticky=tk.W, pady=2, padx=8)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Nameserver:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Entry(frm, textvariable=self.cfgNameserver, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='STUN Server:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Entry(frm, textvariable=self.cfgStunServer, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Checkbutton(frm, text='Ignore STUN failure at startup', variable=self.cfgStunIgnoreError).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
|
||||
self.wTab.add(frm, text='Network')
|
||||
|
||||
def createMediaTab(self):
|
||||
self.cfgClockrate = tk.IntVar(value=self.cfg.epConfig.medConfig.clockRate)
|
||||
self.cfgSndClockrate = tk.IntVar(value=self.cfg.epConfig.medConfig.sndClockRate)
|
||||
self.cfgAudioPtime = tk.IntVar(value=self.cfg.epConfig.medConfig.audioFramePtime)
|
||||
self.cfgMediaQuality = tk.IntVar(value=self.cfg.epConfig.medConfig.quality)
|
||||
self.cfgCodecPtime = tk.IntVar(value=self.cfg.epConfig.medConfig.ptime)
|
||||
self.cfgVad = tk.BooleanVar(value=not self.cfg.epConfig.medConfig.noVad)
|
||||
self.cfgEcTailLen = tk.IntVar(value=self.cfg.epConfig.medConfig.ecTailLen)
|
||||
|
||||
# Build the tab page
|
||||
frm = ttk.Frame(self.frm)
|
||||
frm.columnconfigure(0, weight=1)
|
||||
frm.columnconfigure(1, weight=2)
|
||||
row = 0
|
||||
ttk.Label(frm, text='Max media ports:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Label(frm, text=str(self.cfg.epConfig.medConfig.maxMediaPorts)).grid(row=row, column=1, sticky=tk.W, pady=2, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Core clock rate:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=8000, to=48000, increment=8000, textvariable=self.cfgClockrate, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Snd device clock rate:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=0, to=48000, increment=8000, textvariable=self.cfgSndClockrate, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(0: follow core)').grid(row=row, column=1, sticky=tk.E, pady=6, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Core ptime:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=10, to=400, increment=10, textvariable=self.cfgAudioPtime, width=3).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='RTP ptime:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=20, to=400, increment=10, textvariable=self.cfgCodecPtime, width=3).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Media quality (1-10):').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=1, to=10, textvariable=self.cfgMediaQuality, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
row += 1
|
||||
ttk.Label(frm, text='VAD:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
ttk.Checkbutton(frm, text='Enable', variable=self.cfgVad).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
|
||||
row += 1
|
||||
ttk.Label(frm, text='Echo canceller tail length:').grid(row=row, column=0, sticky=tk.E, pady=2, padx=8)
|
||||
tk.Spinbox(frm, from_=0, to=400, increment=10, textvariable=self.cfgEcTailLen, width=3).grid(row=row, column=1, sticky=tk.W, padx=6)
|
||||
ttk.Label(frm, text='(ms, 0 to disable)').grid(row=row, column=1, sticky=tk.E, pady=6, padx=6)
|
||||
|
||||
self.wTab.add(frm, text='Media')
|
||||
|
||||
def onOk(self):
|
||||
# Check basic settings
|
||||
errors = "";
|
||||
if errors:
|
||||
msgbox.showerror("Error detected:", errors)
|
||||
return
|
||||
|
||||
# Basic settings
|
||||
self.cfg.epConfig.logConfig.filename = self.cfgLogFile.get()
|
||||
flags = pj.PJ_O_APPEND if self.cfgLogAppend.get() else 0
|
||||
self.cfg.epConfig.logConfig.fileFlags = self.cfg.epConfig.logConfig.fileFlags | flags
|
||||
|
||||
# Network settings
|
||||
self.cfg.epConfig.uaConfig.nameserver.clear()
|
||||
if len(self.cfgNameserver.get()):
|
||||
self.cfg.epConfig.uaConfig.nameserver.append(self.cfgNameserver.get())
|
||||
self.cfg.epConfig.uaConfig.stunServer.clear()
|
||||
if len(self.cfgStunServer.get()):
|
||||
self.cfg.epConfig.uaConfig.stunServer.append(self.cfgStunServer.get())
|
||||
|
||||
self.cfg.epConfig.uaConfig.stunIgnoreFailure = self.cfgStunIgnoreError.get()
|
||||
|
||||
self.cfg.udp.enabled = self.cfgUdpEnabled.get()
|
||||
self.cfg.udp.config.port = self.cfgUdpPort.get()
|
||||
self.cfg.tcp.enabled = self.cfgTcpEnabled.get()
|
||||
self.cfg.tcp.config.port = self.cfgTcpPort.get()
|
||||
self.cfg.tls.enabled = self.cfgTlsEnabled.get()
|
||||
self.cfg.tls.config.port = self.cfgTlsPort.get()
|
||||
|
||||
self.cfg.tls.config.tlsConfig.CaListFile = self.cfgTlsCaFile.get()
|
||||
self.cfg.tls.config.tlsConfig.certFile = self.cfgTlsCertFile.get()
|
||||
self.cfg.tls.config.tlsConfig.verifyClient = self.cfgTlsVerifyClient.get()
|
||||
self.cfg.tls.config.tlsConfig.verifyServer = self.cfgTlsVerifyServer.get()
|
||||
|
||||
# Media
|
||||
self.cfg.epConfig.medConfig.clockRate = self.cfgClockrate.get()
|
||||
self.cfg.epConfig.medConfig.sndClockRate = self.cfgSndClockrate.get()
|
||||
self.cfg.epConfig.medConfig.audioFramePtime = self.cfgAudioPtime.get()
|
||||
self.cfg.epConfig.medConfig.quality = self.cfgMediaQuality.get()
|
||||
self.cfg.epConfig.medConfig.ptime = self.cfgCodecPtime.get()
|
||||
self.cfg.epConfig.medConfig.noVad = not self.cfgVad.get()
|
||||
self.cfg.epConfig.medConfig.ecTailLen = self.cfgEcTailLen.get()
|
||||
|
||||
self.isOk = True
|
||||
self.destroy()
|
||||
|
||||
def onCancel(self):
|
||||
self.destroy()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#application.main()
|
||||
acfg = AppConfig()
|
||||
acfg.loadFile('pygui.js')
|
||||
|
||||
dlg = Dialog(None, acfg)
|
||||
if dlg.doModal():
|
||||
acfg.saveFile('pygui.js')
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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>
|
||||
|
||||
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)200;
|
||||
call->answer(prm);
|
||||
}
|
||||
};
|
||||
|
||||
void MyCall::onCallState(OnCallStateParam &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() throw(Error)
|
||||
{
|
||||
Endpoint ep;
|
||||
|
||||
// Create library
|
||||
ep.libCreate();
|
||||
|
||||
// Init library
|
||||
EpConfig ep_cfg;
|
||||
ep_cfg.logConfig.level = 4;
|
||||
ep.libInit( ep_cfg );
|
||||
|
||||
// Transport
|
||||
TransportConfig tcfg;
|
||||
tcfg.port = 5060;
|
||||
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:pjsip.org";
|
||||
acc_cfg.sipConfig.authCreds.push_back( AuthCredInfo("digest", "*",
|
||||
"test1", 0, "test1") );
|
||||
std::auto_ptr<MyAccount> acc(new MyAccount);
|
||||
acc->create(acc_cfg);
|
||||
|
||||
pj_thread_sleep(2000);
|
||||
|
||||
// Make outgoing call
|
||||
Call *call = new MyCall(*acc);
|
||||
acc->calls.push_back(call);
|
||||
CallOpParam prm(true);
|
||||
prm.opt.audioCount = 1;
|
||||
prm.opt.videoCount = 0;
|
||||
call->makeCall("sip:test1@pjsip.org", prm);
|
||||
|
||||
// Hangup all calls
|
||||
pj_thread_sleep(8000);
|
||||
ep.hangupAllCalls();
|
||||
pj_thread_sleep(4000);
|
||||
|
||||
// Destroy library
|
||||
std::cout << "*** PJSUA2 SHUTTING DOWN ***" << std::endl;
|
||||
}
|
||||
|
||||
void mainProg2() throw(Error)
|
||||
{
|
||||
Endpoint ep;
|
||||
|
||||
// Create library
|
||||
ep.libCreate();
|
||||
|
||||
string json_str;
|
||||
|
||||
{
|
||||
EpConfig epCfg;
|
||||
JsonDocument jDoc;
|
||||
|
||||
epCfg.uaConfig.maxCalls = 61;
|
||||
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");
|
||||
}
|
||||
|
||||
ep.libDestroy();
|
||||
}
|
||||
|
||||
void mainProg() throw(Error)
|
||||
{
|
||||
Endpoint ep;
|
||||
|
||||
// Create library
|
||||
ep.libCreate();
|
||||
|
||||
string json_str;
|
||||
|
||||
{
|
||||
JsonDocument jdoc;
|
||||
AccountConfig accCfg;
|
||||
|
||||
accCfg.idUri = "\"Just Test\" <sip:test@pjsip.org>";
|
||||
accCfg.regConfig.registrarUri = "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(1);
|
||||
accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(2);
|
||||
accCfg.mediaConfig.transportConfig.tlsConfig.ciphers.push_back(3);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ep.libDestroy();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* Test endpoint instantiation and destruction without libCreate(),
|
||||
* libInit() etc.
|
||||
*/
|
||||
{
|
||||
Endpoint ep;
|
||||
ep.natDetectType();
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mainProg1();
|
||||
std::cout << "Success" << std::endl;
|
||||
} catch (Error & err) {
|
||||
std::cout << "Exception: " << err.info() << std::endl;
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
include ../../../build.mak
|
||||
|
||||
ifneq ($(findstring android,$(TARGET_NAME)),)
|
||||
# no python for android
|
||||
DIRS = java
|
||||
else
|
||||
DIRS = python java
|
||||
endif
|
||||
|
||||
export SWIG_FLAGS=-I../../../../pjlib/include \
|
||||
-I../../../../pjlib-util/include \
|
||||
-I../../../../pjmedia/include \
|
||||
-I../../../../pjsip/include \
|
||||
-I../../../../pjnath/include -c++
|
||||
export SRC_DIR=../../../../pjsip/include
|
||||
export SRCS=$(SRC_DIR)/pjsua2/endpoint.hpp $(SRC_DIR)/pjsua2/types.hpp
|
||||
|
||||
.PHONY: all clean dep depend distclean print realclean install uninstall
|
||||
|
||||
all: symbols.i
|
||||
|
||||
all clean dep depend distclean print realclean install uninstall:
|
||||
for dir in $(DIRS); do \
|
||||
if $(MAKE) $(MAKE_FLAGS) -C $$dir $@; then \
|
||||
true; \
|
||||
else \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
symbols.i: symbols.lst
|
||||
python importsym.py
|
|
@ -0,0 +1,193 @@
|
|||
# $Id$
|
||||
#
|
||||
# importsym.py: Import C symbol decls (structs, enums, etc) and write them
|
||||
# to another file
|
||||
#
|
||||
# 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 pycparser
|
||||
from pycparser import c_generator
|
||||
import sys
|
||||
import os
|
||||
|
||||
def which(program):
|
||||
import os
|
||||
def is_exe(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
if sys.platform == 'win32' and not program.endswith(".exe"):
|
||||
program += ".exe"
|
||||
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
return program
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
exe_file = os.path.join(path, program)
|
||||
if is_exe(exe_file):
|
||||
return exe_file
|
||||
return None
|
||||
|
||||
#
|
||||
PJ_ROOT_PATH = "../../../"
|
||||
|
||||
# CPP is needed by pycparser.
|
||||
CPP_PATH = which("cpp")
|
||||
if not CPP_PATH:
|
||||
print 'Error: need to have cpp in PATH'
|
||||
sys.exit(1)
|
||||
|
||||
# Hardcoded!
|
||||
if sys.platform == 'win32':
|
||||
PYCPARSER_DIR="C:/devs/tools/pycparser"
|
||||
elif sys.platform == "linux2":
|
||||
PYCPARSER_DIR="/home/bennylp/Desktop/opt/src/pycparser-master"
|
||||
else:
|
||||
PYCPARSER_DIR="/Library/Python/2.7/site-packages/pycparser"
|
||||
|
||||
if not os.path.exists(PYCPARSER_DIR + '/utils/fake_libc_include'):
|
||||
print "Error: couldn't find pycparser utils in '%s'" % PYPARSER_DIR
|
||||
sys.exit(1)
|
||||
|
||||
# Heading, to be placed before the source files
|
||||
C_HEADING_SECTION = """
|
||||
#define PJ_AUTOCONF 1
|
||||
#define jmp_buf int
|
||||
#define __attribute__(x)
|
||||
"""
|
||||
|
||||
# CPP (C preprocessor) settings
|
||||
CPP_CFLAGS = [
|
||||
'-I' + PYCPARSER_DIR + '/utils/fake_libc_include',
|
||||
"-I" + PJ_ROOT_PATH + "pjlib/include",
|
||||
"-I" + PJ_ROOT_PATH + "pjlib-util/include",
|
||||
"-I" + PJ_ROOT_PATH + "pjnath/include",
|
||||
"-I" + PJ_ROOT_PATH + "pjmedia/include",
|
||||
"-I" + PJ_ROOT_PATH + "pjsip/include"
|
||||
]
|
||||
|
||||
|
||||
class SymbolVisitor(pycparser.c_ast.NodeVisitor):
|
||||
def __init__(self, names):
|
||||
self.nodeDict = {}
|
||||
for name in names:
|
||||
self.nodeDict[name] = None
|
||||
|
||||
def _add(self, node):
|
||||
if self.nodeDict.has_key(node.name):
|
||||
self.nodeDict[node.name] = node
|
||||
|
||||
def visit_Struct(self, node):
|
||||
self._add(node)
|
||||
|
||||
def visit_Enum(self, node):
|
||||
self._add(node)
|
||||
|
||||
def visit_Typename(self, node):
|
||||
self._add(node)
|
||||
|
||||
def visit_Typedef(self, node):
|
||||
self._add(node)
|
||||
|
||||
|
||||
TEMP_FILE="tmpsrc.h"
|
||||
|
||||
class SymbolImporter:
|
||||
"""
|
||||
Import C selected declarations from C source file and move it
|
||||
to another file.
|
||||
|
||||
Parameters:
|
||||
- listfile Path of file containing list of C source file
|
||||
and identifier names to be imported. The format
|
||||
of the listfile is:
|
||||
|
||||
filename name1 name2 name3
|
||||
|
||||
for example:
|
||||
|
||||
pj/sock_qos.h pj_qos_type pj_qos_flag
|
||||
pj/types.h pj_status_t PJ_SUCCESS
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def process(self, listfile, outfile):
|
||||
|
||||
# Read listfile
|
||||
f = open(listfile)
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
|
||||
# Process each line in list file, while generating the
|
||||
# temporary C file to be processed by pycparser
|
||||
f = open(TEMP_FILE, "w")
|
||||
f.write(C_HEADING_SECTION)
|
||||
names = []
|
||||
fcnt = 0
|
||||
for line in lines:
|
||||
spec = line.split()
|
||||
if len(spec) < 2:
|
||||
continue
|
||||
fcnt += 1
|
||||
f.write("#include <%s>\n" % spec[0])
|
||||
names.extend(spec[1:])
|
||||
f.close()
|
||||
print 'Parsing %d symbols from %d files..' % (len(names), fcnt)
|
||||
|
||||
# Parse the temporary C file
|
||||
ast = pycparser.parse_file(TEMP_FILE, use_cpp=True, cpp_path=CPP_PATH, cpp_args=CPP_CFLAGS)
|
||||
os.remove(TEMP_FILE)
|
||||
|
||||
# Filter the declarations that we wanted
|
||||
print 'Filtering..'
|
||||
visitor = SymbolVisitor(names)
|
||||
visitor.visit(ast)
|
||||
|
||||
# Print symbol declarations to outfile
|
||||
print 'Writing declarations..'
|
||||
f = open(outfile, 'w')
|
||||
f.write("// This file is autogenerated by importsym script, do not modify!\n\n")
|
||||
gen = pycparser.c_generator.CGenerator()
|
||||
for name in names:
|
||||
node = visitor.nodeDict[name]
|
||||
if not node:
|
||||
print " ** Warning: declaration for '%s' is not found **" % k
|
||||
else:
|
||||
print " writing '%s'.." % name
|
||||
output = gen.visit(node) + ";\n\n"
|
||||
f.write(output)
|
||||
f.close()
|
||||
print "Done."
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "Importing symbols: 'symbols.lst' --> 'symbols.i'"
|
||||
si = SymbolImporter()
|
||||
si.process("symbols.lst", "symbols.i")
|
||||
try:
|
||||
os.remove("lextab.py")
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.remove("yacctab.py")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
include ../../../../build.mak
|
||||
|
||||
ifneq ($(findstring android,$(TARGET_NAME)),)
|
||||
OS=android
|
||||
else
|
||||
ifneq ($(findstring darwin,$(TARGET_NAME)),)
|
||||
OS=darwin
|
||||
endif
|
||||
endif
|
||||
|
||||
OUT_DIR=output
|
||||
ifeq ($(OS),Windows_NT)
|
||||
LIBPJSUA2_SO=$(OUT_DIR)/pjsua2.dll
|
||||
else
|
||||
ifeq ($(OS),darwin)
|
||||
LIBPJSUA2_SO=$(OUT_DIR)/libpjsua2.jnilib
|
||||
else
|
||||
ifeq ($(OS),android)
|
||||
LIBPJSUA2_SO=android/libs/armeabi/libpjsua2.so
|
||||
else
|
||||
LIBPJSUA2_SO=$(OUT_DIR)/libpjsua2.so
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# Get JDK location
|
||||
ifeq ("$(JAVA_HOME)","")
|
||||
# Get javac location to determine JDK location
|
||||
JAVAC_PATH = $(shell which javac)
|
||||
ifeq ("$(JAVAC_PATH)","")
|
||||
$(error Cannot determine JDK location using 'which' command. Please define JAVA_HOME envvar)
|
||||
endif
|
||||
|
||||
JAVAC_PATH := $(realpath $(JAVAC_PATH))
|
||||
JAVA_BIN := $(dir $(JAVAC_PATH))
|
||||
JAVA_HOME := $(patsubst %/bin/,%,$(JAVA_BIN))
|
||||
else
|
||||
ifeq (exists, $(shell test -d $(JAVA_HOME)/bin && echo exists ))
|
||||
JAVA_BIN := $(JAVA_HOME)/bin
|
||||
else
|
||||
JAVA_BIN := $(JAVA_HOME)
|
||||
endif
|
||||
endif
|
||||
|
||||
# OS specific
|
||||
ifeq ($(OS),Windows_NT)
|
||||
MY_JNI_LDFLAGS = -L$(MY_JDK)/lib -Wl,--kill-at
|
||||
else
|
||||
MY_JNI_CFLAGS = -fPIC
|
||||
MY_JNI_LDFLAGS = -L$(MY_JDK)/lib
|
||||
ifeq ($(OS),darwin)
|
||||
MY_JNI_LDFLAGS := $(MY_JNI_LDFLAGS) -Wl,-soname,pjsua2.so
|
||||
endif
|
||||
ifeq ($(OS),android)
|
||||
MY_JNI_CFLAGS := $(MY_JNI_CFLAGS) -D__ANDROID__
|
||||
endif
|
||||
endif
|
||||
|
||||
# Env settings, e.g: path to SWIG, JDK, java(.exe), javac(.exe)
|
||||
MY_SWIG = swig
|
||||
MY_JDK = $(JAVA_HOME)
|
||||
ifneq ($(findstring bin,$(JAVA_BIN)),)
|
||||
MY_JAVA = $(MY_JDK)/bin/java
|
||||
MY_JAVAC = $(MY_JDK)/bin/javac
|
||||
else
|
||||
MY_JAVA = $(MY_JDK)/java
|
||||
MY_JAVAC = $(MY_JDK)/javac
|
||||
endif
|
||||
MY_JNI_CFLAGS := $(MY_JNI_CFLAGS) -I$(MY_JDK)/include -I$(MY_JDK)/include/win32 \
|
||||
-I$(MY_JDK)/include/linux -I.
|
||||
|
||||
# Build settings
|
||||
MY_CFLAGS = $(PJ_CFLAGS) $(MY_JNI_CFLAGS)
|
||||
MY_LDFLAGS = $(PJ_LDFLAGS) -lpjsua2-$(TARGET_NAME) $(PJ_LDLIBS) $(MY_JNI_LDFLAGS)
|
||||
MY_PACKAGE_NAME = org.pjsip.pjsua2
|
||||
ifeq ($(OS),android)
|
||||
MY_PACKAGE_PATH = android/src/$(subst .,/,$(MY_PACKAGE_NAME))
|
||||
else
|
||||
MY_PACKAGE_PATH = $(OUT_DIR)/$(subst .,/,$(MY_PACKAGE_NAME))
|
||||
endif
|
||||
|
||||
MY_APP_JAVA = android/src/$(subst .,/,$(MY_PACKAGE_NAME))/app/MyApp.java
|
||||
|
||||
.PHONY: all java install uninstall
|
||||
|
||||
all: $(LIBPJSUA2_SO) java
|
||||
|
||||
$(LIBPJSUA2_SO): $(OUT_DIR)/pjsua2_wrap.o
|
||||
$(PJ_CXX) -shared -o $(LIBPJSUA2_SO) $(OUT_DIR)/pjsua2_wrap.o $(MY_CFLAGS) $(MY_LDFLAGS)
|
||||
|
||||
$(OUT_DIR)/pjsua2_wrap.o: $(OUT_DIR)/pjsua2_wrap.cpp Makefile
|
||||
$(PJ_CXX) -c $(OUT_DIR)/pjsua2_wrap.cpp -o $(OUT_DIR)/pjsua2_wrap.o $(MY_CFLAGS) $(MY_LDFLAGS)
|
||||
|
||||
$(OUT_DIR)/pjsua2_wrap.cpp: ../pjsua2.i ../symbols.i $(SRCS)
|
||||
mkdir -p $(MY_PACKAGE_PATH)
|
||||
swig $(SWIG_FLAGS) -java -package $(MY_PACKAGE_NAME) -outdir $(MY_PACKAGE_PATH) -o $(OUT_DIR)/pjsua2_wrap.cpp ../pjsua2.i
|
||||
|
||||
clean distclean realclean:
|
||||
rm -rf $(LIBPJSUA2_SO) $(OUT_DIR)/* $(MY_PACKAGE_PATH)/*.java $(MY_PACKAGE_PATH)/*.class
|
||||
|
||||
java: $(MY_PACKAGE_PATH)/Error.class $(MY_PACKAGE_PATH)/test.class $(MY_PACKAGE_PATH)/sample.class
|
||||
|
||||
$(MY_PACKAGE_PATH)/Error.class: $(MY_PACKAGE_PATH)/Error.java
|
||||
$(MY_JAVAC) -d $(OUT_DIR) $(MY_PACKAGE_PATH)/*.java $(MY_APP_JAVA)
|
||||
|
||||
$(MY_PACKAGE_PATH)/test.class: test.java
|
||||
$(MY_JAVAC) -d $(OUT_DIR) -classpath "$(OUT_DIR)" test.java
|
||||
|
||||
$(MY_PACKAGE_PATH)/sample.class: sample.java
|
||||
$(MY_JAVAC) -d $(OUT_DIR) -classpath "$(OUT_DIR)" sample.java
|
||||
|
||||
test:
|
||||
@# Need to specify classpath and library path, alternatively, they can be set via
|
||||
@# CLASSPATH and java.library.path env settings
|
||||
$(MY_JAVA) -cp "$(OUT_DIR)" -Djava.library.path="$(OUT_DIR)" test
|
||||
|
||||
sample:
|
||||
@# Need to specify classpath and library path, alternatively, they can be set via
|
||||
@# CLASSPATH and java.library.path env settings
|
||||
$(MY_JAVA) -cp "$(OUT_DIR)" -Djava.library.path="$(OUT_DIR)" org.pjsip.pjsua2.app.sample
|
||||
|
||||
install:
|
||||
uninstall:
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Pjsua2</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,4 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.pjsip.pjsua2.app"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="11"
|
||||
android:targetSdkVersion="15" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.READ_LOGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="org.pjsip.pjsua2.app.MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.pjsip.pjsua2.app.CallActivity"
|
||||
android:label="@string/title_activity_call" >
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
|
@ -0,0 +1,12 @@
|
|||
include ../../../../../build.mak
|
||||
|
||||
LOCAL_PATH := $(PJDIR)/pjsip-apps/src/swig/java/android
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := libpjsua2
|
||||
LOCAL_CFLAGS := $(APP_CFLAGS) -frtti -fexceptions
|
||||
LOCAL_LDFLAGS := $(APP_LDFLAGS)
|
||||
LOCAL_LDLIBS := $(APP_LDLIBS)
|
||||
LOCAL_SRC_FILES := ../output/pjsua2_wrap.cpp
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
|
@ -0,0 +1 @@
|
|||
APP_STL := gnustl_static
|
|
@ -0,0 +1,20 @@
|
|||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -0,0 +1,14 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-15
|
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@color/pressed_color" />
|
||||
<item android:drawable="@color/default_color" />
|
||||
</selector>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewPeer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="Peer URI"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewCallState"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="Call state" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAccept"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="acceptCall"
|
||||
android:text="Accept" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonHangup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="hangupCall"
|
||||
android:text="Reject" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,65 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/listViewBuddy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:listSelector="@drawable/bkg" >
|
||||
</ListView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonCall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:onClick="makeCall"
|
||||
android:src="@android:drawable/ic_menu_call" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text=" "/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonAddBuddy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:onClick="addBuddy"
|
||||
android:src="@android:drawable/ic_menu_add" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonEditBuddy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:onClick="editBuddy"
|
||||
android:src="@android:drawable/ic_menu_edit" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/buttonDelBuddy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:onClick="delBuddy"
|
||||
android:src="@android:drawable/ic_menu_delete" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TableLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:padding = "20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewInfo"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:paddingBottom="20dp"
|
||||
android:textColor="#b0b0b0" >
|
||||
</TextView>
|
||||
|
||||
<TableRow>
|
||||
<TextView android:text="ID">
|
||||
</TextView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="textUri" >
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TextView android:text="Registrar">
|
||||
</TextView>
|
||||
<EditText
|
||||
android:id="@+id/editTextRegistrar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="textUri" >
|
||||
</EditText>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TextView android:text="Proxy">
|
||||
</TextView>
|
||||
<EditText
|
||||
android:id="@+id/editTextProxy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="textUri" >
|
||||
</EditText>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TextView android:text="Username">
|
||||
</TextView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="text" >
|
||||
|
||||
</EditText>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TextView android:text="Password">
|
||||
</TextView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="textPassword" >
|
||||
|
||||
</EditText>
|
||||
</TableRow>
|
||||
</TableLayout>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TableLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:padding = "20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TableRow>
|
||||
<TextView android:text="Buddy URI">
|
||||
</TextView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextUri"
|
||||
android:layout_weight="1"
|
||||
android:inputType="textUri" >
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<CheckBox
|
||||
android:id="@+id/checkBoxSubscribe"
|
||||
android:layout_column="1"
|
||||
android:text="Subscribe presence" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
|
@ -0,0 +1,9 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/action_settings"/>
|
||||
|
||||
</menu>
|
|
@ -0,0 +1,14 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_acc_config"
|
||||
android:icon="@android:drawable/ic_menu_manage"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="Account Config"/>
|
||||
<item
|
||||
android:id="@+id/action_quit"
|
||||
android:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="Quit"/>
|
||||
|
||||
</menu>
|
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
|
||||
<!--
|
||||
Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw600dp devices (e.g. 7" tablets) here.
|
||||
-->
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,9 @@
|
|||
<resources>
|
||||
|
||||
<!--
|
||||
Customize dimensions originally defined in res/values/dimens.xml (such as
|
||||
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
|
||||
-->
|
||||
<dimen name="activity_horizontal_margin">128dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 11+. This theme completely replaces
|
||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
|
||||
<!-- API 11 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,12 @@
|
|||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 14+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 14+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- API 14 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="pressed_color">#B8F2F5</color>
|
||||
<color name="default_color">#E8FEFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Pjsua2</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="title_activity_call">Call</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,20 @@
|
|||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme, dependent on API level. This theme is replaced
|
||||
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Light">
|
||||
<!--
|
||||
Theme customizations available in newer API levels can go in
|
||||
res/values-vXX/styles.xml, while customizations related to
|
||||
backward-compatibility can go here.
|
||||
-->
|
||||
</style>
|
||||
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,146 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
package org.pjsip.pjsua2.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.app.Activity;
|
||||
|
||||
import org.pjsip.pjsua2.*;
|
||||
|
||||
public class CallActivity extends Activity implements Handler.Callback {
|
||||
|
||||
public static Handler handler_;
|
||||
|
||||
private final Handler handler = new Handler(this);
|
||||
private static CallInfo lastCallInfo;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_call);
|
||||
|
||||
handler_ = handler;
|
||||
if (MainActivity.currentCall != null) {
|
||||
try {
|
||||
lastCallInfo = MainActivity.currentCall.getInfo();
|
||||
updateCallState(lastCallInfo);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
} else {
|
||||
updateCallState(lastCallInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
handler_ = null;
|
||||
}
|
||||
|
||||
public void acceptCall(View view) {
|
||||
CallOpParam prm = new CallOpParam();
|
||||
prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
|
||||
try {
|
||||
MainActivity.currentCall.answer(prm);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void hangupCall(View view) {
|
||||
handler_ = null;
|
||||
finish();
|
||||
|
||||
if (MainActivity.currentCall != null) {
|
||||
CallOpParam prm = new CallOpParam();
|
||||
prm.setStatusCode(pjsip_status_code.PJSIP_SC_DECLINE);
|
||||
try {
|
||||
MainActivity.currentCall.hangup(prm);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
MainActivity.currentCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message m) {
|
||||
|
||||
if (m.what == MainActivity.MSG_TYPE.CALL_STATE) {
|
||||
|
||||
lastCallInfo = (CallInfo) m.obj;
|
||||
updateCallState(lastCallInfo);
|
||||
|
||||
} else {
|
||||
|
||||
/* Message not handled */
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateCallState(CallInfo ci) {
|
||||
TextView tvPeer = (TextView) findViewById(R.id.textViewPeer);
|
||||
TextView tvState = (TextView) findViewById(R.id.textViewCallState);
|
||||
Button buttonHangup = (Button) findViewById(R.id.buttonHangup);
|
||||
Button buttonAccept = (Button) findViewById(R.id.buttonAccept);
|
||||
String call_state = "";
|
||||
|
||||
if (ci.getRole() == pjsip_role_e.PJSIP_ROLE_UAC) {
|
||||
buttonAccept.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (ci.getState().swigValue() < pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED.swigValue())
|
||||
{
|
||||
if (ci.getRole() == pjsip_role_e.PJSIP_ROLE_UAS) {
|
||||
call_state = "Incoming call..";
|
||||
/* Default button texts are already 'Accept' & 'Reject' */
|
||||
} else {
|
||||
buttonHangup.setText("Cancel");
|
||||
call_state = ci.getStateText();
|
||||
}
|
||||
}
|
||||
else if (ci.getState().swigValue() >= pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED.swigValue())
|
||||
{
|
||||
buttonAccept.setVisibility(View.GONE);
|
||||
call_state = ci.getStateText();
|
||||
if (ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED) {
|
||||
buttonHangup.setText("Hangup");
|
||||
} else if (ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) {
|
||||
buttonHangup.setText("OK");
|
||||
call_state = "Call disconnected: " + ci.getLastReason();
|
||||
MainActivity.currentCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
tvPeer.setText(ci.getRemoteUri());
|
||||
tvState.setText(call_state);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,497 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
package org.pjsip.pjsua2.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.pjsip.pjsua2.*;
|
||||
|
||||
public class MainActivity extends Activity implements Handler.Callback, MyAppObserver {
|
||||
public static MyApp app = null;
|
||||
public static MyCall currentCall = null;
|
||||
public static MyAccount account = null;
|
||||
public static AccountConfig accCfg = null;
|
||||
|
||||
private ListView buddyListView;
|
||||
private SimpleAdapter buddyListAdapter;
|
||||
private int buddyListSelectedIdx = -1;
|
||||
ArrayList<Map<String, String>> buddyList;
|
||||
private String lastRegStatus = "";
|
||||
|
||||
private final Handler handler = new Handler(this);
|
||||
public class MSG_TYPE {
|
||||
public final static int INCOMING_CALL = 1;
|
||||
public final static int CALL_STATE = 2;
|
||||
public final static int REG_STATE = 3;
|
||||
public final static int BUDDY_STATE = 4;
|
||||
}
|
||||
|
||||
private HashMap<String, String> putData(String uri, String status) {
|
||||
HashMap<String, String> item = new HashMap<String, String>();
|
||||
item.put("uri", uri);
|
||||
item.put("status", status);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void showCallActivity() {
|
||||
Intent intent = new Intent(this, CallActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (app == null) {
|
||||
app = new MyApp();
|
||||
/* Wait for GDB to init */
|
||||
if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
app.init(this, getFilesDir().getAbsolutePath());
|
||||
}
|
||||
|
||||
if (app.accList.size() == 0) {
|
||||
accCfg = new AccountConfig();
|
||||
accCfg.setIdUri("sip:localhost");
|
||||
account = app.addAcc(accCfg);
|
||||
} else {
|
||||
account = app.accList.get(0);
|
||||
accCfg = account.cfg;
|
||||
}
|
||||
|
||||
buddyList = new ArrayList<Map<String, String>>();
|
||||
for (int i = 0; i < account.buddyList.size(); i++) {
|
||||
buddyList.add(putData(account.buddyList.get(i).cfg.getUri(),
|
||||
account.buddyList.get(i).getStatusText()));
|
||||
}
|
||||
|
||||
String[] from = { "uri", "status" };
|
||||
int[] to = { android.R.id.text1, android.R.id.text2 };
|
||||
buddyListAdapter = new SimpleAdapter(this, buddyList, android.R.layout.simple_list_item_2, from, to);
|
||||
|
||||
buddyListView = (ListView) findViewById(R.id.listViewBuddy);;
|
||||
buddyListView.setAdapter(buddyListAdapter);
|
||||
buddyListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, final View view,
|
||||
int position, long id)
|
||||
{
|
||||
view.setSelected(true);
|
||||
buddyListSelectedIdx = position;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_acc_config:
|
||||
dlgAccountSetting();
|
||||
break;
|
||||
|
||||
case R.id.action_quit:
|
||||
Message m = Message.obtain(handler, 0);
|
||||
m.sendToTarget();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message m) {
|
||||
|
||||
if (m.what == 0) {
|
||||
|
||||
app.deinit();
|
||||
finish();
|
||||
Runtime.getRuntime().gc();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
|
||||
} else if (m.what == MSG_TYPE.CALL_STATE) {
|
||||
|
||||
CallInfo ci = (CallInfo) m.obj;
|
||||
|
||||
/* Forward the message to CallActivity */
|
||||
if (CallActivity.handler_ != null) {
|
||||
Message m2 = Message.obtain(CallActivity.handler_, MSG_TYPE.CALL_STATE, ci);
|
||||
m2.sendToTarget();
|
||||
}
|
||||
|
||||
if (ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED)
|
||||
currentCall = null;
|
||||
|
||||
} else if (m.what == MSG_TYPE.BUDDY_STATE) {
|
||||
|
||||
MyBuddy buddy = (MyBuddy) m.obj;
|
||||
int idx = account.buddyList.indexOf(buddy);
|
||||
if (idx >= 0) {
|
||||
buddyList.get(idx).put("status", buddy.getStatusText());
|
||||
buddyListAdapter.notifyDataSetChanged();
|
||||
// TODO: selection color/mark is gone after this,
|
||||
// dont know how to return it back.
|
||||
//buddyListView.setSelection(buddyListSelectedIdx);
|
||||
//buddyListView.performItemClick(buddyListView, buddyListSelectedIdx,
|
||||
// buddyListView.getItemIdAtPosition(buddyListSelectedIdx));
|
||||
|
||||
/* Return back Call activity */
|
||||
notifyCallState(currentCall);
|
||||
}
|
||||
|
||||
} else if (m.what == MSG_TYPE.REG_STATE) {
|
||||
|
||||
String msg_str = (String) m.obj;
|
||||
lastRegStatus = msg_str;
|
||||
|
||||
} else if (m.what == MSG_TYPE.INCOMING_CALL) {
|
||||
|
||||
/* Incoming call */
|
||||
final MyCall call = (MyCall) m.obj;
|
||||
CallOpParam prm = new CallOpParam();
|
||||
|
||||
/* Only one call at anytime */
|
||||
if (currentCall != null) {
|
||||
prm.setStatusCode(pjsip_status_code.PJSIP_SC_BUSY_HERE);
|
||||
try {
|
||||
call.hangup(prm);
|
||||
} catch (Exception e) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Answer with ringing */
|
||||
prm.setStatusCode(pjsip_status_code.PJSIP_SC_RINGING);
|
||||
try {
|
||||
call.answer(prm);
|
||||
} catch (Exception e) {}
|
||||
|
||||
currentCall = call;
|
||||
showCallActivity();
|
||||
|
||||
} else {
|
||||
|
||||
/* Message not handled */
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void dlgAccountSetting() {
|
||||
|
||||
LayoutInflater li = LayoutInflater.from(this);
|
||||
View view = li.inflate(R.layout.dlg_account_config, null);
|
||||
|
||||
if (!lastRegStatus.isEmpty()) {
|
||||
TextView tvInfo = (TextView)view.findViewById(R.id.textViewInfo);
|
||||
tvInfo.setText("Last status: " + lastRegStatus);
|
||||
}
|
||||
|
||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
||||
adb.setView(view);
|
||||
adb.setTitle("Account Settings");
|
||||
|
||||
final EditText etId = (EditText)view.findViewById(R.id.editTextId);
|
||||
final EditText etReg = (EditText)view.findViewById(R.id.editTextRegistrar);
|
||||
final EditText etProxy = (EditText)view.findViewById(R.id.editTextProxy);
|
||||
final EditText etUser = (EditText)view.findViewById(R.id.editTextUsername);
|
||||
final EditText etPass = (EditText)view.findViewById(R.id.editTextPassword);
|
||||
|
||||
etId. setText(accCfg.getIdUri());
|
||||
etReg. setText(accCfg.getRegConfig().getRegistrarUri());
|
||||
StringVector proxies = accCfg.getSipConfig().getProxies();
|
||||
if (proxies.size() > 0)
|
||||
etProxy.setText(proxies.get(0));
|
||||
else
|
||||
etProxy.setText("");
|
||||
AuthCredInfoVector creds = accCfg.getSipConfig().getAuthCreds();
|
||||
if (creds.size() > 0) {
|
||||
etUser. setText(creds.get(0).getUsername());
|
||||
etPass. setText(creds.get(0).getData());
|
||||
} else {
|
||||
etUser. setText("");
|
||||
etPass. setText("");
|
||||
}
|
||||
|
||||
adb.setCancelable(false);
|
||||
adb.setPositiveButton("OK",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,int id) {
|
||||
String acc_id = etId.getText().toString();
|
||||
String registrar = etReg.getText().toString();
|
||||
String proxy = etProxy.getText().toString();
|
||||
String username = etUser.getText().toString();
|
||||
String password = etPass.getText().toString();
|
||||
|
||||
accCfg.setIdUri(acc_id);
|
||||
accCfg.getRegConfig().setRegistrarUri(registrar);
|
||||
AuthCredInfoVector creds = accCfg.getSipConfig().getAuthCreds();
|
||||
creds.clear();
|
||||
if (!username.isEmpty()) {
|
||||
creds.add(new AuthCredInfo("Digest", "*", username, 0, password));
|
||||
}
|
||||
StringVector proxies = accCfg.getSipConfig().getProxies();
|
||||
proxies.clear();
|
||||
if (!proxy.isEmpty()) {
|
||||
proxies.add(proxy);
|
||||
}
|
||||
|
||||
/* Finally */
|
||||
lastRegStatus = "";
|
||||
try {
|
||||
account.modify(accCfg);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
});
|
||||
adb.setNegativeButton("Cancel",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog ad = adb.create();
|
||||
ad.show();
|
||||
}
|
||||
|
||||
|
||||
public void makeCall(View view) {
|
||||
if (buddyListSelectedIdx == -1)
|
||||
return;
|
||||
|
||||
/* Only one call at anytime */
|
||||
if (currentCall != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, String> item = (HashMap<String, String>) buddyListView.getItemAtPosition(buddyListSelectedIdx);
|
||||
String buddy_uri = item.get("uri");
|
||||
|
||||
MyCall call = new MyCall(account, -1);
|
||||
CallOpParam prm = new CallOpParam();
|
||||
CallSetting opt = prm.getOpt();
|
||||
opt.setAudioCount(1);
|
||||
opt.setVideoCount(0);
|
||||
|
||||
try {
|
||||
call.makeCall(buddy_uri, prm);
|
||||
} catch (Exception e) {
|
||||
currentCall = null;
|
||||
return;
|
||||
}
|
||||
|
||||
currentCall = call;
|
||||
showCallActivity();
|
||||
}
|
||||
|
||||
private void dlgAddEditBuddy(BuddyConfig initial) {
|
||||
final BuddyConfig cfg = new BuddyConfig();
|
||||
final BuddyConfig old_cfg = initial;
|
||||
final boolean is_add = initial == null;
|
||||
|
||||
LayoutInflater li = LayoutInflater.from(this);
|
||||
View view = li.inflate(R.layout.dlg_add_buddy, null);
|
||||
|
||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
||||
adb.setView(view);
|
||||
|
||||
final EditText etUri = (EditText)view.findViewById(R.id.editTextUri);
|
||||
final CheckBox cbSubs = (CheckBox)view.findViewById(R.id.checkBoxSubscribe);
|
||||
|
||||
if (is_add) {
|
||||
adb.setTitle("Add Buddy");
|
||||
} else {
|
||||
adb.setTitle("Edit Buddy");
|
||||
etUri. setText(initial.getUri());
|
||||
cbSubs.setChecked(initial.getSubscribe());
|
||||
}
|
||||
|
||||
adb.setCancelable(false);
|
||||
adb.setPositiveButton("OK",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,int id) {
|
||||
cfg.setUri(etUri.getText().toString());
|
||||
cfg.setSubscribe(cbSubs.isChecked());
|
||||
|
||||
if (is_add) {
|
||||
account.addBuddy(cfg);
|
||||
buddyList.add(putData(cfg.getUri(), ""));
|
||||
buddyListAdapter.notifyDataSetChanged();
|
||||
buddyListSelectedIdx = -1;
|
||||
} else {
|
||||
if (!old_cfg.getUri().equals(cfg.getUri())) {
|
||||
account.delBuddy(buddyListSelectedIdx);
|
||||
account.addBuddy(cfg);
|
||||
buddyList.remove(buddyListSelectedIdx);
|
||||
buddyList.add(putData(cfg.getUri(), ""));
|
||||
buddyListAdapter.notifyDataSetChanged();
|
||||
buddyListSelectedIdx = -1;
|
||||
} else if (old_cfg.getSubscribe() != cfg.getSubscribe()) {
|
||||
MyBuddy bud = account.buddyList.get(buddyListSelectedIdx);
|
||||
try {
|
||||
bud.subscribePresence(cfg.getSubscribe());
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
adb.setNegativeButton("Cancel",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog ad = adb.create();
|
||||
ad.show();
|
||||
}
|
||||
|
||||
public void addBuddy(View view) {
|
||||
dlgAddEditBuddy(null);
|
||||
}
|
||||
|
||||
public void editBuddy(View view) {
|
||||
if (buddyListSelectedIdx == -1)
|
||||
return;
|
||||
|
||||
BuddyConfig old_cfg = account.buddyList.get(buddyListSelectedIdx).cfg;
|
||||
dlgAddEditBuddy(old_cfg);
|
||||
}
|
||||
|
||||
public void delBuddy(View view) {
|
||||
if (buddyListSelectedIdx == -1)
|
||||
return;
|
||||
|
||||
final HashMap<String, String> item = (HashMap<String, String>) buddyListView.getItemAtPosition(buddyListSelectedIdx);
|
||||
String buddy_uri = item.get("uri");
|
||||
|
||||
DialogInterface.OnClickListener ocl = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
account.delBuddy(buddyListSelectedIdx);
|
||||
buddyList.remove(item);
|
||||
buddyListAdapter.notifyDataSetChanged();
|
||||
buddyListSelectedIdx = -1;
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder adb = new AlertDialog.Builder(this);
|
||||
adb.setTitle(buddy_uri);
|
||||
adb.setMessage("\nDelete this buddy?\n");
|
||||
adb.setPositiveButton("Yes", ocl);
|
||||
adb.setNegativeButton("No", ocl);
|
||||
adb.show();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* === MyAppObserver ===
|
||||
*
|
||||
* As we cannot do UI from worker thread, the callbacks mostly just send
|
||||
* a message to UI/main thread.
|
||||
*/
|
||||
|
||||
public void notifyIncomingCall(MyCall call) {
|
||||
Message m = Message.obtain(handler, MSG_TYPE.INCOMING_CALL, call);
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
public void notifyRegState(pjsip_status_code code, String reason, int expiration) {
|
||||
String msg_str = "";
|
||||
if (expiration == 0)
|
||||
msg_str += "Unregistration";
|
||||
else
|
||||
msg_str += "Registration";
|
||||
|
||||
if (code.swigValue()/100 == 2)
|
||||
msg_str += " successful";
|
||||
else
|
||||
msg_str += " failed: " + reason;
|
||||
|
||||
Message m = Message.obtain(handler, MSG_TYPE.REG_STATE, msg_str);
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
public void notifyCallState(MyCall call) {
|
||||
if (currentCall == null || call.getId() != currentCall.getId())
|
||||
return;
|
||||
|
||||
CallInfo ci;
|
||||
try {
|
||||
ci = call.getInfo();
|
||||
} catch (Exception e) {
|
||||
ci = null;
|
||||
}
|
||||
Message m = Message.obtain(handler, MSG_TYPE.CALL_STATE, ci);
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
public void notifyBuddyState(MyBuddy buddy) {
|
||||
Message m = Message.obtain(handler, MSG_TYPE.BUDDY_STATE, buddy);
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
/* === end of MyAppObserver ==== */
|
||||
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
package org.pjsip.pjsua2.app;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import org.pjsip.pjsua2.*;
|
||||
|
||||
|
||||
/* Interface to separate UI & engine a bit better */
|
||||
interface MyAppObserver {
|
||||
abstract void notifyRegState(pjsip_status_code code, String reason, int expiration);
|
||||
abstract void notifyIncomingCall(MyCall call);
|
||||
abstract void notifyCallState(MyCall call);
|
||||
abstract void notifyBuddyState(MyBuddy buddy);
|
||||
}
|
||||
|
||||
|
||||
class MyLogWriter extends LogWriter {
|
||||
@Override
|
||||
public void write(LogEntry entry) {
|
||||
System.out.println(entry.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyCall extends Call {
|
||||
MyCall(MyAccount acc, int call_id) {
|
||||
super(acc, call_id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallState(OnCallStateParam prm) {
|
||||
MyApp.observer.notifyCallState(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallMediaState(OnCallMediaStateParam prm) {
|
||||
CallInfo ci;
|
||||
try {
|
||||
ci = getInfo();
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
CallMediaInfoVector cmiv = ci.getMedia();
|
||||
|
||||
for (int i = 0; i < cmiv.size(); i++) {
|
||||
CallMediaInfo cmi = cmiv.get(i);
|
||||
if (cmi.getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO &&
|
||||
(cmi.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE ||
|
||||
cmi.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_REMOTE_HOLD))
|
||||
{
|
||||
// unfortunately, on Java too, the returned Media cannot be downcasted to AudioMedia
|
||||
Media m = getMedia(i);
|
||||
AudioMedia am = AudioMedia.typecastFromMedia(m);
|
||||
|
||||
// connect ports
|
||||
try {
|
||||
MyApp.ep.audDevManager().getCaptureDevMedia().startTransmit(am);
|
||||
am.startTransmit(MyApp.ep.audDevManager().getPlaybackDevMedia());
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyAccount extends Account {
|
||||
public ArrayList<MyBuddy> buddyList = new ArrayList<MyBuddy>();
|
||||
public AccountConfig cfg;
|
||||
|
||||
MyAccount(AccountConfig config) {
|
||||
super();
|
||||
cfg = config;
|
||||
}
|
||||
|
||||
public MyBuddy addBuddy(BuddyConfig bud_cfg)
|
||||
{
|
||||
/* Create Buddy */
|
||||
MyBuddy bud = new MyBuddy(bud_cfg);
|
||||
try {
|
||||
bud.create(this, bud_cfg);
|
||||
} catch (Exception e) {
|
||||
bud = null;
|
||||
}
|
||||
|
||||
if (bud != null) {
|
||||
buddyList.add(bud);
|
||||
if (bud_cfg.getSubscribe())
|
||||
try {
|
||||
bud.subscribePresence(true);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
return bud;
|
||||
}
|
||||
|
||||
public void delBuddy(MyBuddy buddy) {
|
||||
buddyList.remove(buddy);
|
||||
}
|
||||
|
||||
public void delBuddy(int index) {
|
||||
buddyList.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegState(OnRegStateParam prm) {
|
||||
MyApp.observer.notifyRegState(prm.getCode(), prm.getReason(), prm.getExpiration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncomingCall(OnIncomingCallParam prm) {
|
||||
System.out.println("======== Incoming call ======== ");
|
||||
MyCall call = new MyCall(this, prm.getCallId());
|
||||
MyApp.observer.notifyIncomingCall(call);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstantMessage(OnInstantMessageParam prm) {
|
||||
System.out.println("======== Incoming pager ======== ");
|
||||
System.out.println("From : " + prm.getFromUri());
|
||||
System.out.println("To : " + prm.getToUri());
|
||||
System.out.println("Contact : " + prm.getContactUri());
|
||||
System.out.println("Mimetype : " + prm.getContentType());
|
||||
System.out.println("Body : " + prm.getMsgBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyBuddy extends Buddy {
|
||||
public BuddyConfig cfg;
|
||||
|
||||
MyBuddy(BuddyConfig config) {
|
||||
super();
|
||||
cfg = config;
|
||||
}
|
||||
|
||||
String getStatusText() {
|
||||
BuddyInfo bi;
|
||||
|
||||
try {
|
||||
bi = getInfo();
|
||||
} catch (Exception e) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
String status = "";
|
||||
if (bi.getSubState() == pjsip_evsub_state.PJSIP_EVSUB_STATE_ACTIVE) {
|
||||
if (bi.getPresStatus().getStatus() == pjsua_buddy_status.PJSUA_BUDDY_STATUS_ONLINE) {
|
||||
status = bi.getPresStatus().getStatusText();
|
||||
if (status == null || status.isEmpty()) {
|
||||
status = "Online";
|
||||
}
|
||||
} else if (bi.getPresStatus().getStatus() == pjsua_buddy_status.PJSUA_BUDDY_STATUS_OFFLINE) {
|
||||
status = "Offline";
|
||||
} else {
|
||||
status = "Unknown";
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuddyState() {
|
||||
MyApp.observer.notifyBuddyState(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class MyAccountConfig {
|
||||
public AccountConfig accCfg = new AccountConfig();
|
||||
public ArrayList<BuddyConfig> buddyCfgs = new ArrayList<BuddyConfig>();
|
||||
|
||||
public void readObject(ContainerNode node) {
|
||||
try {
|
||||
ContainerNode acc_node = node.readContainer("Account");
|
||||
accCfg.readObject(acc_node);
|
||||
ContainerNode buddies_node = acc_node.readArray("buddies");
|
||||
buddyCfgs.clear();
|
||||
while (buddies_node.hasUnread()) {
|
||||
BuddyConfig bud_cfg = new BuddyConfig();
|
||||
bud_cfg.readObject(buddies_node);
|
||||
buddyCfgs.add(bud_cfg);
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
public void writeObject(ContainerNode node) {
|
||||
try {
|
||||
ContainerNode acc_node = node.writeNewContainer("Account");
|
||||
accCfg.writeObject(acc_node);
|
||||
ContainerNode buddies_node = acc_node.writeNewArray("buddies");
|
||||
for (int j = 0; j < buddyCfgs.size(); j++) {
|
||||
buddyCfgs.get(j).writeObject(buddies_node);
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyApp {
|
||||
static {
|
||||
System.loadLibrary("pjsua2");
|
||||
System.out.println("Library loaded");
|
||||
}
|
||||
|
||||
public static Endpoint ep = new Endpoint();
|
||||
public static MyAppObserver observer;
|
||||
public ArrayList<MyAccount> accList = new ArrayList<MyAccount>();
|
||||
|
||||
private ArrayList<MyAccountConfig> accCfgs = new ArrayList<MyAccountConfig>();
|
||||
private EpConfig epConfig = new EpConfig();
|
||||
private TransportConfig sipTpConfig = new TransportConfig();
|
||||
private String appDir;
|
||||
|
||||
/* Maintain reference to log writer to avoid premature cleanup by GC */
|
||||
private MyLogWriter logWriter;
|
||||
|
||||
private final String configName = "pjsua2.json";
|
||||
private final int SIP_PORT = 6000;
|
||||
private final int LOG_LEVEL = 4;
|
||||
|
||||
public void init(MyAppObserver obs, String app_dir) {
|
||||
init(obs, app_dir, false);
|
||||
}
|
||||
|
||||
public void init(MyAppObserver obs, String app_dir, boolean own_worker_thread) {
|
||||
observer = obs;
|
||||
appDir = app_dir;
|
||||
|
||||
/* Create endpoint */
|
||||
try {
|
||||
ep.libCreate();
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Load config */
|
||||
String configPath = appDir + "/" + configName;
|
||||
File f = new File(configPath);
|
||||
if (f.exists()) {
|
||||
loadConfig(configPath);
|
||||
} else {
|
||||
/* Set 'default' values */
|
||||
sipTpConfig.setPort(SIP_PORT);
|
||||
}
|
||||
|
||||
/* Override log level setting */
|
||||
epConfig.getLogConfig().setLevel(LOG_LEVEL);
|
||||
epConfig.getLogConfig().setConsoleLevel(LOG_LEVEL);
|
||||
|
||||
/* Set log config. */
|
||||
LogConfig log_cfg = epConfig.getLogConfig();
|
||||
logWriter = new MyLogWriter();
|
||||
log_cfg.setWriter(logWriter);
|
||||
log_cfg.setDecor(log_cfg.getDecor() &
|
||||
~(pj_log_decoration.PJ_LOG_HAS_CR.swigValue() |
|
||||
pj_log_decoration.PJ_LOG_HAS_NEWLINE.swigValue()));
|
||||
|
||||
/* Set ua config. */
|
||||
UaConfig ua_cfg = epConfig.getUaConfig();
|
||||
ua_cfg.setUserAgent("Pjsua2And" + ep.libVersion().getFull());
|
||||
if (own_worker_thread) {
|
||||
ua_cfg.setThreadCnt(0);
|
||||
ua_cfg.setMainThreadOnly(true);
|
||||
}
|
||||
|
||||
/* Init endpoint */
|
||||
try {
|
||||
ep.libInit(epConfig);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create transports. */
|
||||
try {
|
||||
ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, sipTpConfig);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
try {
|
||||
ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, sipTpConfig);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
/* Create accounts. */
|
||||
for (int i = 0; i < accCfgs.size(); i++) {
|
||||
MyAccountConfig my_cfg = accCfgs.get(i);
|
||||
MyAccount acc = addAcc(my_cfg.accCfg);
|
||||
if (acc == null)
|
||||
continue;
|
||||
|
||||
/* Add Buddies */
|
||||
for (int j = 0; j < my_cfg.buddyCfgs.size(); j++) {
|
||||
BuddyConfig bud_cfg = my_cfg.buddyCfgs.get(j);
|
||||
acc.addBuddy(bud_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Start. */
|
||||
try {
|
||||
ep.libStart();
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public MyAccount addAcc(AccountConfig cfg) {
|
||||
MyAccount acc = new MyAccount(cfg);
|
||||
try {
|
||||
acc.create(cfg);
|
||||
} catch (Exception e) {
|
||||
acc = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
accList.add(acc);
|
||||
return acc;
|
||||
}
|
||||
|
||||
public void delAcc(MyAccount acc) {
|
||||
accList.remove(acc);
|
||||
}
|
||||
|
||||
private void loadConfig(String filename) {
|
||||
JsonDocument json = new JsonDocument();
|
||||
|
||||
try {
|
||||
/* Load file */
|
||||
json.loadFile(filename);
|
||||
ContainerNode root = json.getRootContainer();
|
||||
|
||||
/* Read endpoint config */
|
||||
epConfig.readObject(root);
|
||||
|
||||
/* Read transport config */
|
||||
ContainerNode tp_node = root.readContainer("SipTransport");
|
||||
sipTpConfig.readObject(tp_node);
|
||||
|
||||
/* Read account configs */
|
||||
accCfgs.clear();
|
||||
ContainerNode accs_node = root.readArray("accounts");
|
||||
while (accs_node.hasUnread()) {
|
||||
MyAccountConfig acc_cfg = new MyAccountConfig();
|
||||
acc_cfg.readObject(accs_node);
|
||||
accCfgs.add(acc_cfg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
|
||||
/* Force delete json now, as I found that Java somehow destroys it
|
||||
* after lib has been destroyed and from non-registered thread.
|
||||
*/
|
||||
json.delete();
|
||||
}
|
||||
|
||||
private void buildAccConfigs() {
|
||||
/* Sync accCfgs from accList */
|
||||
accCfgs.clear();
|
||||
for (int i = 0; i < accList.size(); i++) {
|
||||
MyAccount acc = accList.get(i);
|
||||
MyAccountConfig my_acc_cfg = new MyAccountConfig();
|
||||
my_acc_cfg.accCfg = acc.cfg;
|
||||
|
||||
my_acc_cfg.buddyCfgs.clear();
|
||||
for (int j = 0; j < acc.buddyList.size(); j++) {
|
||||
MyBuddy bud = acc.buddyList.get(j);
|
||||
my_acc_cfg.buddyCfgs.add(bud.cfg);
|
||||
}
|
||||
|
||||
accCfgs.add(my_acc_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveConfig(String filename) {
|
||||
JsonDocument json = new JsonDocument();
|
||||
|
||||
try {
|
||||
/* Write endpoint config */
|
||||
json.writeObject(epConfig);
|
||||
|
||||
/* Write transport config */
|
||||
ContainerNode tp_node = json.writeNewContainer("SipTransport");
|
||||
sipTpConfig.writeObject(tp_node);
|
||||
|
||||
/* Write account configs */
|
||||
buildAccConfigs();
|
||||
ContainerNode accs_node = json.writeNewArray("accounts");
|
||||
for (int i = 0; i < accCfgs.size(); i++) {
|
||||
accCfgs.get(i).writeObject(accs_node);
|
||||
}
|
||||
|
||||
/* Save file */
|
||||
json.saveFile(filename);
|
||||
} catch (Exception e) {}
|
||||
|
||||
/* Force delete json now, as I found that Java somehow destroys it
|
||||
* after lib has been destroyed and from non-registered thread.
|
||||
*/
|
||||
json.delete();
|
||||
}
|
||||
|
||||
public void deinit() {
|
||||
String configPath = appDir + "/" + configName;
|
||||
saveConfig(configPath);
|
||||
|
||||
/* Try force GC to avoid late destroy of PJ objects as they should be
|
||||
* deleted before lib is destroyed.
|
||||
*/
|
||||
Runtime.getRuntime().gc();
|
||||
|
||||
/* Shutdown pjsua. Note that Endpoint destructor will also invoke
|
||||
* libDestroy(), so this will be a test of double libDestroy().
|
||||
*/
|
||||
try {
|
||||
ep.libDestroy();
|
||||
} catch (Exception e) {}
|
||||
|
||||
/* Force delete Endpoint here, to avoid deletion from a non-
|
||||
* registered thread (by GC?).
|
||||
*/
|
||||
ep.delete();
|
||||
ep = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package org.pjsip.pjsua2.app;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.pjsip.pjsua2.*;
|
||||
import org.pjsip.pjsua2.app.*;
|
||||
|
||||
class MyObserver implements MyAppObserver {
|
||||
private static MyCall currentCall = null;
|
||||
|
||||
@Override
|
||||
public void notifyRegState(pjsip_status_code code, String reason, int expiration) {}
|
||||
|
||||
@Override
|
||||
public void notifyIncomingCall(MyCall call) {
|
||||
/* Auto answer. */
|
||||
CallOpParam call_param = new CallOpParam();
|
||||
call_param.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
|
||||
try {
|
||||
currentCall = call;
|
||||
currentCall.answer(call_param);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyCallState(MyCall call) {
|
||||
if (currentCall == null || call.getId() != currentCall.getId())
|
||||
return;
|
||||
|
||||
CallInfo ci;
|
||||
try {
|
||||
ci = call.getInfo();
|
||||
} catch (Exception e) {
|
||||
ci = null;
|
||||
}
|
||||
if (ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED)
|
||||
currentCall = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyBuddyState(MyBuddy buddy) {}
|
||||
}
|
||||
|
||||
class MyShutdownHook extends Thread {
|
||||
Thread thread;
|
||||
MyShutdownHook(Thread thr) {
|
||||
thread = thr;
|
||||
}
|
||||
public void run() {
|
||||
thread.interrupt();
|
||||
try {
|
||||
thread.join();
|
||||
} catch (Exception e) {
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class sample {
|
||||
private static MyApp app = new MyApp();
|
||||
private static MyAppObserver observer = new MyObserver();
|
||||
private static MyAccount account = null;
|
||||
private static AccountConfig accCfg = null;
|
||||
|
||||
private static void runWorker() {
|
||||
try {
|
||||
app.init(observer, ".", true);
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
app.deinit();
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
if (app.accList.size() == 0) {
|
||||
accCfg = new AccountConfig();
|
||||
accCfg.setIdUri("sip:localhost");
|
||||
account = app.addAcc(accCfg);
|
||||
|
||||
accCfg.setIdUri("sip:301@pjsip.org");
|
||||
AccountSipConfig sipCfg = accCfg.getSipConfig();
|
||||
AuthCredInfoVector ciVec = sipCfg.getAuthCreds();
|
||||
ciVec.add(new AuthCredInfo("Digest",
|
||||
"*",
|
||||
"301",
|
||||
0,
|
||||
"pw301"));
|
||||
|
||||
StringVector proxy = sipCfg.getProxies();
|
||||
proxy.add("sip:pjsip.org;transport=tcp");
|
||||
|
||||
AccountRegConfig regCfg = accCfg.getRegConfig();
|
||||
regCfg.setRegistrarUri("sip:pjsip.org");
|
||||
account = app.addAcc(accCfg);
|
||||
} else {
|
||||
account = app.accList.get(0);
|
||||
accCfg = account.cfg;
|
||||
}
|
||||
|
||||
try {
|
||||
account.modify(accCfg);
|
||||
} catch (Exception e) {}
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
MyApp.ep.libHandleEvents(10);
|
||||
try {
|
||||
Thread.currentThread().sleep(50);
|
||||
} catch (InterruptedException ie) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.deinit();
|
||||
}
|
||||
|
||||
public static void main(String argv[]) {
|
||||
Runtime.getRuntime().addShutdownHook(new MyShutdownHook(Thread.currentThread()));
|
||||
|
||||
runWorker();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import org.pjsip.pjsua2.*;
|
||||
|
||||
public class test {
|
||||
static {
|
||||
System.loadLibrary("pjsua2");
|
||||
System.out.println("Library loaded");
|
||||
}
|
||||
|
||||
public static void main(String argv[]) {
|
||||
|
||||
AuthCredInfo cred = new AuthCredInfo();
|
||||
|
||||
cred.setRealm("Hello world");
|
||||
|
||||
System.out.println(cred.getRealm());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
%module(directors="1") pjsua2
|
||||
|
||||
//
|
||||
// Suppress few warnings
|
||||
//
|
||||
#pragma SWIG nowarn=312 // 312: nested struct (in types.h, sip_auth.h)
|
||||
|
||||
//
|
||||
// Header section
|
||||
//
|
||||
%{
|
||||
#include "pjsua2.hpp"
|
||||
using namespace std;
|
||||
using namespace pj;
|
||||
%}
|
||||
|
||||
#ifdef SWIGPYTHON
|
||||
%feature("director:except") {
|
||||
if( $error != NULL ) {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch( &ptype, &pvalue, &ptraceback );
|
||||
PyErr_Restore( ptype, pvalue, ptraceback );
|
||||
PyErr_Print();
|
||||
//Py_Exit(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allow C++ exceptions to be handled in Java
|
||||
#ifdef SWIGJAVA
|
||||
%typemap(throws, throws="java.lang.Exception") pj::Error {
|
||||
jclass excep = jenv->FindClass("java/lang/Exception");
|
||||
if (excep)
|
||||
jenv->ThrowNew(excep, $1.info(true).c_str());
|
||||
return $null;
|
||||
}
|
||||
|
||||
// Force the Error Java class to extend java.lang.Exception
|
||||
%typemap(javabase) pj::Error "java.lang.Exception";
|
||||
|
||||
// Override getMessage()
|
||||
%typemap(javacode) pj::Error %{
|
||||
public String getMessage() {
|
||||
return getTitle();
|
||||
}
|
||||
%}
|
||||
#endif
|
||||
|
||||
|
||||
// Constants from PJSIP libraries
|
||||
%include "symbols.i"
|
||||
|
||||
|
||||
//
|
||||
// Classes that can be extended in the target language
|
||||
//
|
||||
%feature("director") LogWriter;
|
||||
%feature("director") Endpoint;
|
||||
%feature("director") Account;
|
||||
%feature("director") Call;
|
||||
%feature("director") Buddy;
|
||||
%feature("director") FindBuddyMatch;
|
||||
|
||||
//
|
||||
// STL stuff.
|
||||
//
|
||||
%include "std_string.i"
|
||||
%include "std_vector.i"
|
||||
|
||||
%template(StringVector) std::vector<std::string>;
|
||||
%template(IntVector) std::vector<int>;
|
||||
|
||||
//
|
||||
// Ignore stuffs in pjsua2
|
||||
//
|
||||
%ignore fromPj;
|
||||
%ignore toPj;
|
||||
|
||||
//
|
||||
// Now include the API itself.
|
||||
//
|
||||
%include "pjsua2/types.hpp"
|
||||
|
||||
%ignore pj::ContainerNode::op;
|
||||
%ignore pj::ContainerNode::data;
|
||||
%ignore container_node_op;
|
||||
%ignore container_node_internal_data;
|
||||
%include "pjsua2/persistent.hpp"
|
||||
|
||||
%include "pjsua2/siptypes.hpp"
|
||||
|
||||
%template(SipHeaderVector) std::vector<pj::SipHeader>;
|
||||
%template(AuthCredInfoVector) std::vector<pj::AuthCredInfo>;
|
||||
%template(SipMultipartPartVector) std::vector<pj::SipMultipartPart>;
|
||||
%template(BuddyVector) std::vector<pj::Buddy*>;
|
||||
%template(AudioMediaVector) std::vector<pj::AudioMedia*>;
|
||||
%template(MediaFormatVector) std::vector<pj::MediaFormat*>;
|
||||
%template(AudioDevInfoVector) std::vector<pj::AudioDevInfo*>;
|
||||
%template(CodecInfoVector) std::vector<pj::CodecInfo*>;
|
||||
|
||||
%include "pjsua2/media.hpp"
|
||||
%include "pjsua2/endpoint.hpp"
|
||||
%include "pjsua2/presence.hpp"
|
||||
%include "pjsua2/account.hpp"
|
||||
%include "pjsua2/call.hpp"
|
||||
|
||||
%template(CallMediaInfoVector) std::vector<pj::CallMediaInfo>;
|
||||
|
||||
%ignore pj::JsonDocument::allocElement;
|
||||
%ignore pj::JsonDocument::getPool;
|
||||
%include "pjsua2/json.hpp"
|
|
@ -0,0 +1,29 @@
|
|||
PYTHON_SO=_pjsua2.so
|
||||
|
||||
#PYTHON_SETUP_FLAGS = --inplace
|
||||
ifeq ($(OS),Windows_NT)
|
||||
PYTHON_SETUP_FLAGS += --compiler=mingw32
|
||||
endif
|
||||
|
||||
SWIG_FLAGS += -w312
|
||||
|
||||
.PHONY: all install uninstall
|
||||
|
||||
all: $(PYTHON_SO)
|
||||
|
||||
$(PYTHON_SO): pjsua2_wrap.cpp setup.py
|
||||
python setup.py build $(PYTHON_SETUP_FLAGS)
|
||||
|
||||
pjsua2_wrap.cpp: ../pjsua2.i ../symbols.i Makefile $(SRCS)
|
||||
swig $(SWIG_FLAGS) -python -o pjsua2_wrap.cpp ../pjsua2.i
|
||||
|
||||
clean distclean realclean:
|
||||
rm -rf $(PYTHON_SO) pjsua2_wrap.cpp pjsua2_wrap.h pjsua2.py build *.pyc
|
||||
|
||||
install:
|
||||
python setup.py install --user
|
||||
|
||||
uninstall:
|
||||
rm -f $(HOME)/.local/lib/python2.7/site-packages/pjsua2*
|
||||
rm -f $(HOME)/.local/lib/python2.7/site-packages/_pjsua2*
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
include ../../../../build.mak
|
||||
|
||||
lib_dir:
|
||||
@for token in `echo $(APP_LDFLAGS)`; do \
|
||||
echo $$token | grep L | sed 's/-L//'; \
|
||||
done
|
||||
|
||||
inc_dir:
|
||||
@for token in `echo $(APP_CFLAGS)`; do \
|
||||
echo $$token | grep I | sed 's/-I//'; \
|
||||
done
|
||||
|
||||
libs:
|
||||
@for token in `echo $(APP_LDLIBS)`; do \
|
||||
echo $$token | grep \\-l | sed 's/-l//'; \
|
||||
done
|
||||
|
||||
target_name:
|
||||
@echo $(TARGET_NAME)
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# $Id$
|
||||
#
|
||||
# pjsua2 Setup script.
|
||||
#
|
||||
# Copyright (C)2012 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
|
||||
#
|
||||
from distutils.core import setup, Extension
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
|
||||
# find pjsip version
|
||||
pj_version=""
|
||||
pj_version_major=""
|
||||
pj_version_minor=""
|
||||
pj_version_rev=""
|
||||
pj_version_suffix=""
|
||||
f = open('../../../../version.mak', 'r')
|
||||
for line in f:
|
||||
if line.find("export PJ_VERSION_MAJOR") != -1:
|
||||
tokens=line.split("=")
|
||||
if len(tokens)>1:
|
||||
pj_version_major= tokens[1].strip()
|
||||
elif line.find("export PJ_VERSION_MINOR") != -1:
|
||||
tokens=line.split("=")
|
||||
if len(tokens)>1:
|
||||
pj_version_minor= line.split("=")[1].strip()
|
||||
elif line.find("export PJ_VERSION_REV") != -1:
|
||||
tokens=line.split("=")
|
||||
if len(tokens)>1:
|
||||
pj_version_rev= line.split("=")[1].strip()
|
||||
elif line.find("export PJ_VERSION_SUFFIX") != -1:
|
||||
tokens=line.split("=")
|
||||
if len(tokens)>1:
|
||||
pj_version_suffix= line.split("=")[1].strip()
|
||||
|
||||
f.close()
|
||||
if not pj_version_major:
|
||||
print 'Unable to get PJ_VERSION_MAJOR'
|
||||
sys.exit(1)
|
||||
|
||||
pj_version = pj_version_major + "." + pj_version_minor
|
||||
if pj_version_rev:
|
||||
pj_version += "." + pj_version_rev
|
||||
if pj_version_suffix:
|
||||
pj_version += "-" + pj_version_suffix
|
||||
|
||||
#print 'PJ_VERSION = "'+ pj_version + '"'
|
||||
|
||||
# Get targetname
|
||||
f = os.popen("make --no-print-directory -f helper.mak target_name")
|
||||
pj_target_name = f.read().rstrip("\r\n")
|
||||
f.close()
|
||||
|
||||
# Fill in pj_inc_dirs
|
||||
pj_inc_dirs = []
|
||||
f = os.popen("make --no-print-directory -f helper.mak inc_dir")
|
||||
for line in f:
|
||||
pj_inc_dirs.append(line.rstrip("\r\n"))
|
||||
f.close()
|
||||
|
||||
# Fill in pj_lib_dirs
|
||||
pj_lib_dirs = []
|
||||
f = os.popen("make --no-print-directory -f helper.mak lib_dir")
|
||||
for line in f:
|
||||
pj_lib_dirs.append(line.rstrip("\r\n"))
|
||||
f.close()
|
||||
|
||||
# Fill in pj_libs
|
||||
pj_libs = ['pjsua2-' + pj_target_name]
|
||||
f = os.popen("make --no-print-directory -f helper.mak libs")
|
||||
for line in f:
|
||||
pj_libs.append(line.rstrip("\r\n"))
|
||||
f.close()
|
||||
|
||||
# Fill in extra link args
|
||||
extra_link_args = ['-static-libstdc++']
|
||||
if platform.system() == 'Darwin':
|
||||
# Mac OS X depedencies
|
||||
extra_link_args += ["-framework", "CoreFoundation",
|
||||
"-framework", "AudioToolbox",
|
||||
"-framework", "QTKit"]
|
||||
# OS X Lion support
|
||||
if platform.mac_ver()[0].startswith("10.7"):
|
||||
extra_link_args += ["-framework", "AudioUnit"]
|
||||
|
||||
|
||||
setup(name="pjsua2",
|
||||
version=pj_version,
|
||||
description='SIP User Agent Library based on PJSIP',
|
||||
url='http://www.pjsip.org',
|
||||
ext_modules = [Extension("_pjsua2",
|
||||
["pjsua2_wrap.cpp"],
|
||||
define_macros=[('PJ_AUTOCONF', '1'),],
|
||||
include_dirs=pj_inc_dirs,
|
||||
library_dirs=pj_lib_dirs,
|
||||
libraries=pj_libs,
|
||||
extra_link_args=extra_link_args
|
||||
)
|
||||
],
|
||||
py_modules=["pjsua2"]
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import pjsua2 as pj
|
||||
import sys
|
||||
|
||||
#
|
||||
# Basic data structure test, to make sure basic struct
|
||||
# and array operations work
|
||||
#
|
||||
def ua_data_test():
|
||||
#
|
||||
# AuthCredInfo
|
||||
#
|
||||
print "UA data types test.."
|
||||
the_realm = "pjsip.org"
|
||||
ci = pj.AuthCredInfo()
|
||||
ci.realm = the_realm
|
||||
ci.dataType = 20
|
||||
|
||||
ci2 = ci
|
||||
assert ci.dataType == 20
|
||||
assert ci2.realm == the_realm
|
||||
|
||||
#
|
||||
# UaConfig
|
||||
# See here how we manipulate std::vector
|
||||
#
|
||||
uc = pj.UaConfig()
|
||||
uc.maxCalls = 10
|
||||
uc.userAgent = "Python"
|
||||
uc.nameserver = pj.StringVector(["10.0.0.1", "10.0.0.2"])
|
||||
uc.nameserver.append("NS1")
|
||||
|
||||
uc2 = uc
|
||||
assert uc2.maxCalls == 10
|
||||
assert uc2.userAgent == "Python"
|
||||
assert len(uc2.nameserver) == 3
|
||||
assert uc2.nameserver[0] == "10.0.0.1"
|
||||
assert uc2.nameserver[1] == "10.0.0.2"
|
||||
assert uc2.nameserver[2] == "NS1"
|
||||
|
||||
print " Dumping nameservers: ",
|
||||
for s in uc2.nameserver:
|
||||
print s,
|
||||
print ""
|
||||
|
||||
#
|
||||
# Exception test
|
||||
#
|
||||
def ua_run_test_exception():
|
||||
print "Exception test.."
|
||||
ep = pj.Endpoint()
|
||||
ep.libCreate()
|
||||
got_exception = False
|
||||
try:
|
||||
ep.natDetectType()
|
||||
except pj.Error, e:
|
||||
got_exception = True
|
||||
print " Got exception: status=%u, reason=%s,\n title=%s,\n srcFile=%s, srcLine=%d" % \
|
||||
(e.status, e.reason, e.title, e.srcFile, e.srcLine)
|
||||
assert e.status == 370050
|
||||
assert e.reason.find("PJNATH_ESTUNINSERVER") >= 0
|
||||
assert e.title == "pjsua_detect_nat_type()"
|
||||
assert got_exception
|
||||
|
||||
#
|
||||
# Custom log writer
|
||||
#
|
||||
class MyLogWriter(pj.LogWriter):
|
||||
def write(self, entry):
|
||||
print "This is Python:", entry.msg
|
||||
|
||||
#
|
||||
# Testing log writer callback
|
||||
#
|
||||
def ua_run_log_test():
|
||||
print "Logging test.."
|
||||
ep_cfg = pj.EpConfig()
|
||||
|
||||
lw = MyLogWriter()
|
||||
ep_cfg.logConfig.writer = lw
|
||||
ep_cfg.logConfig.decor = ep_cfg.logConfig.decor & ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE)
|
||||
|
||||
ep = pj.Endpoint()
|
||||
ep.libCreate()
|
||||
ep.libInit(ep_cfg)
|
||||
ep.libDestroy()
|
||||
|
||||
#
|
||||
# Simple create, init, start, and destroy sequence
|
||||
#
|
||||
def ua_run_ua_test():
|
||||
print "UA test run.."
|
||||
ep_cfg = pj.EpConfig()
|
||||
|
||||
ep = pj.Endpoint()
|
||||
ep.libCreate()
|
||||
ep.libInit(ep_cfg)
|
||||
ep.libStart()
|
||||
|
||||
print "************* Endpoint started ok, now shutting down... *************"
|
||||
ep.libDestroy()
|
||||
|
||||
#
|
||||
# main()
|
||||
#
|
||||
if __name__ == "__main__":
|
||||
ua_data_test()
|
||||
ua_run_test_exception()
|
||||
ua_run_log_test()
|
||||
ua_run_ua_test()
|
||||
sys.exit(0)
|
||||
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
// This file is autogenerated by importsym script, do not modify!
|
||||
|
||||
typedef int pj_status_t;
|
||||
|
||||
enum pj_constants_ {PJ_SUCCESS = 0, PJ_TRUE = 1, PJ_FALSE = 0};
|
||||
|
||||
typedef unsigned char pj_uint8_t;
|
||||
|
||||
typedef int pj_int32_t;
|
||||
|
||||
typedef unsigned int pj_uint32_t;
|
||||
|
||||
typedef unsigned short pj_uint16_t;
|
||||
|
||||
enum pj_file_access {PJ_O_RDONLY = 0x1101, PJ_O_WRONLY = 0x1102, PJ_O_RDWR = 0x1103, PJ_O_APPEND = 0x1108};
|
||||
|
||||
enum pj_log_decoration {PJ_LOG_HAS_DAY_NAME = 1, PJ_LOG_HAS_YEAR = 2, PJ_LOG_HAS_MONTH = 4, PJ_LOG_HAS_DAY_OF_MON = 8, PJ_LOG_HAS_TIME = 16, PJ_LOG_HAS_MICRO_SEC = 32, PJ_LOG_HAS_SENDER = 64, PJ_LOG_HAS_NEWLINE = 128, PJ_LOG_HAS_CR = 256, PJ_LOG_HAS_SPACE = 512, PJ_LOG_HAS_COLOR = 1024, PJ_LOG_HAS_LEVEL_TEXT = 2048, PJ_LOG_HAS_THREAD_ID = 4096, PJ_LOG_HAS_THREAD_SWC = 8192, PJ_LOG_HAS_INDENT = 16384};
|
||||
|
||||
typedef enum pj_qos_type {PJ_QOS_TYPE_BEST_EFFORT, PJ_QOS_TYPE_BACKGROUND, PJ_QOS_TYPE_VIDEO, PJ_QOS_TYPE_VOICE, PJ_QOS_TYPE_CONTROL} pj_qos_type;
|
||||
|
||||
typedef enum pj_qos_flag {PJ_QOS_PARAM_HAS_DSCP = 1, PJ_QOS_PARAM_HAS_SO_PRIO = 2, PJ_QOS_PARAM_HAS_WMM = 4} pj_qos_flag;
|
||||
|
||||
typedef enum pj_qos_wmm_prio {PJ_QOS_WMM_PRIO_BULK_EFFORT, PJ_QOS_WMM_PRIO_BULK, PJ_QOS_WMM_PRIO_VIDEO, PJ_QOS_WMM_PRIO_VOICE} pj_qos_wmm_prio;
|
||||
|
||||
typedef struct pj_qos_params
|
||||
{
|
||||
pj_uint8_t flags;
|
||||
pj_uint8_t dscp_val;
|
||||
pj_uint8_t so_prio;
|
||||
pj_qos_wmm_prio wmm_prio;
|
||||
} pj_qos_params;
|
||||
|
||||
typedef enum pj_ssl_cipher {PJ_TLS_NULL_WITH_NULL_NULL = 0x00000000, PJ_TLS_RSA_WITH_NULL_MD5 = 0x00000001, PJ_TLS_RSA_WITH_NULL_SHA = 0x00000002, PJ_TLS_RSA_WITH_NULL_SHA256 = 0x0000003B, PJ_TLS_RSA_WITH_RC4_128_MD5 = 0x00000004, PJ_TLS_RSA_WITH_RC4_128_SHA = 0x00000005, PJ_TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x0000000A, PJ_TLS_RSA_WITH_AES_128_CBC_SHA = 0x0000002F, PJ_TLS_RSA_WITH_AES_256_CBC_SHA = 0x00000035, PJ_TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003C, PJ_TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x0000003D, PJ_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x0000000D, PJ_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000010, PJ_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x00000013, PJ_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x00000016, PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x00000030, PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x00000031, PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x00000032, PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x00000033, PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x00000036, PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x00000037, PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x00000038, PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x00000039, PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x0000003E, PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x0000003F, PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x00000040, PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x00000067, PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x00000068, PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x00000069, PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x0000006A, PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x0000006B, PJ_TLS_DH_anon_WITH_RC4_128_MD5 = 0x00000018, PJ_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x0000001B, PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x00000034, PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x0000003A, PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x0000006C, PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x0000006D, PJ_TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x00000003, PJ_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x00000006, PJ_TLS_RSA_WITH_IDEA_CBC_SHA = 0x00000007, PJ_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000008, PJ_TLS_RSA_WITH_DES_CBC_SHA = 0x00000009, PJ_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0000000B, PJ_TLS_DH_DSS_WITH_DES_CBC_SHA = 0x0000000C, PJ_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0000000E, PJ_TLS_DH_RSA_WITH_DES_CBC_SHA = 0x0000000F, PJ_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x00000011, PJ_TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x00000012, PJ_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x00000014, PJ_TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x00000015, PJ_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x00000017, PJ_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x00000019, PJ_TLS_DH_anon_WITH_DES_CBC_SHA = 0x0000001A, PJ_SSL_FORTEZZA_KEA_WITH_NULL_SHA = 0x0000001C, PJ_SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA = 0x0000001D, PJ_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA = 0x0000001E, PJ_SSL_CK_RC4_128_WITH_MD5 = 0x00010080, PJ_SSL_CK_RC4_128_EXPORT40_WITH_MD5 = 0x00020080, PJ_SSL_CK_RC2_128_CBC_WITH_MD5 = 0x00030080, PJ_SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 = 0x00040080, PJ_SSL_CK_IDEA_128_CBC_WITH_MD5 = 0x00050080, PJ_SSL_CK_DES_64_CBC_WITH_MD5 = 0x00060040, PJ_SSL_CK_DES_192_EDE3_CBC_WITH_MD5 = 0x000700C0} pj_ssl_cipher;
|
||||
|
||||
typedef enum pj_stun_nat_type {PJ_STUN_NAT_TYPE_UNKNOWN, PJ_STUN_NAT_TYPE_ERR_UNKNOWN, PJ_STUN_NAT_TYPE_OPEN, PJ_STUN_NAT_TYPE_BLOCKED, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP, PJ_STUN_NAT_TYPE_FULL_CONE, PJ_STUN_NAT_TYPE_SYMMETRIC, PJ_STUN_NAT_TYPE_RESTRICTED, PJ_STUN_NAT_TYPE_PORT_RESTRICTED} pj_stun_nat_type;
|
||||
|
||||
typedef enum pj_turn_tp_type {PJ_TURN_TP_UDP = 17, PJ_TURN_TP_TCP = 6, PJ_TURN_TP_TLS = 255} pj_turn_tp_type;
|
||||
|
||||
typedef enum pjmedia_event_type {PJMEDIA_EVENT_NONE, PJMEDIA_EVENT_FMT_CHANGED = ((('H' << 24) | ('C' << 16)) | ('M' << 8)) | 'F', PJMEDIA_EVENT_WND_CLOSING = ((('L' << 24) | ('C' << 16)) | ('N' << 8)) | 'W', PJMEDIA_EVENT_WND_CLOSED = ((('O' << 24) | ('C' << 16)) | ('N' << 8)) | 'W', PJMEDIA_EVENT_WND_RESIZED = ((('Z' << 24) | ('R' << 16)) | ('N' << 8)) | 'W', PJMEDIA_EVENT_MOUSE_BTN_DOWN = ((('N' << 24) | ('D' << 16)) | ('S' << 8)) | 'M', PJMEDIA_EVENT_KEYFRAME_FOUND = ((('F' << 24) | ('R' << 16)) | ('F' << 8)) | 'I', PJMEDIA_EVENT_KEYFRAME_MISSING = ((('M' << 24) | ('R' << 16)) | ('F' << 8)) | 'I', PJMEDIA_EVENT_ORIENT_CHANGED = ((('T' << 24) | ('N' << 16)) | ('R' << 8)) | 'O'} pjmedia_event_type;
|
||||
|
||||
typedef enum pjmedia_srtp_use {PJMEDIA_SRTP_DISABLED, PJMEDIA_SRTP_OPTIONAL, PJMEDIA_SRTP_MANDATORY} pjmedia_srtp_use;
|
||||
|
||||
typedef enum pjmedia_vid_stream_rc_method {PJMEDIA_VID_STREAM_RC_NONE = 0, PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING = 1} pjmedia_vid_stream_rc_method;
|
||||
|
||||
typedef pj_int32_t pjmedia_vid_dev_index;
|
||||
|
||||
enum pjmedia_vid_dev_std_index {PJMEDIA_VID_DEFAULT_CAPTURE_DEV = -1, PJMEDIA_VID_DEFAULT_RENDER_DEV = -2, PJMEDIA_VID_INVALID_DEV = -3};
|
||||
|
||||
typedef enum pjmedia_aud_dev_route {PJMEDIA_AUD_DEV_ROUTE_DEFAULT = 0, PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER = 1, PJMEDIA_AUD_DEV_ROUTE_EARPIECE = 2, PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH = 4} pjmedia_aud_dev_route;
|
||||
|
||||
typedef enum pjmedia_aud_dev_cap {PJMEDIA_AUD_DEV_CAP_EXT_FORMAT = 1, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY = 2, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY = 4, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING = 8, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING = 16, PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER = 32, PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER = 64, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE = 128, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE = 256, PJMEDIA_AUD_DEV_CAP_EC = 512, PJMEDIA_AUD_DEV_CAP_EC_TAIL = 1024, PJMEDIA_AUD_DEV_CAP_VAD = 2048, PJMEDIA_AUD_DEV_CAP_CNG = 4096, PJMEDIA_AUD_DEV_CAP_PLC = 8192, PJMEDIA_AUD_DEV_CAP_MAX = 16384} pjmedia_aud_dev_cap;
|
||||
|
||||
enum pjmedia_file_writer_option {PJMEDIA_FILE_WRITE_PCM = 0, PJMEDIA_FILE_WRITE_ALAW = 1, PJMEDIA_FILE_WRITE_ULAW = 2};
|
||||
|
||||
enum pjmedia_file_player_option {PJMEDIA_FILE_NO_LOOP = 1};
|
||||
|
||||
typedef enum pjmedia_type {PJMEDIA_TYPE_NONE, PJMEDIA_TYPE_AUDIO, PJMEDIA_TYPE_VIDEO, PJMEDIA_TYPE_APPLICATION, PJMEDIA_TYPE_UNKNOWN} pjmedia_type;
|
||||
|
||||
typedef enum pjmedia_dir {PJMEDIA_DIR_NONE = 0, PJMEDIA_DIR_ENCODING = 1, PJMEDIA_DIR_CAPTURE = PJMEDIA_DIR_ENCODING, PJMEDIA_DIR_DECODING = 2, PJMEDIA_DIR_PLAYBACK = PJMEDIA_DIR_DECODING, PJMEDIA_DIR_RENDER = PJMEDIA_DIR_DECODING, PJMEDIA_DIR_ENCODING_DECODING = 3, PJMEDIA_DIR_CAPTURE_PLAYBACK = PJMEDIA_DIR_ENCODING_DECODING, PJMEDIA_DIR_CAPTURE_RENDER = PJMEDIA_DIR_ENCODING_DECODING} pjmedia_dir;
|
||||
|
||||
typedef enum pjmedia_tp_proto {PJMEDIA_TP_PROTO_NONE = 0, PJMEDIA_TP_PROTO_RTP_AVP, PJMEDIA_TP_PROTO_RTP_SAVP, PJMEDIA_TP_PROTO_UNKNOWN} pjmedia_tp_proto;
|
||||
|
||||
typedef enum pjmedia_format_id {PJMEDIA_FORMAT_L16 = 0, PJMEDIA_FORMAT_PCM = PJMEDIA_FORMAT_L16, PJMEDIA_FORMAT_PCMA = ((('W' << 24) | ('A' << 16)) | ('L' << 8)) | 'A', PJMEDIA_FORMAT_ALAW = PJMEDIA_FORMAT_PCMA, PJMEDIA_FORMAT_PCMU = ((('W' << 24) | ('A' << 16)) | ('L' << 8)) | 'u', PJMEDIA_FORMAT_ULAW = PJMEDIA_FORMAT_PCMU, PJMEDIA_FORMAT_AMR = ((('R' << 24) | ('M' << 16)) | ('A' << 8)) | ' ', PJMEDIA_FORMAT_G729 = ((('9' << 24) | ('2' << 16)) | ('7' << 8)) | 'G', PJMEDIA_FORMAT_ILBC = ((('C' << 24) | ('B' << 16)) | ('L' << 8)) | 'I', PJMEDIA_FORMAT_RGB24 = ((('3' << 24) | ('B' << 16)) | ('G' << 8)) | 'R', PJMEDIA_FORMAT_RGBA = ((('A' << 24) | ('B' << 16)) | ('G' << 8)) | 'R', PJMEDIA_FORMAT_BGRA = ((('A' << 24) | ('R' << 16)) | ('G' << 8)) | 'B', PJMEDIA_FORMAT_RGB32 = PJMEDIA_FORMAT_RGBA, PJMEDIA_FORMAT_DIB = (((' ' << 24) | ('B' << 16)) | ('I' << 8)) | 'D', PJMEDIA_FORMAT_GBRP = ((('P' << 24) | ('R' << 16)) | ('B' << 8)) | 'G', PJMEDIA_FORMAT_AYUV = ((('V' << 24) | ('U' << 16)) | ('Y' << 8)) | 'A', PJMEDIA_FORMAT_YUY2 = ((('2' << 24) | ('Y' << 16)) | ('U' << 8)) | 'Y', PJMEDIA_FORMAT_UYVY = ((('Y' << 24) | ('V' << 16)) | ('Y' << 8)) | 'U', PJMEDIA_FORMAT_YVYU = ((('U' << 24) | ('Y' << 16)) | ('V' << 8)) | 'Y', PJMEDIA_FORMAT_I420 = ((('0' << 24) | ('2' << 16)) | ('4' << 8)) | 'I', PJMEDIA_FORMAT_IYUV = PJMEDIA_FORMAT_I420, PJMEDIA_FORMAT_YV12 = ((('2' << 24) | ('1' << 16)) | ('V' << 8)) | 'Y', PJMEDIA_FORMAT_I422 = ((('2' << 24) | ('2' << 16)) | ('4' << 8)) | 'I', PJMEDIA_FORMAT_I420JPEG = ((('0' << 24) | ('2' << 16)) | ('4' << 8)) | 'J', PJMEDIA_FORMAT_I422JPEG = ((('2' << 24) | ('2' << 16)) | ('4' << 8)) | 'J', PJMEDIA_FORMAT_H261 = ((('1' << 24) | ('6' << 16)) | ('2' << 8)) | 'H', PJMEDIA_FORMAT_H263 = ((('3' << 24) | ('6' << 16)) | ('2' << 8)) | 'H', PJMEDIA_FORMAT_H263P = ((('3' << 24) | ('6' << 16)) | ('2' << 8)) | 'P', PJMEDIA_FORMAT_H264 = ((('4' << 24) | ('6' << 16)) | ('2' << 8)) | 'H', PJMEDIA_FORMAT_MJPEG = ((('G' << 24) | ('P' << 16)) | ('J' << 8)) | 'M', PJMEDIA_FORMAT_MPEG1VIDEO = ((('V' << 24) | ('1' << 16)) | ('P' << 8)) | 'M', PJMEDIA_FORMAT_MPEG2VIDEO = ((('V' << 24) | ('2' << 16)) | ('P' << 8)) | 'M', PJMEDIA_FORMAT_MPEG4 = ((('4' << 24) | ('G' << 16)) | ('P' << 8)) | 'M'} pjmedia_format_id;
|
||||
|
||||
typedef enum pjsip_cred_data_type {PJSIP_CRED_DATA_PLAIN_PASSWD = 0, PJSIP_CRED_DATA_DIGEST = 1, PJSIP_CRED_DATA_EXT_AKA = 16} pjsip_cred_data_type;
|
||||
|
||||
typedef enum pjsip_dialog_cap_status {PJSIP_DIALOG_CAP_UNSUPPORTED = 0, PJSIP_DIALOG_CAP_SUPPORTED = 1, PJSIP_DIALOG_CAP_UNKNOWN = 2} pjsip_dialog_cap_status;
|
||||
|
||||
typedef enum pjsip_event_id_e {PJSIP_EVENT_UNKNOWN, PJSIP_EVENT_TIMER, PJSIP_EVENT_TX_MSG, PJSIP_EVENT_RX_MSG, PJSIP_EVENT_TRANSPORT_ERROR, PJSIP_EVENT_TSX_STATE, PJSIP_EVENT_USER} pjsip_event_id_e;
|
||||
|
||||
typedef enum pjsip_status_code {PJSIP_SC_TRYING = 100, PJSIP_SC_RINGING = 180, PJSIP_SC_CALL_BEING_FORWARDED = 181, PJSIP_SC_QUEUED = 182, PJSIP_SC_PROGRESS = 183, PJSIP_SC_OK = 200, PJSIP_SC_ACCEPTED = 202, PJSIP_SC_MULTIPLE_CHOICES = 300, PJSIP_SC_MOVED_PERMANENTLY = 301, PJSIP_SC_MOVED_TEMPORARILY = 302, PJSIP_SC_USE_PROXY = 305, PJSIP_SC_ALTERNATIVE_SERVICE = 380, PJSIP_SC_BAD_REQUEST = 400, PJSIP_SC_UNAUTHORIZED = 401, PJSIP_SC_PAYMENT_REQUIRED = 402, PJSIP_SC_FORBIDDEN = 403, PJSIP_SC_NOT_FOUND = 404, PJSIP_SC_METHOD_NOT_ALLOWED = 405, PJSIP_SC_NOT_ACCEPTABLE = 406, PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED = 407, PJSIP_SC_REQUEST_TIMEOUT = 408, PJSIP_SC_GONE = 410, PJSIP_SC_REQUEST_ENTITY_TOO_LARGE = 413, PJSIP_SC_REQUEST_URI_TOO_LONG = 414, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE = 415, PJSIP_SC_UNSUPPORTED_URI_SCHEME = 416, PJSIP_SC_BAD_EXTENSION = 420, PJSIP_SC_EXTENSION_REQUIRED = 421, PJSIP_SC_SESSION_TIMER_TOO_SMALL = 422, PJSIP_SC_INTERVAL_TOO_BRIEF = 423, PJSIP_SC_TEMPORARILY_UNAVAILABLE = 480, PJSIP_SC_CALL_TSX_DOES_NOT_EXIST = 481, PJSIP_SC_LOOP_DETECTED = 482, PJSIP_SC_TOO_MANY_HOPS = 483, PJSIP_SC_ADDRESS_INCOMPLETE = 484, PJSIP_AC_AMBIGUOUS = 485, PJSIP_SC_BUSY_HERE = 486, PJSIP_SC_REQUEST_TERMINATED = 487, PJSIP_SC_NOT_ACCEPTABLE_HERE = 488, PJSIP_SC_BAD_EVENT = 489, PJSIP_SC_REQUEST_UPDATED = 490, PJSIP_SC_REQUEST_PENDING = 491, PJSIP_SC_UNDECIPHERABLE = 493, PJSIP_SC_INTERNAL_SERVER_ERROR = 500, PJSIP_SC_NOT_IMPLEMENTED = 501, PJSIP_SC_BAD_GATEWAY = 502, PJSIP_SC_SERVICE_UNAVAILABLE = 503, PJSIP_SC_SERVER_TIMEOUT = 504, PJSIP_SC_VERSION_NOT_SUPPORTED = 505, PJSIP_SC_MESSAGE_TOO_LARGE = 513, PJSIP_SC_PRECONDITION_FAILURE = 580, PJSIP_SC_BUSY_EVERYWHERE = 600, PJSIP_SC_DECLINE = 603, PJSIP_SC_DOES_NOT_EXIST_ANYWHERE = 604, PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE = 606, PJSIP_SC_TSX_TIMEOUT = PJSIP_SC_REQUEST_TIMEOUT, PJSIP_SC_TSX_TRANSPORT_ERROR = PJSIP_SC_SERVICE_UNAVAILABLE, PJSIP_SC__force_32bit = 0x7FFFFFFF} pjsip_status_code;
|
||||
|
||||
typedef enum pjsip_hdr_e {PJSIP_H_ACCEPT, PJSIP_H_ACCEPT_ENCODING_UNIMP, PJSIP_H_ACCEPT_LANGUAGE_UNIMP, PJSIP_H_ALERT_INFO_UNIMP, PJSIP_H_ALLOW, PJSIP_H_AUTHENTICATION_INFO_UNIMP, PJSIP_H_AUTHORIZATION, PJSIP_H_CALL_ID, PJSIP_H_CALL_INFO_UNIMP, PJSIP_H_CONTACT, PJSIP_H_CONTENT_DISPOSITION_UNIMP, PJSIP_H_CONTENT_ENCODING_UNIMP, PJSIP_H_CONTENT_LANGUAGE_UNIMP, PJSIP_H_CONTENT_LENGTH, PJSIP_H_CONTENT_TYPE, PJSIP_H_CSEQ, PJSIP_H_DATE_UNIMP, PJSIP_H_ERROR_INFO_UNIMP, PJSIP_H_EXPIRES, PJSIP_H_FROM, PJSIP_H_IN_REPLY_TO_UNIMP, PJSIP_H_MAX_FORWARDS, PJSIP_H_MIME_VERSION_UNIMP, PJSIP_H_MIN_EXPIRES, PJSIP_H_ORGANIZATION_UNIMP, PJSIP_H_PRIORITY_UNIMP, PJSIP_H_PROXY_AUTHENTICATE, PJSIP_H_PROXY_AUTHORIZATION, PJSIP_H_PROXY_REQUIRE_UNIMP, PJSIP_H_RECORD_ROUTE, PJSIP_H_REPLY_TO_UNIMP, PJSIP_H_REQUIRE, PJSIP_H_RETRY_AFTER, PJSIP_H_ROUTE, PJSIP_H_SERVER_UNIMP, PJSIP_H_SUBJECT_UNIMP, PJSIP_H_SUPPORTED, PJSIP_H_TIMESTAMP_UNIMP, PJSIP_H_TO, PJSIP_H_UNSUPPORTED, PJSIP_H_USER_AGENT_UNIMP, PJSIP_H_VIA, PJSIP_H_WARNING_UNIMP, PJSIP_H_WWW_AUTHENTICATE, PJSIP_H_OTHER} pjsip_hdr_e;
|
||||
|
||||
typedef enum pjsip_transport_type_e {PJSIP_TRANSPORT_UNSPECIFIED, PJSIP_TRANSPORT_UDP, PJSIP_TRANSPORT_TCP, PJSIP_TRANSPORT_TLS, PJSIP_TRANSPORT_SCTP, PJSIP_TRANSPORT_LOOP, PJSIP_TRANSPORT_LOOP_DGRAM, PJSIP_TRANSPORT_START_OTHER, PJSIP_TRANSPORT_IPV6 = 128, PJSIP_TRANSPORT_UDP6 = PJSIP_TRANSPORT_UDP + PJSIP_TRANSPORT_IPV6, PJSIP_TRANSPORT_TCP6 = PJSIP_TRANSPORT_TCP + PJSIP_TRANSPORT_IPV6, PJSIP_TRANSPORT_TLS6 = PJSIP_TRANSPORT_TLS + PJSIP_TRANSPORT_IPV6} pjsip_transport_type_e;
|
||||
|
||||
enum pjsip_transport_flags_e {PJSIP_TRANSPORT_RELIABLE = 1, PJSIP_TRANSPORT_SECURE = 2, PJSIP_TRANSPORT_DATAGRAM = 4};
|
||||
|
||||
typedef enum pjsip_transport_state {PJSIP_TP_STATE_CONNECTED, PJSIP_TP_STATE_DISCONNECTED} pjsip_transport_state;
|
||||
|
||||
typedef enum pjsip_ssl_method {PJSIP_SSL_UNSPECIFIED_METHOD = 0, PJSIP_TLSV1_METHOD = 31, PJSIP_SSLV2_METHOD = 20, PJSIP_SSLV3_METHOD = 30, PJSIP_SSLV23_METHOD = 23} pjsip_ssl_method;
|
||||
|
||||
typedef enum pjsip_tsx_state_e {PJSIP_TSX_STATE_NULL, PJSIP_TSX_STATE_CALLING, PJSIP_TSX_STATE_TRYING, PJSIP_TSX_STATE_PROCEEDING, PJSIP_TSX_STATE_COMPLETED, PJSIP_TSX_STATE_CONFIRMED, PJSIP_TSX_STATE_TERMINATED, PJSIP_TSX_STATE_DESTROYED, PJSIP_TSX_STATE_MAX} pjsip_tsx_state_e;
|
||||
|
||||
typedef enum pjsip_role_e {PJSIP_ROLE_UAC, PJSIP_ROLE_UAS, PJSIP_UAC_ROLE = PJSIP_ROLE_UAC, PJSIP_UAS_ROLE = PJSIP_ROLE_UAS} pjsip_role_e;
|
||||
|
||||
typedef enum pjsip_redirect_op {PJSIP_REDIRECT_REJECT, PJSIP_REDIRECT_ACCEPT, PJSIP_REDIRECT_ACCEPT_REPLACE, PJSIP_REDIRECT_PENDING, PJSIP_REDIRECT_STOP} pjsip_redirect_op;
|
||||
|
||||
typedef enum pjrpid_activity {PJRPID_ACTIVITY_UNKNOWN, PJRPID_ACTIVITY_AWAY, PJRPID_ACTIVITY_BUSY} pjrpid_activity;
|
||||
|
||||
typedef enum pjsip_evsub_state {PJSIP_EVSUB_STATE_NULL, PJSIP_EVSUB_STATE_SENT, PJSIP_EVSUB_STATE_ACCEPTED, PJSIP_EVSUB_STATE_PENDING, PJSIP_EVSUB_STATE_ACTIVE, PJSIP_EVSUB_STATE_TERMINATED, PJSIP_EVSUB_STATE_UNKNOWN} pjsip_evsub_state;
|
||||
|
||||
typedef enum pjsip_inv_state {PJSIP_INV_STATE_NULL, PJSIP_INV_STATE_CALLING, PJSIP_INV_STATE_INCOMING, PJSIP_INV_STATE_EARLY, PJSIP_INV_STATE_CONNECTING, PJSIP_INV_STATE_CONFIRMED, PJSIP_INV_STATE_DISCONNECTED} pjsip_inv_state;
|
||||
|
||||
enum pjsua_invalid_id_const_ {PJSUA_INVALID_ID = -1};
|
||||
|
||||
typedef enum pjsua_state {PJSUA_STATE_NULL, PJSUA_STATE_CREATED, PJSUA_STATE_INIT, PJSUA_STATE_STARTING, PJSUA_STATE_RUNNING, PJSUA_STATE_CLOSING} pjsua_state;
|
||||
|
||||
typedef enum pjsua_stun_use {PJSUA_STUN_USE_DEFAULT, PJSUA_STUN_USE_DISABLED} pjsua_stun_use;
|
||||
|
||||
typedef enum pjsua_call_hold_type {PJSUA_CALL_HOLD_TYPE_RFC3264, PJSUA_CALL_HOLD_TYPE_RFC2543} pjsua_call_hold_type;
|
||||
|
||||
typedef int pjsua_acc_id;
|
||||
|
||||
typedef enum pjsua_destroy_flag {PJSUA_DESTROY_NO_RX_MSG = 1, PJSUA_DESTROY_NO_TX_MSG = 2, PJSUA_DESTROY_NO_NETWORK = PJSUA_DESTROY_NO_RX_MSG | PJSUA_DESTROY_NO_TX_MSG} pjsua_destroy_flag;
|
||||
|
||||
typedef enum pjsua_100rel_use {PJSUA_100REL_NOT_USED, PJSUA_100REL_MANDATORY, PJSUA_100REL_OPTIONAL} pjsua_100rel_use;
|
||||
|
||||
typedef enum pjsua_sip_timer_use {PJSUA_SIP_TIMER_INACTIVE, PJSUA_SIP_TIMER_OPTIONAL, PJSUA_SIP_TIMER_REQUIRED, PJSUA_SIP_TIMER_ALWAYS} pjsua_sip_timer_use;
|
||||
|
||||
typedef enum pjsua_ipv6_use {PJSUA_IPV6_DISABLED, PJSUA_IPV6_ENABLED} pjsua_ipv6_use;
|
||||
|
||||
typedef enum pjsua_buddy_status {PJSUA_BUDDY_STATUS_UNKNOWN, PJSUA_BUDDY_STATUS_ONLINE, PJSUA_BUDDY_STATUS_OFFLINE} pjsua_buddy_status;
|
||||
|
||||
typedef enum pjsua_call_media_status {PJSUA_CALL_MEDIA_NONE, PJSUA_CALL_MEDIA_ACTIVE, PJSUA_CALL_MEDIA_LOCAL_HOLD, PJSUA_CALL_MEDIA_REMOTE_HOLD, PJSUA_CALL_MEDIA_ERROR} pjsua_call_media_status;
|
||||
|
||||
typedef int pjsua_vid_win_id;
|
||||
|
||||
typedef int pjsua_call_id;
|
||||
|
||||
typedef enum pjsua_med_tp_st {PJSUA_MED_TP_NULL, PJSUA_MED_TP_CREATING, PJSUA_MED_TP_IDLE, PJSUA_MED_TP_INIT, PJSUA_MED_TP_RUNNING, PJSUA_MED_TP_DISABLED} pjsua_med_tp_st;
|
||||
|
||||
typedef enum pjsua_call_vid_strm_op {PJSUA_CALL_VID_STRM_NO_OP, PJSUA_CALL_VID_STRM_ADD, PJSUA_CALL_VID_STRM_REMOVE, PJSUA_CALL_VID_STRM_CHANGE_DIR, PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV, PJSUA_CALL_VID_STRM_START_TRANSMIT, PJSUA_CALL_VID_STRM_STOP_TRANSMIT, PJSUA_CALL_VID_STRM_SEND_KEYFRAME} pjsua_call_vid_strm_op;
|
||||
|
||||
typedef enum pjsua_vid_req_keyframe_method {PJSUA_VID_REQ_KEYFRAME_SIP_INFO = 1, PJSUA_VID_REQ_KEYFRAME_RTCP_PLI = 2} pjsua_vid_req_keyframe_method;
|
||||
|
||||
typedef enum pjsua_call_flag {PJSUA_CALL_UNHOLD = 1, PJSUA_CALL_UPDATE_CONTACT = 2, PJSUA_CALL_INCLUDE_DISABLED_MEDIA = 4} pjsua_call_flag;
|
||||
|
||||
typedef enum pjsua_create_media_transport_flag {PJSUA_MED_TP_CLOSE_MEMBER = 1} pjsua_create_media_transport_flag;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
pj/types.h pj_status_t pj_constants_ pj_uint8_t pj_int32_t pj_uint32_t pj_uint16_t
|
||||
pj/file_io.h pj_file_access
|
||||
pj/log.h pj_log_decoration
|
||||
pj/sock_qos.h pj_qos_type pj_qos_flag pj_qos_wmm_prio pj_qos_params
|
||||
pj/ssl_sock.h pj_ssl_cipher
|
||||
|
||||
pjnath/nat_detect.h pj_stun_nat_type
|
||||
pjnath/turn_session.h pj_turn_tp_type
|
||||
|
||||
pjmedia/event.h pjmedia_event_type
|
||||
pjmedia/transport_srtp.h pjmedia_srtp_use
|
||||
pjmedia/vid_stream.h pjmedia_vid_stream_rc_method
|
||||
pjmedia-videodev/videodev.h pjmedia_vid_dev_index pjmedia_vid_dev_std_index
|
||||
pjmedia-audiodev/audiodev.h pjmedia_aud_dev_route pjmedia_aud_dev_cap
|
||||
pjmedia/wav_port.h pjmedia_file_writer_option pjmedia_file_player_option
|
||||
pjmedia/types.h pjmedia_type pjmedia_dir pjmedia_tp_proto
|
||||
pjmedia/format.h pjmedia_format_id
|
||||
|
||||
pjsip/sip_auth.h pjsip_cred_data_type
|
||||
pjsip/sip_dialog.h pjsip_dialog_cap_status
|
||||
pjsip/sip_event.h pjsip_event_id_e
|
||||
pjsip/sip_msg.h pjsip_status_code pjsip_hdr_e
|
||||
pjsip/sip_transport.h pjsip_transport_type_e pjsip_transport_flags_e pjsip_transport_state
|
||||
pjsip/sip_transport_tls.h pjsip_ssl_method
|
||||
pjsip/sip_transaction.h pjsip_tsx_state_e
|
||||
pjsip/sip_types.h pjsip_role_e
|
||||
pjsip/sip_util.h pjsip_redirect_op
|
||||
|
||||
pjsip-simple/rpid.h pjrpid_activity
|
||||
pjsip-simple/evsub.h pjsip_evsub_state
|
||||
|
||||
pjsip-ua/sip_inv.h pjsip_inv_state
|
||||
|
||||
pjsua-lib/pjsua.h pjsua_invalid_id_const_ pjsua_state pjsua_stun_use pjsua_call_hold_type pjsua_acc_id pjsua_destroy_flag pjsua_100rel_use pjsua_sip_timer_use pjsua_ipv6_use pjsua_buddy_status pjsua_call_media_status pjsua_vid_win_id pjsua_call_id pjsua_med_tp_st pjsua_call_vid_strm_op pjsua_vid_req_keyframe_method pjsua_call_flag pjsua_create_media_transport_flag
|
|
@ -21,6 +21,7 @@ export PJSIP_LIB:=libpjsip-$(TARGET_NAME)$(LIBEXT)
|
|||
export PJSIP_UA_LIB:=libpjsip-ua-$(TARGET_NAME)$(LIBEXT)
|
||||
export PJSIP_SIMPLE_LIB:=libpjsip-simple-$(TARGET_NAME)$(LIBEXT)
|
||||
export PJSUA_LIB_LIB:=libpjsua-$(TARGET_NAME)$(LIBEXT)
|
||||
export PJSUA2_LIB_LIB=../lib/libpjsua2-$(TARGET_NAME)$(LIBEXT)
|
||||
|
||||
ifeq ($(PJ_SHARED_LIBRARIES),)
|
||||
else
|
||||
|
@ -32,6 +33,8 @@ export PJSIP_SIMPLE_SONAME := libpjsip-simple.$(SHLIB_SUFFIX)
|
|||
export PJSIP_SIMPLE_SHLIB := $(PJSIP_SIMPLE_SONAME).$(PJ_VERSION_MAJOR)
|
||||
export PJSUA_LIB_SONAME := libpjsua.$(SHLIB_SUFFIX)
|
||||
export PJSUA_LIB_SHLIB := $(PJSUA_LIB_SONAME).$(PJ_VERSION_MAJOR)
|
||||
export PJSUA2_LIB_SONAME := ../lib/libpjsua2.$(SHLIB_SUFFIX)
|
||||
export PJSUA2_LIB_SHLIB := $(PJSUA2_LIB_SONAME).$(PJ_VERSION_MAJOR)
|
||||
endif
|
||||
|
||||
###############################################################################
|
||||
|
@ -129,6 +132,31 @@ export PJSUA_LIB_LDFLAGS += $(PJSIP_UA_LDLIB) \
|
|||
|
||||
export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
|
||||
|
||||
###############################################################################
|
||||
# Defines for building pjsua2 library
|
||||
#
|
||||
export PJSUA2_LIB_SRCDIR = ../src/pjsua2
|
||||
export PJSUA2_LIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
|
||||
account.o endpoint.o json.o persistent.o types.o \
|
||||
siptypes.o call.o presence.o media.o
|
||||
export PJSUA2_LIB_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
|
||||
export PJSUA2_LIB_CXXFLAGS = $(PJSUA2_LIB_CFLAGS)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Defines for building pjsua2-test application
|
||||
#
|
||||
export PJSUA2_TEST_SRCDIR = ../src/pjsua2-test
|
||||
export PJSUA2_TEST_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
|
||||
main.o
|
||||
export PJSUA2_TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS)
|
||||
export PJSUA2_TEST_CXXFLAGS = $(PJSUA2_LIB_CFLAGS)
|
||||
export PJSUA2_TEST_LDFLAGS += -lpjsua2-$(TARGET_NAME) -lstdc++ $(PJ_LDFLAGS) $(PJ_LDLIBS) $(LDFLAGS)
|
||||
export PJSUA2_TEST_EXE := pjsua2-test-$(TARGET_NAME)$(HOST_EXE)
|
||||
|
||||
export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Defines for building test application
|
||||
#
|
||||
|
@ -166,7 +194,9 @@ TARGETS := $(PJSIP_LIB) $(PJSIP_SONAME) \
|
|||
$(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME) \
|
||||
$(PJSIP_UA_LIB) $(PJSIP_UA_SONAME) \
|
||||
$(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME) \
|
||||
$(TEST_EXE)
|
||||
$(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME) \
|
||||
$(TEST_EXE) \
|
||||
$(PJSUA2_TEST_EXE)
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
|
@ -190,7 +220,9 @@ distclean: realclean
|
|||
.PHONY: $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
|
||||
.PHONY: $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME)
|
||||
.PHONY: $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME)
|
||||
.PHONY: $(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME)
|
||||
.PHONY: $(TEST_EXE)
|
||||
.PHONY: $(PJSUA2_TEST_EXE)
|
||||
|
||||
pjsip: $(PJSIP_LIB)
|
||||
$(PJSIP_SONAME): $(PJSIP_LIB)
|
||||
|
@ -220,6 +252,14 @@ $(TEST_EXE): $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME)
|
|||
$(TEST_EXE):
|
||||
$(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $(subst /,$(HOST_PSEP),$(BINDIR)/$@)
|
||||
|
||||
pjsua2-test: $(PJSUA2_TEST_EXE)
|
||||
$(PJSUA2_TEST_EXE): $(PJSIP_LIB) $(PJSIP_SONAME)
|
||||
$(PJSUA2_TEST_EXE): $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
|
||||
$(PJSUA2_TEST_EXE): $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME)
|
||||
$(PJSUA2_TEST_EXE): $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME)
|
||||
$(PJSUA2_TEST_EXE): $(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME)
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_TEST app=pjsua2-test $(subst /,$(HOST_PSEP),$(BINDIR)/$@)
|
||||
|
||||
.PHONY: pjsip.ko
|
||||
pjsip.ko:
|
||||
echo Making $@
|
||||
|
@ -240,31 +280,45 @@ pjsua-lib.ko:
|
|||
echo Making $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $(subst /,$(HOST_PSEP),$(LIBDIR)/$@)
|
||||
|
||||
.PHONY: pjsua2-lib.ko
|
||||
pjsua2-lib.ko:
|
||||
echo Making $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $(subst /,$(HOST_PSEP),$(LIBDIR)/$@)
|
||||
|
||||
clean:
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP app=pjsip $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_UA app=pjsip-ua $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_SIMPLE app=pjsip-simple $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_TEST app=pjsua2-test $@
|
||||
|
||||
depend:
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP app=pjsip $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_UA app=pjsip-ua $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_SIMPLE app=pjsip-simple $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_TEST app=pjsua2-test $@
|
||||
echo '$(BINDIR)/$(TEST_EXE): $(PJMEDIA_LIB) $(LIBDIR)/$(PJSUA_LIB_LIB) $(LIBDIR)/$(PJSIP_SIMPLE_LIB) $(LIBDIR)/$(PJSIP_UA_LIB) $(LIBDIR)/$(PJSIP_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB)' >> .pjsip-test-$(TARGET_NAME).depend
|
||||
echo '$(BINDIR)/$(PJSUA2_TEST_EXE): $(PJSUA2_LIB_LIB) $(PJMEDIA_LIB) $(LIBDIR)/$(PJSUA_LIB_LIB) $(LIBDIR)/$(PJSIP_SIMPLE_LIB) $(LIBDIR)/$(PJSIP_UA_LIB) $(LIBDIR)/$(PJSIP_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB)' >> .pjsua2-test-$(TARGET_NAME).depend
|
||||
|
||||
realclean:
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsip-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsip-ua-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsip-simple-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsua-lib-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsua2-lib-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(subst @@,$(subst /,$(HOST_PSEP),.pjsua2-test-$(TARGET_NAME).depend),$(HOST_RMR))
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP app=pjsip $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_UA app=pjsip-ua $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSIP_SIMPLE app=pjsip-simple $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $@
|
||||
$(MAKE) -f $(RULES_MAK) APP=PJSUA2_TEST app=pjsua2-test $@
|
||||
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue