Created top-level directory tests and moved test-pjsua there. This will be the placeholder for future developed tests

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2392 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/tests/pjsua/README.TXT b/tests/pjsua/README.TXT
new file mode 100644
index 0000000..0475bac
--- /dev/null
+++ b/tests/pjsua/README.TXT
@@ -0,0 +1,65 @@
+

+                           PJSUA TEST FRAMEWORK

+                        =========================

+

+0. What is this

+---------------

+This is the automated testing scripts for pjsua. It can do many things (just 

+don't ask it to write good documentation :) ).

+

+

+1. Requirements

+---------------

+To run the tests you need:

+ - Python (tested with Python 2.5.2)

+ - pjsua application, built and placed in pjsip-apps/bin directory

+ - the pjsua must be built with:

+     - SRTP enabled (the default)

+

+

+2. Using

+--------

+To run all the tests:

+  $ python [OPTIONS] runall.py

+

+To run individual test:

+  $ python [OPTIONS] run.py MODULE CONFIG

+

+Where options:

+  -e EXE	use EXE as pjsua executable

+  -n		use null audio

+  -r TEST	(for runall.py only) resume test at TEST

+

+  

+For each individual tests, the run.py is the main entry for the test. It 

+imports the various inc_xxx.py files, and it will load the MODULE. The MODULE 

+contains specific test flows, and we have few of them:

+

+  - mod_run.py: 

+  	a simple test which just run pjsua with the configuration from CONFIG 

+	file and checks if pjsua can start properly.

+

+  - mod_call.py: 

+  	call testing where it spawns two pjsua instances each with configura-

+	tions as specified in CONFIG file, makes one pjsua call the other, and 

+	checks if the call can be established.

+

+  - mod_pres.py:

+  	presence testing

+

+  - mod_sendto.py:

+  	Simple UAC to send arbitrary SIP message to pjsua. Good to test

+	various incoming INVITE scenarios

+

+  - mod_media_playrec.py:

+  	Mainly for resampling quality testing

+

+  - mod_pesq.py

+  	Measure call quality of various call settings with PESQ, for people 

+	who have PESQ tool and license

+

+Example:

+  $ python run.py mod_run.py scripts-run/100_simple.py

+  $ python run.py mod_call.py scripts-call/100_simple.py

+

+

diff --git a/tests/pjsua/config_site.py b/tests/pjsua/config_site.py
new file mode 100644
index 0000000..a6e4f29
--- /dev/null
+++ b/tests/pjsua/config_site.py
@@ -0,0 +1,4 @@
+# $Id$
+
+# Specify if host has sound device, or test should be performed using sound device
+HAS_SND_DEV = 0
diff --git a/tests/pjsua/inc_cfg.py b/tests/pjsua/inc_cfg.py
new file mode 100644
index 0000000..d75c17f
--- /dev/null
+++ b/tests/pjsua/inc_cfg.py
@@ -0,0 +1,107 @@
+# $Id$
+import random
+import config_site
+
+DEFAULT_ECHO = True
+DEFAULT_TRACE = True
+DEFAULT_START_SIP_PORT = 50000
+
+# Shared vars
+ARGS = []		# arguments containing script module & config
+HAS_SND_DEV = config_site.HAS_SND_DEV
+
+# Individual pjsua instance configuration class
+class InstanceParam:
+	# Name to identify this pjsua instance (e.g. "caller", "callee", etc.)
+	name = ""
+	# pjsua command line arguments, concatenated in string
+	arg = ""
+	# Specify whether pjsua output should be echoed to stdout
+	echo_enabled = DEFAULT_ECHO
+	# Enable/disable test tracing
+	trace_enabled = DEFAULT_TRACE
+	# SIP URI to send request to this instance
+	uri = ""
+	# SIP port number, zero to automatically assign
+	sip_port = 0
+	# Does this have registration? If yes then the test function will
+	# wait until the UA is registered before doing anything else
+	have_reg = False
+	# Does this have PUBLISH?
+	have_publish = False
+	# Enable stdout buffer?
+	enable_buffer = False
+	def __init__(	self, 
+			name,			# Instance name
+			arg, 			# Cmd-line arguments
+			uri="", 		# URI
+			uri_param="",		# Additional URI param
+			sip_port=0, 		# SIP port
+			have_reg=False,		# Have registration?
+			have_publish=False,	# Have publish?
+			echo_enabled=DEFAULT_ECHO, 
+			trace_enabled=DEFAULT_TRACE,
+			enable_buffer = False):
+		# Instance name
+		self.name = name
+		# Give random sip_port if it's not specified
+		if sip_port==0:
+			self.sip_port = random.randint(DEFAULT_START_SIP_PORT, 65534)
+		else:
+			self.sip_port = sip_port
+		# Autogenerate URI if it's empty.
+		self.uri = uri
+		if self.uri=="":
+			self.uri = "sip:pjsip@127.0.0.1:" + str(self.sip_port)
+		# Add uri_param to the URI
+		self.uri = self.uri + uri_param
+		# Add bracket to the URI
+		if self.uri[0] != "<":
+			self.uri = "<" + self.uri + ">"
+		# Add SIP local port to the argument
+		self.arg = arg + " --local-port=" + str(self.sip_port)
+		self.have_reg = have_reg
+		self.have_publish = have_publish
+		if have_publish and have_reg and not ("--publish" in self.arg):
+			self.arg = self.arg + " --publish"
+		self.echo_enabled = echo_enabled
+		self.trace_enabled = trace_enabled
+		self.enable_buffer = enable_buffer
+
+
+############################################
+# Test parameter class
+class TestParam:
+	title = ""
+	# params is list containing InstanceParams objects
+	inst_params = []
+	# flag if this tes should be skipped
+	skip = None
+	# list of Expect instances, to be filled at run-time by
+        # the test program	
+	process = []
+	# the function for test body
+	test_func = None
+	post_func = None
+	def __init__(	self, 
+			title, 		# Test title
+			inst_params, 	# InstanceParam's as list
+			func=None,
+			skip=False,
+			post_func=None,
+			need_stdout_buffer=False):
+		self.title = title
+		self.inst_params = inst_params
+		self.skip = skip
+		self.test_func = func
+		self.post_func = post_func
+
+
+###################################
+# TestError exception
+class TestError:
+	desc = ""
+	def __init__(self, desc):
+		self.desc = desc
+
+
diff --git a/tests/pjsua/inc_const.py b/tests/pjsua/inc_const.py
new file mode 100644
index 0000000..1f6d84d
--- /dev/null
+++ b/tests/pjsua/inc_const.py
@@ -0,0 +1,61 @@
+# $Id$
+# Useful constants
+
+
+##########################
+# MENU OUTPUT
+#
+
+
+##########################
+# EVENTS
+#
+
+# Text to expect when there is incoming call
+EVENT_INCOMING_CALL = "Press .* answer"
+
+
+##########################
+# CALL STATES
+#
+
+# Call state is CALLING
+STATE_CALLING = "state.*CALLING"
+# Call state is CONFIRMED
+STATE_CONFIRMED = "state.*CONFIRMED"
+# Call state is DISCONNECTED
+STATE_DISCONNECTED = "Call .* DISCONNECTED"
+
+# Media call is put on-hold
+MEDIA_HOLD = "Media for call [0-9]+ is suspended.*hold"
+# Media call is active
+MEDIA_ACTIVE = "Media for call [0-9]+ is active"
+# RX_DTMF
+RX_DTMF = "Incoming DTMF on call [0-9]+: "
+
+##########################
+# MEDIA
+#
+
+# Connecting/disconnecting ports
+MEDIA_CONN_PORT_SUCCESS = "Port \d+ \(.+\) transmitting to port"
+MEDIA_DISCONN_PORT_SUCCESS = "Port \d+ \(.+\) stop transmitting to port"
+
+# Filename to play / record
+MEDIA_PLAY_FILE = "--play-file\s+(\S+)"
+MEDIA_REC_FILE = "--rec-file\s+(\S+)"
+
+##########################
+# MISC
+#
+
+# The command prompt
+PROMPT = ">>>"
+# When pjsua has been destroyed
+DESTROYED = "PJSUA destroyed"
+# Assertion failure
+ASSERT = "Assertion failed"
+# Stdout refresh text
+STDOUT_REFRESH = "XXSTDOUT_REFRESHXX"
+
+
diff --git a/tests/pjsua/inc_sdp.py b/tests/pjsua/inc_sdp.py
new file mode 100644
index 0000000..97dfdc7
--- /dev/null
+++ b/tests/pjsua/inc_sdp.py
@@ -0,0 +1,38 @@
+# $Id$
+
+# SDP template
+sdp_templ = \
+"""v=0\r
+o=- 1 1 $NET_TYPE $ADDR_TYPE $LOCAL_IP\r
+s=pjmedia\r
+t=0 0\r
+$SDP_LINES"""
+
+sdp_media_templ = \
+"""m=$MEDIA_TYPE $PORT $TRANSPORT 0\r
+c=$NET_TYPE $ADDR_TYPE $LOCAL_IP\r
+$SDP_LINES"""
+
+# Create SDP session
+def session(local_ip="127.0.0.1", extra_lines="", net_type="IN", addr_type="IP4"):
+	sdp = sdp_templ
+	sdp = sdp.replace("$NET_TYPE", net_type)
+	sdp = sdp.replace("$ADDR_TYPE", addr_type)
+	sdp = sdp.replace("$LOCAL_IP", local_ip)
+	sdp = sdp.replace("$SDP_LINES", extra_lines)
+	return sdp
+
+# Create basic SDP media
+def media(media_type="audio", local_port=4000, local_ip="127.0.0.1", extra_lines="", 
+			  net_type = "IN", addr_type="IP4", transport="RTP/AVP"):
+	sdp = sdp_media_templ
+	sdp = sdp.replace("$MEDIA_TYPE", media_type)
+	sdp = sdp.replace("$LOCAL_IP", local_ip)
+	sdp = sdp.replace("$PORT", str(local_port))
+	sdp = sdp.replace("$NET_TYPE", net_type)
+	sdp = sdp.replace("$ADDR_TYPE", addr_type)
+	sdp = sdp.replace("$TRANSPORT", transport)
+	sdp = sdp.replace("$SDP_LINES", extra_lines)
+	return sdp
+
+
diff --git a/tests/pjsua/inc_sip.py b/tests/pjsua/inc_sip.py
new file mode 100644
index 0000000..6d5ef05
--- /dev/null
+++ b/tests/pjsua/inc_sip.py
@@ -0,0 +1,330 @@
+# $Id$
+#
+from socket import *
+import re
+import random
+import time
+import sys
+import inc_cfg as cfg
+from select import *
+
+# SIP request template
+req_templ = \
+"""$METHOD $TARGET_URI SIP/2.0\r
+Via: SIP/2.0/UDP $LOCAL_IP:$LOCAL_PORT;rport;branch=z9hG4bK$BRANCH\r
+Max-Forwards: 70\r
+From: <sip:caller@pjsip.org>$FROM_TAG\r
+To: <$TARGET_URI>$TO_TAG\r
+Contact: <sip:$LOCAL_IP:$LOCAL_PORT;transport=udp>\r
+Call-ID: $CALL_ID@pjsip.org\r
+CSeq: $CSEQ $METHOD\r
+Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, REFER\r
+Supported: replaces, 100rel, norefersub\r
+User-Agent: pjsip.org Python tester\r
+Content-Length: $CONTENT_LENGTH\r
+$SIP_HEADERS"""
+
+
+def is_request(msg):
+	return msg.split(" ", 1)[0] != "SIP/2.0"
+	
+def is_response(msg):
+	return msg.split(" ", 1)[0] == "SIP/2.0"
+
+def get_code(msg):
+	if msg=="":
+		return 0
+	return int(msg.split(" ", 2)[1])
+
+def get_tag(msg, hdr="To"):
+	pat = "^" + hdr + ":.*"
+	result = re.search(pat, msg, re.M | re.I)
+	if result==None:
+		return ""
+	line = result.group()
+	#print "line=", line
+	tags = line.split(";tag=")
+	if len(tags)>1:
+		return tags[1]
+	return ""
+	#return re.split("[;& ]", s)
+
+def get_header(msg, hname):
+	headers = msg.splitlines()
+	for hdr in headers:
+		hfields = hdr.split(": ", 2)
+		if hfields[0]==hname:
+			return hfields[1]
+	return None
+
+class Dialog:
+	sock = None
+	dst_addr = ""
+	dst_port = 5060
+	local_ip = ""
+	local_port = 0
+	tcp = False
+	call_id = str(random.random())
+	cseq = 0
+	local_tag = ";tag=" + str(random.random())
+	rem_tag = ""
+	last_resp_code = 0
+	inv_branch = ""
+	trace_enabled = True
+	last_request = ""
+	def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True, local_port=0):
+		self.dst_addr = dst_addr
+		self.dst_port = dst_port
+		self.tcp = tcp
+		self.trace_enabled = trace
+		if tcp==True:
+			self.sock = socket(AF_INET, SOCK_STREAM)
+			self.sock.connect(dst_addr, dst_port)
+		else:
+			self.sock = socket(AF_INET, SOCK_DGRAM)
+			self.sock.bind(("127.0.0.1", local_port))
+		
+		self.local_ip, self.local_port = self.sock.getsockname()
+		self.trace("Dialog socket bound to " + self.local_ip + ":" + str(self.local_port))
+
+	def trace(self, txt):
+		if self.trace_enabled:
+			print str(time.strftime("%H:%M:%S ")) + txt
+
+	def create_req(self, method, sdp, branch="", extra_headers=""):
+		if branch=="":
+			self.cseq = self.cseq + 1
+		msg = req_templ
+		msg = msg.replace("$METHOD", method)
+		if self.tcp:
+			transport_param = ";transport=tcp"
+		else:
+			transport_param = ""
+		msg = msg.replace("$TARGET_URI", "sip:"+self.dst_addr+":"+str(self.dst_port) + transport_param)
+		msg = msg.replace("$LOCAL_IP", self.local_ip)
+		msg = msg.replace("$LOCAL_PORT", str(self.local_port))
+		if branch=="":
+			branch=str(random.random())
+		msg = msg.replace("$BRANCH", branch)
+		msg = msg.replace("$FROM_TAG", self.local_tag)
+		msg = msg.replace("$TO_TAG", self.rem_tag)
+		msg = msg.replace("$CALL_ID", self.call_id)
+		msg = msg.replace("$CSEQ", str(self.cseq))
+		msg = msg.replace("$SIP_HEADERS", extra_headers)
+		if sdp!="":
+			msg = msg.replace("$CONTENT_LENGTH", str(len(sdp)))
+			msg = msg + "Content-Type: application/sdp\r\n"
+		else:
+			msg = msg.replace("$CONTENT_LENGTH", "0")
+		msg = msg + "\r\n"
+		msg = msg + sdp
+		return msg
+
+	def create_response(self, request, code, reason, to_tag=""):
+		response = "SIP/2.0 " + str(code) + " " + reason + "\r\n"
+		lines = request.splitlines()
+		for line in lines:
+			hdr = line.split(":", 1)[0]
+			if hdr in ["Via", "From", "To", "CSeq", "Call-ID"]:
+				if hdr=="To" and to_tag!="":
+					line = line + ";tag=" + to_tag
+				elif hdr=="Via":
+					line = line + ";received=127.0.0.1"
+				response = response + line + "\r\n"
+		return response
+
+	def create_invite(self, sdp, extra_headers=""):
+		self.inv_branch = str(random.random())
+		return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers)
+
+	def create_ack(self, sdp="", extra_headers=""):
+		return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch)
+
+	def create_bye(self, extra_headers=""):
+		return self.create_req("BYE", "", extra_headers)
+
+	def send_msg(self, msg, dst_addr=None):
+		if (is_request(msg)):
+			self.last_request = msg.split(" ", 1)[0]
+		if not dst_addr:
+			dst_addr = (self.dst_addr, self.dst_port)
+		self.trace("============== TX MSG to " + str(dst_addr) + " ============= \n" + msg)
+		self.sock.sendto(msg, 0, dst_addr)
+
+	def wait_msg_from(self, timeout):
+		endtime = time.time() + timeout
+		msg = ""
+		src_addr = None
+		while time.time() < endtime:
+			readset = select([self.sock], [], [], timeout)
+			if len(readset) < 1 or not self.sock in readset[0]:
+				if len(readset) < 1:
+					print "select() returns " + str(len(readset))
+				elif not self.sock in readset[0]:
+					print "select() alien socket"
+				else:
+					print "select other error"
+				continue
+			try:
+				msg, src_addr = self.sock.recvfrom(2048)
+			except:
+				print "recv() exception: ", sys.exc_info()[0]
+				continue
+
+		if msg=="":
+			return "", None
+		if self.last_request=="INVITE" and self.rem_tag=="":
+			self.rem_tag = get_tag(msg, "To")
+			self.rem_tag = self.rem_tag.rstrip("\r\n;")
+			if self.rem_tag != "":
+				self.rem_tag = ";tag=" + self.rem_tag
+			self.trace("=== rem_tag:" + self.rem_tag)
+		self.trace("=========== RX MSG from " + str(src_addr) +  " ===========\n" + msg)
+		return (msg, src_addr)
+	
+	def wait_msg(self, timeout):
+		return self.wait_msg_from(timeout)[0]
+		
+	# Send request and wait for final response
+	def send_request_wait(self, msg, timeout):
+		t1 = 1.0
+		endtime = time.time() + timeout
+		resp = ""
+		code = 0
+		for i in range(0,5):
+			self.send_msg(msg)
+			resp = self.wait_msg(t1)
+			if resp!="" and is_response(resp):
+				code = get_code(resp)
+				break
+		last_resp = resp
+		while code < 200 and time.time() < endtime:
+			resp = self.wait_msg(endtime - time.time())
+			if resp != "" and is_response(resp):
+				code = get_code(resp)
+				last_resp = resp
+			elif resp=="":
+				break
+		return last_resp
+	
+	def hangup(self, last_code=0):
+		self.trace("====== hangup =====")
+		if last_code!=0:
+			self.last_resp_code = last_code
+		if self.last_resp_code>0 and self.last_resp_code<200:
+			msg = self.create_req("CANCEL", "", branch=self.inv_branch, extra_headers="")
+			self.send_request_wait(msg, 5)
+			msg = self.create_ack()
+			self.send_msg(msg)
+		elif self.last_resp_code>=200 and self.last_resp_code<300:
+			msg = self.create_ack()
+			self.send_msg(msg)
+			msg = self.create_bye()
+			self.send_request_wait(msg, 5)
+		else:
+			msg = self.create_ack()
+			self.send_msg(msg)
+
+
+class SendtoCfg:
+	# Test name
+	name = ""
+	# pjsua InstanceParam
+	inst_param = None
+	# Complete INVITE message. If this is not empty, then this
+	# message will be sent instead and the "sdp" and "extra_headers"
+	# settings will be ignored.
+	complete_msg = ""
+	# Initial SDP
+	sdp = ""
+	# Extra headers to add to request
+	extra_headers = ""
+	# Expected code
+	resp_code = 0
+	# Use TCP?
+	use_tcp = False
+	# List of RE patterns that must exist in response
+	resp_include = []
+	# List of RE patterns that must NOT exist in response
+	resp_exclude = []
+	# Constructor
+	def __init__(self, name, pjsua_args, sdp, resp_code, 
+		     resp_inc=[], resp_exc=[], use_tcp=False,
+		     extra_headers="", complete_msg="",
+		     enable_buffer = False):
+	 	self.complete_msg = complete_msg
+		self.sdp = sdp
+		self.resp_code = resp_code
+		self.resp_include = resp_inc
+		self.resp_exclude = resp_exc
+		self.use_tcp = use_tcp
+		self.extra_headers = extra_headers
+		self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
+		self.inst_param.enable_buffer = enable_buffer 
+
+
+class RecvfromTransaction:
+	# The test title for this transaction
+	title = ""
+	# Optinal list of pjsua command and optional expect patterns 
+	# to be invoked to make pjsua send a request
+	# Sample:
+	#	(to make call and wait for INVITE to be sent)
+	#	cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"]  ]
+	cmds = []
+	# Check if the CSeq must be greater than last Cseq?
+	check_cseq = True
+	# List of RE patterns that must exists in incoming request
+	include = []
+	# List of RE patterns that MUST NOT exist in incoming request
+	exclude = []
+	# Response code to send
+	resp_code = 0
+	# Additional list of headers to be sent on the response
+	# Note: no need to add CRLF on the header
+	resp_hdr = []
+	# Message body. This should include the Content-Type header too.
+	# Sample:
+	#	body = """Content-Type: application/sdp\r\n
+	#	\r\n
+	#	v=0\r\n
+	#	...
+	#	"""
+	body = None
+	# Pattern to be expected on pjsua when receiving the response
+	expect = ""
+	
+	def __init__(self, title, resp_code, check_cseq=True,
+			include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect=""):
+		self.title = title
+		self.cmds = cmds
+		self.include = include
+		self.exclude = exclude
+		self.resp_code = resp_code
+		self.resp_hdr = resp_hdr
+		self.resp_body = resp_body
+		self.expect = expect
+			
+
+class RecvfromCfg:
+	# Test name
+	name = ""
+	# pjsua InstanceParam
+	inst_param = None
+	# List of RecvfromTransaction
+	transaction = None
+	# Use TCP?
+	tcp = False
+
+	# Note:
+	#  Any "$PORT" string in the pjsua_args will be replaced
+	#  by server port
+	def __init__(self, name, pjsua_args, transaction, tcp=False):
+		self.name = name
+		self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
+		self.transaction = transaction
+		self.tcp=tcp
+
+
+
+
diff --git a/tests/pjsua/mod_call.py b/tests/pjsua/mod_call.py
new file mode 100644
index 0000000..7081aed
--- /dev/null
+++ b/tests/pjsua/mod_call.py
@@ -0,0 +1,226 @@
+# $Id$
+import time
+import imp
+import sys
+import inc_const as const
+from inc_cfg import *
+
+# Load configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# Check media flow between ua1 and ua2
+def check_media(ua1, ua2):
+	ua1.send("#")
+	ua1.expect("#")
+	ua1.send("1122")
+	ua2.expect(const.RX_DTMF + "1")
+	ua2.expect(const.RX_DTMF + "1")
+	ua2.expect(const.RX_DTMF + "2")
+	ua2.expect(const.RX_DTMF + "2")
+
+
+# Test body function
+def test_func(t):
+	callee = t.process[0]
+	caller = t.process[1]
+
+	# if have_reg then wait for couple of seconds for PUBLISH
+	# to complete (just in case pUBLISH is used)
+	if callee.inst_param.have_reg:
+		time.sleep(1)
+	if caller.inst_param.have_reg:
+		time.sleep(1)
+
+	# Caller making call
+	caller.send("m")
+	caller.send(t.inst_params[0].uri)
+	caller.expect(const.STATE_CALLING)
+	
+	# Callee waits for call and answers with 180/Ringing
+	time.sleep(0.2)
+	callee.expect(const.EVENT_INCOMING_CALL)
+	callee.send("a")
+	callee.send("180")
+	callee.expect("SIP/2.0 180")
+	caller.expect("SIP/2.0 180")
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Callee answers with 200/OK
+	callee.send("a")
+	callee.send("200")
+
+	# Wait until call is connected in both endpoints
+	time.sleep(0.2)
+	caller.expect(const.STATE_CONFIRMED)
+	callee.expect(const.STATE_CONFIRMED)
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+	time.sleep(0.1)
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Test that media is okay
+	time.sleep(0.3)
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# Hold call by caller
+	caller.send("H")
+	caller.expect("INVITE sip:")
+	callee.expect("INVITE sip:")
+	caller.expect(const.MEDIA_HOLD)
+	callee.expect(const.MEDIA_HOLD)
+	
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Release hold
+	time.sleep(0.5)
+	caller.send("v")
+	caller.expect("INVITE sip:")
+	callee.expect("INVITE sip:")
+	caller.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
+	callee.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Test that media is okay
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Hold call by callee
+	callee.send("H")
+	callee.expect("INVITE sip:")
+	caller.expect("INVITE sip:")
+	caller.expect(const.MEDIA_HOLD)
+	callee.expect(const.MEDIA_HOLD)
+	
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Release hold
+	time.sleep(0.1)
+	callee.send("v")
+	callee.expect("INVITE sip:")
+	caller.expect("INVITE sip:")
+	callee.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
+	caller.expect(const.MEDIA_ACTIVE, title="waiting for media active after call hold")
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Test that media is okay
+	# Wait for some time for ICE negotiation
+	time.sleep(0.6)
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# UPDATE (by caller)
+	caller.send("U")
+	#caller.sync_stdout()
+	callee.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
+	caller.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
+	
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Test that media is okay
+	time.sleep(0.1)
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# UPDATE (by callee)
+	callee.send("U")
+	callee.expect("UPDATE sip:")
+	caller.expect("UPDATE sip:")
+	caller.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
+	callee.expect(const.MEDIA_ACTIVE, title="waiting for media active with UPDATE")
+	
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Test that media is okay
+	time.sleep(0.1)
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# Synchronize stdout
+	caller.sync_stdout()
+	callee.sync_stdout()
+
+	# Set codecs in both caller and callee so that there is
+	# no common codec between them.
+	# In caller we only enable PCMU, in callee we only enable PCMA
+	caller.send("Cp")
+	caller.expect("Enter codec")
+	caller.send("* 0")
+	caller.send("Cp")
+	caller.expect("Enter codec")
+	caller.send("pcmu 120")
+	
+	callee.send("Cp")
+	callee.expect("Enter codec")
+	callee.send("* 0")
+	callee.send("Cp")
+	callee.expect("Enter codec")
+	callee.send("pcma 120")
+
+	# Test when UPDATE fails (by callee)
+	callee.send("U")
+	caller.expect("SIP/2.0 488")
+	callee.expect("SIP/2.0 488")
+	callee.sync_stdout()
+	caller.sync_stdout()
+	
+	# Test that media is still okay
+	time.sleep(0.1)
+	check_media(caller, callee)
+	check_media(callee, caller)
+
+	# Test when UPDATE fails (by caller)
+	caller.send("U")
+	caller.expect("UPDATE sip:")
+	callee.expect("UPDATE sip:")
+	callee.expect("SIP/2.0 488")
+	caller.expect("SIP/2.0 488")
+	caller.sync_stdout()
+	callee.sync_stdout()
+	
+	# Test that media is still okay
+	time.sleep(0.1)
+	check_media(callee, caller)
+	check_media(caller, callee)
+
+	# Hangup call
+	time.sleep(0.1)
+	caller.send("h")
+
+	# Wait until calls are cleared in both endpoints
+	caller.expect(const.STATE_DISCONNECTED)
+	callee.expect(const.STATE_DISCONNECTED)
+	
+
+# Here where it all comes together
+test = cfg_file.test_param
+test.test_func = test_func
+
diff --git a/tests/pjsua/mod_media_playrec.py b/tests/pjsua/mod_media_playrec.py
new file mode 100644
index 0000000..ef6ed79
--- /dev/null
+++ b/tests/pjsua/mod_media_playrec.py
@@ -0,0 +1,101 @@
+# $Id$
+
+# PLAYFILE -> RECFILE:
+# Input file is played and is recorded to output, then compare them.
+# Useful to tes clock rates compatibility and resample quality
+#	null-audio
+#	port 1: wav file input xxxxxx.clock_rate.wav, e.g: test1.8.wav
+#	port 2: wav file ouput xxxxxx.clock_rate.wav, e.g: res1.8.wav
+#	wav input must be more than 3 seconds long
+
+import time
+import imp
+import sys
+import re
+import subprocess
+import inc_const as const
+from inc_cfg import *
+
+# Load configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# WAV similarity calculator
+COMPARE_WAV_EXE = "tools/cmp_wav.exe"
+
+# Threshold to declare degradation is too high when result is lower than this value
+COMPARE_THRESHOLD = 2
+
+# COMPARE params
+input_filename	= ""			# Input filename
+output_filename = ""			# Output filename
+
+# Test body function
+def test_func(t):
+	global input_filename
+	global output_filename
+
+	endpt = t.process[0]
+	
+	# Get input file name
+	input_filename = re.compile(const.MEDIA_PLAY_FILE).search(endpt.inst_param.arg).group(1)
+	endpt.trace("Input file = " + input_filename)
+
+	# Get output file name
+	output_filename = re.compile(const.MEDIA_REC_FILE).search(endpt.inst_param.arg).group(1)
+	endpt.trace("Output file = " + output_filename)
+
+	# Find appropriate clock rate for the input file
+	clock_rate = re.compile(".+(\.\d+\.wav)$").match(output_filename).group(1)
+	if (clock_rate==None):
+		endpt.trace("Cannot compare input & output, incorrect output filename format")
+		return
+	input_filename = re.sub("\.\d+\.wav$", clock_rate, input_filename)
+	endpt.trace("WAV file to be compared with output = " + input_filename)
+
+	# Connect input-output file
+	endpt.sync_stdout()
+
+	endpt.send("cc 1 2")
+	endpt.expect(const.MEDIA_CONN_PORT_SUCCESS)
+
+	# Wait
+	time.sleep(3)
+
+	endpt.sync_stdout()
+
+	# Disconnect input-output file
+	endpt.send("cd 1 2")
+	endpt.expect(const.MEDIA_DISCONN_PORT_SUCCESS)
+
+
+# Post body function
+def post_func(t):
+	global input_filename
+	global output_filename
+
+	endpt = t.process[0]
+
+	# Check WAV similarity
+	fullcmd = COMPARE_WAV_EXE + " " + input_filename + " " + output_filename + " " + "3000"
+	endpt.trace("Popen " + fullcmd)
+	cmp_proc = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, universal_newlines=True)
+
+	# Parse similarity ouput
+	line = cmp_proc.stdout.readline()
+	mo_sim_val = re.match(".+=\s+(\d+)", line)
+	if (mo_sim_val == None):
+		raise TestError("Error comparing WAV files")
+		return
+
+	# Evaluate the similarity value
+	sim_val = mo_sim_val.group(1)
+	if (sim_val >= COMPARE_THRESHOLD):
+		endpt.trace("WAV similarity = " + sim_val)
+	else:
+		raise TestError("WAV degraded heavily, similarity = " + sim_val)
+
+
+# Here where it all comes together
+test = cfg_file.test_param
+test.test_func = test_func
+test.post_func = post_func
diff --git a/tests/pjsua/mod_pesq.py b/tests/pjsua/mod_pesq.py
new file mode 100644
index 0000000..2e86d98
--- /dev/null
+++ b/tests/pjsua/mod_pesq.py
@@ -0,0 +1,166 @@
+# $Id$
+
+# Quality test of media calls.
+# - UA1 calls UA2
+# - UA1 plays a file until finished to be streamed to UA2
+# - UA2 records from stream
+# - Apply PESQ to played file (reference) and recorded file (degraded)
+#
+# File should be:
+# - naming: xxxxxx.CLOCK_RATE.wav, e.g: test1.8.wav
+# - clock-rate of those files can only be 8khz or 16khz
+
+import time
+import imp
+import sys
+import re
+import subprocess
+import wave
+import shutil
+import inc_const as const
+
+from inc_cfg import *
+
+# Load configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# PESQ configs
+PESQ = "tools/pesq.exe"			# PESQ executable path
+PESQ_DEFAULT_THRESHOLD = 3.4		# Default minimum acceptable PESQ MOS value
+
+# PESQ params
+pesq_sample_rate_opt = ""		# Sample rate option for PESQ
+input_filename	= ""			# Input/Reference filename
+output_filename = ""			# Output/Degraded filename
+
+
+# Test body function
+def test_func(t):
+	global pesq_sample_rate_opt
+	global input_filename
+	global output_filename
+
+	ua1 = t.process[0]
+	ua2 = t.process[1]
+
+	# Get input file name
+	input_filename = re.compile(const.MEDIA_PLAY_FILE).search(ua1.inst_param.arg).group(1)
+
+	# Get output file name
+	output_filename = re.compile(const.MEDIA_REC_FILE).search(ua2.inst_param.arg).group(1)
+
+	# Get WAV input length, in seconds
+	fin = wave.open(input_filename, "r")
+	if fin == None:
+		raise TestError("Failed opening input WAV file")
+	inwavlen = fin.getnframes() * 1.0 / fin.getframerate()
+	inwavlen += 0.2
+	fin.close()
+	print "WAV input len = " + str(inwavlen) + "s"
+
+	# Get clock rate of the output
+	mo_clock_rate = re.compile("\.(\d+)\.wav").search(output_filename)
+	if (mo_clock_rate==None):
+		raise TestError("Cannot compare input & output, incorrect output filename format")
+	clock_rate = mo_clock_rate.group(1)
+	
+	# Get channel count of the output
+	channel_count = 1
+	if re.search("--stereo", ua2.inst_param.arg) != None:
+		channel_count = 2
+	
+	# Get matched input file from output file
+	# (PESQ evaluates only files whose same clock rate & channel count)
+	if channel_count == 2:
+	    if re.search("\.\d+\.\d+\.wav", input_filename) != None:
+		    input_filename = re.sub("\.\d+\.\d+\.wav", "." + str(channel_count) + "."+clock_rate+".wav", input_filename)
+	    else:
+		    input_filename = re.sub("\.\d+\.wav", "." + str(channel_count) + "."+clock_rate+".wav", input_filename)
+
+	if (clock_rate != "8") & (clock_rate != "16"):
+		raise TestError("PESQ only works on clock rate 8kHz or 16kHz, clock rate used = "+clock_rate+ "kHz")
+
+	# Get conference clock rate of UA2 for PESQ sample rate option
+	pesq_sample_rate_opt = "+" + clock_rate + "000"
+
+	# UA1 making call
+	ua1.send("m")
+	ua1.send(t.inst_params[1].uri)
+	ua1.expect(const.STATE_CALLING)
+
+	# UA2 wait until call established
+	ua2.expect(const.STATE_CONFIRMED)
+
+	ua1.sync_stdout()
+	ua2.sync_stdout()
+	time.sleep(2)
+
+	# Disconnect mic -> rec file, to avoid echo recorded when using sound device
+	# Disconnect stream -> spk, make it silent
+	# Connect stream -> rec file, start recording
+	ua2.send("cd 0 1\ncd 4 0\ncc 4 1")
+
+	# Disconnect mic -> stream, make stream purely sending from file
+	# Disconnect stream -> spk, make it silent
+	# Connect file -> stream, start sending
+	ua1.send("cd 0 4\ncd 4 0\ncc 1 4")
+
+	time.sleep(inwavlen)
+
+	# Disconnect files from bridge
+	ua2.send("cd 4 1")
+	ua2.expect(const.MEDIA_DISCONN_PORT_SUCCESS)
+	ua1.send("cd 1 4")
+	ua1.expect(const.MEDIA_DISCONN_PORT_SUCCESS)
+
+
+# Post body function
+def post_func(t):
+	global pesq_sample_rate_opt
+	global input_filename
+	global output_filename
+
+	endpt = t.process[0]
+
+	# Execute PESQ
+	fullcmd = PESQ + " " + pesq_sample_rate_opt + " " + input_filename + " " + output_filename
+	endpt.trace("Popen " + fullcmd)
+	pesq_proc = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, universal_newlines=True)
+	pesq_out  = pesq_proc.communicate()
+
+	# Parse ouput
+	mo_pesq_out = re.compile("Prediction[^=]+=\s+([\-\d\.]+)\s*").search(pesq_out[0])
+	if (mo_pesq_out == None):
+		raise TestError("Failed to fetch PESQ result")
+
+	# Get threshold
+	if (cfg_file.pesq_threshold != None) | (cfg_file.pesq_threshold > -0.5 ):
+		threshold = cfg_file.pesq_threshold
+	else:
+		threshold = PESQ_DEFAULT_THRESHOLD
+
+	# Evaluate the PESQ MOS value
+	pesq_res = mo_pesq_out.group(1)
+	if (float(pesq_res) >= threshold):
+		endpt.trace("Success, PESQ result = " + pesq_res + " (target=" + str(threshold) + ").")
+	else:
+		endpt.trace("Failed, PESQ result = " + pesq_res + " (target=" + str(threshold) + ").")
+		# Save the wav file
+		wavoutname = ARGS[1]
+		wavoutname = re.sub("[\\\/]", "_", wavoutname)
+		wavoutname = re.sub("\.py$", ".wav", wavoutname)
+		wavoutname = "logs/" + wavoutname
+		try:
+			shutil.copyfile(output_filename, wavoutname)
+			print "Output WAV is copied to " + wavoutname
+		except:
+			print "Couldn't copy output WAV, please check if 'logs' directory exists."
+
+		raise TestError("WAV seems to be degraded badly, PESQ = "+ pesq_res + " (target=" + str(threshold) + ").")
+
+
+# Here where it all comes together
+test = cfg_file.test_param
+test.test_func = test_func
+test.post_func = post_func
+
diff --git a/tests/pjsua/mod_pres.py b/tests/pjsua/mod_pres.py
new file mode 100644
index 0000000..7dafd52
--- /dev/null
+++ b/tests/pjsua/mod_pres.py
@@ -0,0 +1,125 @@
+# $Id$
+import time
+import imp
+import sys
+import inc_const as const
+from inc_cfg import *
+
+# Load configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+
+# Test body function
+def test_func(t):
+	u1 = t.process[0]
+	uri1 = cfg_file.test_param.inst_params[0].uri
+	acc1 = "-1"
+	u2 = t.process[1]
+	uri2 = cfg_file.test_param.inst_params[1].uri
+	acc2 = "-1"
+
+	# if have_reg then wait for couple of seconds for PUBLISH
+	# to complete (just in case pUBLISH is used)
+	if u1.inst_param.have_reg:
+		time.sleep(1)
+	if u2.inst_param.have_reg:
+		time.sleep(1)
+
+	# U1 adds U2 as buddy
+	u1.send("+b")
+	u1.send(uri2)
+	u1.expect("Subscription state changed NULL --> SENT")
+	u1.expect("Presence subscription.*is ACCEPTED")
+	if not u2.inst_param.have_publish:
+		# Process incoming SUBSCRIBE in U2
+		# Finds out which account gets the subscription in U2
+		line = u2.expect("pjsua_pres.*subscription.*using account")
+		acc2 = line.split("using account ")[1]
+	# wait until we've got Online notification
+	u1.expect(uri2 + ".*Online")
+
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# U2 adds U1 as buddy
+	u2.send("+b")
+	u2.send(uri1)
+	u2.expect("Subscription state changed NULL --> SENT")
+	u2.expect("Presence subscription.*is ACCEPTED")
+	if not u1.inst_param.have_publish:
+		# Process incoming SUBSCRIBE in U1
+		# Finds out which account gets the subscription in U1
+		line = u1.expect("pjsua_pres.*subscription.*using account")
+		acc1 = line.split("using account ")[1]
+	# wait until we've got Online notification
+	u2.expect(uri1 + ".*Online")
+
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# Set current account in both U1 and U2
+	if acc1!="-1":
+		u1.send(">")
+		u1.send(acc1)
+		u1.expect("Current account changed")
+	if acc2!="-1":
+		u2.send(">")
+		u2.send(acc2)
+		u2.expect("Current account changed")
+
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# u2 toggles online status
+	u2.send("t")
+	u1.expect(uri2 + ".*status.*Offline")
+	u2.expect("offline")
+	
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# u1 toggles online status
+	u1.send("t")
+	u2.expect(uri1 + ".*status.*Offline")
+	u1.expect("offline")
+
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# u2 set online status to On the phone
+	u2.send("T")
+	u2.send("3")
+	u1.expect(uri2 + ".*status.*On the phone")
+	u2.expect("On the phone")
+	
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+	# U1 send IM
+	im_text = "Hello World from U1"
+	u1.send("i")
+	u1.send(uri2)
+	u2.expect(" is typing")
+	u1.send(im_text)
+	u1.expect(im_text+".*delivered successfully")
+	u2.expect("MESSAGE from.*"+im_text)
+	
+	# Synchronize stdout
+	u1.sync_stdout()
+	u2.sync_stdout()
+
+
+# Here where it all comes together
+test = cfg_file.test_param
+test.test_func = test_func
+
diff --git a/tests/pjsua/mod_recvfrom.py b/tests/pjsua/mod_recvfrom.py
new file mode 100644
index 0000000..1510aed
--- /dev/null
+++ b/tests/pjsua/mod_recvfrom.py
@@ -0,0 +1,94 @@
+# $Id$
+import imp
+import sys
+import inc_sip as sip
+import inc_const as const
+import re
+from inc_cfg import *
+
+# Read configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# Default server port (should we randomize?)
+srv_port = 50070
+
+def test_func(test):
+	pjsua = test.process[0]
+	dlg = sip.Dialog("127.0.0.1", pjsua.inst_param.sip_port, 
+			 local_port=srv_port, 
+			 tcp=cfg_file.recvfrom_cfg.tcp)
+
+	last_cseq = 0
+	last_method = ""
+	for t in cfg_file.recvfrom_cfg.transaction:
+		# Print transaction title
+		if t.title != "":
+			dlg.trace(t.title)
+		# Run command and expect patterns
+		for c in t.cmds:
+			if c[0] and c[0] != "":
+				pjsua.send(c[0])
+			if len(c)>1 and c[1] and c[1] != "":
+				pjsua.expect(c[1])
+		# Wait for request
+		if t.check_cseq:
+			# Absorbs retransmissions
+			cseq = 0
+			method = last_method
+			while cseq <= last_cseq and method == last_method:
+				request, src_addr = dlg.wait_msg_from(10)
+				if request==None or request=="":
+					raise TestError("Timeout waiting for request")
+				method = request.split(" ", 1)[0]
+				cseq_hval = sip.get_header(request, "CSeq")
+				cseq_hval = cseq_hval.split(" ")[0]
+				cseq = int(cseq_hval)
+			last_cseq = cseq
+			last_method = method
+		else:
+			request, src_addr = dlg.wait_msg_from(10)
+			if request==None or request=="":
+				raise TestError("Timeout waiting for request")
+
+		# Check for include patterns
+		for pat in t.include:
+			if re.search(pat, request, re.M | re.I)==None:
+				if t.title:
+					tname = " in " + t.title + " transaction"
+				else:
+					tname = ""
+				raise TestError("Pattern " + pat + " not found" + tname)
+		# Check for exclude patterns
+		for pat in t.exclude:
+			if re.search(pat, request, re.M | re.I)!=None:
+				if t.title:
+					tname = " in " + t.title + " transaction"
+				else:
+					tname = ""
+				raise TestError("Excluded pattern " + pat + " found" + tname)
+		# Create response
+		if t.resp_code!=0:
+			response = dlg.create_response(request, t.resp_code, "Status reason")
+			# Add headers to response
+			for h in t.resp_hdr:
+				response = response + h + "\r\n"
+			# Add message body if required
+			if t.body:
+				response = response + t.body
+			# Send response
+			dlg.send_msg(response, src_addr)
+
+		# Expect something to happen in pjsua
+		if t.expect != "":
+			pjsua.expect(t.expect)
+		# Sync
+		pjsua.sync_stdout()
+
+# Replace "$PORT" with server port in pjsua args
+cfg_file.recvfrom_cfg.inst_param.arg = cfg_file.recvfrom_cfg.inst_param.arg.replace("$PORT", str(srv_port))
+
+# Here where it all comes together
+test = TestParam(cfg_file.recvfrom_cfg.name, 
+		 [cfg_file.recvfrom_cfg.inst_param], 
+		 test_func)
+
diff --git a/tests/pjsua/mod_run.py b/tests/pjsua/mod_run.py
new file mode 100644
index 0000000..03548ee
--- /dev/null
+++ b/tests/pjsua/mod_run.py
@@ -0,0 +1,11 @@
+# $Id$
+import imp
+import sys
+
+from inc_cfg import *
+
+# Read configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# Here where it all comes together
+test = cfg_file.test_param
diff --git a/tests/pjsua/mod_sendto.py b/tests/pjsua/mod_sendto.py
new file mode 100644
index 0000000..4cc12a4
--- /dev/null
+++ b/tests/pjsua/mod_sendto.py
@@ -0,0 +1,53 @@
+# $Id$
+import imp
+import sys
+import inc_sip as sip
+import inc_const as const
+import re
+from inc_cfg import *
+
+# Read configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# Test body function
+def test_func(t):
+	pjsua = t.process[0]
+	# Create dialog
+	dlg = sip.Dialog("127.0.0.1", pjsua.inst_param.sip_port, 
+			  tcp=cfg_file.sendto_cfg.use_tcp)
+	#dlg = sip.Dialog("127.0.0.1", 5060, tcp=cfg_file.sendto_cfg.use_tcp)
+	cfg = cfg_file.sendto_cfg
+	
+	if len(cfg.complete_msg) != 0:
+		req = cfg.complete_msg
+	else:
+		req = dlg.create_invite(cfg.sdp, cfg.extra_headers)
+	resp = dlg.send_request_wait(req, 10)
+	if resp=="":
+		raise TestError("Timed-out waiting for response")
+	# Check response code
+	code = int(sip.get_code(resp))
+	if code != cfg.resp_code:
+		dlg.hangup(code)
+		raise TestError("Expecting code " + str(cfg.resp_code) + 
+				" got " + str(code))
+	# Check for patterns that must exist
+	for p in cfg.resp_include:
+		if re.search(p, resp, re.M | re.I)==None:
+			dlg.hangup(code)
+			raise TestError("Pattern " + p + " not found")
+	# Check for patterns that must not exist
+	for p in cfg.resp_exclude:
+		if re.search(p, resp, re.M | re.I)!=None:
+			dlg.hangup(code)
+			raise TestError("Excluded pattern " + p + " found")
+	pjsua.sync_stdout()
+	dlg.hangup(code)
+	pjsua.sync_stdout()
+
+# Here where it all comes together
+test = TestParam(cfg_file.sendto_cfg.name, 
+		 [cfg_file.sendto_cfg.inst_param], 
+		 test_func)
+
+
diff --git a/tests/pjsua/run.py b/tests/pjsua/run.py
new file mode 100644
index 0000000..df940ea
--- /dev/null
+++ b/tests/pjsua/run.py
@@ -0,0 +1,262 @@
+# $Id$
+import sys
+import imp
+import re
+import os
+import subprocess
+import random
+import time
+import getopt
+
+import inc_const as const
+import inc_cfg as inc
+
+# Vars
+G_EXE = ""		# pjsua executable path
+G_INUNIX = False	# flags that test is running in Unix
+
+
+# Usage string
+usage = \
+"""
+run.py - Automated test driver
+
+Usage:
+	run.py [options] MODULE CONFIG
+Options:
+	--exe, -e		pjsua executable path
+	--null-audio, -n	use null audio
+Sample:
+	run.py -n mod_run.py scripts-run/100_simple.py
+"""
+
+# Parse arguments
+try:
+    opts, args = getopt.getopt(sys.argv[1:], "hne:", ["help", "null-audio", "exe="])
+except getopt.GetoptError, err:
+    print str(err)
+    print usage
+    sys.exit(2)
+for o, a in opts:
+    if o in ("-h", "--help"):
+	print usage
+	sys.exit()
+    elif o in ("-n", "--null-audio"):
+        inc.HAS_SND_DEV = 0
+    elif o in ("-e", "--exe"):
+        G_EXE = a
+    else:
+        print "Unknown options"
+	sys.exit(2)
+
+if len(args) != 2:
+	print "Invalid arguments"
+	print usage
+	sys.exit(2)
+
+# Set global ARGS to be used by modules
+inc.ARGS = args
+
+# Get the pjsua executable name
+if G_EXE == "":
+	if sys.platform.find("win32")!=-1:
+	    e = "../../pjsip-apps/bin/pjsua_vc6d.exe"
+	    if os.access(e, os.F_OK):
+	    	st1 = os.stat(e)
+	    else:
+	    	st1 = None
+	    if st1 != None:
+		G_EXE = e
+	    e = "../../pjsip-apps/bin/pjsua_vc6.exe"
+	    if os.access(e, os.F_OK):
+	    	st2 = os.stat(e)
+	    else:
+	    	st2 = None
+	    if st2 != None and (st1==None or st2.st_mtime > st1.st_mtime):
+		G_EXE = e
+		st1 = st2
+	    if G_EXE=="":
+		print "Unable to find valid pjsua. Please build pjsip first"
+		sys.exit(1)
+	    G_INUNIX = False
+	else:
+	    f = open("../../../build.mak", "r")
+	    while True:
+		line = f.readline()
+		if not line:
+		    break
+		if line.find("TARGET_NAME")!=-1:
+		    print line
+		    G_EXE="../../pjsip-apps/bin/pjsua-" + line.split(":= ")[1]
+		    break
+	    if G_EXE=="":
+		print "Unable to find ../../../build.mak. Please build pjsip first"
+		sys.exit(1)
+	    G_INUNIX = True
+
+
+G_EXE = G_EXE.rstrip("\n\r \t")
+
+###################################
+# Poor man's 'expect'-like class
+class Expect:
+	proc = None
+	echo = False
+	trace_enabled = False
+	name = ""
+	inst_param = None
+	rh = re.compile(const.DESTROYED)
+	ra = re.compile(const.ASSERT, re.I)
+	rr = re.compile(const.STDOUT_REFRESH)
+	t0 = time.time()
+	def __init__(self, inst_param):
+		self.inst_param = inst_param
+		self.name = inst_param.name
+		self.echo = inst_param.echo_enabled
+		self.trace_enabled = inst_param.trace_enabled
+		fullcmd = G_EXE + " " + inst_param.arg + " --stdout-refresh=5 --stdout-refresh-text=" + const.STDOUT_REFRESH
+		if not inst_param.enable_buffer:
+			fullcmd = fullcmd + " --stdout-no-buf"
+		self.trace("Popen " + fullcmd)
+		self.proc = subprocess.Popen(fullcmd, shell=G_INUNIX, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=False)
+	def send(self, cmd):
+		self.trace("send " + cmd)
+		self.proc.stdin.writelines(cmd + "\n")
+		self.proc.stdin.flush()
+	def expect(self, pattern, raise_on_error=True, title=""):
+		self.trace("expect " + pattern)
+		r = re.compile(pattern, re.I)
+		refresh_cnt = 0
+		while True:
+			line = self.proc.stdout.readline()
+		  	if line == "":
+				raise inc.TestError(self.name + ": Premature EOF")
+			# Print the line if echo is ON
+			if self.echo:
+				print self.name + ": " + line,
+			# Trap assertion error
+			if self.ra.search(line) != None:
+				if raise_on_error:
+					raise inc.TestError(self.name + ": " + line)
+				else:
+					return None
+			# Count stdout refresh text. 
+			if self.rr.search(line) != None:
+				refresh_cnt = refresh_cnt+1
+				if refresh_cnt >= 6:
+					self.trace("Timed-out!")
+					if raise_on_error:
+						raise inc.TestError(self.name + " " + title + ": Timeout expecting pattern: \"" + pattern + "\"")
+					else:
+						return None		# timeout
+			# Search for expected text
+			if r.search(line) != None:
+				return line
+
+	def sync_stdout(self):
+		self.trace("sync_stdout")
+		cmd = "echo 1" + str(random.randint(1000,9999))
+		self.send(cmd)
+		self.expect(cmd)
+
+	def wait(self):
+		self.trace("wait")
+		self.proc.wait()
+	def trace(self, s):
+		if self.trace_enabled:
+			now = time.time()
+			fmt = self.name + ": " + "================== " + s + " ==================" + " [at t=%(time)03d]"
+			print fmt % {'time':int(now - self.t0)}
+
+#########################
+# Error handling
+def handle_error(errmsg, t, close_processes = True):
+	print "====== Caught error: " + errmsg + " ======"
+	if (close_processes):
+		time.sleep(1)
+		for p in t.process:
+			p.send("q")
+			p.send("q")
+			p.expect(const.DESTROYED, False)
+			p.wait()
+	print "Test completed with error: " + errmsg
+	sys.exit(1)
+
+
+#########################
+# MAIN	
+
+# Import the test script
+script = imp.load_source("script", inc.ARGS[0])  
+
+# Init random seed
+random.seed()
+
+# Validate
+if script.test == None:
+	print "Error: no test defined"
+	sys.exit(1)
+
+if script.test.skip:
+	print "Test " + script.test.title + " is skipped"
+	sys.exit(0)
+
+if len(script.test.inst_params) == 0:
+	print "Error: test doesn't contain pjsua run descriptions"
+	sys.exit(1)
+
+# Instantiate pjsuas
+print "====== Running " + script.test.title + " ======"
+print "Using " + G_EXE + " as pjsua executable"
+
+for inst_param in script.test.inst_params:
+	try:
+		# Create pjsua's Expect instance from the param
+		p = Expect(inst_param)
+		# Wait until registration completes
+		if inst_param.have_reg:
+			p.expect(inst_param.uri+".*registration success")
+	 	# Synchronize stdout
+		p.send("")
+		p.expect(const.PROMPT)
+		p.send("echo 1")
+		p.send("echo 1")
+		p.expect("echo 1")
+		# add running instance
+		script.test.process.append(p)
+
+	except inc.TestError, e:
+		handle_error(e.desc, script.test)
+
+# Run the test function
+if script.test.test_func != None:
+	try:
+		script.test.test_func(script.test)
+	except inc.TestError, e:
+		handle_error(e.desc, script.test)
+
+# Shutdown all instances
+time.sleep(2)
+for p in script.test.process:
+	# Unregister if we have_reg to make sure that next tests
+	# won't wail
+	if p.inst_param.have_reg:
+		p.send("ru")
+		p.expect(p.inst_param.uri+".*unregistration success")
+	p.send("q")
+	p.send("q")
+	time.sleep(0.5)
+	p.expect(const.DESTROYED, False)
+	p.wait()
+
+# Run the post test function
+if script.test.post_func != None:
+	try:
+		script.test.post_func(script.test)
+	except inc.TestError, e:
+		handle_error(e.desc, script.test, False)
+
+# Done
+print "Test " + script.test.title + " completed successfully"
+sys.exit(0)
+
diff --git a/tests/pjsua/runall.py b/tests/pjsua/runall.py
new file mode 100644
index 0000000..4785fde
--- /dev/null
+++ b/tests/pjsua/runall.py
@@ -0,0 +1,134 @@
+# $Id$
+import os
+import sys
+import time
+import re
+import shutil
+
+
+# Usage:
+#  runall.py [test-to-resume]
+
+
+# Initialize test list
+tests = []
+
+# Excluded tests (because they fail?)
+excluded_tests = [ "svn",
+		   "pyc",
+		   "scripts-call/150_srtp_2_1",				# SRTP optional 'cannot' call SRTP mandatory
+		   "scripts-call/301_ice_public_a.py",			# Unreliable, proxy returns 408 sometimes
+		   "scripts-call/301_ice_public_b.py",			# Doesn't work because OpenSER modifies SDP
+		   "scripts-pres/200_publish.py",			# Ok from cmdline, error from runall.py
+		   "scripts-media-playrec/100_resample_lf_8_11.py",	# related to clock-rate 11 kHz problem
+		   "scripts-media-playrec/100_resample_lf_8_22.py",	# related to clock-rate 22 kHz problem
+		   "scripts-media-playrec/100_resample_lf_11"		# related to clock-rate 11 kHz problem
+                   ]
+
+# Add basic tests
+for f in os.listdir("scripts-run"):
+    tests.append("mod_run.py scripts-run/" + f)
+
+# Add basic call tests
+for f in os.listdir("scripts-call"):
+    tests.append("mod_call.py scripts-call/" + f)
+
+# Add presence tests
+for f in os.listdir("scripts-pres"):
+    tests.append("mod_pres.py scripts-pres/" + f)
+
+# Add mod_sendto tests
+for f in os.listdir("scripts-sendto"):
+    tests.append("mod_sendto.py scripts-sendto/" + f)
+
+# Add mod_media_playrec tests
+for f in os.listdir("scripts-media-playrec"):
+    tests.append("mod_media_playrec.py scripts-media-playrec/" + f)
+
+# Add mod_pesq tests
+for f in os.listdir("scripts-pesq"):
+    tests.append("mod_pesq.py scripts-pesq/" + f)
+
+# Add recvfrom tests
+for f in os.listdir("scripts-recvfrom"):
+    tests.append("mod_recvfrom.py scripts-recvfrom/" + f)
+
+# Filter-out excluded tests
+for pat in excluded_tests:
+    tests = [t for t in tests if t.find(pat)==-1]
+
+# Resume test?
+resume_script=""
+if len(sys.argv) > 1:
+    if sys.argv[1]=='-r' or sys.argv[1]=='--resume':
+	resume_script=sys.argv[2]
+    if sys.argv[1]=='/h' or sys.argv[1]=='-h' or sys.argv[1]=='--help' or sys.argv[1]=='/help':
+        print "Usage:"
+	print "  runall.py [OPTIONS] [run.py-OPTIONS]"
+	print "Options:"
+	print "  --resume,-r RESUME"
+	print "      RESUME is string/substring to specify where to resume tests."
+	print "      If this argument is omited, tests will start from the beginning."
+	print "  run.py-OPTIONS are applicable here"
+	sys.exit(0)
+
+
+# Generate arguments for run.py
+argv = sys.argv
+argv_to_skip = 1
+if resume_script != "":
+    argv_to_skip += 2
+argv_st = ""
+for a in argv:
+    if argv_to_skip > 0:
+        argv_to_skip -= 1
+    else:
+        argv_st += a + " "
+
+
+# Init vars
+fails_cnt = 0
+tests_cnt = 0
+
+# Re-create "logs" directory
+try:
+    shutil.rmtree("logs")
+except:
+    print "Warning: failed in removing directory 'logs'"
+
+try:
+    os.mkdir("logs")
+except:
+    print "Warning: failed in creating directory 'logs'"
+
+# Now run the tests
+for t in tests:
+	if resume_script!="" and t.find(resume_script)==-1:
+	    print "Skipping " + t +".."
+	    continue
+	resume_script=""
+	cmdline = "python run.py " + argv_st + t
+	t0 = time.time()
+	print "Running " + cmdline + "...",
+	ret = os.system(cmdline + " > output.log")
+	t1 = time.time()
+	if ret != 0:
+		dur = int(t1 - t0)
+		print " failed!! [" + str(dur) + "s]"
+		logname = re.search(".*\s+(.*)", t).group(1)
+		logname = re.sub("[\\\/]", "_", logname)
+		logname = re.sub("\.py$", ".log", logname)
+		logname = "logs/" + logname
+		shutil.move("output.log", logname)
+		print "Please see '" + logname + "' for the test log."
+		fails_cnt += 1
+	else:
+		dur = int(t1 - t0)
+		print " ok [" + str(dur) + "s]"
+	tests_cnt += 1
+
+if fails_cnt == 0:
+	print "All " + str(tests_cnt) + " tests completed successfully"
+else:
+	print str(tests_cnt) + " tests completed, " +  str(fails_cnt) + " test(s) failed"
+
diff --git a/tests/pjsua/scripts-call/100_simplecall.py b/tests/pjsua/scripts-call/100_simplecall.py
new file mode 100644
index 0000000..c6bfcb3
--- /dev/null
+++ b/tests/pjsua/scripts-call/100_simplecall.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Simple call
+test_param = TestParam(
+		"Basic call",
+		[
+			InstanceParam("callee", "--null-audio --max-calls=1"),
+			InstanceParam("caller", "--null-audio --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_0_1.py b/tests/pjsua/scripts-call/150_srtp_0_1.py
new file mode 100644
index 0000000..92b0ff4
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_0_1.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+test_param= TestParam(
+		"Callee=no SRTP, caller=optional SRTP",
+		[
+			InstanceParam("callee", "--null-audio --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_1_0.py b/tests/pjsua/scripts-call/150_srtp_1_0.py
new file mode 100644
index 0000000..fa128b6
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_1_0.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+test_param = TestParam(
+		"Callee=optional SRTP, caller=no SRTP",
+		[
+			InstanceParam("callee", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1"),
+			InstanceParam("caller", "--null-audio --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_1_1.py b/tests/pjsua/scripts-call/150_srtp_1_1.py
new file mode 100644
index 0000000..3b4446c
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_1_1.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+test_param = TestParam(
+		"Callee=optional SRTP, caller=optional SRTP",
+		[
+			InstanceParam("callee", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_1_2.py b/tests/pjsua/scripts-call/150_srtp_1_2.py
new file mode 100644
index 0000000..03f5886
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_1_2.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+test_param = TestParam(
+		"Callee=optional SRTP, caller=mandatory SRTP",
+		[
+			InstanceParam("callee", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-srtp=2 --srtp-secure=0 --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_2_1.py b/tests/pjsua/scripts-call/150_srtp_2_1.py
new file mode 100644
index 0000000..16341eb
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_2_1.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Simple call
+test_param = TestParam(
+		"Callee=mandatory SRTP, caller=optional SRTP",
+		[
+			InstanceParam("callee", "--null-audio --use-srtp=2 --srtp-secure=0 --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-srtp=1 --srtp-secure=0 --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/150_srtp_2_2.py b/tests/pjsua/scripts-call/150_srtp_2_2.py
new file mode 100644
index 0000000..9ec2ab6
--- /dev/null
+++ b/tests/pjsua/scripts-call/150_srtp_2_2.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+test_param = TestParam(
+		"Callee=mandatory SRTP, caller=mandatory SRTP",
+		[
+			InstanceParam("callee", "--null-audio --use-srtp=2 --srtp-secure=0 --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-srtp=2 --srtp-secure=0 --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/200_tcp.py b/tests/pjsua/scripts-call/200_tcp.py
new file mode 100644
index 0000000..3414f17
--- /dev/null
+++ b/tests/pjsua/scripts-call/200_tcp.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# TCP call
+test_param = TestParam(
+		"TCP transport",
+		[
+			InstanceParam("callee", "--null-audio --no-udp --max-calls=1", uri_param=";transport=tcp"),
+			InstanceParam("caller", "--null-audio --no-udp --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/300_ice_0_1.py b/tests/pjsua/scripts-call/300_ice_0_1.py
new file mode 100644
index 0000000..fb0b9de
--- /dev/null
+++ b/tests/pjsua/scripts-call/300_ice_0_1.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# ICE mismatch
+test_param = TestParam(
+		"Callee=no ICE, caller=use ICE",
+		[
+			InstanceParam("callee", "--null-audio --max-calls=1"),
+			InstanceParam("caller", "--null-audio --use-ice --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/300_ice_1_0.py b/tests/pjsua/scripts-call/300_ice_1_0.py
new file mode 100644
index 0000000..ef800e7
--- /dev/null
+++ b/tests/pjsua/scripts-call/300_ice_1_0.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+ 
+# ICE mismatch
+test_param = TestParam(
+		"Callee=use ICE, caller=no ICE",
+		[
+			InstanceParam("callee", "--null-audio --use-ice --max-calls=1"),
+			InstanceParam("caller", "--null-audio --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/300_ice_1_1.py b/tests/pjsua/scripts-call/300_ice_1_1.py
new file mode 100644
index 0000000..456aa46
--- /dev/null
+++ b/tests/pjsua/scripts-call/300_ice_1_1.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# ICE mismatch
+test_param = TestParam(
+		"Callee=use ICE, caller=use ICE",
+		[
+			InstanceParam("callee", "--null-audio --use-ice --max-calls=1", enable_buffer=True),
+			InstanceParam("caller", "--null-audio --use-ice --max-calls=1", enable_buffer=True)
+		]
+		)
diff --git a/tests/pjsua/scripts-call/301_ice_public_a.py b/tests/pjsua/scripts-call/301_ice_public_a.py
new file mode 100644
index 0000000..daa4c6c
--- /dev/null
+++ b/tests/pjsua/scripts-call/301_ice_public_a.py
@@ -0,0 +1,22 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Note:
+#	- need --dis-codec to make INVITE packet less than typical MTU
+uas_args = "--null-audio --id=\"<sip:test1@pjsip.org>\" --registrar=sip:sip.pjsip.org --username=test1 --password=test1  --realm=pjsip.org  --proxy=\"sip:sip.pjsip.org;lr\"  --rtp-port 0 --stun-srv stun.pjsip.org  --use-ice --use-compact-form --max-calls 1 --dis-codec=i --dis-codec=s --dis-codec=g"
+
+uac_args = "--null-audio --id=\"<sip:test2@pjsip.org>\" --registrar=sip:sip.pjsip.org --username=test2 --password=test2 --realm=pjsip.org --proxy=\"sip:sip.pjsip.org;lr\" --rtp-port 0 --stun-srv stun.pjsip.org --use-ice --use-compact-form --max-calls 1 --dis-codec=i --dis-codec=s --dis-codec=g"
+
+test_param = TestParam(
+		"ICE via public internet",
+		[
+			InstanceParam(	"callee", uas_args, 
+					uri="<sip:test1@pjsip.org>",
+					have_reg=True, have_publish=False),
+			InstanceParam(	"caller", uac_args,
+					uri="<sip:test2@pjsip.org>",
+					have_reg=True, have_publish=False),
+		]
+		)
+
diff --git a/tests/pjsua/scripts-call/301_ice_public_b.py b/tests/pjsua/scripts-call/301_ice_public_b.py
new file mode 100644
index 0000000..d78992c
--- /dev/null
+++ b/tests/pjsua/scripts-call/301_ice_public_b.py
@@ -0,0 +1,25 @@
+# $Id$
+#
+from inc_cfg import *
+
+# This test:
+#   to make call with ICE but without STUN.
+
+# Note:
+#	- need --dis-codec to make INVITE packet less than typical MTU
+uas_args = "--null-audio --id=\"<sip:test1@pjsip.org>\" --registrar=sip:sip.pjsip.org --username=test1 --password=test1  --realm=pjsip.org  --proxy=\"sip:sip.pjsip.org;lr\"  --rtp-port 0 --use-ice --use-compact-form --max-calls 1 --dis-codec=i --dis-codec=s --dis-codec=g --log-file callee.log"
+
+uac_args = "--null-audio --id=\"<sip:test2@pjsip.org>\" --registrar=sip:sip.pjsip.org --username=test2 --password=test2 --realm=pjsip.org --proxy=\"sip:sip.pjsip.org;lr\" --rtp-port 0 --use-ice --use-compact-form --max-calls 1 --dis-codec=i --dis-codec=s --dis-codec=g --log-file caller.log"
+
+test_param = TestParam(
+		"ICE via public internet with no STUN",
+		[
+			InstanceParam(	"callee", uas_args, 
+					uri="<sip:test1@pjsip.org>",
+					have_reg=True, have_publish=False),
+			InstanceParam(	"caller", uac_args,
+					uri="<sip:test2@pjsip.org>",
+					have_reg=True, have_publish=False),
+		]
+		)
+
diff --git a/tests/pjsua/scripts-call/305_ice_comp_1_2.py b/tests/pjsua/scripts-call/305_ice_comp_1_2.py
new file mode 100644
index 0000000..7580b20
--- /dev/null
+++ b/tests/pjsua/scripts-call/305_ice_comp_1_2.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Different number of ICE components
+test_param = TestParam(
+		"Callee=use ICE, caller=use ICE",
+		[
+			InstanceParam("callee", "--null-audio --use-ice --max-calls=1 --ice-no-rtcp", enable_buffer=True),
+			InstanceParam("caller", "--null-audio --use-ice --max-calls=1", enable_buffer=True)
+		]
+		)
diff --git a/tests/pjsua/scripts-call/305_ice_comp_2_1.py b/tests/pjsua/scripts-call/305_ice_comp_2_1.py
new file mode 100644
index 0000000..a0ddaf7
--- /dev/null
+++ b/tests/pjsua/scripts-call/305_ice_comp_2_1.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Different number of ICE components
+test_param = TestParam(
+		"Callee=use ICE, caller=use ICE",
+		[
+			InstanceParam("callee", "--null-audio --use-ice --max-calls=1", enable_buffer=True),
+			InstanceParam("caller", "--null-audio --use-ice --max-calls=1 --ice-no-rtcp", enable_buffer=True)
+		]
+		)
diff --git a/tests/pjsua/scripts-call/350_prack_a.py b/tests/pjsua/scripts-call/350_prack_a.py
new file mode 100644
index 0000000..13649f6
--- /dev/null
+++ b/tests/pjsua/scripts-call/350_prack_a.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# TCP call
+test_param = TestParam(
+		"Callee requires PRACK",
+		[
+			InstanceParam("callee", "--null-audio --max-calls=1 --use-100rel"),
+			InstanceParam("caller", "--null-audio --max-calls=1")
+		]
+		)
diff --git a/tests/pjsua/scripts-call/350_prack_b.py b/tests/pjsua/scripts-call/350_prack_b.py
new file mode 100644
index 0000000..b97bdc2
--- /dev/null
+++ b/tests/pjsua/scripts-call/350_prack_b.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# TCP call
+test_param = TestParam(
+		"Caller requires PRACK",
+		[
+			InstanceParam("callee", "--null-audio --max-calls=1"),
+			InstanceParam("caller", "--null-audio --max-calls=1 --use-100rel")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_16.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_16.py
new file mode 100644
index 0000000..41dd7d4
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_16.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 16 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 16000 --play-file wavs/input.11.wav --rec-file wavs/tmp.16.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_22.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_22.py
new file mode 100644
index 0000000..d966a1f
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_22.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 22 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 22050 --play-file wavs/input.11.wav --rec-file wavs/tmp.22.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_32.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_32.py
new file mode 100644
index 0000000..a00c525
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_32.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 32 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 32000 --play-file wavs/input.11.wav --rec-file wavs/tmp.32.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_44.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_44.py
new file mode 100644
index 0000000..89aadcf
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_44.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 44 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 44100 --play-file wavs/input.11.wav --rec-file wavs/tmp.44.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_48.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_48.py
new file mode 100644
index 0000000..9a4e93b
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_48.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 48 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 48000 --play-file wavs/input.11.wav --rec-file wavs/tmp.48.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_11_8.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_8.py
new file mode 100644
index 0000000..5813b0b
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_11_8.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 11 KHZ to 8 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 8000 --play-file wavs/input.11.wav --rec-file wavs/tmp.8.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_11.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_11.py
new file mode 100644
index 0000000..7d19f6f
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_11.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 11 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 11025 --play-file wavs/input.8.wav --rec-file wavs/tmp.11.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_16.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_16.py
new file mode 100644
index 0000000..b936175
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_16.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 16 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 16000 --play-file wavs/input.8.wav --rec-file wavs/tmp.16.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_22.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_22.py
new file mode 100644
index 0000000..268c6a5
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_22.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 22 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 22050 --play-file wavs/input.8.wav --rec-file wavs/tmp.22.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_32.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_32.py
new file mode 100644
index 0000000..05f3a30
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_32.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 32 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 32000 --play-file wavs/input.8.wav --rec-file wavs/tmp.32.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_44.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_44.py
new file mode 100644
index 0000000..73e4b10
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_44.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 44 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 44100 --play-file wavs/input.8.wav --rec-file wavs/tmp.44.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-media-playrec/100_resample_lf_8_48.py b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_48.py
new file mode 100644
index 0000000..4d72603
--- /dev/null
+++ b/tests/pjsua/scripts-media-playrec/100_resample_lf_8_48.py
@@ -0,0 +1,11 @@
+# $Id$
+#
+from inc_cfg import *
+
+# simple test
+test_param = TestParam(
+		"Resample (large filter) 8 KHZ to 48 KHZ",
+		[
+			InstanceParam("endpt", "--null-audio --quality 10 --clock-rate 48000 --play-file wavs/input.8.wav --rec-file wavs/tmp.48.wav")
+		]
+		)
diff --git a/tests/pjsua/scripts-pesq/100_defaults.py b/tests/pjsua/scripts-pesq/100_defaults.py
new file mode 100644
index 0000000..2901f46
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/100_defaults.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with default pjsua settings
+test_param = TestParam(
+		"PESQ defaults pjsua settings",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --play-file wavs/input.16.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --rec-file  wavs/tmp.16.wav --clock-rate 16000 --auto-answer 200")
+		]
+		)
+
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/101_defaults.py b/tests/pjsua/scripts-pesq/101_defaults.py
new file mode 100644
index 0000000..67b53e3
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/101_defaults.py
@@ -0,0 +1,18 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with default pjsua settings
+test_param = TestParam(
+		"PESQ defaults pjsua settings (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --play-file wavs/input.16.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --rec-file  wavs/tmp.16.wav --clock-rate 16000 --auto-answer 200")
+		]
+		)
+
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/200_codec_g711a.py b/tests/pjsua/scripts-pesq/200_codec_g711a.py
new file mode 100644
index 0000000..9e91c96
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_g711a.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with PCMA codec
+test_param = TestParam(
+		"PESQ codec PCMA",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec pcma --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec pcma --clock-rate 8000 --rec-file  wavs/tmp.8.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/200_codec_g711u.py b/tests/pjsua/scripts-pesq/200_codec_g711u.py
new file mode 100644
index 0000000..fa64e7a
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_g711u.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with PCMU codec
+test_param = TestParam(
+		"PESQ codec PCMU",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec pcmu --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec pcmu --clock-rate 8000 --rec-file  wavs/tmp.8.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/200_codec_g722.py b/tests/pjsua/scripts-pesq/200_codec_g722.py
new file mode 100644
index 0000000..4cb85da
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_g722.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with G722 codec
+test_param = TestParam(
+		"PESQ codec G722",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec g722 --clock-rate 16000 --play-file wavs/input.16.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec g722 --clock-rate 16000 --rec-file  wavs/tmp.16.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.7
diff --git a/tests/pjsua/scripts-pesq/200_codec_gsm.py b/tests/pjsua/scripts-pesq/200_codec_gsm.py
new file mode 100644
index 0000000..221372e
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_gsm.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with GSM codec
+test_param = TestParam(
+		"PESQ codec GSM",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec gsm --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec gsm --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pesq/200_codec_ilbc.py b/tests/pjsua/scripts-pesq/200_codec_ilbc.py
new file mode 100644
index 0000000..45480b8
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_ilbc.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with iLBC codec
+test_param = TestParam(
+		"PESQ codec iLBC",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec ilbc --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec ilbc --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pesq/200_codec_l16_16000.py b/tests/pjsua/scripts-pesq/200_codec_l16_16000.py
new file mode 100644
index 0000000..691a362
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_l16_16000.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with L16/16000/1 codec
+test_param = TestParam(
+		"PESQ codec L16/16000/1",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec L16/16000/1 --clock-rate 16000 --play-file wavs/input.16.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec L16/16000/1 --clock-rate 16000 --rec-file  wavs/tmp.16.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/200_codec_l16_16000_stereo.py b/tests/pjsua/scripts-pesq/200_codec_l16_16000_stereo.py
new file mode 100644
index 0000000..406a182
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_l16_16000_stereo.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with L16/16000/2 codec
+test_param = TestParam(
+		"PESQ defaults pjsua settings",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --stereo --max-calls=1 --clock-rate 16000 --add-codec L16/16000/2 --play-file wavs/input.2.16.wav"),
+			InstanceParam("UA2", "--null-audio --stereo --max-calls=1 --clock-rate 16000 --add-codec L16/16000/2 --rec-file  wavs/tmp.2.16.wav   --auto-answer 200")
+		]
+		)
+
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/200_codec_l16_8000.py b/tests/pjsua/scripts-pesq/200_codec_l16_8000.py
new file mode 100644
index 0000000..df05a9d
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_l16_8000.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with L16/8000/1 codec
+test_param = TestParam(
+		"PESQ codec L16/8000/1",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec L16/8000/1 --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec L16/8000/1 --clock-rate 8000 --rec-file  wavs/tmp.8.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/200_codec_l16_8000_stereo.py b/tests/pjsua/scripts-pesq/200_codec_l16_8000_stereo.py
new file mode 100644
index 0000000..b114a1a
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_l16_8000_stereo.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with L16/8000/2 codec
+test_param = TestParam(
+		"PESQ defaults pjsua settings",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --stereo --max-calls=1 --clock-rate 8000 --add-codec L16/8000/2 --play-file wavs/input.2.8.wav"),
+			InstanceParam("UA2", "--null-audio --stereo --max-calls=1 --clock-rate 8000 --add-codec L16/8000/2 --rec-file  wavs/tmp.2.8.wav   --auto-answer 200")
+		]
+		)
+
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/200_codec_speex_16000.py b/tests/pjsua/scripts-pesq/200_codec_speex_16000.py
new file mode 100644
index 0000000..448e04c
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_speex_16000.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with Speex/16000 codec
+test_param = TestParam(
+		"PESQ codec Speex WB",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --clock-rate 16000 --add-codec speex/16000 --play-file wavs/input.16.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --clock-rate 16000 --add-codec speex/16000 --rec-file  wavs/tmp.16.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.7
diff --git a/tests/pjsua/scripts-pesq/200_codec_speex_8000.py b/tests/pjsua/scripts-pesq/200_codec_speex_8000.py
new file mode 100644
index 0000000..cf4b169
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/200_codec_speex_8000.py
@@ -0,0 +1,19 @@
+# $Id$
+#
+from inc_cfg import *
+
+ADD_PARAM = ""
+
+if (HAS_SND_DEV == 0):
+	ADD_PARAM += "--null-audio"
+
+# Call with Speex/8000 codec
+test_param = TestParam(
+		"PESQ codec Speex NB",
+		[
+			InstanceParam("UA1", ADD_PARAM + " --max-calls=1 --add-codec speex/8000 --clock-rate 8000 --play-file wavs/input.8.wav"),
+			InstanceParam("UA2", "--null-audio --max-calls=1 --add-codec speex/8000 --clock-rate 8000 --rec-file  wavs/tmp.8.wav --auto-answer 200")
+		]
+		)
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pesq/201_codec_g711a.py b/tests/pjsua/scripts-pesq/201_codec_g711a.py
new file mode 100644
index 0000000..b7458c3
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_g711a.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with PCMA codec
+test_param = TestParam(
+		"PESQ codec PCMA (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec pcma --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec pcma --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/201_codec_g711u.py b/tests/pjsua/scripts-pesq/201_codec_g711u.py
new file mode 100644
index 0000000..43cff65
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_g711u.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with PCMU codec
+test_param = TestParam(
+		"PESQ codec PCMU (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec pcmu --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec pcmu --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/201_codec_g722.py b/tests/pjsua/scripts-pesq/201_codec_g722.py
new file mode 100644
index 0000000..24c8929
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_g722.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with G722 codec
+test_param = TestParam(
+		"PESQ codec G722 (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec g722 --clock-rate 16000 --play-file wavs/input.16.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec g722 --clock-rate 16000 --rec-file  wavs/tmp.16.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.7
diff --git a/tests/pjsua/scripts-pesq/201_codec_gsm.py b/tests/pjsua/scripts-pesq/201_codec_gsm.py
new file mode 100644
index 0000000..d250b77
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_gsm.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with GSM codec
+test_param = TestParam(
+		"PESQ codec GSM (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec gsm --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec gsm --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pesq/201_codec_ilbc.py b/tests/pjsua/scripts-pesq/201_codec_ilbc.py
new file mode 100644
index 0000000..e6d4d35
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_ilbc.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with iLBC codec
+test_param = TestParam(
+		"PESQ codec iLBC (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec ilbc --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec ilbc --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pesq/201_codec_l16_16000.py b/tests/pjsua/scripts-pesq/201_codec_l16_16000.py
new file mode 100644
index 0000000..a71004c
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_l16_16000.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with L16/16000/1 codec
+test_param = TestParam(
+		"PESQ codec L16/16000/1 (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec L16/16000/1 --clock-rate 16000 --play-file wavs/input.16.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec L16/16000/1 --clock-rate 16000 --rec-file  wavs/tmp.16.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/201_codec_l16_16000_stereo.py b/tests/pjsua/scripts-pesq/201_codec_l16_16000_stereo.py
new file mode 100644
index 0000000..c20bc5f
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_l16_16000_stereo.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with L16/16000/2 codec
+test_param = TestParam(
+		"PESQ defaults pjsua settings",
+		[
+			InstanceParam("UA1", "--stereo --max-calls=1 --clock-rate 16000 --add-codec L16/16000/2 --play-file wavs/input.2.16.wav --null-audio"),
+			InstanceParam("UA2", "--stereo --max-calls=1 --clock-rate 16000 --add-codec L16/16000/2 --rec-file  wavs/tmp.2.16.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+	
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/201_codec_l16_8000.py b/tests/pjsua/scripts-pesq/201_codec_l16_8000.py
new file mode 100644
index 0000000..d404f2d
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_l16_8000.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with L16/8000/1 codec
+test_param = TestParam(
+		"PESQ codec L16/8000/1 (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec L16/8000/1 --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec L16/8000/1 --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.5
diff --git a/tests/pjsua/scripts-pesq/201_codec_l16_8000_stereo.py b/tests/pjsua/scripts-pesq/201_codec_l16_8000_stereo.py
new file mode 100644
index 0000000..a40cd7e
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_l16_8000_stereo.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with L16/8000/2 codec
+test_param = TestParam(
+		"PESQ defaults pjsua settings",
+		[
+			InstanceParam("UA1", "--stereo --max-calls=1 --clock-rate 8000 --add-codec L16/8000/2 --play-file wavs/input.2.8.wav --null-audio"),
+			InstanceParam("UA2", "--stereo --max-calls=1 --clock-rate 8000 --add-codec L16/8000/2 --rec-file  wavs/tmp.2.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+	
+pesq_threshold = None
diff --git a/tests/pjsua/scripts-pesq/201_codec_speex_16000.py b/tests/pjsua/scripts-pesq/201_codec_speex_16000.py
new file mode 100644
index 0000000..252e4ef
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_speex_16000.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with Speex/16000 codec
+test_param = TestParam(
+		"PESQ codec Speex WB (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --clock-rate 16000 --add-codec speex/16000 --play-file wavs/input.16.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --clock-rate 16000 --add-codec speex/16000 --rec-file  wavs/tmp.16.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.7
diff --git a/tests/pjsua/scripts-pesq/201_codec_speex_8000.py b/tests/pjsua/scripts-pesq/201_codec_speex_8000.py
new file mode 100644
index 0000000..8f04dbc
--- /dev/null
+++ b/tests/pjsua/scripts-pesq/201_codec_speex_8000.py
@@ -0,0 +1,17 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Call with Speex/8000 codec
+test_param = TestParam(
+		"PESQ codec Speex NB (RX side uses snd dev)",
+		[
+			InstanceParam("UA1", "--max-calls=1 --add-codec speex/8000 --clock-rate 8000 --play-file wavs/input.8.wav --null-audio"),
+			InstanceParam("UA2", "--max-calls=1 --add-codec speex/8000 --clock-rate 8000 --rec-file  wavs/tmp.8.wav   --auto-answer 200")
+		]
+		)
+
+if (HAS_SND_DEV == 0):
+	test_param.skip = True
+
+pesq_threshold = 3.0
diff --git a/tests/pjsua/scripts-pres/100_peertopeer.py b/tests/pjsua/scripts-pres/100_peertopeer.py
new file mode 100644
index 0000000..84e6c7a
--- /dev/null
+++ b/tests/pjsua/scripts-pres/100_peertopeer.py
@@ -0,0 +1,12 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Direct peer to peer presence
+test_param = TestParam(
+		"Direct peer to peer presence",
+		[
+			InstanceParam("client1", "--null-audio"),
+			InstanceParam("client2", "--null-audio")
+		]
+		)
diff --git a/tests/pjsua/scripts-pres/200_publish.py b/tests/pjsua/scripts-pres/200_publish.py
new file mode 100644
index 0000000..50308a5
--- /dev/null
+++ b/tests/pjsua/scripts-pres/200_publish.py
@@ -0,0 +1,35 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Basic registration
+test_param = TestParam(
+		"Presence with PUBLISH",
+		[
+			InstanceParam(	"ua1", 
+					"--null-audio"+
+						" --id=\"<sip:test1@pjsip.org>\""+
+						" --registrar=sip:sip.pjsip.org" +
+						" --username=test1" +
+						" --password=test1" +
+						" --realm=*" +
+						" --proxy=\"sip:sip.pjsip.org;lr\"" +
+						" --publish",
+					uri="<sip:test1@pjsip.org>",
+					have_reg=True,
+					have_publish=True),
+			InstanceParam(	"ua2", 
+					"--null-audio"+
+						" --id=\"<sip:test2@pjsip.org>\""+
+						" --registrar=sip:sip.pjsip.org" +
+						" --username=test2" +
+						" --password=test2" +
+						" --realm=*" +
+						" --proxy=\"sip:sip.pjsip.org;lr\"" +
+						" --publish",
+					uri="<sip:test2@pjsip.org>",
+					have_reg=True,
+					have_publish=True),
+		]
+		)
+
diff --git a/tests/pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py b/tests/pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py
new file mode 100644
index 0000000..33d12b1
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py
@@ -0,0 +1,15 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT"
+
+req1 = sip.RecvfromTransaction("", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+				expect="PJSIP_ENOCREDENTIAL"
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration test",
+			       pjsua, [req1])
diff --git a/tests/pjsua/scripts-recvfrom/201_reg_good_ok.py b/tests/pjsua/scripts-recvfrom/201_reg_good_ok.py
new file mode 100644
index 0000000..9b5a9f9
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/201_reg_good_ok.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--username user --realm python --password passwd --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+				expect="SIP/2.0 401"
+			  )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+				include=["REGISTER sip", "Authorization:", 
+					     "realm=\"python\"", "username=\"user\"", 
+					     "nonce=\"1234\"", "response="],
+				expect="registration success"	     
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful registration test",
+			       pjsua, [req1, req2])
diff --git a/tests/pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py b/tests/pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py
new file mode 100644
index 0000000..66ed19e
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--username user --realm \"*\" --password passwd --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+				expect="SIP/2.0 401"
+			  )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+				include=["REGISTER sip", "Authorization:", 
+					     "realm=\"python\"", "username=\"user\"", 
+					     "nonce=\"1234\"", "response="],
+				expect="registration success"	     
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful registration with wildcard realm test",
+			       pjsua, [req1, req2])
diff --git a/tests/pjsua/scripts-recvfrom/205_reg_good_no_realm.py b/tests/pjsua/scripts-recvfrom/205_reg_good_no_realm.py
new file mode 100644
index 0000000..199493e
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/205_reg_good_no_realm.py
@@ -0,0 +1,16 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=provider --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+				expect="PJSIP_ENOCREDENTIAL"
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration because of realm test",
+			       pjsua, [req1])
diff --git a/tests/pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py b/tests/pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py
new file mode 100644
index 0000000..dc4a4db
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Authentication failure test with same nonce
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+			  	)
+
+req2 = sip.RecvfromTransaction("REGISTER retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"1\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""],
+				expect="PJSIP_EFAILEDCREDENTIAL"
+			  	)
+
+
+recvfrom_cfg = sip.RecvfromCfg("Authentication failure with same nonce",
+			       pjsua, [req1, req2])
diff --git a/tests/pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py b/tests/pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py
new file mode 100644
index 0000000..46e1728
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py
@@ -0,0 +1,29 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=python --user=username --password=password " + \
+	"--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+			  	)
+
+req2 = sip.RecvfromTransaction("REGISTER first retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"1\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+			  	)
+
+
+req3 = sip.RecvfromTransaction("REGISTER retry with new nonce", 200,
+				include=["REGISTER sip", "Authorization", "nonce=\"2\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				expect="registration success"
+			  	)
+
+recvfrom_cfg = sip.RecvfromCfg("Authentication okay after retry with new nonce",
+			       pjsua, [req1, req2, req3])
diff --git a/tests/pjsua/scripts-recvfrom/215_reg_good_multi_ok.py b/tests/pjsua/scripts-recvfrom/215_reg_good_multi_ok.py
new file mode 100644
index 0000000..61194bd
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/215_reg_good_multi_ok.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--username theuser1 --realm python1 --password passwd --next-cred " + \
+	"--username theuser2 --realm python2 --password passwd " + \
+	"--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+				include=["REGISTER sip"], 
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python1\", nonce=\"1234\"",
+					  "WWW-Authenticate: Digest realm=\"python2\", nonce=\"6789\""],
+				expect="SIP/2.0 401"
+			  )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+				include=["REGISTER sip", 
+					 "Authorization:[\\s\\S]+Authorization:", # Must have 2 Auth hdrs
+					 "realm=\"python1\"", "realm=\"python2\"", 
+					 "username=\"theuser1\"", "username=\"theuser2\"", 
+					 "nonce=\"1234\"", "nonce=\"6789\"", 
+					 "response="],
+				expect="registration success"	     
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Multiple authentication challenges",
+			       pjsua, [req1, req2])
diff --git a/tests/pjsua/scripts-recvfrom/220_reg_good_ims_ok.py b/tests/pjsua/scripts-recvfrom/220_reg_good_ims_ok.py
new file mode 100644
index 0000000..d37add1
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/220_reg_good_ims_ok.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--username user@ims-domain --realm python --password passwd --use-ims --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+				include=["REGISTER sip", "Authorization", 
+					 "username=\"user@ims-domain\"",
+					 "realm=\"python\""], 
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+				expect="SIP/2.0 401"
+			  )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+				include=["REGISTER sip", "Authorization:", 
+					     "realm=\"python\"", "username=\"user@ims-domain\"", 
+					     "nonce=\"1234\"", "response="],
+				# Must not have double Authorization header:
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				expect="registration success"	     
+			  )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful IMS registration test",
+			       pjsua, [req1, req2])
diff --git a/tests/pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py b/tests/pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py
new file mode 100644
index 0000000..4bceaf4
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py
@@ -0,0 +1,41 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where it always sends
+# stale=true with all 401 responses. We should expect pjsip to
+# retry the authentication until PJSIP_MAX_STALE_COUNT is
+# exceeded. When pjsip retries the authentication, it should
+# use the new nonce from server
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+			  	)
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"1\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+			  	)
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"2\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\", stale=true"]
+				)
+
+req4 = sip.RecvfromTransaction("Third retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"3\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"4\", stale=true"],
+				expect="PJSIP_EAUTHSTALECOUNT"
+			  	)
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration retry (server rejects with stale=true) ",
+			       pjsua, [req1, req2, req3, req4])
diff --git a/tests/pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py b/tests/pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py
new file mode 100644
index 0000000..443b91b
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py
@@ -0,0 +1,41 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where:
+#	- it wants to signal that NONCE has change
+#	- but it sets stale=false
+# For this case pjsip will retry authentication until
+# PJSIP_MAX_STALE_COUNT is exceeded.
+# 
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+			  	)
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"1\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+			  	)
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"2\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\", stale=true"]
+				)
+
+req4 = sip.RecvfromTransaction("Third retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"3\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"4\", stale=true"],
+				expect="PJSIP_EAUTHSTALECOUNT"
+			  	)
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration retry (server rejects with stale=true) ",
+			       pjsua, [req1, req2, req3, req4])
diff --git a/tests/pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py b/tests/pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py
new file mode 100644
index 0000000..cfa9403
--- /dev/null
+++ b/tests/pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py
@@ -0,0 +1,41 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where it wants to
+# change the nonce, but it fails to set stale to true. In this
+# case, we should expect pjsip to retry the authentication until
+# PJSIP_MAX_STALE_COUNT is exceeded as it should have detected
+# that that nonce has changed
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+	"--realm=python --user=username --password=password " + \
+	"--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+				include=["REGISTER sip"], 
+				exclude=["Authorization"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+			  	)
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"1\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\""]
+			  	)
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+				include=["REGISTER sip", "Authorization", "nonce=\"2\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\""]
+				)
+
+req4 = sip.RecvfromTransaction("Third retry", 200,
+				include=["REGISTER sip", "Authorization", "nonce=\"3\""], 
+				exclude=["Authorization:[\\s\\S]+Authorization:"],
+				expect="registration success"
+			  	)
+
+recvfrom_cfg = sip.RecvfromCfg("Successful auth server changes nonce but with stale=false",
+			       pjsua, [req1, req2, req3, req4])
diff --git a/tests/pjsua/scripts-run/100_simple.py b/tests/pjsua/scripts-run/100_simple.py
new file mode 100644
index 0000000..7ab96ef
--- /dev/null
+++ b/tests/pjsua/scripts-run/100_simple.py
@@ -0,0 +1,13 @@
+# $Id$
+#
+# Just about the simple pjsua command line parameter, which should
+# never fail in any circumstances
+from inc_cfg import *
+
+test_param = TestParam(
+		"Basic run", 
+		[
+			InstanceParam("pjsua", "--null-audio --rtp-port 0")
+		]
+		)
+
diff --git a/tests/pjsua/scripts-run/200_register.py b/tests/pjsua/scripts-run/200_register.py
new file mode 100644
index 0000000..3f4338d
--- /dev/null
+++ b/tests/pjsua/scripts-run/200_register.py
@@ -0,0 +1,20 @@
+# $Id$
+#
+from inc_cfg import *
+
+# Basic registration
+test_param = TestParam(
+		"Basic registration",
+		[
+			InstanceParam(	"client", 
+					"--null-audio"+
+						" --id=\"<sip:test1@pjsip.org>\""+
+						" --registrar=sip:sip.pjsip.org" +
+						" --username=test1" +
+						" --password=test1" +
+						" --realm=*",
+					uri="sip:test1@pjsip.org",
+					have_reg=True),
+		]
+		)
+
diff --git a/tests/pjsua/scripts-sendto/100_simplecall.py b/tests/pjsua/scripts-sendto/100_simplecall.py
new file mode 100644
index 0000000..3fc52df
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/100_simplecall.py
@@ -0,0 +1,20 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+sendto_cfg = sip.SendtoCfg( "simple call", "--null-audio --auto-answer 200", sdp, 200)
+
diff --git a/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_1.py b/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_1.py
new file mode 100644
index 0000000..e96919f
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_1.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Video uses dynamic payload type
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=video 4000 RTP/AVP 100
+a=rtpmap:100 myvideo/80000
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=video 0 RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and video", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_2.py b/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_2.py
new file mode 100644
index 0000000..6bdda92
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/120_sdp_with_video_dynamic_2.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this case the video codec uses dynamic payload type
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=video 4000 RTP/AVP 100
+a=rtpmap:100 myvideo/96000
+m=audio 5000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=video 0 RTP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and video", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/121_sdp_with_video_static_1.py b/tests/pjsua/scripts-sendto/121_sdp_with_video_static_1.py
new file mode 100644
index 0000000..f05da5d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/121_sdp_with_video_static_1.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Video uses static payload type which will cause failure
+# when session.c looks-up the codec in codec manager
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=video 4000 RTP/AVP 54
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=video 0 RTP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and video", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/121_sdp_with_video_static_2.py b/tests/pjsua/scripts-sendto/121_sdp_with_video_static_2.py
new file mode 100644
index 0000000..67e0132
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/121_sdp_with_video_static_2.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Video uses static payload type which will cause failure
+# when session.c looks-up the codec in codec manager
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=video 4000 RTP/AVP 54
+m=audio 5000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=video 0 RTP/AVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and video", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_1.py b/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_1.py
new file mode 100644
index 0000000..0e7ced7
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_1.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=xapplicationx 4000 RTP/AVP 100
+a=rtpmap:100 myapp/80000
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=xapplicationx 0 RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and unknown", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_2.py b/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_2.py
new file mode 100644
index 0000000..7bdb14e
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/122_sdp_with_unknown_dynamic_2.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=xapplicationx 4000 RTP/AVP 100
+a=rtpmap:100 myapp/80000
+m=audio 5000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=xapplicationx 0 RTP/AVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and unknown", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_1.py b/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_1.py
new file mode 100644
index 0000000..3da1440
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_1.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# The unknown media uses static payload type
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=xapplicationx 4000 RTP/AVP 54
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=xapplicationx 0 RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and unknown", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_2.py b/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_2.py
new file mode 100644
index 0000000..76ff13b
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/123_sdp_with_unknown_static_2.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# The unknown media uses static payload type
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=xapplicationx 4000 RTP/AVP 54
+m=audio 5000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=xapplicationx 0 RTP/AVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and unknown", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/124_sdp_with_unknown_static_unknown_transport.py b/tests/pjsua/scripts-sendto/124_sdp_with_unknown_static_unknown_transport.py
new file mode 100644
index 0000000..5d1b7d3
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/124_sdp_with_unknown_static_unknown_transport.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=xapplicationx 4000 XRTPX/XAVPX 54
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=xapplicationx 0 XRTPX/XAVPX "
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mixed audio and unknown and with unknown transport", 
+			   pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_0.py b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_0.py
new file mode 100644
index 0000000..dbbedff
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_0.py
@@ -0,0 +1,30 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Multiple good m=audio lines! The current algorithm in pjsua-lib will
+# select the last audio (which should be okay, as we're entitled to
+# select any of them)
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=audio 4000 RTP/AVP 0
+m=audio 3000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 RTP/AVP[\\s\\S]+m=audio 0 RTP/AVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Mutiple good m=audio lines", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_1.py b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_1.py
new file mode 100644
index 0000000..3cfc611
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_1.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Multiple m=audio, one of them is bad
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+m=audio 4000 UNKNOWN 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+m=audio 0 UNKNOWN"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Audio and bad audio", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_2.py b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_2.py
new file mode 100644
index 0000000..80cbf4b
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_2.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Multiple m=audio, one of them is bad
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 UNKNOWN 0
+m=audio 5000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 UNKNOWN[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Audio and bad audio", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_3.py b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_3.py
new file mode 100644
index 0000000..a0397c5
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_3.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Multiple m=audio, one of them has dynamic PT codec that we don't support
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 100
+a=rtpmap:100 someunknowncodec/8000
+m=audio 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 RTP/AVP[\s\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Multiple audio lines", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_4.py b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_4.py
new file mode 100644
index 0000000..814de0d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/125_sdp_with_multi_audio_4.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Multiple m=audio, one of them has static PT codec that we don't support
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 80
+m=audio 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 RTP/AVP[\s\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Multiple audio lines", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/150_err_extension.py b/tests/pjsua/scripts-sendto/150_err_extension.py
new file mode 100644
index 0000000..cb46d1f
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/150_err_extension.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = "Require: xxx-my-extension\n"
+include = ["Unsupported: xxx-my-extension"]
+exclude = []
+sendto_cfg = sip.SendtoCfg("Bad extension", pjsua_args, sdp, 420, 
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+			   
+
diff --git a/tests/pjsua/scripts-sendto/151_err_sdp_video.py b/tests/pjsua/scripts-sendto/151_err_sdp_video.py
new file mode 100644
index 0000000..1dc8422
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/151_err_sdp_video.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=video 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Video not acceptable", pjsua_args, sdp, 488,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/152_err_sdp_no_media.py b/tests/pjsua/scripts-sendto/152_err_sdp_no_media.py
new file mode 100644
index 0000000..4e7465a
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/152_err_sdp_no_media.py
@@ -0,0 +1,22 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("No media in SDP", pjsua_args, sdp, 400,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/153_err_sdp_unsupported_codec.py b/tests/pjsua/scripts-sendto/153_err_sdp_unsupported_codec.py
new file mode 100644
index 0000000..1d6da2a
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/153_err_sdp_unsupported_codec.py
@@ -0,0 +1,24 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=video 4000 RTP/AVP 101
+a=rtpmap:101 my-proprietary-codec
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Unsupported codec", pjsua_args, sdp, 488,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/155_err_sdp_bad_syntax.py b/tests/pjsua/scripts-sendto/155_err_sdp_bad_syntax.py
new file mode 100644
index 0000000..d32ef3d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/155_err_sdp_bad_syntax.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=
+o=
+s=
+c=
+t=
+a=
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = [ "Warning: " ]	# better have Warning header
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Bad SDP syntax", pjsua_args, sdp, 400,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/156_err_sdp_bad_net_type.py b/tests/pjsua/scripts-sendto/156_err_sdp_bad_net_type.py
new file mode 100644
index 0000000..7c1536b
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/156_err_sdp_bad_net_type.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=AF IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = [ "Warning: " ]	# better have Warning header
+exclude = []
+sendto_cfg = sip.SendtoCfg("Bad SDP network type", pjsua_args, sdp, 400, 
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+			   
+
diff --git a/tests/pjsua/scripts-sendto/157_err_sdp_bad_addr_type.py b/tests/pjsua/scripts-sendto/157_err_sdp_bad_addr_type.py
new file mode 100644
index 0000000..cac5c98
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/157_err_sdp_bad_addr_type.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP7 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = [ "Warning: " ]	# better have Warning header
+exclude = []
+sendto_cfg = sip.SendtoCfg("Bad SDP address type", pjsua_args, sdp, 400, 
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+			   
+
diff --git a/tests/pjsua/scripts-sendto/158_err_sdp_bad_transport_type.py b/tests/pjsua/scripts-sendto/158_err_sdp_bad_transport_type.py
new file mode 100644
index 0000000..63dd811
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/158_err_sdp_bad_transport_type.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/XAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = ""
+include = []
+exclude = []
+sendto_cfg = sip.SendtoCfg("Unsupported transport type", pjsua_args, sdp, 488, 
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+			   
+
diff --git a/tests/pjsua/scripts-sendto/160_err_duplicate_replaces.py b/tests/pjsua/scripts-sendto/160_err_duplicate_replaces.py
new file mode 100644
index 0000000..ec4b0fd
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/160_err_duplicate_replaces.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = "Replaces: abcd;from_tag=1\r\nReplaces: efgh;from_tag=2\r\n"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Duplicate replaces header", pjsua_args, sdp, 400,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/161_err_replaces_dlg_not_found.py b/tests/pjsua/scripts-sendto/161_err_replaces_dlg_not_found.py
new file mode 100644
index 0000000..863a1a5
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/161_err_replaces_dlg_not_found.py
@@ -0,0 +1,23 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200"
+extra_headers = "Replaces: abcd;from_tag=1\r\n"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Replaced dialog not found", pjsua_args, sdp, 481,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/200_ice_no_ice.py b/tests/pjsua/scripts-sendto/200_ice_no_ice.py
new file mode 100644
index 0000000..07a08f2
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/200_ice_no_ice.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = []
+exclude = ["a=ice", "a=candidate"]
+
+sendto_cfg = sip.SendtoCfg( "caller has no ice, answer must not have ICE", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/200_ice_success_1.py b/tests/pjsua/scripts-sendto/200_ice_success_1.py
new file mode 100644
index 0000000..a2ff50d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/200_ice_success_1.py
@@ -0,0 +1,31 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-ufrag"]		   	# must have ICE
+exclude = ["a=candidate:[0-9a-zA-Z]+ 2 UDP", 	# must not answer with 2 components
+	   "ice-mismatch"		     	# must not mismatch
+	  ]
+
+sendto_cfg = sip.SendtoCfg( "caller sends only one component", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/200_ice_success_2.py b/tests/pjsua/scripts-sendto/200_ice_success_2.py
new file mode 100644
index 0000000..caf64b9
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/200_ice_success_2.py
@@ -0,0 +1,36 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtcp:4382 IN IP4 192.168.0.4
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+a=candidate:YY 2 UDP 1234 192.168.0.4 4382 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-ufrag",			# must have ICE
+	   "a=candidate:[0-9a-zA-Z]+ 2 UDP"	# must have RTCP component
+	   ]		   	
+exclude = [
+	   "ice-mismatch"		     	# must not mismatch
+	  ]
+
+sendto_cfg = sip.SendtoCfg( "caller sends only one component", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude,
+			    enable_buffer = True)
+
diff --git a/tests/pjsua/scripts-sendto/200_ice_success_3.py b/tests/pjsua/scripts-sendto/200_ice_success_3.py
new file mode 100644
index 0000000..2c741e4
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/200_ice_success_3.py
@@ -0,0 +1,35 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+a=candidate:YY 2 UDP 1234 127.0.0.1 4001 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-ufrag",			# must have ICE
+	   "a=candidate:[0-9a-zA-Z]+ 2 UDP"	# must have RTCP component
+	   ]		   	
+exclude = [
+	   "ice-mismatch"		     	# must not mismatch
+	  ]
+
+sendto_cfg = sip.SendtoCfg( "caller sends two components without a=rtcp line", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude,
+			    enable_buffer = True)
+
diff --git a/tests/pjsua/scripts-sendto/200_ice_success_4.py b/tests/pjsua/scripts-sendto/200_ice_success_4.py
new file mode 100644
index 0000000..0ec3849
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/200_ice_success_4.py
@@ -0,0 +1,35 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtcp:4382 IN IP4 192.168.0.4
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+a=candidate:YY 2 UDP 1234 127.0.0.2 4002 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1 --ice-no-rtcp"
+include = ["a=ice-ufrag"]			# must have ICE
+exclude = [
+	   "ice-mismatch",		     	# must not mismatch
+	   "a=candidate:[0-9a-zA-Z]+ 2 UDP"	# must not have RTCP component
+	  ]
+
+sendto_cfg = sip.SendtoCfg( "pjsua with --ice-no-rtcp ignores RTCP things in the SDP", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude,
+			    enable_buffer = True)
+
diff --git a/tests/pjsua/scripts-sendto/201_ice_mismatch_1.py b/tests/pjsua/scripts-sendto/201_ice_mismatch_1.py
new file mode 100644
index 0000000..11b0933
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/201_ice_mismatch_1.py
@@ -0,0 +1,29 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1 1.1.1.1 2222 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-mismatch"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller sends mismatched offer for comp 1", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/201_ice_mismatch_2.py b/tests/pjsua/scripts-sendto/201_ice_mismatch_2.py
new file mode 100644
index 0000000..7371092
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/201_ice_mismatch_2.py
@@ -0,0 +1,31 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtcp:4382 IN IP4 192.168.0.4
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+a=candidate:XX 2 UDP 1234 127.0.0.1 4000 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-mismatch"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller sends mismatched offer for comp 2", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/201_ice_mismatch_3.py b/tests/pjsua/scripts-sendto/201_ice_mismatch_3.py
new file mode 100644
index 0000000..7706a15
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/201_ice_mismatch_3.py
@@ -0,0 +1,30 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=pjmedia
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtcp:4382 IN IP4 192.168.0.4
+a=ice-ufrag:1234
+a=ice-pwd:5678
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=candidate:XX 1 UDP 1234 127.0.0.1 4000 typ host
+"""
+
+args = "--null-audio --use-ice --auto-answer 200 --max-calls 1"
+include = ["a=ice-mismatch"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller sends mismatched offer for comp 2", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/300_srtp_crypto_case_insensitive.py b/tests/pjsua/scripts-sendto/300_srtp_crypto_case_insensitive.py
new file mode 100644
index 0000000..2e3923e
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_crypto_case_insensitive.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AeS_Cm_128_HmAC_shA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 aEs_cM_128_HMaC_ShA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 2 --srtp-secure 0"
+include = ["m=audio \d+ RTP/SAVP", "a=crypto"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has used mixed case in crypto attr, callee must process that normally", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_duplicated_crypto_tag.py b/tests/pjsua/scripts-sendto/300_srtp_duplicated_crypto_tag.py
new file mode 100644
index 0000000..d9228c8
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_duplicated_crypto_tag.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has used invalid crypto tag, callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_non_numeric.py b/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_non_numeric.py
new file mode 100644
index 0000000..e1b0535
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_non_numeric.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:x AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has used invalid crypto tag (non-numeric), callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_zero.py b/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_zero.py
new file mode 100644
index 0000000..51e95d6
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_invalid_crypto_tag_zero.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has used invalid crypto tag (zero), callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_1.py b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_1.py
new file mode 100644
index 0000000..7cc91a7
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_1.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:0 AES_CM_128_HMAC_SHA1_80
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller send crypto attr without key, callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_2.py b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_2.py
new file mode 100644
index 0000000..0e34700
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_2.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller send crypto attr without key, callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_3.py b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_3.py
new file mode 100644
index 0000000..2849f71
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/300_srtp_receive_no_key_3.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:0 AES_CM_128_HMAC_SHA1_80 inline:
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller send crypto attr without key, callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/301_srtp0_recv_avp.py b/tests/pjsua/scripts-sendto/301_srtp0_recv_avp.py
new file mode 100644
index 0000000..2ef19f3
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/301_srtp0_recv_avp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 0 --srtp-secure 0"
+include = []
+exclude = ["a=crypto"]
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP disabled but receive RTP/AVP with crypto, should accept without crypto", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/301_srtp0_recv_savp.py b/tests/pjsua/scripts-sendto/301_srtp0_recv_savp.py
new file mode 100644
index 0000000..d8a1ade
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/301_srtp0_recv_savp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 0 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP disabled but receive RTP/SAVP, should reject the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/310_srtp1_no_crypto.py b/tests/pjsua/scripts-sendto/310_srtp1_no_crypto.py
new file mode 100644
index 0000000..5bfb92e
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/310_srtp1_no_crypto.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = ["m=audio \d+ RTP/AVP"]
+exclude = ["a=crypto"]
+
+sendto_cfg = sip.SendtoCfg( "caller has no crypto attr, answer must accept without crypto attr", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/311_srtp1_recv_avp.py b/tests/pjsua/scripts-sendto/311_srtp1_recv_avp.py
new file mode 100644
index 0000000..8b173f2
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/311_srtp1_recv_avp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = ["m=audio \d+ RTP/AVP", "a=crypto"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP optional and receive RTP/AVP with crypto, should accept with RTP/AVP & crypto", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/312_srtp1_recv_savp.py b/tests/pjsua/scripts-sendto/312_srtp1_recv_savp.py
new file mode 100644
index 0000000..a3b3021
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/312_srtp1_recv_savp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = ["m=audio \d+ RTP/SAVP", "a=crypto"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP optional receive RTP/SAVP, should answer RTP/SAVP too", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/313_srtp1_unsupported_crypto.py b/tests/pjsua/scripts-sendto/313_srtp1_unsupported_crypto.py
new file mode 100644
index 0000000..7b45755
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/313_srtp1_unsupported_crypto.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 CRYPTO_X inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 1 --srtp-secure 0"
+include = []
+exclude = ["a=crypto"]
+
+sendto_cfg = sip.SendtoCfg( "caller has used unsupported crypto, callee (SRTP optional) accept the call without crypto", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/320_srtp2_no_crypto.py b/tests/pjsua/scripts-sendto/320_srtp2_no_crypto.py
new file mode 100644
index 0000000..6613b74
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/320_srtp2_no_crypto.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 2 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has no crypto attr on RTP/SAVP, callee must not accept the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_1.py b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_1.py
new file mode 100644
index 0000000..4ee7bfe
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_1.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+m=xapplicationx 4000 RTP/AVP 100
+a=rtpmap:100 myapp/80000
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 1 --srtp-secure 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+a=crypto[\\s\\S]+m=xapplicationx 0 RTP/AVP"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("SRTP audio and unknown media", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_2.py b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_2.py
new file mode 100644
index 0000000..8536f1a
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_media_2.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=xapplicationx 4000 RTP/AVP 100
+a=rtpmap:100 myapp/80000
+m=audio 5000 RTP/AVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 1 --srtp-secure 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=xapplicationx 0 RTP/AVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+a=crypto"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Unknown media and SRTP audio", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_1.py b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_1.py
new file mode 100644
index 0000000..12aa61c
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_1.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/AVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+m=audio 4000 UNKNOWN 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 1 --srtp-secure 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+a=crypto[\\s\\S]+m=audio 0 UNKNOWN"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("SRTP audio and unknown media", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_2.py b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_2.py
new file mode 100644
index 0000000..a1ac6ce
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/320_srtp_with_unknown_transport_2.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 UNKNOWN 0
+m=audio 5000 RTP/AVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 1 --srtp-secure 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 UNKNOWN[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP[\\s\\S]+a=crypto"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("SRTP audio and unknown media", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/321_srtp2_recv_avp.py b/tests/pjsua/scripts-sendto/321_srtp2_recv_avp.py
new file mode 100644
index 0000000..75c0245
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/321_srtp2_recv_avp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 2 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP mandatory and receive RTP/AVP with crypto, should reject the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/322_srtp2_recv_savp.py b/tests/pjsua/scripts-sendto/322_srtp2_recv_savp.py
new file mode 100644
index 0000000..5d5d53d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/322_srtp2_recv_savp.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:t0r0/apkukU7JjjfR0mY8GEimBq4OiPEm9eKSFOx
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 2 --srtp-secure 0"
+include = ["m=audio \d+ RTP/SAVP", "a=crypto"]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "Callee has SRTP mandatory receive RTP/SAVP, should answer RTP/SAVP too", 
+			    pjsua_args=args, sdp=sdp, resp_code=200, 
+			    resp_inc=include, resp_exc=exclude)
+
diff --git a/tests/pjsua/scripts-sendto/323_srtp2_unsupported_crypto.py b/tests/pjsua/scripts-sendto/323_srtp2_unsupported_crypto.py
new file mode 100644
index 0000000..782beba
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/323_srtp2_unsupported_crypto.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=tester
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/SAVP 0 101
+a=rtpmap:0 PCMU/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+a=crypto:1 CRYPTO_X inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+args = "--null-audio --auto-answer 200 --max-calls 1 --use-srtp 2 --srtp-secure 0"
+include = []
+exclude = []
+
+sendto_cfg = sip.SendtoCfg( "caller has used unsupported crypto, callee (SRTP mandatory) must reject the call", 
+			    pjsua_args=args, sdp=sdp, resp_code=406, 
+			    resp_inc=include, resp_exc=exclude)
diff --git a/tests/pjsua/scripts-sendto/330_srtp_prefer_rtp_savp.py b/tests/pjsua/scripts-sendto/330_srtp_prefer_rtp_savp.py
new file mode 100644
index 0000000..e809736
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/330_srtp_prefer_rtp_savp.py
@@ -0,0 +1,30 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# When SRTP is enabled in pjsua, it should prefer to use
+# RTP/SAVP media line if there are multiple m=audio lines
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 4000 RTP/AVP 0
+a=rtpmap:0 pcmu/8000
+m=audio 5000 RTP/SAVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 1 --srtp-secure 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 RTP/AVP[\\s\\S]+a=rtpmap:0[\\s\\S]+m=audio [1-9]+[0-9]* RTP/SAVP[\\s\\S]+a=crypto"
+	   ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Prefer RTP/SAVP", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/331_srtp_prefer_rtp_avp.py b/tests/pjsua/scripts-sendto/331_srtp_prefer_rtp_avp.py
new file mode 100644
index 0000000..e42d20b
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/331_srtp_prefer_rtp_avp.py
@@ -0,0 +1,29 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# When SRTP is NOT enabled in pjsua, it should prefer to use
+# RTP/AVP media line if there are multiple m=audio lines
+sdp = \
+"""
+v=0
+o=- 0 0 IN IP4 127.0.0.1
+s=-
+c=IN IP4 127.0.0.1
+t=0 0
+m=audio 5000 RTP/SAVP 0
+a=crypto:1 aes_cm_128_hmac_sha1_80 inline:WnD7c1ksDGs+dIefCEo8omPg4uO8DYIinNGL5yxQ
+m=audio 4000 RTP/AVP 0
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --use-srtp 0"
+extra_headers = ""
+include = ["Content-Type: application/sdp",	# response must include SDP
+	   "m=audio 0 RTP/SAVP[\\s\\S]+m=audio [1-9]+[0-9]* RTP/AVP"
+	   ]
+exclude = ["a=crypto"]
+
+sendto_cfg = sip.SendtoCfg("Prefer RTP/SAVP", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/360_non_sip_uri.py b/tests/pjsua/scripts-sendto/360_non_sip_uri.py
new file mode 100644
index 0000000..c4861aa
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/360_non_sip_uri.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Some non-SIP URI's in Contact header
+#
+complete_msg = \
+"""INVITE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 0 INVITE
+Contact: mailto:dontspam@pjsip.org
+Contact: <mailto:dontspam@pjsip.org>
+Contact: http://www.pjsip.org/the%20path.cgi?pname=pvalue
+Contact: <sip:localhost>
+User-Agent: PJSUA v0.9.0-trunk/win32
+Content-Length: 0
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Non SIP URI in Contact", 
+			    "--null-audio --auto-answer 200", 
+			    "", 200, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/361_non_sip_uri.py b/tests/pjsua/scripts-sendto/361_non_sip_uri.py
new file mode 100644
index 0000000..d24685d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/361_non_sip_uri.py
@@ -0,0 +1,26 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# No SIP URI in Contact header 
+#
+complete_msg = \
+"""INVITE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 0 INVITE
+Contact: mailto:dontspam@pjsip.org
+Contact: <mailto:dontspam@pjsip.org>
+Contact: http://www.pjsip.org/the%20path.cgi?pname=pvalue
+User-Agent: PJSUA v0.9.0-trunk/win32
+Content-Length: 0
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "No SIP URI in Contact", 
+			    "--null-audio --auto-answer 200", 
+			    "", 500, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/362_non_sip_uri.py b/tests/pjsua/scripts-sendto/362_non_sip_uri.py
new file mode 100644
index 0000000..0c4b002
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/362_non_sip_uri.py
@@ -0,0 +1,27 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Some non-SIP URI's in Contact header
+#
+complete_msg = \
+"""INVITE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 0 INVITE
+Contact: <sip:localhost>
+Contact: mailto:dontspam@pjsip.org
+Contact: <mailto:dontspam@pjsip.org>
+Contact: http://www.pjsip.org/the%20path.cgi?pname=pvalue
+User-Agent: PJSUA v0.9.0-trunk/win32
+Content-Length: 0
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Non SIP URI in Contact", 
+			    "--null-audio --auto-answer 200", 
+			    "", 200, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/363_non_sip_uri_subscribe.py b/tests/pjsua/scripts-sendto/363_non_sip_uri_subscribe.py
new file mode 100644
index 0000000..5d3d42d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/363_non_sip_uri_subscribe.py
@@ -0,0 +1,31 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Some non-SIP URI's in Contact header
+#
+complete_msg = \
+"""SUBSCRIBE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 0 SUBSCRIBE
+Contact: mailto:dontspam@pjsip.org
+Contact: <mailto:dontspam@pjsip.org>
+Contact: http://www.pjsip.org/the%20path.cgi?pname=pvalue
+Contact: <sip:localhost>
+Event: presence
+Expires: 600
+Accept: application/pidf+xml, application/xpidf+xml
+Allow-Events: presence, refer
+User-Agent: PJSUA v0.9.0-trunk/win32
+Content-Length: 0
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Non SIP URI in Contact", 
+			    "--null-audio --auto-answer 200", 
+			    "", 200, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/364_non_sip_uri_subscribe.py b/tests/pjsua/scripts-sendto/364_non_sip_uri_subscribe.py
new file mode 100644
index 0000000..f9beb05
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/364_non_sip_uri_subscribe.py
@@ -0,0 +1,30 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Some non-SIP URI's in Contact header
+#
+complete_msg = \
+"""SUBSCRIBE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 0 SUBSCRIBE
+Contact: mailto:dontspam@pjsip.org
+Contact: <mailto:dontspam@pjsip.org>
+Contact: http://www.pjsip.org/the%20path.cgi?pname=pvalue
+Event: presence
+Expires: 600
+Accept: application/pidf+xml, application/xpidf+xml
+Allow-Events: presence, refer
+User-Agent: PJSUA v0.9.0-trunk/win32
+Content-Length: 0
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Non SIP URI in Contact", 
+			    "--null-audio --auto-answer 200", 
+			    "", 400, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/400_fmtp_g7221_with_bitrate.py b/tests/pjsua/scripts-sendto/400_fmtp_g7221_with_bitrate.py
new file mode 100644
index 0000000..f21b4e7
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/400_fmtp_g7221_with_bitrate.py
@@ -0,0 +1,34 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer for codec G722.1 should contain fmtp bitrate
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 99 100 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:99 G7221/16000
+a=fmtp:99 bitrate=24000
+a=rtpmap:100 G7221/16000
+a=fmtp:100 bitrate=32000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec G7221"
+extra_headers = ""
+include = ["fmtp:[\d]+ bitrate="]	# response must include fmtp bitrate
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Answer should contain fmtp bitrate for codec G722.1", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_24000.py b/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_24000.py
new file mode 100644
index 0000000..53064ff
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_24000.py
@@ -0,0 +1,35 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer with codec G722.1 should choose the same bitrate
+# which in this test is 24000
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 100 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:100 G7221/16000
+a=fmtp:100 bitrate=24000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec G7221"
+extra_headers = ""
+include = ["a=rtpmap:[\d]+ G7221/16000",  # response must choose G722.1
+	   "fmtp:[\d]+ bitrate=24000"	  # response must choose the same bitrate
+	  ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Answer with G722.1 should choose bitrate 24000", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_32000.py b/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_32000.py
new file mode 100644
index 0000000..d60a4a4
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/401_fmtp_g7221_with_bitrate_32000.py
@@ -0,0 +1,35 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer with codec G722.1 should choose the same bitrate
+# which in this test is 32000
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 100 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:100 G7221/16000
+a=fmtp:100 bitrate=32000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec G7221"
+extra_headers = ""
+include = ["a=rtpmap:[\d]+ G7221/16000",  # response must choose G722.1
+	   "fmtp:[\d]+ bitrate=32000"	  # response must choose the same bitrate
+	  ]
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("Answer with G722.1 should choose bitrate 32000", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/410_fmtp_amrnb_offer_octet_align.py b/tests/pjsua/scripts-sendto/410_fmtp_amrnb_offer_octet_align.py
new file mode 100644
index 0000000..87a2dfa
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/410_fmtp_amrnb_offer_octet_align.py
@@ -0,0 +1,32 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer for codec AMR should contain fmtp octet-align=1
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 99 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:99 AMR/8000
+a=fmtp:99 octet-align=1
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec AMR"
+extra_headers = ""
+include = ["octet-align=1"]	# response must include 'octet-align=1'
+exclude = []
+
+sendto_cfg = sip.SendtoCfg("AMR negotiation should response with fmtp 'octet-align=1'", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/411_fmtp_amrnb_offer_band_eff.py b/tests/pjsua/scripts-sendto/411_fmtp_amrnb_offer_band_eff.py
new file mode 100644
index 0000000..44a2c30
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/411_fmtp_amrnb_offer_band_eff.py
@@ -0,0 +1,31 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer for codec AMR should not contain fmtp octet-align=1
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 99 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:99 AMR/8000
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec AMR"
+extra_headers = ""
+include = [""]
+exclude = ["octet-align=1"]	# response must not include fmtp 'octet-align=1'
+
+sendto_cfg = sip.SendtoCfg("AMR negotiation should not contain 'octet-align=1'", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/412_fmtp_amrnb_offer_band_eff2.py b/tests/pjsua/scripts-sendto/412_fmtp_amrnb_offer_band_eff2.py
new file mode 100644
index 0000000..e69102c
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/412_fmtp_amrnb_offer_band_eff2.py
@@ -0,0 +1,32 @@
+# $Id $
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Answer for codec AMR should not contain fmtp octet-align=1
+
+sdp = \
+"""
+v=0
+o=- 3428650655 3428650655 IN IP4 192.168.1.9
+s=pjmedia
+c=IN IP4 192.168.1.9
+t=0 0
+a=X-nat:0
+m=audio 4000 RTP/AVP 99 101
+a=rtcp:4001 IN IP4 192.168.1.9
+a=rtpmap:99 AMR/8000
+a=fmtp:99 octet-align=0
+a=sendrecv
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-15
+"""
+
+pjsua_args = "--null-audio --auto-answer 200 --add-codec AMR"
+extra_headers = ""
+include = [""]
+exclude = ["octet-align=1"]	# response must not include fmtp 'octet-align=1'
+
+sendto_cfg = sip.SendtoCfg("AMR negotiation should not contain 'octet-align=1'", pjsua_args, sdp, 200,
+			   extra_headers=extra_headers,
+			   resp_inc=include, resp_exc=exclude) 
+
diff --git a/tests/pjsua/scripts-sendto/500_pres_subscribe_with_bad_event.py b/tests/pjsua/scripts-sendto/500_pres_subscribe_with_bad_event.py
new file mode 100644
index 0000000..70f5ab9
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/500_pres_subscribe_with_bad_event.py
@@ -0,0 +1,28 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Ticket http://trac.pjsip.org/repos/ticket/623, based on
+# http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2008-September/004709.html:
+#
+# Assertion when receiving SUBSCRIBE with non-presence Event.
+complete_msg = \
+"""SUBSCRIBE sip:localhost;transport=UDP SIP/2.0
+Call-ID: f20e8783e764cae325dba17be4b8fe19@10.0.2.15
+CSeq: 1 SUBSCRIBE
+From: <sip:localhost>;tag=1710895
+To: <sip:localhost>
+Via: SIP/2.0/UDP localhost;rport;branch=z9hG4bKd88a.18c427d2.0
+Max-Forwards: 69
+Event:  message-summary
+Contact: <sip:localhost>
+Allow: NOTIFY, SUBSCRIBE
+Content-Length: 0
+
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Incoming SUBSCRIBE with non presence", 
+			    "--null-audio", 
+			    "", 489, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/999_asterisk_err.py b/tests/pjsua/scripts-sendto/999_asterisk_err.py
new file mode 100644
index 0000000..580b46d
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/999_asterisk_err.py
@@ -0,0 +1,45 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2008-June/003426.html:
+#
+# Report in pjsip mailing list on 27/6/2008 that this message will
+# cause pjsip to respond with 500 and then second request will cause
+# segfault.
+complete_msg = \
+"""INVITE sip:5001@192.168.1.200:5060;transport=UDP SIP/2.0
+Via: SIP/2.0/UDP 192.168.1.11:5060;branch=z9hG4bK74a60ee5;rport
+From: \"A user\" <sip:66660000@192.168.1.11>;tag=as2858a32c
+To: <sip:5001@192.168.1.200:5060;transport=UDP>
+Contact: <sip:66660000@192.168.1.11>
+Call-ID: 0bc7612c665e875a4a46411442b930a6@192.168.1.11
+CSeq: 102 INVITE
+User-Agent: Asterisk PBX
+Max-Forwards: 70
+Date: Fri, 27 Jun 2008 08:46:47 GMT
+Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY
+Supported: replaces
+Content-Type: application/sdp
+Content-Length: 285
+
+v=0
+o=root 4236 4236 IN IP4 192.168.1.11
+s=session
+c=IN IP4 192.168.1.11
+t=0 0
+m=audio 14390 RTP/AVP 0 3 8 101
+a=rtpmap:0 PCMU/8000
+a=rtpmap:3 GSM/8000
+a=rtpmap:8 PCMA/8000
+a=rtpmap:101 telephone-event/8000
+a=fmtp:101 0-16
+a=silenceSupp:off - - - -
+a=ptime:20
+a=sendrecv
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "Asterisk 500", "--null-audio --auto-answer 200", 
+			    "", 200, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sendto/999_message_no_body.py b/tests/pjsua/scripts-sendto/999_message_no_body.py
new file mode 100644
index 0000000..2e369ed
--- /dev/null
+++ b/tests/pjsua/scripts-sendto/999_message_no_body.py
@@ -0,0 +1,24 @@
+# $Id$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# There's some report that incoming MESSAGE without body will crash pjsua
+#
+complete_msg = \
+"""MESSAGE sip:localhost SIP/2.0
+Via: SIP/2.0/UDP 192.168.0.14:5060;rport;branch=z9hG4bKPj9db9
+Max-Forwards: 70
+From: <sip:192.168.0.14>;tag=08cd5bfc2d8a4fddb1f5e59c6961d298
+To: <sip:localhost>
+Call-ID: 3373d9eb32aa458db7e69c7ea51e0bd7
+CSeq: 23809 MESSAGE
+Contact: <sip:192.168.0.14:5060>
+User-Agent: PJSUA v0.8.0-trunk/win32
+Content-Type: text/plain
+Content-Length: 50
+"""
+
+
+sendto_cfg = sip.SendtoCfg( "empty MESSAGE", "--null-audio --auto-answer 200", 
+			    "", 488, complete_msg=complete_msg)
+
diff --git a/tests/pjsua/scripts-sipp/inv_401_retry_after_100.xml b/tests/pjsua/scripts-sipp/inv_401_retry_after_100.xml
new file mode 100644
index 0000000..6debd13
--- /dev/null
+++ b/tests/pjsua/scripts-sipp/inv_401_retry_after_100.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- 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             -->
+<!--                                                                    -->
+<!--                                                                    -->
+
+<scenario name="Authorization retry after 1xx response test">
+  <!-- Wait for INVITE request -->
+  <recv request="INVITE" crlf="true">
+  </recv>
+
+  <!-- Send 100 Trying -->
+  <send>
+    <![CDATA[
+
+      SIP/2.0 100 Trying
+      [last_Via:]
+      [last_From:]
+      [last_To:]
+      [last_Call-ID:]
+      [last_CSeq:]
+    ]]>
+  </send>
+
+  <!-- Send 180 Ringing (with tag) -->
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+    ]]>
+  </send>
+
+  <!-- Send 401 Unauthorized -->
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 401 Unauthorized
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      WWW-Authenticate: Digest realm="sipp", nonce="1234"
+      Content-Length: 0
+    ]]>
+  </send>
+
+  <!-- Wait for ACK -->
+  <recv request="ACK"
+        optional="false"
+        rtd="true"
+        crlf="true">
+  </recv>
+
+  <!-- Wait for INVITE retransmission -->
+  <recv request="INVITE" crlf="true">
+  </recv>
+
+  <!-- Send 500 Test Success to terminate the call -->
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 500 Test Success
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Content-Length: 0
+    ]]>
+  </send>
+
+  <!-- Wait for ACK -->
+  <recv request="ACK"
+        optional="false"
+        rtd="true"
+        crlf="true">
+  </recv>
+
+  <!-- definition of the response time repartition table (unit is ms)   -->
+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+  <!-- definition of the call length repartition table (unit is ms)     -->
+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
+
diff --git a/tests/pjsua/scripts-sipp/uas-template.xml b/tests/pjsua/scripts-sipp/uas-template.xml
new file mode 100644
index 0000000..d51f89c
--- /dev/null
+++ b/tests/pjsua/scripts-sipp/uas-template.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- 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             -->
+<!--                                                                    -->
+<!--                 Sipp default 'uas' scenario.                       -->
+<!--                                                                    -->
+
+<scenario name="Basic UAS responder">
+  <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
+  <!-- are saved and used for following messages sent. Useful to test   -->
+  <!-- against stateful SIP proxies/B2BUAs.                             -->
+  <recv request="INVITE" crlf="true">
+  </recv>
+
+  <!-- The '[last_*]' keyword is replaced automatically by the          -->
+  <!-- specified header if it was present in the last message received  -->
+  <!-- (except if it was a retransmission). If the header was not       -->
+  <!-- present or if no message has been received, the '[last_*]'       -->
+  <!-- keyword is discarded, and all bytes until the end of the line    -->
+  <!-- are also discarded.                                              -->
+  <!--                                                                  -->
+  <!-- If the specified header was present several times in the         -->
+  <!-- message, all occurences are concatenated (CRLF seperated)        -->
+  <!-- to be used in place of the '[last_*]' keyword.                   -->
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 100 Trying
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+    ]]>
+  </send>
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 301 Redirection
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:target@192.168.254.254>
+      Content-Length: 0
+    ]]>
+  </send>
+
+  <recv request="ACK"
+        optional="false"
+        rtd="true"
+        crlf="true">
+  </recv>
+
+  <!-- Keep the call open for a while in case the 200 is lost to be     -->
+  <!-- able to retransmit it if we receive the BYE again.               -->
+  <pause milliseconds="4000"/>
+
+
+  <!-- definition of the response time repartition table (unit is ms)   -->
+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+  <!-- definition of the call length repartition table (unit is ms)     -->
+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
+
diff --git a/tests/pjsua/tools/cmp_wav.c b/tests/pjsua/tools/cmp_wav.c
new file mode 100644
index 0000000..398a721
--- /dev/null
+++ b/tests/pjsua/tools/cmp_wav.c
@@ -0,0 +1,245 @@
+/* $Id$ */
+
+#include <pjmedia.h>
+#include <pjlib-util.h>
+#include <pjlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define app_perror(a,b,c) printf("%s: %s (%d)", a, b, c)
+
+
+/* For logging purpose. */
+#define THIS_FILE   "cmp_wav.c"
+#define BYTES_PER_FRAME	    512
+
+static const char *desc = 
+" FILE		    						    \n"
+"		    						    \n"
+"  cmp_wav.c	    						    \n"
+"		    						    \n"
+" PURPOSE	    						    \n"
+"		    						    \n"
+"  Compare two WAV files.					    \n"
+"		    						    \n"
+" USAGE		    						    \n"
+"		    						    \n"
+"  cmp_wav ORIGINAL_WAV DEGRADED_WAV [TIME] [DETAIL]		    \n"
+"		    						    \n"
+"  ORIGINAL_WAV	    The original WAV file as reference.		    \n"
+"  DEGRADED_WAV	    The degraded WAV file.			    \n"
+"  TIME	            Compare only some part of the files		    \n"
+"                   (in ms, since the beginning).		    \n"
+"                   Specify 0 (default) to compare the whole time.  \n"
+"  DETAIL           Show detail result, 1 or 0 (default=0, means no)\n"
+"		    						    \n"
+"  Both files must have same clock rate and must contain	    \n"
+"  uncompressed (i.e. 16bit) PCM.				    \n";
+
+
+/* Sum of multiplication of corresponding samples in buf1 & buf2 */
+double sum_mult_sig(pj_int16_t *buf1, pj_int16_t *buf2, unsigned nsamples)
+{
+    double mag = 0;
+
+    while (nsamples--)
+	mag += (double)*buf1++ * (double)*buf2++;
+
+    return mag;
+}
+
+
+/*
+ * main()
+ */
+int main(int argc, char *argv[])
+{
+    pj_caching_pool cp;
+    pjmedia_endpt *med_endpt;
+    pj_pool_t *pool;
+    pjmedia_port *file_ori_port;
+    pjmedia_port *file_deg_port;
+    pj_status_t status;
+    unsigned first_nsamples = 0;
+    unsigned samples_compared = 0;
+
+    char buf1[BYTES_PER_FRAME];
+    char buf2[BYTES_PER_FRAME];
+
+    double ref_mag = 0;
+    double deg_mag = 0;
+    double mix_mag = 0;
+
+    int detail = 0;
+    int res_deg, res_mix, res_overall;
+
+    if (argc < 3) {
+    	puts("Error: original & degraded filename required");
+	puts(desc);
+	return 1;
+    }
+
+    /* Set log level. */
+    pj_log_set_level(3);
+
+    /* Must init PJLIB first: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+    /* 
+     * Initialize media endpoint.
+     * This will implicitly initialize PJMEDIA too.
+     */
+    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Create memory pool for our file player */
+    pool = pj_pool_create( &cp.factory,	    /* pool factory	    */
+			   "wav",	    /* pool name.	    */
+			   4000,	    /* init size	    */
+			   4000,	    /* increment size	    */
+			   NULL		    /* callback on error    */
+			   );
+
+    /* Create file media port from the original WAV file */
+    status = pjmedia_wav_player_port_create(  pool,	/* memory pool	    */
+					      argv[1],	/* file to play	    */
+					      40,	/* ptime.	    */
+					      PJMEDIA_FILE_NO_LOOP,	/* flags	    */
+					      0,	/* default buffer   */
+					      &file_ori_port/* returned port    */
+					      );
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to use WAV file", status);
+	return 1;
+    }
+
+    /* Create file media port from the degraded WAV file */
+    status = pjmedia_wav_player_port_create(  pool,	/* memory pool	    */
+					      argv[2],	/* file to play	    */
+					      40,	/* ptime.	    */
+					      PJMEDIA_FILE_NO_LOOP,	/* flags	    */
+					      0,	/* default buffer   */
+					      &file_deg_port/* returned port    */
+					      );
+    if (status != PJ_SUCCESS) {
+	app_perror(THIS_FILE, "Unable to use WAV file", status);
+	return 1;
+    }
+
+    if (file_ori_port->info.clock_rate != file_deg_port->info.clock_rate) {
+	app_perror(THIS_FILE, "Clock rates must be same.", PJ_EINVAL);
+	return 1;
+    }
+
+    if (argc > 3)
+	first_nsamples = atoi(argv[3]) * file_ori_port->info.clock_rate / 1000;
+
+    if (argc > 4)
+	detail = atoi(argv[4]);
+
+    while (1) {
+	pjmedia_frame f1, f2;
+
+	f1.buf = buf1;
+	f1.size = BYTES_PER_FRAME;
+	f2.buf = buf2;
+	f2.size = BYTES_PER_FRAME;
+
+	status = pjmedia_port_get_frame(file_ori_port, &f1);
+	if (status == PJ_EEOF) {
+	    break;
+	} else if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Error occured while reading file", status);
+	    break;
+	}
+	status = pjmedia_port_get_frame(file_deg_port, &f2);
+	if (status == PJ_EEOF) {
+	    break;
+	} else if (status != PJ_SUCCESS) {
+	    app_perror(THIS_FILE, "Error occured while reading file", status);
+	    break;
+	}
+
+	/* Calculate magnitudes */
+	ref_mag += sum_mult_sig(f1.buf, f1.buf, BYTES_PER_FRAME >> 1);
+	deg_mag += sum_mult_sig(f2.buf, f2.buf, BYTES_PER_FRAME >> 1);
+	mix_mag += sum_mult_sig(f1.buf, f2.buf, BYTES_PER_FRAME >> 1);
+
+	samples_compared += BYTES_PER_FRAME >> 1;
+	if (first_nsamples && samples_compared >= first_nsamples)
+	    break;
+    }
+
+    /* Degraded magnitude compared to reference magnitude 
+     */
+    res_deg = (int) (deg_mag / ref_mag * 100.0);
+    if (res_deg < 0)
+	res_deg = -1;
+    else if (res_deg >= 81)
+	res_deg = 9;
+    else
+	res_deg = pj_isqrt(res_deg);
+
+    /* Mixed magnitude (don't know what this is actually :D) compared to 
+     * reference magnitude 
+     */
+    res_mix = (int) (mix_mag / ref_mag * 100.0);
+    if (res_mix < 0)
+	res_mix = -1;
+    else if (res_mix >= 81)
+	res_mix = 9;
+    else
+	res_mix = pj_isqrt(res_mix);
+
+    /* Overall score.
+     * If mixed score is -1, then overall score should be -1 as well.
+     * Apply no weighting (1:1) for now.
+     */
+    if (res_mix == -1)
+	res_overall = -1;
+    else
+	res_overall = (res_mix*1 + res_deg*1) / 2;
+
+    if (detail) {
+	printf("Reference = %.0f\n", ref_mag);
+	printf("Degraded  = %.0f\n", deg_mag);
+	printf("Mixed     = %.0f\n", mix_mag);
+
+	printf("\n");
+
+	printf("Score 1   = %d\n", res_deg);
+	printf("Score 2   = %d\n", res_mix);
+
+	printf("\n");
+    }
+
+    printf("Overall   = %d\n", res_overall);
+
+    /* Destroy file port */
+    status = pjmedia_port_destroy( file_ori_port );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    status = pjmedia_port_destroy( file_deg_port );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Release application pool */
+    pj_pool_release( pool );
+
+    /* Destroy media endpoint. */
+    pjmedia_endpt_destroy( med_endpt );
+
+    /* Destroy pool factory */
+    pj_caching_pool_destroy( &cp );
+
+    /* Shutdown PJLIB */
+    pj_shutdown();
+
+
+    /* Done. */
+    return 0;
+}
+
diff --git a/tests/pjsua/tools/cmp_wav.exe b/tests/pjsua/tools/cmp_wav.exe
new file mode 100644
index 0000000..6220771
--- /dev/null
+++ b/tests/pjsua/tools/cmp_wav.exe
Binary files differ
diff --git a/tests/pjsua/wavs/input.11.wav b/tests/pjsua/wavs/input.11.wav
new file mode 100644
index 0000000..712aea7
--- /dev/null
+++ b/tests/pjsua/wavs/input.11.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.16.wav b/tests/pjsua/wavs/input.16.wav
new file mode 100644
index 0000000..da25cfc
--- /dev/null
+++ b/tests/pjsua/wavs/input.16.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.2.16.wav b/tests/pjsua/wavs/input.2.16.wav
new file mode 100644
index 0000000..a885fe9
--- /dev/null
+++ b/tests/pjsua/wavs/input.2.16.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.2.8.wav b/tests/pjsua/wavs/input.2.8.wav
new file mode 100644
index 0000000..95b2310
--- /dev/null
+++ b/tests/pjsua/wavs/input.2.8.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.22.wav b/tests/pjsua/wavs/input.22.wav
new file mode 100644
index 0000000..bc309dd
--- /dev/null
+++ b/tests/pjsua/wavs/input.22.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.32.wav b/tests/pjsua/wavs/input.32.wav
new file mode 100644
index 0000000..4fc356a
--- /dev/null
+++ b/tests/pjsua/wavs/input.32.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.44.wav b/tests/pjsua/wavs/input.44.wav
new file mode 100644
index 0000000..6262d91
--- /dev/null
+++ b/tests/pjsua/wavs/input.44.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.48.wav b/tests/pjsua/wavs/input.48.wav
new file mode 100644
index 0000000..5f32bb3
--- /dev/null
+++ b/tests/pjsua/wavs/input.48.wav
Binary files differ
diff --git a/tests/pjsua/wavs/input.8.wav b/tests/pjsua/wavs/input.8.wav
new file mode 100644
index 0000000..4fa44d4
--- /dev/null
+++ b/tests/pjsua/wavs/input.8.wav
Binary files differ