Re #1395: Backport of PJSIP 1.x branch into PJSIP 2.0 trunk

* Backport of r3557:r3832

TODO: ticket #1268 (Option for automatic/manual sending of RTCP SDES/BYE for the stream) for video stream.



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3841 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Sauw Ming 2011-10-24 09:28:13 +00:00
parent 3f0ff80097
commit e7dbbc8f30
80 changed files with 13020 additions and 3755 deletions

13135
aconfigure

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,7 @@ if test "$AR" = ""; then AR="${CROSS_COMPILE}ar rv"; fi
AC_SUBST(AR)
if test "$LD" = ""; then LD="$CC"; fi
AC_SUBST(LD)
if test "$LDOUT" = ""; then LDOUT="-o"; fi
if test "$LDOUT" = ""; then LDOUT="-o "; fi
AC_SUBST(LDOUT)
if test "$RANLIB" = ""; then RANLIB="${CROSS_COMPILE}ranlib"; fi
AC_SUBST(RANLIB)
@ -62,7 +62,7 @@ if test "$LIBEXT" = ""; then LIBEXT='a'; fi
AC_SUBST(LIBEXT)
if test "$LIBEXT2" = ""; then LIBEXT2=""; fi
AC_SUBST(LIBEXT2)
if test "$CC_OUT" = ""; then CC_OUT="-o"; fi
if test "$CC_OUT" = ""; then CC_OUT="-o "; fi
AC_SUBST(CC_OUT)
if test "$CC_INC" = ""; then CC_INC="-I"; fi
AC_SUBST(CC_INC)
@ -127,6 +127,12 @@ dnl Endianness detection
dnl
AC_C_BIGENDIAN
if test "x$ac_cv_c_bigendian" = "xyes"; then
CFLAGS="$CFLAGS -DPJ_IS_BIG_ENDIAN=1 -DPJ_IS_LITTLE_ENDIAN=0"
else
CFLAGS="$CFLAGS -DPJ_IS_BIG_ENDIAN=0 -DPJ_IS_LITTLE_ENDIAN=1"
fi
dnl
dnl Legacy macros
dnl
@ -1178,6 +1184,34 @@ AC_ARG_ENABLE(ssl,
fi
])
dnl # Include opencore-amrnb support
AC_SUBST(ac_no_opencore_amrnb)
AC_ARG_ENABLE(opencore_amrnb,
AC_HELP_STRING([--disable-opencore-amrnb],
[Exclude OpenCORE AMR-NB support from the build (default: autodetect)])
,
[
if test "$enable_opencore_amrnb" = "no"; then
[ac_no_opencore_amrnb=1]
AC_DEFINE(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC,0)
AC_MSG_RESULT([Checking if OpenCORE AMR-NB support is disabled... yes])
fi
],
[
AC_MSG_RESULT([checking for OpenCORE AMR-NB installations..])
AC_SUBST(opencore_amrnb_h_present)
AC_SUBST(opencore_amrnb_present)
AC_CHECK_HEADER(opencore-amrnb/interf_enc.h,[opencore_amrnb_h_present=1])
AC_CHECK_LIB(opencore-amrnb,Encoder_Interface_init,[opencore_amrnb_present=1 && LIBS="$LIBS -lopencore-amrnb"])
if test "x$opencore_amrnb_h_present" = "x1" -a "x$opencore_amrnb_present" = "x1"; then
AC_MSG_RESULT([OpenCORE AMR-NB library found, AMR-NB support enabled])
AC_DEFINE(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC,1)
else
[ac_no_opencore_amrnb=1]
AC_DEFINE(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC,0)
fi
])
dnl ##########################################
dnl #

View File

@ -71,20 +71,29 @@ fi
# full path as it's not normally in user's PATH
if test "${CC}" = ""; then
for archpath in `ls -d ${SDKPATH}/usr/lib/gcc/arm-apple-darwin*`; do
archname=`basename ${archpath}`
for gccver in `ls ${archpath}`; do
gccpath="${DEVPATH}/usr/bin/${archname}-gcc-${gccver}"
if test -e ${gccpath}; then
export CC="${gccpath}"
# break
fi
done
if test ! "${CC}" = ""; then
echo "$F: CC is not specified, choosing ${CC}"
break
# Try to use llvm-gcc if available
gccpath="${DEVPATH}/usr/bin/llvm-gcc"
if test -e ${gccpath}; then
export CC="${gccpath}"
if test "${ARCH}" = ""; then
export ARCH="-arch armv7"
echo "$F: ARCH is not specified, choosing ${ARCH}"
fi
done
else
for archpath in `ls -d ${SDKPATH}/usr/lib/gcc/arm-apple-darwin*`; do
archname=`basename ${archpath}`
for gccver in `ls ${archpath}`; do
gccpath="${DEVPATH}/usr/bin/${archname}-gcc-${gccver}"
if test -e ${gccpath}; then
export CC="${gccpath}"
fi
done
done
fi
if test ! "${CC}" = ""; then
echo "$F: CC is not specified, choosing ${CC}"
fi
fi
if test "${CC}" = ""; then

View File

@ -188,6 +188,36 @@ typedef struct pj_http_req_param
*/
pj_http_auth_cred auth_cred;
/**
* Optional source port range to use when binding the socket.
* This can be used if the source port needs to be within a certain range
* for instance due to strict firewall settings. The port used will be
* randomized within the range.
*
* Note that if authentication is configured, the authentication response
* will be a new transaction
*
* Default is 0 (The OS will select the source port automatically)
*/
pj_uint16_t source_port_range_start;
/**
* Optional source port range to use when binding.
* The size of the port restriction range
*
* Default is 0 (The OS will select the source port automatically))
*/
pj_uint16_t source_port_range_size;
/**
* Max number of retries if binding to a port fails.
* Note that this does not adress the scenario where a request times out
* or errors. This needs to be taken care of by the on_complete callback.
*
* Default is 3
*/
pj_uint16_t max_retries;
} pj_http_req_param;
/**

View File

@ -26,15 +26,17 @@
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/timer.h>
#include <pj/rand.h>
#include <pjlib-util/base64.h>
#include <pjlib-util/errno.h>
#include <pjlib-util/md5.h>
#include <pjlib-util/scanner.h>
#include <pjlib-util/string.h>
#define THIS_FILE "http_client.c"
#if 0
/* Enable some tracing */
#define THIS_FILE "http_client.c"
#define TRACE_(arg) PJ_LOG(3,arg)
#else
#define TRACE_(arg)
@ -764,6 +766,7 @@ PJ_DEF(void) pj_http_req_param_default(pj_http_req_param *param)
pj_strset2(&param->version, (char*)HTTP_1_0);
param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT;
pj_time_val_normalize(&param->timeout);
param->max_retries = 3;
}
/* Get the location of '@' character to indicate the end of
@ -1004,11 +1007,13 @@ PJ_DEF(void*) pj_http_req_get_user_data(pj_http_req *http_req)
return http_req->param.user_data;
}
PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
static pj_status_t start_http_req(pj_http_req *http_req,
pj_bool_t notify_on_fail)
{
pj_sock_t sock = PJ_INVALID_SOCKET;
pj_status_t status;
pj_activesock_cb asock_cb;
int retry = 0;
PJ_ASSERT_RETURN(http_req, PJ_EINVAL);
/* Http request is not idle, a request was initiated before and
@ -1031,7 +1036,7 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
(http_req->param.addr_family==pj_AF_INET() &&
http_req->addr.ipv4.sin_addr.s_addr==PJ_INADDR_NONE))
{
return status; // cannot resolve host name
goto on_return;
}
http_req->resolved = PJ_TRUE;
}
@ -1045,6 +1050,32 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
asock_cb.on_data_read = &http_on_data_read;
asock_cb.on_data_sent = &http_on_data_sent;
asock_cb.on_connect_complete = &http_on_connect;
do
{
pj_sockaddr_in bound_addr;
pj_uint16_t port = 0;
/* If we are using port restriction.
* Get a random port within the range
*/
if (http_req->param.source_port_range_start != 0) {
port = (pj_uint16_t)
(http_req->param.source_port_range_start +
(pj_rand() % http_req->param.source_port_range_size));
}
pj_sockaddr_in_init(&bound_addr, NULL, port);
status = pj_sock_bind(sock, &bound_addr, sizeof(bound_addr));
} while (status != PJ_SUCCESS && (retry++ < http_req->param.max_retries));
if (status != PJ_SUCCESS) {
PJ_PERROR(1,(THIS_FILE, status,
"Unable to bind to the requested port"));
pj_sock_close(sock);
goto on_return;
}
// TODO: should we set whole data to 0 by default?
// or add it in the param?
@ -1052,8 +1083,7 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
NULL, http_req->ioqueue,
&asock_cb, http_req, &http_req->asock);
if (status != PJ_SUCCESS) {
if (sock != PJ_INVALID_SOCKET)
pj_sock_close(sock);
pj_sock_close(sock);
goto on_return; // error creating activesock
}
@ -1074,7 +1104,9 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
pj_sockaddr_get_len(&http_req->addr));
if (status == PJ_SUCCESS) {
http_req->state = SENDING_REQUEST;
return http_req_start_sending(http_req);
status = http_req_start_sending(http_req);
if (status != PJ_SUCCESS)
goto on_return;
} else if (status != PJ_EPENDING) {
goto on_return; // error connecting
}
@ -1082,10 +1114,21 @@ PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
return PJ_SUCCESS;
on_return:
http_req_end_request(http_req);
http_req->error = status;
if (notify_on_fail)
pj_http_req_cancel(http_req, PJ_TRUE);
else
http_req_end_request(http_req);
return status;
}
/* Starts an asynchronous HTTP request to the URL specified. */
PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req)
{
return start_http_req(http_req, PJ_FALSE);
}
/* Respond to basic authentication challenge */
static pj_status_t auth_respond_basic(pj_http_req *hreq)
{
@ -1345,7 +1388,7 @@ static pj_status_t auth_respond_digest(pj_http_req *hreq)
*/
const pj_str_t STR_MD5 = { "MD5", 3 };
const pj_str_t qop = pj_str("auth");
const pj_str_t nc = pj_str("1");
const pj_str_t nc = pj_str("00000001");
const pj_str_t cnonce = pj_str("b39971");
auth_create_digest_response(&digest_response, cred,
@ -1433,7 +1476,7 @@ static void restart_req_with_auth(pj_http_req *hreq)
http_req_end_request(hreq);
status = pj_http_req_start(hreq);
status = start_http_req(hreq, PJ_TRUE);
if (status != PJ_SUCCESS)
goto on_error;

View File

@ -368,8 +368,20 @@ PJ_DEF(pj_status_t) pj_pcap_read_udp(pj_pcap_file *file,
*udp_payload_size = sz;
// Some layers may have trailer, e.g: link eth2.
/* Check that we've read all the packets */
PJ_ASSERT_RETURN(sz_read == rec_incl, PJ_EBUG);
//PJ_ASSERT_RETURN(sz_read == rec_incl, PJ_EBUG);
/* Skip trailer */
while (sz_read < rec_incl) {
sz = rec_incl - sz_read;
status = read_file(file, &tmp.eth, &sz);
if (status != PJ_SUCCESS) {
TRACE_((file->obj_name, "Error reading trailer: %d", status));
return status;
}
sz_read += sz;
}
return PJ_SUCCESS;
}

View File

@ -177,6 +177,8 @@
# include "Availability.h"
/* Use CFHost API for pj_getaddrinfo() (see ticket #1246) */
# define PJ_GETADDRINFO_USE_CFHOST 1
/* Disable local host resolution in pj_gethostip() (see ticket #1342) */
# define PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION 1
# ifdef __IPHONE_4_0
/* Is multitasking support available? (see ticket #1107) */
# define PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT 1

View File

@ -108,6 +108,11 @@
# define NULL 0
#endif
/* Endianness */
#ifndef PJ_IS_LITTLE_ENDIAN
# define PJ_IS_LITTLE_ENDIAN 1
# define PJ_IS_BIG_ENDIAN 0
#endif
/* Doesn't seem to allow more than this */
#define PJ_IOQUEUE_MAX_HANDLES 8

View File

@ -98,6 +98,12 @@
*/
#define PJ_SELECT_NEEDS_NFDS 0
/* Endianness */
#ifndef PJ_IS_LITTLE_ENDIAN
# define PJ_IS_LITTLE_ENDIAN 1
# define PJ_IS_BIG_ENDIAN 0
#endif
/* Default threading is enabled, unless it's overridden. */
#ifndef PJ_HAS_THREADS
# define PJ_HAS_THREADS (1)

View File

@ -170,7 +170,7 @@
#elif defined(PJ_M_IA64) || defined(__ia64__) || defined(_IA64) || \
defined(__IA64__) || defined( _M_IA64)
/*
* Intel IA64 processor, little endian
* Intel IA64 processor, default to little endian
*/
# undef PJ_M_IA64
# define PJ_M_IA64 1
@ -208,15 +208,14 @@
#elif defined(PJ_M_MIPS) || defined(__mips__) || defined(__mips) || \
defined(__MIPS__) || defined(MIPS) || defined(_MIPS_)
/*
* MIPS, default to little endian
* MIPS, bi-endian, so raise error if endianness is not configured
*/
# undef PJ_M_MIPS
# define PJ_M_MIPS 1
# define PJ_M_NAME "mips"
# define PJ_HAS_PENTIUM 0
# if !defined(PJ_IS_LITTLE_ENDIAN) && !defined(PJ_IS_BIG_ENDIAN)
# define PJ_IS_LITTLE_ENDIAN 1
# define PJ_IS_BIG_ENDIAN 0
# if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN
# error Endianness must be declared for this processor
# endif
@ -234,29 +233,29 @@
#elif defined (PJ_M_ARMV4) || defined(ARM) || defined(_ARM_) || \
defined(ARMV4) || defined(__arm__)
/*
* ARM, default to little endian
* ARM, bi-endian, so raise error if endianness is not configured
*/
# undef PJ_M_ARMV4
# define PJ_M_ARMV4 1
# define PJ_M_NAME "armv4"
# define PJ_HAS_PENTIUM 0
# if !defined(PJ_IS_LITTLE_ENDIAN) && !defined(PJ_IS_BIG_ENDIAN)
# define PJ_IS_LITTLE_ENDIAN 1
# define PJ_IS_BIG_ENDIAN 0
# if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN
# error Endianness must be declared for this processor
# endif
#elif defined (PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || \
defined(__POWERPC__) || defined(__ppc__) || defined(_M_PPC) || \
defined(_ARCH_PPC)
/*
* PowerPC, big endian
* PowerPC, bi-endian, so raise error if endianness is not configured
*/
# undef PJ_M_POWERPC
# define PJ_M_POWERPC 1
# define PJ_M_NAME "powerpc"
# define PJ_HAS_PENTIUM 0
# define PJ_IS_LITTLE_ENDIAN 0
# define PJ_IS_BIG_ENDIAN 1
# if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN
# error Endianness must be declared for this processor
# endif
#elif defined (PJ_M_NIOS2) || defined(__nios2) || defined(__nios2__) || \
defined(__NIOS2__) || defined(__M_NIOS2) || defined(_ARCH_NIOS2)

View File

@ -125,8 +125,8 @@ PJ_DECL(void *) pj_hash_get( pj_hash_table_t *ht,
* @param pool the pool to allocate the new entry if a new entry has to be
* created.
* @param ht the hash table.
* @param key the key, which MUST point to buffer that remains valid
* for the duration of the entry.
* @param key the key. If pool is not specified, the key MUST point to
* buffer that remains valid for the duration of the entry.
* @param keylen the length of the key, or PJ_HASH_KEY_STRING to use the
* string length of the key.
* @param hval if the value is not zero, then the hash table will use

View File

@ -493,6 +493,13 @@ typedef struct pj_in_addr
*/
#define PJ_INET6_ADDRSTRLEN 46
/**
* The size of sin_zero field in pj_sockaddr_in structure. Most OSes
* use 8, but others such as the BSD TCP/IP stack in eCos uses 24.
*/
#ifndef PJ_SOCKADDR_IN_SIN_ZERO_LEN
# define PJ_SOCKADDR_IN_SIN_ZERO_LEN 8
#endif
/**
* This structure describes Internet socket address.
@ -512,7 +519,7 @@ struct pj_sockaddr_in
#endif
pj_uint16_t sin_port; /**< Transport layer port number. */
pj_in_addr sin_addr; /**< IP address. */
char sin_zero[8]; /**< Padding. */
char sin_zero[PJ_SOCKADDR_IN_SIN_ZERO_LEN]; /**< Padding.*/
};

View File

@ -340,6 +340,7 @@ CPjSocket *CIoqueueCallback::HandleAcceptCompletion()
//
void CIoqueueCallback::RunL()
{
pj_ioqueue_t *ioq = ioqueue_;
Type cur_type = type_;
type_ = TYPE_NONE;
@ -399,7 +400,7 @@ void CIoqueueCallback::RunL()
}
}
ioqueue_->eventCount++;
ioq->eventCount++;
}
//

View File

@ -784,6 +784,8 @@ PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr)
addr->addr.sa_family = (pj_uint16_t)af;
PJ_SOCKADDR_RESET_LEN(addr);
#if !defined(PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION) || \
PJ_GETHOSTIP_DISABLE_LOCAL_RESOLUTION == 0
/* Get hostname's IP address */
count = 1;
status = pj_getaddrinfo(af, pj_gethostname(), &count, &ai);
@ -797,7 +799,10 @@ PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr)
TRACE_((THIS_FILE, "hostname IP is %s",
pj_sockaddr_print(&ai.ai_addr, strip, sizeof(strip), 0)));
}
#else
PJ_UNUSED_ARG(ai);
PJ_UNUSED_ARG(count);
#endif
/* Get default interface (interface for default route) */
if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) {

View File

@ -310,8 +310,10 @@ static pj_status_t init_openssl(void)
meth = (SSL_METHOD*)TLSv1_server_method();
if (!meth)
meth = (SSL_METHOD*)SSLv3_server_method();
#ifndef OPENSSL_NO_SSL2
if (!meth)
meth = (SSL_METHOD*)SSLv2_server_method();
#endif
pj_assert(meth);
ctx=SSL_CTX_new(meth);
@ -488,9 +490,11 @@ static pj_status_t create_ssl(pj_ssl_sock_t *ssock)
case PJ_SSL_SOCK_PROTO_TLS1:
ssl_method = (SSL_METHOD*)TLSv1_method();
break;
#ifndef OPENSSL_NO_SSL2
case PJ_SSL_SOCK_PROTO_SSL2:
ssl_method = (SSL_METHOD*)SSLv2_method();
break;
#endif
case PJ_SSL_SOCK_PROTO_SSL3:
ssl_method = (SSL_METHOD*)SSLv3_method();
break;

View File

@ -22,11 +22,81 @@
#include <pj/os.h>
#if INCLUDE_OS_TEST
static int endianness_test32(void)
{
union t
{
pj_uint32_t u32;
pj_uint16_t u16[2];
pj_uint8_t u8[4];
} t;
PJ_LOG(3,("", " Testing endianness.."));
t.u32 = 0x11223344;
#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN
PJ_LOG(3,("", " Library is set to little endian"));
# if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN
# error Error: Both PJ_IS_LITTLE_ENDIAN and PJ_IS_BIG_ENDIAN are set!
# endif
if ((t.u16[0] & 0xFFFF) != 0x3344 ||
(t.u16[1] & 0xFFFF) != 0x1122)
{
PJ_LOG(3,("", " Error: wrong 16bit values 0x%x and 0x%x",
(t.u16[0] & 0xFFFF), (t.u16[1] & 0xFFFF)));
return 10;
}
if ((t.u8[0] & 0xFF) != 0x44 ||
(t.u8[1] & 0xFF) != 0x33 ||
(t.u8[2] & 0xFF) != 0x22 ||
(t.u8[3] & 0xFF) != 0x11)
{
PJ_LOG(3,("", " Error: wrong 8bit values"));
return 12;
}
#elif defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN
PJ_LOG(3,("", " Library is set to big endian"));
if ((t.u16[0] & 0xFFFF) != 0x1122 ||
(t.u16[1] & 0xFFFF) != 0x3344)
{
PJ_LOG(3,("", " Error: wrong 16bit values 0x%x and 0x%x",
(t.u16[0] & 0xFFFF), (t.u16[1] & 0xFFFF)));
return 20;
}
if ((t.u8[0] & 0xFF) != 0x11 ||
(t.u8[1] & 0xFF) != 0x22 ||
(t.u8[2] & 0xFF) != 0x33 ||
(t.u8[3] & 0xFF) != 0x44)
{
PJ_LOG(3,("", " Error: wrong 8bit values"));
return 22;
}
# if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN
# error Error: Both PJ_IS_LITTLE_ENDIAN and PJ_IS_BIG_ENDIAN are set!
# endif
#else
# error Error: Endianness is not set properly!
#endif
return 0;
}
int os_test(void)
{
const pj_sys_info *si;
int rc = 0;
PJ_LOG(3,("", " Sys info:"));
si = pj_get_sys_info();
PJ_LOG(3,("", " machine: %s", si->machine.ptr));
PJ_LOG(3,("", " os_name: %s", si->os_name.ptr));
@ -35,6 +105,8 @@ int os_test(void)
PJ_LOG(3,("", " sdk_ver: 0x%x", si->sdk_ver));
PJ_LOG(3,("", " info: %s", si->info.ptr));
rc = endianness_test32();
return rc;
}

View File

@ -510,7 +510,7 @@ static int send_recv_test(int sock_type,
}
if (srclen != addrlen)
return -151;
if (pj_memcmp(&addr, srcaddr, srclen) != 0) {
if (pj_sockaddr_cmp(&addr, srcaddr) != 0) {
char srcaddr_str[32], addr_str[32];
strcpy(srcaddr_str, pj_inet_ntoa(srcaddr->sin_addr));
strcpy(addr_str, pj_inet_ntoa(addr.sin_addr));

View File

@ -19,11 +19,11 @@
#
# 3. Success conditions, started with '!', followed by condition name
# and its maximum tolerable value, in frames unit. Recognized condition
# names are: burst, discard, lost, empty, delay. These conditions will
# be verified with jitter buffer statistics after all session test data
# are executed.
# names are: burst, discard, lost, empty, delay, delay_min. These
# conditions will be verified with jitter buffer statistics after all
# session test data are executed.
# Example:
# !delay 10 <- maximum average delay of jbuf is 10 frames
# !delay 10 <- average delay of jbuf is 10 frames
#
# 4. Session test data, containing sequence of jitter buffer events,
# an event is represented by a character as follow:
@ -295,7 +295,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG
!discard 50 <- frames discarded for delay adaptation
!lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal
!empty 0
!delay 25 <- average delay, JB is able to adapt the delay
!delay_min 2 <- minimum delay, JB is able to adapt the delay
PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
@ -311,13 +311,30 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
.
= Fixed mode prefetch 5, with two empty events
%fixed 5
!burst 1
!discard 0
!lost 0
!discard 4 <- the burst level is about 1, but prefetching will cause delay by 5 frames prefetching, delay adjustment may take place later on
!lost 4 <- progressive discard drops frames as if they were lost
!empty 10
!delay 5
G
@ -329,8 +346,8 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
= Fixed mode prefetch 5, with random burst
%fixed 5
!burst 3
!discard 0
!lost 0
!discard 4 <- (see above test scenario)
!lost 4 <- (see above test scenario)
!empty 5
!delay 5
PGPGPPGGPPPPGGPGGGPG PGGGGPPPGPPGPPPGGPGG PGPGPPGGPPPPGGPGGGPG
@ -349,7 +366,7 @@ PGGGGPPPGPPGPPPGGPGG PGPGPPGGPPGGPPPGGGPG PGGGGPPPGPPGPPPGGPGG
!discard 50 <- frames discarded for delay adaptation
!lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal
!empty 0
!delay 20 <- average delay, twice of minimal prefetch
!delay_min 20 <- minimum delay, twice of minimal prefetch
PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
@ -369,7 +386,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG
= Large PUT burst at beginning, then normal with burst level 10 and periodic burst spikes
%adaptive 0 0 40
!burst 10
!burst 12
!discard 300 <- not so relevant for long period session with many delay adjustments needed (i.e: for first burst and periodic spikes)
!lost 300 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal
!empty 60 <- delay adjustment effect, as there is actually no drift

View File

@ -57,6 +57,7 @@ AC_NO_SPEEX_CODEC=@ac_no_speex_codec@
AC_NO_ILBC_CODEC=@ac_no_ilbc_codec@
AC_NO_G722_CODEC=@ac_no_g722_codec@
AC_NO_G7221_CODEC=@ac_no_g7221_codec@
AC_NO_OPENCORE_AMRNB=@ac_no_opencore_amrnb@
export CODEC_OBJS=
@ -109,6 +110,12 @@ export CODEC_OBJS += g7221.o
export G7221_CFLAGS += -I$(THIRD_PARTY)
endif
ifeq ($(AC_NO_OPENCORE_AMRNB),1)
export CFLAGS += -DPJMEDIA_HAS_OPENCORE_AMRNB_CODEC=0
else
export CODEC_OBJS += opencore_amrnb.o
endif
#
# PortAudio

View File

@ -2966,6 +2966,10 @@
/>
</FileConfiguration>
</File>
<File
RelativePath="..\src\pjmedia-codec\opencore_amrnb.c"
>
</File>
<File
RelativePath="..\src\pjmedia-codec\passthrough.c"
>
@ -3101,6 +3105,10 @@
RelativePath="..\include\pjmedia-codec\l16.h"
>
</File>
<File
RelativePath="..\include\pjmedia-codec\opencore_amrnb.h"
>
</File>
<File
RelativePath="..\include\pjmedia-codec\passthrough.h"
>

View File

@ -144,6 +144,21 @@ PJ_BEGIN_DECL
#endif
/**
* This setting controls whether the Symbian audio with built-in multimedia
* framework backend should be started synchronously. Note that synchronous
* start will block the application/UI, e.g: about 40ms for each direction
* on N95. While asynchronous start may cause invalid value (always zero)
* returned in input/output volume query, if the query is performed when
* the internal start procedure is not completely finished.
*
* Default: 1 (yes)
*/
#ifndef PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START
# define PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START 1
#endif
/**
* This setting controls whether the Audio Device API should support
* device implementation that is based on the old sound device API

View File

@ -35,6 +35,7 @@
#include <pjmedia-codec/g7221.h>
#include <pjmedia-codec/ipp_codecs.h>
#include <pjmedia-codec/passthrough.h>
#include <pjmedia-codec/opencore_amrnb.h>
#endif /* __PJMEDIA_CODEC_PJMEDIA_CODEC_H__ */

View File

@ -567,7 +567,7 @@ const pj_int16_t* const pjmedia_codec_amrwb_ordermaps[9] =
* Constant of AMR-NB frame lengths in bytes.
*/
const pj_uint8_t pjmedia_codec_amrnb_framelen[16] =
{12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 5};
{12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0};
/**
* Constant of AMR-NB frame lengths in bits.
*/
@ -583,7 +583,7 @@ const pj_uint16_t pjmedia_codec_amrnb_bitrates[8] =
* Constant of AMR-WB frame lengths in bytes.
*/
const pj_uint8_t pjmedia_codec_amrwb_framelen[16] =
{17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 5};
{17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 0};
/**
* Constant of AMR-WB frame lengths in bits.
*/
@ -606,6 +606,7 @@ typedef struct pjmedia_codec_amr_bit_info {
pj_int8_t mode; /**< AMR mode. */
pj_uint8_t start_bit; /**< Frame start bit. */
pj_uint8_t good_quality:1; /**< Flag if frame is good/degraded. */
pj_uint8_t STI:1; /**< STI mode (first/update). */
} pjmedia_codec_amr_bit_info;
#pragma pack()
@ -1020,9 +1021,7 @@ PJ_INLINE (pj_status_t) pjmedia_codec_amr_pack(
} else if (info->frame_type == SID_FT) {
/* SID */
pj_uint8_t STI = 0;
amr_bits[35] = (pj_uint8_t)(STI & 1);
amr_bits[35] |= info->STI;
if (setting->amr_nb) {
amr_bits[36] = (pj_uint8_t)((info->mode >> 2) & 1);
@ -1163,6 +1162,7 @@ PJ_INLINE(pj_status_t) pjmedia_codec_amr_parse(
info->mode = (pj_int8_t)((FT < SID_FT)? FT : -1);
info->good_quality = (pj_uint8_t)(Q == 1);
info->start_bit = 0;
info->STI = 0;
frames[cnt].timestamp = ts_;
frames[cnt].type = PJMEDIA_FRAME_TYPE_AUDIO;
@ -1186,6 +1186,13 @@ PJ_INLINE(pj_status_t) pjmedia_codec_amr_parse(
frames[cnt].buf = r;
info->start_bit = r_bitptr;
if (FT == SID_FT) {
unsigned sti_bitptr;
sti_bitptr = r_bitptr + 35;
info->STI = (pj_uint8_t)
(r[sti_bitptr >> 3] >> (7 - (sti_bitptr % 8))) & 1;
}
if (setting->octet_aligned) {
r += framelen_tbl[FT];
frames[cnt].size = framelen_tbl[FT];

View File

@ -312,6 +312,39 @@
# define PJMEDIA_HAS_G7221_CODEC 0
#endif
/**
* Enable OpenCORE AMR-NB codec.
* See https://trac.pjsip.org/repos/ticket/1388 for some info.
*
* Default: 0
*/
#ifndef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
# define PJMEDIA_HAS_OPENCORE_AMRNB_CODEC 0
#endif
/**
* Link with libopencore-amrXX via pragma comment on Visual Studio.
* This option only makes sense if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
* is enabled.
*
* Default: 1
*/
#ifndef PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS
# define PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS 1
#endif
/**
* Link with libopencore-amrXX.a that has been produced with gcc.
* This option only makes sense if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
* and PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS are enabled.
*
* Default: 1
*/
#ifndef PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC
# define PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC 1
#endif
/**
* Default G.722.1 codec encoder and decoder level adjustment.
* If the value is non-zero, then PCM input samples to the encoder will

View File

@ -69,6 +69,11 @@
#undef PJMEDIA_HAS_G7221_CODEC
#endif
/* OpenCORE AMR-NB codec */
#ifndef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
#undef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
#endif
#endif /* __PJMEDIA_CODEC_CONFIG_AUTO_H_ */

View File

@ -0,0 +1,89 @@
/* $Id$ */
/*
* Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2011 Dan Arrhenius <dan@keystream.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __PJMEDIA_CODEC_OPENCORE_AMRNB_H__
#define __PJMEDIA_CODEC_OPENCORE_AMRNB_H__
#include <pjmedia-codec/types.h>
/**
* @defgroup PJMED_OC_AMRNB OpenCORE AMR-NB Codec
* @ingroup PJMEDIA_CODEC_CODECS
* @brief AMRCodec wrapper for OpenCORE AMR-NB codec
* @{
*/
PJ_BEGIN_DECL
/**
* Settings. Use #pjmedia_codec_opencore_amrnb_set_config() to
* activate.
*/
typedef struct pjmedia_codec_amrnb_config
{
/**
* Control whether to use octent align.
*/
pj_bool_t octet_align;
/**
* Set the bitrate.
*/
unsigned bitrate;
} pjmedia_codec_amrnb_config;
/**
* Initialize and register AMR-NB codec factory to pjmedia endpoint.
*
* @param endpt The pjmedia endpoint.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_init(pjmedia_endpt* endpt);
/**
* Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize
* the OpenCORE codec library.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void);
/**
* Set AMR-NB parameters.
*
* @param cfg The settings;
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_set_config(
const pjmedia_codec_amrnb_config* cfg);
PJ_END_DECL
/**
* @}
*/
#endif /* __PJMEDIA_CODEC_OPENCORE_AMRNB_H__ */

View File

@ -387,7 +387,7 @@
/**
* Number of packets received from different source IP address from the
* Number of RTP packets received from different source IP address from the
* remote address required to make the stream switch transmission
* to the source address.
*/
@ -396,6 +396,16 @@
#endif
/**
* Number of RTCP packets received from different source IP address from the
* remote address required to make the stream switch RTCP transmission
* to the source address.
*/
#ifndef PJMEDIA_RTCP_NAT_PROBATION_CNT
# define PJMEDIA_RTCP_NAT_PROBATION_CNT 3
#endif
/**
* Specify whether RTCP should be advertised in SDP. This setting would
* affect whether RTCP candidate will be added in SDP when ICE is used.
@ -916,7 +926,7 @@
* Default: 5 seconds
*/
#ifndef PJMEDIA_STREAM_KA_INTERVAL
# define PJMEDIA_STREAM_KA_INTERVAL 5
# define PJMEDIA_STREAM_KA_INTERVAL 5
#endif
@ -1056,6 +1066,63 @@
#endif
/**
* Minimum gap between two consecutive discards in jitter buffer,
* in milliseconds.
*
* Default: 200 ms
*/
#ifndef PJMEDIA_JBUF_DISC_MIN_GAP
# define PJMEDIA_JBUF_DISC_MIN_GAP 200
#endif
/**
* Minimum burst level reference used for calculating discard duration
* in jitter buffer progressive discard algorithm, in frames.
*
* Default: 1 frame
*/
#ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST
# define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1
#endif
/**
* Maximum burst level reference used for calculating discard duration
* in jitter buffer progressive discard algorithm, in frames.
*
* Default: 200 frames
*/
#ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST
# define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100
#endif
/**
* Duration for progressive discard algotithm in jitter buffer to discard
* an excessive frame when burst is equal to or lower than
* PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds.
*
* Default: 2000 ms
*/
#ifndef PJMEDIA_JBUF_PRO_DISC_T1
# define PJMEDIA_JBUF_PRO_DISC_T1 2000
#endif
/**
* Duration for progressive discard algotithm in jitter buffer to discard
* an excessive frame when burst is equal to or lower than
* PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds.
*
* Default: 10000 ms
*/
#ifndef PJMEDIA_JBUF_PRO_DISC_T2
# define PJMEDIA_JBUF_PRO_DISC_T2 10000
#endif
/**
* Video stream will discard old picture from the jitter buffer as soon as
* new picture is received, to reduce latency.

View File

@ -63,6 +63,19 @@ PJ_BEGIN_DECL
/** Opaque declaration for delay buffer. */
typedef struct pjmedia_delay_buf pjmedia_delay_buf;
/**
* Delay buffer options.
*/
typedef enum pjmedia_delay_buf_flag
{
/**
* Use simple FIFO mechanism for the delay buffer, i.e.
* without WSOLA for expanding and shrinking audio samples.
*/
PJMEDIA_DELAY_BUF_SIMPLE_FIFO = 1
} pjmedia_delay_buf_flag;
/**
* Create the delay buffer. Once the delay buffer is created, it will
* enter learning state unless the delay argument is specified, which
@ -79,7 +92,12 @@ typedef struct pjmedia_delay_buf pjmedia_delay_buf;
* in ms, if this value is negative or less than
* one frame time, default maximum delay used is
* 400 ms.
* @param options Option flags, must be zero for now.
* @param options Options. If PJMEDIA_DELAY_BUF_SIMPLE_FIFO is
* specified, then a simple FIFO mechanism
* will be used instead of the adaptive
* implementation (which uses WSOLA to expand
* or shrink audio samples).
* See #pjmedia_delay_buf_flag for other options.
* @param p_b Pointer to receive the delay buffer instance.
*
* @return PJ_SUCCESS if the delay buffer has been

View File

@ -88,7 +88,15 @@ typedef enum pjmedia_echo_flag
* for the echo canceller, but application will guarantee that echo
* canceller will not be called by different threads at the same time.
*/
PJMEDIA_ECHO_NO_LOCK = 16
PJMEDIA_ECHO_NO_LOCK = 16,
/**
* If PJMEDIA_ECHO_USE_SIMPLE_FIFO flag is specified, the delay buffer
* created for the echo canceller will use simple FIFO mechanism, i.e.
* without using WSOLA to expand and shrink audio samples.
*/
PJMEDIA_ECHO_USE_SIMPLE_FIFO = 32
} pjmedia_echo_flag;

View File

@ -48,7 +48,7 @@ PJ_BEGIN_DECL
/**
* Types of frame returned by the jitter buffer.
*/
enum pjmedia_jb_frame_type
typedef enum pjmedia_jb_frame_type
{
PJMEDIA_JB_MISSING_FRAME = 0, /**< No frame because it's missing */
PJMEDIA_JB_NORMAL_FRAME = 1, /**< Normal frame is being returned */
@ -56,13 +56,41 @@ enum pjmedia_jb_frame_type
because JB is bufferring. */
PJMEDIA_JB_ZERO_EMPTY_FRAME = 3 /**< Zero frame is being returned
because JB is empty. */
};
} pjmedia_jb_frame_type;
/**
* @see pjmedia_jb_frame_type.
* Enumeration of jitter buffer discard algorithm. The jitter buffer
* continuously calculates the jitter level to get the optimum latency at
* any time and in order to adjust the latency, the jitter buffer may need
* to discard some frames.
*/
typedef enum pjmedia_jb_frame_type pjmedia_jb_frame_type;
typedef enum pjmedia_jb_discard_algo
{
/**
* Jitter buffer should not discard any frame, except when the jitter
* buffer is full and a new frame arrives, one frame will be discarded
* to make space for the new frame.
*/
PJMEDIA_JB_DISCARD_NONE = 0,
/**
* Only discard one frame in at least 200ms when the latency is considered
* much higher than it should be. When the jitter buffer is full and a new
* frame arrives, one frame will be discarded to make space for the new
* frame.
*/
PJMEDIA_JB_DISCARD_STATIC,
/**
* The discard rate is dynamically calculated based on actual parameters
* such as jitter level and latency. When the jitter buffer is full and
* a new frame arrives, one frame will be discarded to make space for the
* new frame.
*/
PJMEDIA_JB_DISCARD_PROGRESSIVE
} pjmedia_jb_discard_algo;
/**
@ -107,7 +135,9 @@ typedef struct pjmedia_jbuf pjmedia_jbuf;
/**
* Create an adaptive jitter buffer according to the specification. If
* application wants to have a fixed jitter buffer, it may call
* #pjmedia_jbuf_set_fixed() after the jitter buffer is created.
* #pjmedia_jbuf_set_fixed() after the jitter buffer is created. Also
* if application wants to alter the discard algorithm, which the default
* PJMEDIA_JB_DISCARD_PROGRESSIVE, it may call #pjmedia_jbuf_set_discard().
*
* This function may allocate large chunk of memory to keep the frames in
* the buffer.
@ -174,16 +204,16 @@ PJ_DECL(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
/**
* Enable/disable the jitter buffer drift detection and handling mechanism.
* The default behavior is enabled.
* Set the jitter buffer discard algorithm. The default discard algorithm,
* set in jitter buffer creation, is PJMEDIA_JB_DISCARD_PROGRESSIVE.
*
* @param jb The jitter buffer
* @param enable Set to PJ_TRUE to enable or PJ_FALSE to disable.
* @param jb The jitter buffer.
* @param algo The discard algorithm to be used.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t) pjmedia_jbuf_enable_discard(pjmedia_jbuf *jb,
pj_bool_t enable);
PJ_DECL(pj_status_t) pjmedia_jbuf_set_discard(pjmedia_jbuf *jb,
pjmedia_jb_discard_algo algo);
/**

View File

@ -231,6 +231,26 @@ PJ_DECL(pj_status_t) pjmedia_session_resume_stream(pjmedia_session *session,
unsigned index,
pjmedia_dir dir);
/**
* Send RTCP SDES for the session.
*
* @param session The media session.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t)
pjmedia_session_send_rtcp_sdes( const pjmedia_session *session );
/**
* Send RTCP BYE for the session.
*
* @param session The media session.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t)
pjmedia_session_send_rtcp_bye( const pjmedia_session *session );
/**
* Enumerate media streams in the session.
*

View File

@ -135,6 +135,9 @@ typedef struct pjmedia_stream_info
(see #PJMEDIA_STREAM_ENABLE_KA)
is enabled? */
#endif
pj_bool_t rtcp_sdes_bye_disabled;
/**< Disable automatic sending of RTCP
SDES and BYE. */
} pjmedia_stream_info;
@ -403,6 +406,26 @@ pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream,
void *user_data);
/**
* Send RTCP SDES for the media stream.
*
* @param stream The media stream.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t)
pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream );
/**
* Send RTCP BYE for the media stream.
*
* @param stream The media stream.
*
* @return PJ_SUCCESS on success.
*/
PJ_DECL(pj_status_t)
pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream );
/**
* @}
*/

View File

@ -49,15 +49,18 @@ PJ_BEGIN_DECL
* @param endpt The media endpoint.
* @param name Optional name to identify this media transport
* for logging purposes.
* @param transport The underlying media transport to send and receive
* RTP/RTCP packets.
* @param base_tp The base/underlying media transport to send and
* receive RTP/RTCP packets.
* @param del_base Specify whether the base transport should also be
* destroyed when destroy() is called upon us.
* @param p_tp Pointer to receive the media transport instance.
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
const char *name,
pjmedia_transport *transport,
pjmedia_transport *base_tp,
pj_bool_t del_base,
pjmedia_transport **p_tp);
PJ_END_DECL

View File

@ -490,11 +490,13 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void)
}
--aud_subsys.init_count;
for (i=0; i<aud_subsys.drv_cnt; ++i) {
deinit_driver(i);
}
if (aud_subsys.init_count == 0) {
for (i=0; i<aud_subsys.drv_cnt; ++i) {
deinit_driver(i);
}
aud_subsys.pf = NULL;
aud_subsys.pf = NULL;
}
return PJ_SUCCESS;
}

View File

@ -1111,7 +1111,17 @@ static void interruptionListener(void *inClientData, UInt32 inInterruption)
{
struct stream_list *it, *itBegin;
pj_status_t status;
pj_thread_desc thread_desc;
pj_thread_t *thread;
/* Register the thread with PJLIB, this is must for any external threads
* which need to use the PJLIB framework.
*/
if (!pj_thread_is_registered()) {
pj_bzero(thread_desc, sizeof(pj_thread_desc));
status = pj_thread_register("intListener", thread_desc, &thread);
}
PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---",
inInterruption == kAudioSessionBeginInterruption ?
"Begin Interruption" : "End Interruption"));

View File

@ -1086,7 +1086,7 @@ static void PlayCb(TAPSCommBuffer &buf, void *user_data)
buf.iBuffer.Append((TUint8*)sf->data, len);
} else {
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
buf.iBuffer.Append(amr_header);
}
@ -1095,7 +1095,7 @@ static void PlayCb(TAPSCommBuffer &buf, void *user_data)
} else { /* PJMEDIA_FRAME_TYPE_NONE */
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
buf.iBuffer.Append(amr_header);

View File

@ -239,6 +239,7 @@ private:
TPtr8 iFramePtr_;
TInt lastError_;
pj_uint32_t timeStamp_;
CActiveSchedulerWait startAsw_;
// cache variable
// to avoid calculating frame length repeatedly
@ -363,6 +364,13 @@ pj_status_t CPjAudioInputEngine::StartRecord()
lastError_ = KRequestPending;
iInputStream_->Open(&iStreamSettings);
#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
startAsw_.Start();
#endif
// Success
PJ_LOG(4,(THIS_FILE, "Sound capture started."));
return PJ_SUCCESS;
@ -386,6 +394,10 @@ void CPjAudioInputEngine::Stop()
iInputStream_ = NULL;
}
if (startAsw_.IsStarted()) {
startAsw_.AsyncStop();
}
state_ = STATE_INACTIVE;
}
@ -399,12 +411,25 @@ TPtr8 & CPjAudioInputEngine::GetFrame()
void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
{
if (startAsw_.IsStarted()) {
startAsw_.AsyncStop();
}
lastError_ = aError;
if (aError != KErrNone) {
snd_perror("Error in MaiscOpenComplete()", aError);
return;
}
/* Apply input volume setting if specified */
if (parentStrm_->param.flags &
PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING)
{
stream_set_cap(&parentStrm_->base,
PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
&parentStrm_->param.input_vol);
}
// set stream priority to normal and time sensitive
iInputStream_->SetPriority(EPriorityNormal,
EMdaPriorityPreferenceTime);
@ -414,7 +439,12 @@ void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
TRAPD(err2, iInputStream_->ReadL(frm));
if (err2) {
PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
lastError_ = err2;
return;
}
// input stream opened succesfully, set status to Active
state_ = STATE_ACTIVE;
}
void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
@ -547,6 +577,7 @@ private:
TPtrC8 frame_;
TInt lastError_;
unsigned timestamp_;
CActiveSchedulerWait startAsw_;
CPjAudioOutputEngine(struct mda_stream *parent_strm,
pjmedia_aud_play_cb play_cb,
@ -638,6 +669,13 @@ pj_status_t CPjAudioOutputEngine::StartPlay()
// Open stream.
lastError_ = KRequestPending;
iOutputStream_->Open(&iStreamSettings);
#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
startAsw_.Start();
#endif
// Success
PJ_LOG(4,(THIS_FILE, "Sound playback started"));
@ -662,17 +700,22 @@ void CPjAudioOutputEngine::Stop()
iOutputStream_ = NULL;
}
if (startAsw_.IsStarted()) {
startAsw_.AsyncStop();
}
state_ = STATE_INACTIVE;
}
void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
{
if (startAsw_.IsStarted()) {
startAsw_.AsyncStop();
}
lastError_ = aError;
if (aError==KErrNone) {
// output stream opened succesfully, set status to Active
state_ = STATE_ACTIVE;
// set stream properties, 16bit 8KHz mono
TMdaAudioDataSettings iSettings;
iSettings.iChannels =
@ -683,8 +726,17 @@ void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate,
iSettings.iChannels);
// set volume to 1/2th of stream max volume
iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
/* Apply output volume setting if specified */
if (parentStrm_->param.flags &
PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
{
stream_set_cap(&parentStrm_->base,
PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
&parentStrm_->param.output_vol);
} else {
// set volume to 1/2th of stream max volume
iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
}
// set stream priority to normal and time sensitive
iOutputStream_->SetPriority(EPriorityNormal,
@ -718,6 +770,9 @@ void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
// until whole data buffer is written.
frame_.Set(frameBuf_, frameBufSize_);
iOutputStream_->WriteL(frame_);
// output stream opened succesfully, set status to Active
state_ = STATE_ACTIVE;
} else {
snd_perror("Error in MaoscOpenComplete()", aError);
}
@ -881,7 +936,8 @@ static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
param->channel_count = 1;
param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
param->bits_per_sample = BITS_PER_SAMPLE;
param->flags = af->dev_info.caps;
// Don't set the flags without specifying the flags value.
//param->flags = af->dev_info.caps;
return PJ_SUCCESS;
}
@ -956,6 +1012,20 @@ static pj_status_t stream_get_param(pjmedia_aud_stream *s,
pj_memcpy(pi, &strm->param, sizeof(*pi));
/* Update the output volume setting */
if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
&pi->output_vol) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
}
/* Update the input volume setting */
if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
&pi->input_vol) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING;
}
return PJ_SUCCESS;
}
@ -1034,7 +1104,7 @@ static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
}
break;
case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
TInt max_vol = strm->out_engine->GetMaxVolume();

View File

@ -1130,7 +1130,7 @@ static void PlayCb(CVoIPDataBuffer *buf, void *user_data)
buffer.Append((TUint8*)sf->data, len);
} else {
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
buffer.Append(amr_header);
}
@ -1139,7 +1139,7 @@ static void PlayCb(CVoIPDataBuffer *buf, void *user_data)
} else { /* PJMEDIA_FRAME_TYPE_NONE */
enum {NO_DATA_FT = 15 };
pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3);
pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
buffer.Append(amr_header);
@ -1746,12 +1746,6 @@ static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
return PJ_RETURN_OS_ERROR(err);
}
/* Apply output volume setting if specified */
if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
&param->output_vol);
}
/* Done */
strm->base.op = &stream_op;
*p_aud_strm = &strm->base;
@ -1945,10 +1939,21 @@ static pj_status_t stream_start(pjmedia_aud_stream *strm)
} while (!stream->engine->IsStarted() &&
(now.MicroSecondsFrom(start) < VAS_WAIT_START * 1000));
if (stream->engine->IsStarted())
if (stream->engine->IsStarted()) {
/* Apply output volume setting if specified */
if (stream->param.flags &
PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
{
stream_set_cap(strm,
PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
&stream->param.output_vol);
}
return PJ_SUCCESS;
else
} else {
return PJ_ETIMEDOUT;
}
}
return PJ_EINVALIDOP;

View File

@ -107,6 +107,13 @@ pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt,
return status;
#endif /* PJMEDIA_HAS_L16_CODEC */
#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
/* Register OpenCORE AMR-NB */
status = pjmedia_codec_opencore_amrnb_init(endpt);
if (status != PJ_SUCCESS)
return status;
#endif
return PJ_SUCCESS;
}

View File

@ -42,6 +42,7 @@
#define THIS_FILE "ipp_codecs.c"
/* Prototypes for IPP codecs factory */
static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory,
const pjmedia_codec_info *id );
@ -238,9 +239,8 @@ static struct ipp_codec {
ipp_codec[] =
{
# if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR
/* AMR-NB SID seems to produce noise, so let's just disable its VAD. */
{1, "AMR", PJMEDIA_RTP_PT_AMR, &USC_GSMAMR_Fxns, 8000, 1, 160,
7400, 12200, 2, 0, 1,
7400, 12200, 2, 1, 1,
&predecode_amr, &parse_amr, &pack_amr,
{1, {{{"octet-align", 11}, {"1", 1}}} }
},
@ -488,9 +488,7 @@ static void predecode_amr( ipp_private_t *codec_data,
} else if (frame.size == 5) {
/* SID */
if (info->good_quality) {
pj_bool_t STI;
STI = (((pj_uint8_t*)frame.buf)[35 >> 3] & 0x10) != 0;
usc_frame->frametype = STI? 2 : 1;
usc_frame->frametype = info->STI? 2 : 1;
} else {
usc_frame->frametype = setting->amr_nb ? 6 : 7;
}
@ -534,6 +532,7 @@ static pj_status_t pack_amr(ipp_private_t *codec_data, void *pkt,
info->frame_type = (pj_uint8_t)(info_ & 0x0F);
info->good_quality = (pj_uint8_t)((info_ & 0x80) == 0);
info->mode = (pj_int8_t) ((info_ >> 8) & 0x0F);
info->STI = (pj_uint8_t)((info_ >> 5) & 1);
frames[nframes].buf = r + 2;
frames[nframes].size = info->frame_type <= SID_FT ?
@ -1419,6 +1418,7 @@ static pj_status_t ipp_codec_encode( pjmedia_codec *codec,
/* Two octets for AMR frame info, 0=LSB:
* bit 0-3 : frame type
* bit 5 : STI flag
* bit 6 : last frame flag
* bit 7 : quality flag
* bit 8-11 : mode
@ -1442,6 +1442,9 @@ static pj_status_t ipp_codec_encode( pjmedia_codec *codec,
/* Quality */
if (out.frametype == 6 || out.frametype == 7)
*info |= 0x80;
/* STI */
if (out.frametype != 1)
*info |= 0x20;
} else {
/* Untransmited */
*info = 15;

View File

@ -0,0 +1,820 @@
/* $Id */
/*
* Copyright (C) 2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2011 Dan Arrhenius <dan@keystream.se>
*
* 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
*/
/*
* AMR-NB codec implementation with OpenCORE AMRNB library
*/
#include <pjmedia-codec/g722.h>
#include <pjmedia/codec.h>
#include <pjmedia/errno.h>
#include <pjmedia/endpoint.h>
#include <pjmedia/plc.h>
#include <pjmedia/port.h>
#include <pjmedia/silencedet.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/os.h>
#include <pj/math.h>
#if defined(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC) && \
(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC != 0)
#include <opencore-amrnb/interf_enc.h>
#include <opencore-amrnb/interf_dec.h>
#include <pjmedia-codec/amr_helper.h>
#include <pjmedia-codec/opencore_amrnb.h>
#define THIS_FILE "opencore_amrnb.c"
/* Tracing */
#define PJ_TRACE 0
#if PJ_TRACE
# define TRACE_(expr) PJ_LOG(4,expr)
#else
# define TRACE_(expr)
#endif
/* Use PJMEDIA PLC */
#define USE_PJMEDIA_PLC 1
/* Prototypes for AMR-NB factory */
static pj_status_t amr_test_alloc(pjmedia_codec_factory *factory,
const pjmedia_codec_info *id );
static pj_status_t amr_default_attr(pjmedia_codec_factory *factory,
const pjmedia_codec_info *id,
pjmedia_codec_param *attr );
static pj_status_t amr_enum_codecs(pjmedia_codec_factory *factory,
unsigned *count,
pjmedia_codec_info codecs[]);
static pj_status_t amr_alloc_codec(pjmedia_codec_factory *factory,
const pjmedia_codec_info *id,
pjmedia_codec **p_codec);
static pj_status_t amr_dealloc_codec(pjmedia_codec_factory *factory,
pjmedia_codec *codec );
/* Prototypes for AMR-NB implementation. */
static pj_status_t amr_codec_init(pjmedia_codec *codec,
pj_pool_t *pool );
static pj_status_t amr_codec_open(pjmedia_codec *codec,
pjmedia_codec_param *attr );
static pj_status_t amr_codec_close(pjmedia_codec *codec );
static pj_status_t amr_codec_modify(pjmedia_codec *codec,
const pjmedia_codec_param *attr );
static pj_status_t amr_codec_parse(pjmedia_codec *codec,
void *pkt,
pj_size_t pkt_size,
const pj_timestamp *ts,
unsigned *frame_cnt,
pjmedia_frame frames[]);
static pj_status_t amr_codec_encode(pjmedia_codec *codec,
const struct pjmedia_frame *input,
unsigned output_buf_len,
struct pjmedia_frame *output);
static pj_status_t amr_codec_decode(pjmedia_codec *codec,
const struct pjmedia_frame *input,
unsigned output_buf_len,
struct pjmedia_frame *output);
static pj_status_t amr_codec_recover(pjmedia_codec *codec,
unsigned output_buf_len,
struct pjmedia_frame *output);
/* Definition for AMR-NB codec operations. */
static pjmedia_codec_op amr_op =
{
&amr_codec_init,
&amr_codec_open,
&amr_codec_close,
&amr_codec_modify,
&amr_codec_parse,
&amr_codec_encode,
&amr_codec_decode,
&amr_codec_recover
};
/* Definition for AMR-NB codec factory operations. */
static pjmedia_codec_factory_op amr_factory_op =
{
&amr_test_alloc,
&amr_default_attr,
&amr_enum_codecs,
&amr_alloc_codec,
&amr_dealloc_codec
};
/* AMR-NB factory */
static struct amr_codec_factory
{
pjmedia_codec_factory base;
pjmedia_endpt *endpt;
pj_pool_t *pool;
} amr_codec_factory;
/* AMR-NB codec private data. */
struct amr_data
{
pj_pool_t *pool;
void *encoder;
void *decoder;
pj_bool_t plc_enabled;
pj_bool_t vad_enabled;
int enc_mode;
pjmedia_codec_amr_pack_setting enc_setting;
pjmedia_codec_amr_pack_setting dec_setting;
#if USE_PJMEDIA_PLC
pjmedia_plc *plc;
#endif
pj_timestamp last_tx;
};
static pjmedia_codec_amrnb_config def_config =
{
PJ_FALSE, /* octet align */
5900 /* bitrate */
};
/*
* Initialize and register AMR-NB codec factory to pjmedia endpoint.
*/
PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_init( pjmedia_endpt *endpt )
{
pjmedia_codec_mgr *codec_mgr;
pj_status_t status;
if (amr_codec_factory.pool != NULL)
return PJ_SUCCESS;
/* Create AMR-NB codec factory. */
amr_codec_factory.base.op = &amr_factory_op;
amr_codec_factory.base.factory_data = NULL;
amr_codec_factory.endpt = endpt;
amr_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "amrnb", 1000,
1000);
if (!amr_codec_factory.pool)
return PJ_ENOMEM;
/* Get the codec manager. */
codec_mgr = pjmedia_endpt_get_codec_mgr(endpt);
if (!codec_mgr) {
status = PJ_EINVALIDOP;
goto on_error;
}
/* Register codec factory to endpoint. */
status = pjmedia_codec_mgr_register_factory(codec_mgr,
&amr_codec_factory.base);
if (status != PJ_SUCCESS)
goto on_error;
/* Done. */
return PJ_SUCCESS;
on_error:
pj_pool_release(amr_codec_factory.pool);
amr_codec_factory.pool = NULL;
return status;
}
/*
* Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize
* the AMR-NB codec library.
*/
PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void)
{
pjmedia_codec_mgr *codec_mgr;
pj_status_t status;
if (amr_codec_factory.pool == NULL)
return PJ_SUCCESS;
/* Get the codec manager. */
codec_mgr = pjmedia_endpt_get_codec_mgr(amr_codec_factory.endpt);
if (!codec_mgr) {
pj_pool_release(amr_codec_factory.pool);
amr_codec_factory.pool = NULL;
return PJ_EINVALIDOP;
}
/* Unregister AMR-NB codec factory. */
status = pjmedia_codec_mgr_unregister_factory(codec_mgr,
&amr_codec_factory.base);
/* Destroy pool. */
pj_pool_release(amr_codec_factory.pool);
amr_codec_factory.pool = NULL;
return status;
}
PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_set_config(
const pjmedia_codec_amrnb_config *config)
{
unsigned nbitrates;
def_config = *config;
/* Normalize bitrate. */
nbitrates = PJ_ARRAY_SIZE(pjmedia_codec_amrnb_bitrates);
if (def_config.bitrate < pjmedia_codec_amrnb_bitrates[0])
def_config.bitrate = pjmedia_codec_amrnb_bitrates[0];
else if (def_config.bitrate > pjmedia_codec_amrnb_bitrates[nbitrates-1])
def_config.bitrate = pjmedia_codec_amrnb_bitrates[nbitrates-1];
else
{
unsigned i;
for (i = 0; i < nbitrates; ++i) {
if (def_config.bitrate <= pjmedia_codec_amrnb_bitrates[i])
break;
}
def_config.bitrate = pjmedia_codec_amrnb_bitrates[i];
}
return PJ_SUCCESS;
}
/*
* Check if factory can allocate the specified codec.
*/
static pj_status_t amr_test_alloc( pjmedia_codec_factory *factory,
const pjmedia_codec_info *info )
{
PJ_UNUSED_ARG(factory);
/* Check payload type. */
if (info->pt != PJMEDIA_RTP_PT_AMR)
return PJMEDIA_CODEC_EUNSUP;
/* Ignore the rest, since it's static payload type. */
return PJ_SUCCESS;
}
/*
* Generate default attribute.
*/
static pj_status_t amr_default_attr( pjmedia_codec_factory *factory,
const pjmedia_codec_info *id,
pjmedia_codec_param *attr )
{
PJ_UNUSED_ARG(factory);
PJ_UNUSED_ARG(id);
pj_bzero(attr, sizeof(pjmedia_codec_param));
attr->info.clock_rate = 8000;
attr->info.channel_cnt = 1;
attr->info.avg_bps = def_config.bitrate;
attr->info.max_bps = pjmedia_codec_amrnb_bitrates[7];
attr->info.pcm_bits_per_sample = 16;
attr->info.frm_ptime = 20;
attr->info.pt = PJMEDIA_RTP_PT_AMR;
attr->setting.frm_per_pkt = 2;
attr->setting.vad = 1;
attr->setting.plc = 1;
if (def_config.octet_align) {
attr->setting.dec_fmtp.cnt = 1;
attr->setting.dec_fmtp.param[0].name = pj_str("octet-align");
attr->setting.dec_fmtp.param[0].val = pj_str("1");
}
/* Default all other flag bits disabled. */
return PJ_SUCCESS;
}
/*
* Enum codecs supported by this factory (i.e. only AMR-NB!).
*/
static pj_status_t amr_enum_codecs( pjmedia_codec_factory *factory,
unsigned *count,
pjmedia_codec_info codecs[])
{
PJ_UNUSED_ARG(factory);
PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
pj_bzero(&codecs[0], sizeof(pjmedia_codec_info));
codecs[0].encoding_name = pj_str("AMR");
codecs[0].pt = PJMEDIA_RTP_PT_AMR;
codecs[0].type = PJMEDIA_TYPE_AUDIO;
codecs[0].clock_rate = 8000;
codecs[0].channel_cnt = 1;
*count = 1;
return PJ_SUCCESS;
}
/*
* Allocate a new AMR-NB codec instance.
*/
static pj_status_t amr_alloc_codec( pjmedia_codec_factory *factory,
const pjmedia_codec_info *id,
pjmedia_codec **p_codec)
{
pj_pool_t *pool;
pjmedia_codec *codec;
struct amr_data *amr_data;
pj_status_t status;
PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL);
PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL);
pool = pjmedia_endpt_create_pool(amr_codec_factory.endpt, "amrnb-inst",
512, 512);
codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec);
PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM);
codec->op = &amr_op;
codec->factory = factory;
amr_data = PJ_POOL_ZALLOC_T(pool, struct amr_data);
codec->codec_data = amr_data;
amr_data->pool = pool;
#if USE_PJMEDIA_PLC
/* Create PLC */
status = pjmedia_plc_create(pool, 8000, 160, 0, &amr_data->plc);
if (status != PJ_SUCCESS) {
return status;
}
#else
PJ_UNUSED_ARG(status);
#endif
*p_codec = codec;
return PJ_SUCCESS;
}
/*
* Free codec.
*/
static pj_status_t amr_dealloc_codec( pjmedia_codec_factory *factory,
pjmedia_codec *codec )
{
struct amr_data *amr_data;
PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL);
amr_data = (struct amr_data*) codec->codec_data;
/* Close codec, if it's not closed. */
amr_codec_close(codec);
pj_pool_release(amr_data->pool);
amr_data = NULL;
return PJ_SUCCESS;
}
/*
* Init codec.
*/
static pj_status_t amr_codec_init( pjmedia_codec *codec,
pj_pool_t *pool )
{
PJ_UNUSED_ARG(codec);
PJ_UNUSED_ARG(pool);
return PJ_SUCCESS;
}
/*
* Open codec.
*/
static pj_status_t amr_codec_open( pjmedia_codec *codec,
pjmedia_codec_param *attr )
{
struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
pjmedia_codec_amr_pack_setting *setting;
unsigned i;
pj_uint8_t octet_align = 0;
pj_int8_t enc_mode;
const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11};
PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP);
enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps);
pj_assert(enc_mode >= 0 && enc_mode <= 7);
/* Check octet-align */
for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) {
if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name,
&STR_FMTP_OCTET_ALIGN) == 0)
{
octet_align = (pj_uint8_t)
(pj_strtoul(&attr->setting.dec_fmtp.param[i].val));
break;
}
}
/* Check mode-set */
for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) {
const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8};
if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name,
&STR_FMTP_MODE_SET) == 0)
{
const char *p;
pj_size_t l;
pj_int8_t diff = 99;
/* Encoding mode is chosen based on local default mode setting:
* - if local default mode is included in the mode-set, use it
* - otherwise, find the closest mode to local default mode;
* if there are two closest modes, prefer to use the higher
* one, e.g: local default mode is 4, the mode-set param
* contains '2,3,5,6', then 5 will be chosen.
*/
p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val);
l = pj_strlen(&attr->setting.enc_fmtp.param[i].val);
while (l--) {
if (*p>='0' && *p<='7') {
pj_int8_t tmp = *p - '0' - enc_mode;
if (PJ_ABS(diff) > PJ_ABS(tmp) ||
(PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff))
{
diff = tmp;
if (diff == 0) break;
}
}
++p;
}
PJ_ASSERT_RETURN(diff != 99, PJMEDIA_CODEC_EFAILED);
enc_mode = enc_mode + diff;
break;
}
}
amr_data->vad_enabled = (attr->setting.vad != 0);
amr_data->plc_enabled = (attr->setting.plc != 0);
amr_data->enc_mode = enc_mode;
amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled);
if (amr_data->encoder == NULL) {
TRACE_((THIS_FILE, "Encoder_Interface_init() failed"));
amr_codec_close(codec);
return PJMEDIA_CODEC_EFAILED;
}
setting = &amr_data->enc_setting;
pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting));
setting->amr_nb = 1;
setting->reorder = 0;
setting->octet_aligned = octet_align;
setting->cmr = 15;
amr_data->decoder = Decoder_Interface_init();
if (amr_data->decoder == NULL) {
TRACE_((THIS_FILE, "Decoder_Interface_init() failed"));
amr_codec_close(codec);
return PJMEDIA_CODEC_EFAILED;
}
setting = &amr_data->dec_setting;
pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting));
setting->amr_nb = 1;
setting->reorder = 0;
setting->octet_aligned = octet_align;
TRACE_((THIS_FILE, "AMR-NB codec allocated: vad=%d, plc=%d, bitrate=%d",
amr_data->vad_enabled, amr_data->plc_enabled,
pjmedia_codec_amrnb_bitrates[amr_data->enc_mode]));
return PJ_SUCCESS;
}
/*
* Close codec.
*/
static pj_status_t amr_codec_close( pjmedia_codec *codec )
{
struct amr_data *amr_data;
PJ_ASSERT_RETURN(codec, PJ_EINVAL);
amr_data = (struct amr_data*) codec->codec_data;
PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP);
if (amr_data->encoder) {
Encoder_Interface_exit(amr_data->encoder);
amr_data->encoder = NULL;
}
if (amr_data->decoder) {
Decoder_Interface_exit(amr_data->decoder);
amr_data->decoder = NULL;
}
TRACE_((THIS_FILE, "AMR-NB codec closed"));
return PJ_SUCCESS;
}
/*
* Modify codec settings.
*/
static pj_status_t amr_codec_modify( pjmedia_codec *codec,
const pjmedia_codec_param *attr )
{
struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
pj_bool_t prev_vad_state;
pj_assert(amr_data != NULL);
pj_assert(amr_data->encoder != NULL && amr_data->decoder != NULL);
prev_vad_state = amr_data->vad_enabled;
amr_data->vad_enabled = (attr->setting.vad != 0);
amr_data->plc_enabled = (attr->setting.plc != 0);
if (prev_vad_state != amr_data->vad_enabled) {
/* Reinit AMR encoder to update VAD setting */
TRACE_((THIS_FILE, "Reiniting AMR encoder to update VAD setting."));
Encoder_Interface_exit(amr_data->encoder);
amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled);
if (amr_data->encoder == NULL) {
TRACE_((THIS_FILE, "Encoder_Interface_init() failed"));
amr_codec_close(codec);
return PJMEDIA_CODEC_EFAILED;
}
}
TRACE_((THIS_FILE, "AMR-NB codec modified: vad=%d, plc=%d",
amr_data->vad_enabled, amr_data->plc_enabled));
return PJ_SUCCESS;
}
/*
* Get frames in the packet.
*/
static pj_status_t amr_codec_parse( pjmedia_codec *codec,
void *pkt,
pj_size_t pkt_size,
const pj_timestamp *ts,
unsigned *frame_cnt,
pjmedia_frame frames[])
{
struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
pj_uint8_t cmr;
pj_status_t status;
status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, &amr_data->dec_setting,
frames, frame_cnt, &cmr);
if (status != PJ_SUCCESS)
return status;
/* Check for Change Mode Request. */
if (cmr <= 7 && amr_data->enc_mode != cmr) {
amr_data->enc_mode = cmr;
TRACE_((THIS_FILE, "AMR-NB encoder switched mode to %d (%dbps)",
amr_data->enc_mode,
pjmedia_codec_amrnb_bitrates[amr_data->enc_mode]));
}
return PJ_SUCCESS;
}
/*
* Encode frame.
*/
static pj_status_t amr_codec_encode( pjmedia_codec *codec,
const struct pjmedia_frame *input,
unsigned output_buf_len,
struct pjmedia_frame *output)
{
struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
unsigned char *bitstream;
pj_int16_t *speech;
unsigned nsamples, samples_per_frame;
enum {MAX_FRAMES_PER_PACKET = 16};
pjmedia_frame frames[MAX_FRAMES_PER_PACKET];
pj_uint8_t *p;
unsigned i, out_size = 0, nframes = 0;
pj_size_t payload_len;
unsigned dtx_cnt, sid_cnt;
pj_status_t status;
int size;
pj_assert(amr_data != NULL);
PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
nsamples = input->size >> 1;
samples_per_frame = 160;
PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0,
PJMEDIA_CODEC_EPCMFRMINLEN);
nframes = nsamples / samples_per_frame;
PJ_ASSERT_RETURN(nframes <= MAX_FRAMES_PER_PACKET,
PJMEDIA_CODEC_EFRMTOOSHORT);
/* Encode the frames */
speech = (pj_int16_t*)input->buf;
bitstream = (unsigned char*)output->buf;
while (nsamples >= samples_per_frame) {
size = Encoder_Interface_Encode (amr_data->encoder, amr_data->enc_mode,
speech, bitstream, 0);
if (size == 0) {
output->size = 0;
output->buf = NULL;
output->type = PJMEDIA_FRAME_TYPE_NONE;
TRACE_((THIS_FILE, "AMR-NB encode() failed"));
return PJMEDIA_CODEC_EFAILED;
}
nsamples -= 160;
speech += samples_per_frame;
bitstream += size;
out_size += size;
TRACE_((THIS_FILE, "AMR-NB encode(): mode=%d, size=%d",
amr_data->enc_mode, out_size));
}
/* Pack payload */
p = (pj_uint8_t*)output->buf + output_buf_len - out_size;
pj_memmove(p, output->buf, out_size);
dtx_cnt = sid_cnt = 0;
for (i = 0; i < nframes; ++i) {
pjmedia_codec_amr_bit_info *info = (pjmedia_codec_amr_bit_info*)
&frames[i].bit_info;
info->frame_type = (pj_uint8_t)((*p >> 3) & 0x0F);
info->good_quality = (pj_uint8_t)((*p >> 2) & 0x01);
info->mode = (pj_int8_t)amr_data->enc_mode;
info->start_bit = 0;
frames[i].buf = p + 1;
frames[i].size = (info->frame_type <= 8)?
pjmedia_codec_amrnb_framelen[info->frame_type] : 0;
p += frames[i].size + 1;
/* Count the number of SID and DTX frames */
if (info->frame_type == 15) /* DTX*/
++dtx_cnt;
else if (info->frame_type == 8) /* SID */
++sid_cnt;
}
/* VA generates DTX frames as DTX+SID frames switching quickly and it
* seems that the SID frames occur too often (assuming the purpose is
* only for keeping NAT alive?). So let's modify the behavior a bit.
* Only an SID frame will be sent every PJMEDIA_CODEC_MAX_SILENCE_PERIOD
* milliseconds.
*/
if (sid_cnt + dtx_cnt == nframes) {
pj_int32_t dtx_duration;
dtx_duration = pj_timestamp_diff32(&amr_data->last_tx,
&input->timestamp);
if (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 ||
dtx_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000)
{
output->size = 0;
output->type = PJMEDIA_FRAME_TYPE_NONE;
output->timestamp = input->timestamp;
return PJ_SUCCESS;
}
}
payload_len = output_buf_len;
status = pjmedia_codec_amr_pack(frames, nframes, &amr_data->enc_setting,
output->buf, &payload_len);
if (status != PJ_SUCCESS) {
output->size = 0;
output->buf = NULL;
output->type = PJMEDIA_FRAME_TYPE_NONE;
TRACE_((THIS_FILE, "Failed to pack AMR payload, status=%d", status));
return status;
}
output->size = payload_len;
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
output->timestamp = input->timestamp;
amr_data->last_tx = input->timestamp;
return PJ_SUCCESS;
}
/*
* Decode frame.
*/
static pj_status_t amr_codec_decode( pjmedia_codec *codec,
const struct pjmedia_frame *input,
unsigned output_buf_len,
struct pjmedia_frame *output)
{
struct amr_data *amr_data = (struct amr_data*) codec->codec_data;
pjmedia_frame input_;
pjmedia_codec_amr_bit_info *info;
/* VA AMR-NB decoding buffer: AMR-NB max frame size + 1 byte header. */
unsigned char bitstream[32];
pj_assert(amr_data != NULL);
PJ_ASSERT_RETURN(input && output, PJ_EINVAL);
if (output_buf_len < 320)
return PJMEDIA_CODEC_EPCMTOOSHORT;
input_.buf = &bitstream[1];
input_.size = 31; /* AMR-NB max frame size */
pjmedia_codec_amr_predecode(input, &amr_data->dec_setting, &input_);
info = (pjmedia_codec_amr_bit_info*)&input_.bit_info;
/* VA AMRNB decoder requires frame info in the first byte. */
bitstream[0] = (info->frame_type << 3) | (info->good_quality << 2);
TRACE_((THIS_FILE, "AMR-NB decode(): mode=%d, ft=%d, size=%d",
info->mode, info->frame_type, input_.size));
/* Decode */
Decoder_Interface_Decode(amr_data->decoder, bitstream,
(pj_int16_t*)output->buf, 0);
output->size = 320;
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
output->timestamp = input->timestamp;
#if USE_PJMEDIA_PLC
if (amr_data->plc_enabled)
pjmedia_plc_save(amr_data->plc, (pj_int16_t*)output->buf);
#endif
return PJ_SUCCESS;
}
/*
* Recover lost frame.
*/
#if USE_PJMEDIA_PLC
/*
* Recover lost frame.
*/
static pj_status_t amr_codec_recover( pjmedia_codec *codec,
unsigned output_buf_len,
struct pjmedia_frame *output)
{
struct amr_data *amr_data = codec->codec_data;
TRACE_((THIS_FILE, "amr_codec_recover"));
PJ_ASSERT_RETURN(amr_data->plc_enabled, PJ_EINVALIDOP);
PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT);
pjmedia_plc_generate(amr_data->plc, (pj_int16_t*)output->buf);
output->size = 320;
output->type = PJMEDIA_FRAME_TYPE_AUDIO;
return PJ_SUCCESS;
}
#endif
#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS
# if PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC
# pragma comment( lib, "libopencore-amrnb.a")
# else
# error Unsupported OpenCORE AMR library, fix here
# endif
#endif
#endif

View File

@ -267,6 +267,8 @@ static pj_status_t pack_amr ( codec_private_t *codec_data,
}
info->good_quality = 1;
info->mode = setting->enc_mode;
if (info->frame_type == SID_FT)
info->STI = (sf->data[4] >> 4) & 1;
frames[i].buf = sf->data;
frames[i].size = len;

View File

@ -128,6 +128,12 @@ PJ_DEF(pj_uint8_t) pjmedia_linear2alaw(
} else {
mask = 0x55; /* sign bit = 0 */
pcm_val = -pcm_val - 8;
/* https://trac.pjsip.org/repos/ticket/1301
* Thank you K Johnson - Zetron - 27 May 2011
*/
if (pcm_val < 0)
pcm_val = 0;
}
/* Convert the scaled magnitude to segment number. */

View File

@ -100,9 +100,6 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool,
PJ_ASSERT_RETURN(pool && samples_per_frame && clock_rate && channel_count &&
p_b, PJ_EINVAL);
PJ_ASSERT_RETURN(options==0, PJ_EINVAL);
PJ_UNUSED_ARG(options);
if (!name) {
name = "delaybuf";
@ -127,11 +124,16 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool,
if (status != PJ_SUCCESS)
return status;
/* Create WSOLA */
status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
PJMEDIA_WSOLA_NO_FADING, &b->wsola);
if (status != PJ_SUCCESS)
return status;
if (!(options & PJMEDIA_DELAY_BUF_SIMPLE_FIFO)) {
/* Create WSOLA */
status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1,
PJMEDIA_WSOLA_NO_FADING, &b->wsola);
if (status != PJ_SUCCESS)
return status;
PJ_LOG(5, (b->obj_name, "Using delay buffer with WSOLA."));
} else {
PJ_LOG(5, (b->obj_name, "Using simple FIFO delay buffer."));
}
/* Finally, create mutex */
status = pj_lock_create_recursive_mutex(pool, b->obj_name,
@ -148,15 +150,17 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool,
PJ_DEF(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b)
{
pj_status_t status;
pj_status_t status = PJ_SUCCESS;
PJ_ASSERT_RETURN(b, PJ_EINVAL);
pj_lock_acquire(b->lock);
status = pjmedia_wsola_destroy(b->wsola);
if (status == PJ_SUCCESS)
b->wsola = NULL;
if (b->wsola) {
status = pjmedia_wsola_destroy(b->wsola);
if (status == PJ_SUCCESS)
b->wsola = NULL;
}
pj_lock_release(b->lock);
@ -265,12 +269,14 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b,
pj_lock_acquire(b->lock);
update(b, OP_PUT);
if (b->wsola) {
update(b, OP_PUT);
status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE);
if (status != PJ_SUCCESS) {
pj_lock_release(b->lock);
return status;
status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE);
if (status != PJ_SUCCESS) {
pj_lock_release(b->lock);
return status;
}
}
/* Overflow checking */
@ -278,13 +284,15 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b,
b->max_cnt)
{
unsigned erase_cnt;
/* shrink one frame or just the diff? */
//erase_cnt = b->samples_per_frame;
erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) +
b->samples_per_frame - b->max_cnt;
shrink_buffer(b, erase_cnt);
if (b->wsola) {
/* shrink one frame or just the diff? */
//erase_cnt = b->samples_per_frame;
erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) +
b->samples_per_frame - b->max_cnt;
shrink_buffer(b, erase_cnt);
}
/* Check if shrinking failed or erased count is less than requested,
* delaybuf needs to drop eldest samples, this is bad since the voice
@ -298,9 +306,9 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b,
pjmedia_circ_buf_adv_read_ptr(b->circ_buf, erase_cnt);
PJ_LOG(4,(b->obj_name,"Shrinking failed or insufficient, dropping"
" %d eldest samples, buf_cnt=%d", erase_cnt,
pjmedia_circ_buf_get_len(b->circ_buf)));
PJ_LOG(4,(b->obj_name,"%sDropping %d eldest samples, buf_cnt=%d",
(b->wsola? "Shrinking failed or insufficient. ": ""),
erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf)));
}
}
@ -313,13 +321,14 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b,
PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b,
pj_int16_t frame[])
{
pj_status_t status;
pj_status_t status = PJ_SUCCESS;
PJ_ASSERT_RETURN(b && frame, PJ_EINVAL);
pj_lock_acquire(b->lock);
update(b, OP_GET);
if (b->wsola)
update(b, OP_GET);
/* Starvation checking */
if (pjmedia_circ_buf_get_len(b->circ_buf) < b->samples_per_frame) {
@ -327,24 +336,29 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b,
PJ_LOG(4,(b->obj_name,"Underflow, buf_cnt=%d, will generate 1 frame",
pjmedia_circ_buf_get_len(b->circ_buf)));
status = pjmedia_wsola_generate(b->wsola, frame);
if (b->wsola) {
status = pjmedia_wsola_generate(b->wsola, frame);
if (status == PJ_SUCCESS) {
TRACE__((b->obj_name,"Successfully generate 1 frame"));
if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) {
pj_lock_release(b->lock);
return PJ_SUCCESS;
}
if (status == PJ_SUCCESS) {
TRACE__((b->obj_name,"Successfully generate 1 frame"));
if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) {
pj_lock_release(b->lock);
return PJ_SUCCESS;
}
/* Put generated frame into buffer */
pjmedia_circ_buf_write(b->circ_buf, frame, b->samples_per_frame);
/* Put generated frame into buffer */
pjmedia_circ_buf_write(b->circ_buf, frame,
b->samples_per_frame);
}
}
} else {
if (!b->wsola || status != PJ_SUCCESS) {
unsigned buf_len = pjmedia_circ_buf_get_len(b->circ_buf);
/* Give all what delay buffer has, then pad with zeroes */
PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d",
status));
if (b->wsola)
PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d",
status));
pjmedia_circ_buf_read(b->circ_buf, frame, buf_len);
pjmedia_zero_samples(&frame[buf_len],
@ -379,7 +393,8 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b)
pjmedia_circ_buf_reset(b->circ_buf);
/* Reset WSOLA */
pjmedia_wsola_reset(b->wsola, 0);
if (b->wsola)
pjmedia_wsola_reset(b->wsola, 0);
pj_lock_release(b->lock);

View File

@ -145,6 +145,7 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool,
pjmedia_echo_state **p_echo )
{
unsigned ptime, lat_cnt;
unsigned delay_buf_opt = 0;
pjmedia_echo_state *ec;
pj_status_t status;
@ -212,10 +213,12 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool,
}
/* Create delay buffer to compensate drifts */
if (options & PJMEDIA_ECHO_USE_SIMPLE_FIFO)
delay_buf_opt |= PJMEDIA_DELAY_BUF_SIMPLE_FIFO;
status = pjmedia_delay_buf_create(ec->pool, ec->obj_name, clock_rate,
samples_per_frame, channel_count,
(PJMEDIA_SOUND_BUFFER_COUNT+1) * ptime,
0, &ec->delay_buf);
delay_buf_opt, &ec->delay_buf);
if (status != PJ_SUCCESS) {
pj_pool_release(pool);
return status;

View File

@ -32,14 +32,6 @@
#define THIS_FILE "jbuf.c"
/* Minimal difference between JB size and 2*burst-level to perform
* JB shrinking.
*/
#define SAFE_SHRINKING_DIFF 1
/* Minimal gap (in ms) between JB shrinking */
#define MIN_SHRINK_GAP_MSEC 200
/* Invalid sequence number, used as the initial value. */
#define INVALID_OFFSET -9999
@ -54,6 +46,12 @@
#define INIT_CYCLE 10
/* Minimal difference between JB size and 2*burst-level to perform
* JB shrinking in static discard algorithm.
*/
#define STA_DISC_SAFE_SHRINKING_DIFF 1
/* Struct of JB internal buffer, represented in a circular buffer containing
* frame content, frame type, frame length, and frame bit info.
*/
@ -82,6 +80,11 @@ typedef struct jb_framelist_t
} jb_framelist_t;
typedef void (*discard_algo)(pjmedia_jbuf *jb);
static void jbuf_discard_static(pjmedia_jbuf *jb);
static void jbuf_discard_progressive(pjmedia_jbuf *jb);
struct pjmedia_jbuf
{
/* Settings (consts) */
@ -98,6 +101,7 @@ struct pjmedia_jbuf
won't be included in level
calculation */
int jb_min_shrink_gap; /**< How often can we shrink */
discard_algo jb_discard_algo; /**< Discard algorithm */
/* Buffer */
jb_framelist_t jb_framelist; /**< the buffer */
@ -120,13 +124,16 @@ struct pjmedia_jbuf
operation), the value may be
continuously updated based on
current frame burst level. */
pj_bool_t jb_prefetching; /**< flag if jbuf is prefetching. */
int jb_status; /**< status is 'init' until the first
'put' operation */
int jb_init_cycle_cnt; /**< status is 'init' until the first
'put' operation */
int jb_last_del_seq; /**< Seq # of last frame deleted */
int jb_last_discard_seq;/**< Seq # of last frame discarded */
int jb_discard_ref; /**< Seq # of last frame deleted or
discarded */
unsigned jb_discard_dist; /**< Distance from jb_discard_ref
to perform discard (in frm) */
/* Statistics */
pj_math_stat jb_delay; /**< Delay statistics of jitter buffer
@ -141,7 +148,6 @@ struct pjmedia_jbuf
#define JB_STATUS_INITIALIZING 0
#define JB_STATUS_PROCESSING 1
#define JB_STATUS_PREFETCHING 2
@ -451,7 +457,7 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist,
enum { MAX_MISORDER = 100 };
enum { MAX_DROPOUT = 3000 };
assert(frame_size <= framelist->frame_size);
PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL);
/* too late or sequence restart */
if (index < framelist->origin) {
@ -507,15 +513,32 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist,
/* copy frame content */
pj_memcpy(framelist->content + pos * framelist->frame_size,
frame, frame_size);
return PJ_SUCCESS;
} else {
/* frame is being discarded */
framelist->discarded_num++;
return PJ_EIGNORED;
}
return PJ_SUCCESS;
}
static pj_status_t jb_framelist_discard(jb_framelist_t *framelist,
int index)
{
unsigned pos;
PJ_ASSERT_RETURN(index >= framelist->origin &&
index < framelist->origin + (int)framelist->size,
PJ_EINVAL);
/* Get the slot position */
pos = (framelist->head + (index - framelist->origin)) %
framelist->max_count;
/* Discard the frame */
framelist->frame_type[pos] = PJMEDIA_JB_DISCARDED_FRAME;
framelist->discarded_num++;
return PJ_SUCCESS;
}
enum pjmedia_jb_op
{
@ -548,13 +571,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool,
jb->jb_min_prefetch = 0;
jb->jb_max_prefetch = max_count*4/5;
jb->jb_max_count = max_count;
jb->jb_min_shrink_gap= MIN_SHRINK_GAP_MSEC / ptime;
jb->jb_min_shrink_gap= PJMEDIA_JBUF_DISC_MIN_GAP / ptime;
jb->jb_max_burst = PJ_MAX(MAX_BURST_MSEC / ptime, max_count*3/4);
jb->jb_last_discard_seq = 0;
pj_math_stat_init(&jb->jb_delay);
pj_math_stat_init(&jb->jb_burst);
pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_PROGRESSIVE);
pjmedia_jbuf_reset(jb);
*p_jb = jb;
@ -589,7 +612,7 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
unsigned max_prefetch)
{
PJ_ASSERT_RETURN(jb, PJ_EINVAL);
PJ_ASSERT_RETURN(min_prefetch < max_prefetch &&
PJ_ASSERT_RETURN(min_prefetch <= max_prefetch &&
prefetch <= max_prefetch &&
max_prefetch <= jb->jb_max_count,
PJ_EINVAL);
@ -602,6 +625,30 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb,
}
PJ_DEF(pj_status_t) pjmedia_jbuf_set_discard( pjmedia_jbuf *jb,
pjmedia_jb_discard_algo algo)
{
PJ_ASSERT_RETURN(jb, PJ_EINVAL);
PJ_ASSERT_RETURN(algo >= PJMEDIA_JB_DISCARD_NONE &&
algo <= PJMEDIA_JB_DISCARD_PROGRESSIVE,
PJ_EINVAL);
switch(algo) {
case PJMEDIA_JB_DISCARD_PROGRESSIVE:
jb->jb_discard_algo = &jbuf_discard_progressive;
break;
case PJMEDIA_JB_DISCARD_STATIC:
jb->jb_discard_algo = &jbuf_discard_static;
break;
default:
jb->jb_discard_algo = NULL;
break;
}
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
{
jb->jb_level = 0;
@ -610,6 +657,8 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb)
jb->jb_status = JB_STATUS_INITIALIZING;
jb->jb_init_cycle_cnt= 0;
jb->jb_max_hist_level= 0;
jb->jb_prefetching = (jb->jb_prefetch != 0);
jb->jb_discard_dist = 0;
jb_framelist_reset(&jb->jb_framelist);
@ -621,11 +670,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb)
{
PJ_LOG(5, (jb->jb_name.ptr, ""
"JB summary:\n"
" size=%d prefetch=%d level=%d\n"
" size=%d/eff=%d prefetch=%d level=%d\n"
" delay (min/max/avg/dev)=%d/%d/%d/%d ms\n"
" burst (min/max/avg/dev)=%d/%d/%d/%d frames\n"
" lost=%d discard=%d empty=%d",
jb->jb_framelist.size, jb->jb_prefetch, jb->jb_eff_level,
jb_framelist_size(&jb->jb_framelist),
jb_framelist_eff_size(&jb->jb_framelist),
jb->jb_prefetch, jb->jb_eff_level,
jb->jb_delay.min, jb->jb_delay.max, jb->jb_delay.mean,
pj_math_stat_get_stddev(&jb->jb_delay),
jb->jb_burst.min, jb->jb_burst.max, jb->jb_burst.mean,
@ -712,6 +763,136 @@ static void jbuf_calculate_jitter(pjmedia_jbuf *jb)
}
}
static void jbuf_discard_static(pjmedia_jbuf *jb)
{
/* These code is used for shortening the delay in the jitter buffer.
* It needs shrink only when there is possibility of drift. Drift
* detection is performed by inspecting the jitter buffer size, if
* its size is twice of current burst level, there can be drift.
*
* Moreover, normally drift level is quite low, so JB shouldn't need
* to shrink aggresively, it will shrink maximum one frame per
* PJMEDIA_JBUF_DISC_MIN_GAP ms. Theoritically, JB may handle drift level
* as much as = FRAME_PTIME/PJMEDIA_JBUF_DISC_MIN_GAP * 100%
*
* Whenever there is drift, where PUT > GET, this method will keep
* the latency (JB size) as much as twice of burst level.
*/
/* Shrinking due of drift will be implicitly done by progressive discard,
* so just disable it when progressive discard is active.
*/
int diff, burst_level;
burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2;
if (diff >= STA_DISC_SAFE_SHRINKING_DIFF) {
int seq_origin;
/* Check and adjust jb_discard_ref, in case there was
* seq restart
*/
seq_origin = jb_framelist_origin(&jb->jb_framelist);
if (seq_origin < jb->jb_discard_ref)
jb->jb_discard_ref = seq_origin;
if (seq_origin - jb->jb_discard_ref >= jb->jb_min_shrink_gap)
{
/* Shrink slowly, one frame per cycle */
diff = 1;
/* Drop frame(s)! */
diff = jb_framelist_remove_head(&jb->jb_framelist, diff);
jb->jb_discard_ref = jb_framelist_origin(&jb->jb_framelist);
jb->jb_discard += diff;
TRACE__((jb->jb_name.ptr,
"JB shrinking %d frame(s), cur size=%d", diff,
jb_framelist_eff_size(&jb->jb_framelist)));
}
}
}
static void jbuf_discard_progressive(pjmedia_jbuf *jb)
{
unsigned cur_size, burst_level, overflow, T, discard_dist;
int last_seq;
/* Should be done in PUT operation */
if (jb->jb_last_op != JB_OP_PUT)
return;
/* Check if latency is longer than burst */
cur_size = jb_framelist_eff_size(&jb->jb_framelist);
burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
if (cur_size <= burst_level) {
/* Reset any scheduled discard */
jb->jb_discard_dist = 0;
return;
}
/* Estimate discard duration needed for adjusting latency */
if (burst_level <= PJMEDIA_JBUF_PRO_DISC_MIN_BURST)
T = PJMEDIA_JBUF_PRO_DISC_T1;
else if (burst_level >= PJMEDIA_JBUF_PRO_DISC_MAX_BURST)
T = PJMEDIA_JBUF_PRO_DISC_T2;
else
T = PJMEDIA_JBUF_PRO_DISC_T1 +
(PJMEDIA_JBUF_PRO_DISC_T2 - PJMEDIA_JBUF_PRO_DISC_T1) *
(burst_level - PJMEDIA_JBUF_PRO_DISC_MIN_BURST) /
(PJMEDIA_JBUF_PRO_DISC_MAX_BURST-PJMEDIA_JBUF_PRO_DISC_MIN_BURST);
/* Calculate current discard distance */
overflow = cur_size - burst_level;
discard_dist = T / overflow / jb->jb_frame_ptime;
/* Get last seq number in the JB */
last_seq = jb_framelist_origin(&jb->jb_framelist) +
jb_framelist_size(&jb->jb_framelist) - 1;
/* Setup new discard schedule if none, otherwise, update the existing
* discard schedule (can be delayed or accelerated).
*/
if (jb->jb_discard_dist == 0) {
/* Setup new discard schedule */
jb->jb_discard_ref = last_seq;
} else if (last_seq < jb->jb_discard_ref) {
/* Seq restarted, update discard reference */
jb->jb_discard_ref = last_seq;
}
jb->jb_discard_dist = PJ_MAX(jb->jb_min_shrink_gap, (int)discard_dist);
/* Check if we need to discard now */
if (last_seq >= (jb->jb_discard_ref + (int)jb->jb_discard_dist)) {
int discard_seq;
discard_seq = jb->jb_discard_ref + jb->jb_discard_dist;
if (discard_seq < jb_framelist_origin(&jb->jb_framelist))
discard_seq = jb_framelist_origin(&jb->jb_framelist);
jb_framelist_discard(&jb->jb_framelist, discard_seq);
TRACE__((jb->jb_name.ptr,
"Discard #%d: ref=#%d dist=%d orig=%d size=%d/%d "
"burst=%d/%d",
discard_seq,
jb->jb_discard_ref,
jb->jb_discard_dist,
jb_framelist_origin(&jb->jb_framelist),
cur_size,
jb_framelist_size(&jb->jb_framelist),
jb->jb_eff_level,
burst_level));
/* Update discard reference */
jb->jb_discard_ref = discard_seq;
}
}
PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
{
if(jb->jb_last_op != oper) {
@ -747,63 +928,10 @@ PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
jb->jb_level = 0;
}
/* These code is used for shortening the delay in the jitter buffer.
* It needs shrink only when there is possibility of drift. Drift
* detection is performed by inspecting the jitter buffer size, if
* its size is twice of current burst level, there can be drift.
*
* Moreover, normally drift level is quite low, so JB shouldn't need
* to shrink aggresively, it will shrink maximum one frame per
* MIN_SHRINK_GAP_MSEC ms. Theoritically, JB may handle drift level
* as much as = FRAME_PTIME/MIN_SHRINK_GAP_MSEC * 100%
*
* Whenever there is drift, where PUT > GET, this method will keep
* the latency (JB size) as much as twice of burst level.
*/
/* Shrinking due of drift will be implicitly done by progressive discard,
* so just disable it when progressive discard is active.
*/
#if !PROGRESSIVE_DISCARD
if (jb->jb_status != JB_STATUS_PROCESSING)
return;
{
int diff, burst_level;
burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2;
if (diff >= SAFE_SHRINKING_DIFF) {
int seq_origin;
/* Check and adjust jb_last_del_seq, in case there was
* seq restart
*/
seq_origin = jb_framelist_origin(&jb->jb_framelist);
if (seq_origin < jb->jb_last_del_seq)
jb->jb_last_del_seq = seq_origin;
if (seq_origin - jb->jb_last_del_seq >= jb->jb_min_shrink_gap)
{
/* Shrink slowly, one frame per cycle */
diff = 1;
/* Drop frame(s)! */
diff = jb_framelist_remove_head(&jb->jb_framelist, diff);
jb->jb_last_del_seq = jb_framelist_origin(&jb->jb_framelist);
jb->jb_discard += diff;
TRACE__((jb->jb_name.ptr,
"JB shrinking %d frame(s), cur size=%d", diff,
jb_framelist_eff_size(&jb->jb_framelist)));
}
}
/* Call discard algorithm */
if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) {
(*jb->jb_discard_algo)(jb);
}
#endif /* !PROGRESSIVE_DISCARD */
}
PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb,
@ -834,96 +962,35 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb,
pj_bool_t *discarded)
{
pj_size_t min_frame_size;
int new_size, cur_size, frame_type = PJMEDIA_JB_NORMAL_FRAME;
int new_size, cur_size;
pj_status_t status;
cur_size = jb_framelist_eff_size(&jb->jb_framelist);
#if PROGRESSIVE_DISCARD
{
unsigned interval, seq_delta;
unsigned burst_level, burst_factor;
/* Calculating discard interval (aggressiveness) based on
* (current size / burst level).
*/
if (jb->jb_status == JB_STATUS_PROCESSING) {
burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level);
burst_factor = cur_size / burst_level;
/* Tolerate small spikes */
if ((burst_level <= 5) && (burst_factor < 3))
burst_factor = 0;
} else {
burst_factor = 0;
}
switch (burst_factor) {
case 0:
interval = 0;
break;
case 1:
interval = 7;
break;
case 2:
interval = 5;
break;
default:
interval = 4;
break;
}
/* Do the math now to see if we should discard this packet.
* Calculate the distance from the last sequence
* discarded. If negative, then this is an out of
* order frame so just proceed with discard. Else
* see if the delta is at least the intervals worth away
* from the last frame discarded.
*/
seq_delta = (pj_uint16_t)(frame_seq - jb->jb_last_discard_seq);
if ((0 != interval) && (seq_delta >= interval)) {
frame_type = PJMEDIA_JB_DISCARDED_FRAME;
jb->jb_last_discard_seq = frame_seq;
TRACE__((jb->jb_name.ptr,
"Discarding frame #%d: eff=%d disc=%d orig:%d"
" seq_delta:%d",
frame_seq,
cur_size,
jb_framelist_size(&jb->jb_framelist) - cur_size,
jb_framelist_origin(&jb->jb_framelist),
(int)seq_delta));
}
}
#endif /* PROGRESSIVE_DISCARD */
/* Attempt to store the frame */
min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size);
status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
min_frame_size, bit_info, ts, frame_type);
min_frame_size, bit_info, ts,
PJMEDIA_JB_NORMAL_FRAME);
/* Jitter buffer is full, remove some older frames */
while (status == PJ_ETOOMANY) {
int distance;
unsigned removed;
/* When progressive discard activated, just remove as few as possible
* just to make this frame in.
*/
#if PROGRESSIVE_DISCARD
/* The cases of seq-jump, out-of-order, and seq restart should have
/* Remove as few as possible just to make this frame in. Note that
* the cases of seq-jump, out-of-order, and seq restart should have
* been handled/normalized by previous call of jb_framelist_put_at().
* So we're confident about 'distance' value here.
*/
distance = (frame_seq - jb_framelist_origin(&jb->jb_framelist)) -
jb->jb_max_count + 1;
pj_assert(distance > 0);
#else
distance = PJ_MAX(jb->jb_max_count/4, 1);
#endif
removed = jb_framelist_remove_head(&jb->jb_framelist, distance);
status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
min_frame_size, bit_info, ts, frame_type);
min_frame_size, bit_info, ts,
PJMEDIA_JB_NORMAL_FRAME);
jb->jb_discard += removed;
}
@ -936,11 +1003,11 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb,
*discarded = (status != PJ_SUCCESS);
if (status == PJ_SUCCESS) {
if (jb->jb_status == JB_STATUS_PREFETCHING) {
if (jb->jb_prefetching) {
TRACE__((jb->jb_name.ptr, "PUT prefetch_cnt=%d/%d",
new_size, jb->jb_prefetch));
if (new_size >= jb->jb_prefetch)
jb->jb_status = JB_STATUS_PROCESSING;
jb->jb_prefetching = PJ_FALSE;
}
jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1);
jbuf_update(jb, JB_OP_PUT);
@ -983,7 +1050,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
pj_uint32_t *ts,
int *seq)
{
if (jb->jb_status == JB_STATUS_PREFETCHING) {
if (jb->jb_prefetching) {
/* Can't return frame because jitter buffer is filling up
* minimum prefetch.
@ -1030,7 +1097,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb,
} else {
/* Jitter buffer is empty */
if (jb->jb_prefetch)
jb->jb_status = JB_STATUS_PREFETCHING;
jb->jb_prefetching = PJ_TRUE;
//pj_bzero(frame, jb->jb_frame_size);
*p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME;

View File

@ -1330,7 +1330,10 @@ static pj_status_t match_offer(pj_pool_t *pool,
if (!pj_stricmp(&or_.enc_name, &lr.enc_name) &&
or_.clock_rate == lr.clock_rate &&
(pj_stricmp(&or_.param, &lr.param)==0 ||
(or_.param.slen==1 && *or_.param.ptr=='1')))
(lr.param.slen==0 && or_.param.slen==1 &&
*or_.param.ptr=='1') ||
(or_.param.slen==0 && lr.param.slen==1 &&
*lr.param.ptr=='1')))
{
/* Match! */
if (is_codec) {

View File

@ -272,6 +272,40 @@ PJ_DEF(pj_status_t) pjmedia_session_resume_stream( pjmedia_session *session,
return pjmedia_stream_resume(session->stream[index], dir);
}
/**
* Send RTCP SDES for the session.
*/
PJ_DEF(pj_status_t)
pjmedia_session_send_rtcp_sdes( const pjmedia_session *session )
{
unsigned i;
PJ_ASSERT_RETURN(session, PJ_EINVAL);
for (i=0; i<session->stream_cnt; ++i) {
pjmedia_stream_send_rtcp_sdes(session->stream[i]);
}
return PJ_SUCCESS;
}
/**
* Send RTCP BYE for the session.
*/
PJ_DEF(pj_status_t)
pjmedia_session_send_rtcp_bye( const pjmedia_session *session )
{
unsigned i;
PJ_ASSERT_RETURN(session, PJ_EINVAL);
for (i=0; i<session->stream_cnt; ++i) {
pjmedia_stream_send_rtcp_bye(session->stream[i]);
}
return PJ_SUCCESS;
}
/**
* Enumerate media stream in the session.
*/

View File

@ -154,6 +154,7 @@ struct pjmedia_stream
pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */
pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */
pj_bool_t initial_rr; /**< Initial RTCP RR sent */
pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/
/* RFC 2833 DTMF transmission queue: */
int tx_event_pt; /**< Outgoing pt for dtmf. */
@ -1824,19 +1825,24 @@ on_return:
/* Build RR or SR */
pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len);
pkt = (pj_uint8_t*) stream->enc->out_pkt;
pj_memcpy(pkt, sr_rr_pkt, len);
pkt += len;
/* Append SDES */
len = create_rtcp_sdes(stream, (pj_uint8_t*)pkt,
stream->enc->out_pkt_size - len);
if (len > 0) {
pkt += len;
len = ((pj_uint8_t*)pkt) - ((pj_uint8_t*)stream->enc->out_pkt);
pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
}
if (!stream->rtcp_sdes_bye_disabled) {
pkt = (pj_uint8_t*) stream->enc->out_pkt;
pj_memcpy(pkt, sr_rr_pkt, len);
pkt += len;
/* Append SDES */
len = create_rtcp_sdes(stream, (pj_uint8_t*)pkt,
stream->enc->out_pkt_size - len);
if (len > 0) {
pkt += len;
len = ((pj_uint8_t*)pkt) - ((pj_uint8_t*)stream->enc->out_pkt);
pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
}
} else {
pjmedia_transport_send_rtcp(stream->transport, sr_rr_pkt, len);
}
stream->initial_rr = PJ_TRUE;
}
@ -1959,7 +1965,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
enum { M = 32 };
pjmedia_stream *stream;
pj_str_t name;
unsigned jb_init, jb_max, jb_min_pre, jb_max_pre, len;
unsigned jb_init, jb_max, jb_min_pre, jb_max_pre;
pjmedia_audio_format_detail *afd;
pj_pool_t *own_pool = NULL;
char *p;
@ -2010,6 +2016,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
stream->user_data = user_data;
stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
info->fmt.clock_rate / 1000;
stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;
stream->tx_event_pt = info->tx_event_pt ? info->tx_event_pt : -1;
stream->rx_event_pt = info->rx_event_pt ? info->rx_event_pt : -1;
@ -2322,11 +2329,8 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
#endif
/* Send RTCP SDES */
len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->out_pkt,
stream->enc->out_pkt_size);
if (len != 0) {
pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
if (!stream->rtcp_sdes_bye_disabled) {
pjmedia_stream_send_rtcp_sdes(stream);
}
#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
@ -2343,7 +2347,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt,
pj_ansi_snprintf(trace_name, sizeof(trace_name),
TRACE_JB_PATH_PREFIX "%s.csv",
stream->port.info.name.ptr);
status = pj_file_open(pool, trace_name, PJ_O_RDWR, &stream->trace_jb_fd);
status = pj_file_open(pool, trace_name, PJ_O_WRONLY, &stream->trace_jb_fd);
if (status != PJ_SUCCESS) {
stream->trace_jb_fd = TRACE_JB_INVALID_FD;
PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'",
@ -2381,7 +2385,6 @@ err_cleanup:
*/
PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream )
{
unsigned len;
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
@ -2423,13 +2426,8 @@ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream )
#endif
/* Send RTCP BYE */
if (stream->enc && stream->transport) {
len = create_rtcp_bye(stream, (pj_uint8_t*)stream->enc->out_pkt,
stream->enc->out_pkt_size);
if (len != 0) {
pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
}
if (!stream->rtcp_sdes_bye_disabled) {
pjmedia_stream_send_rtcp_bye(stream);
}
/* Detach from transport
@ -3298,3 +3296,45 @@ PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp(
return status;
}
/*
* Send RTCP SDES.
*/
PJ_DEF(pj_status_t)
pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream )
{
unsigned len;
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->out_pkt,
stream->enc->out_pkt_size);
if (len != 0) {
return pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
}
return PJ_SUCCESS;
}
/*
* Send RTCP BYE.
*/
PJ_DEF(pj_status_t)
pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream )
{
PJ_ASSERT_RETURN(stream, PJ_EINVAL);
if (stream->enc && stream->transport) {
unsigned len;
len = create_rtcp_bye(stream, (pj_uint8_t*)stream->enc->out_pkt,
stream->enc->out_pkt_size);
if (len != 0) {
return pjmedia_transport_send_rtcp(stream->transport,
stream->enc->out_pkt, len);
}
}
return PJ_SUCCESS;
}

View File

@ -94,6 +94,7 @@ static struct pjmedia_transport_op tp_adapter_op =
struct tp_adapter
{
pjmedia_transport base;
pj_bool_t del_base;
pj_pool_t *pool;
@ -118,6 +119,7 @@ struct tp_adapter
PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
const char *name,
pjmedia_transport *transport,
pj_bool_t del_base,
pjmedia_transport **p_tp)
{
pj_pool_t *pool;
@ -138,6 +140,7 @@ PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt,
/* Save the transport as the slave transport */
adapter->slave_tp = transport;
adapter->del_base = del_base;
/* Done */
*p_tp = &adapter->base;
@ -421,7 +424,9 @@ static pj_status_t transport_destroy (pjmedia_transport *tp)
struct tp_adapter *adapter = (struct tp_adapter*)tp;
/* Close the slave transport */
pjmedia_transport_close(adapter->slave_tp);
if (adapter->del_base) {
pjmedia_transport_close(adapter->slave_tp);
}
/* Self destruct.. */
pj_pool_release(adapter->pool);

View File

@ -72,6 +72,7 @@ struct transport_ice
pj_sockaddr rtp_src_addr; /**< Actual source RTP address. */
pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */
unsigned rtp_src_cnt; /**< How many pkt from this addr. */
unsigned rtcp_src_cnt; /**< How many pkt from this addr. */
unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
@ -1516,6 +1517,8 @@ static pj_status_t transport_get_info(pjmedia_transport *tp,
*/
if (tp_ice->use_ice || tp_ice->rtp_src_cnt) {
info->src_rtp_name = tp_ice->rtp_src_addr;
}
if (tp_ice->use_ice || tp_ice->rtcp_src_cnt) {
info->src_rtcp_name = tp_ice->rtcp_src_addr;
}
@ -1581,6 +1584,7 @@ static pj_status_t transport_attach (pjmedia_transport *tp,
tp_ice->rtp_src_addr = tp_ice->remote_rtp;
tp_ice->rtcp_src_addr = tp_ice->remote_rtcp;
tp_ice->rtp_src_cnt = 0;
tp_ice->rtcp_src_cnt = 0;
return PJ_SUCCESS;
}
@ -1656,6 +1660,7 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
unsigned src_addr_len)
{
struct transport_ice *tp_ice;
pj_bool_t discard = PJ_FALSE;
tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
@ -1671,20 +1676,23 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
}
}
(*tp_ice->rtp_cb)(tp_ice->stream, pkt, size);
/* See if source address of RTP packet is different than the
* configured address, and switch RTP remote address to
* source packet address after several consecutive packets
* have been received.
*/
if (!tp_ice->use_ice) {
pj_bool_t enable_switch =
((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0);
/* Increment counter and avoid zero */
if (++tp_ice->rtp_src_cnt == 0)
tp_ice->rtp_src_cnt = 1;
if (!enable_switch ||
pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) == 0)
{
/* Don't switch while we're receiving from remote_rtp */
tp_ice->rtp_src_cnt = 0;
} else {
if (pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) != 0) {
++tp_ice->rtp_src_cnt;
/* Check if the source address is recognized. */
if (pj_sockaddr_cmp(src_addr, &tp_ice->rtp_src_addr) != 0) {
@ -1692,11 +1700,12 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
pj_sockaddr_cp(&tp_ice->rtp_src_addr, src_addr);
/* Reset counter */
tp_ice->rtp_src_cnt = 0;
discard = PJ_TRUE;
}
if ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0 &&
tp_ice->rtp_src_cnt >= PJMEDIA_RTP_NAT_PROBATION_CNT)
{
if (tp_ice->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) {
discard = PJ_TRUE;
} else {
char addr_text[80];
/* Set remote RTP address to source address */
@ -1725,7 +1734,8 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
pj_sockaddr_set_port(&tp_ice->remote_rtcp, port);
PJ_LOG(4,(tp_ice->base.name,
"Remote RTCP address switched to %s",
"Remote RTCP address switched to predicted "
"address %s",
pj_sockaddr_print(&tp_ice->remote_rtcp,
addr_text,
sizeof(addr_text), 3)));
@ -1733,31 +1743,45 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
}
}
}
if (!discard)
(*tp_ice->rtp_cb)(tp_ice->stream, pkt, size);
} else if (comp_id==2 && tp_ice->rtcp_cb) {
(*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size);
/* Check if RTCP source address is the same as the configured
* remote address, and switch the address when they are
* different.
*/
if (!tp_ice->use_ice &&
pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) != 0)
(tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0)
{
pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr);
if ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0) {
if (pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) == 0) {
tp_ice->rtcp_src_cnt = 0;
} else {
char addr_text[80];
pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr);
++tp_ice->rtcp_src_cnt;
if (tp_ice->rtcp_src_cnt < PJMEDIA_RTCP_NAT_PROBATION_CNT) {
discard = PJ_TRUE;
} else {
tp_ice->rtcp_src_cnt = 0;
pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr);
pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr);
pj_assert(tp_ice->addr_len == pj_sockaddr_get_len(src_addr));
pj_assert(tp_ice->addr_len==pj_sockaddr_get_len(src_addr));
PJ_LOG(4,(tp_ice->base.name,
"Remote RTCP address switched to %s",
pj_sockaddr_print(&tp_ice->remote_rtcp, addr_text,
sizeof(addr_text), 3)));
PJ_LOG(4,(tp_ice->base.name,
"Remote RTCP address switched to %s",
pj_sockaddr_print(&tp_ice->remote_rtcp,
addr_text, sizeof(addr_text),
3)));
}
}
}
if (!discard)
(*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size);
}
PJ_UNUSED_ARG(src_addr_len);

View File

@ -83,6 +83,7 @@ struct transport_udp
pj_sock_t rtcp_sock; /**< RTCP socket */
pj_sockaddr rtcp_addr_name; /**< Published RTCP address. */
pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */
unsigned rtcp_src_cnt; /**< How many pkt from this addr. */
int rtcp_addr_len; /**< Length of RTCP src address. */
pj_ioqueue_key_t *rtcp_key; /**< RTCP socket key in ioqueue */
pj_ioqueue_op_key_t rtcp_read_op; /**< Pending read operation */
@ -452,6 +453,7 @@ static void on_rx_rtp( pj_ioqueue_key_t *key,
do {
void (*cb)(void*,void*,pj_ssize_t);
void *user_data;
pj_bool_t discard = PJ_FALSE;
cb = udp->rtp_cb;
user_data = udp->user_data;
@ -462,14 +464,10 @@ static void on_rx_rtp( pj_ioqueue_key_t *key,
PJ_LOG(5,(udp->base.name,
"RX RTP packet dropped because of pkt lost "
"simulation"));
goto read_next_packet;
discard = PJ_TRUE;
}
}
if (udp->attached && cb)
(*cb)(user_data, udp->rtp_pkt, bytes_read);
/* See if source address of RTP packet is different than the
* configured address, and switch RTP remote address to
* source packet address after several consecutive packets
@ -478,11 +476,15 @@ static void on_rx_rtp( pj_ioqueue_key_t *key,
if (bytes_read>0 &&
(udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0)
{
if (pj_sockaddr_cmp(&udp->rem_rtp_addr, &udp->rtp_src_addr) != 0) {
if (pj_sockaddr_cmp(&udp->rem_rtp_addr, &udp->rtp_src_addr) == 0) {
/* We're still receiving from rem_rtp_addr. Don't switch. */
udp->rtp_src_cnt = 0;
} else {
udp->rtp_src_cnt++;
if (udp->rtp_src_cnt >= PJMEDIA_RTP_NAT_PROBATION_CNT) {
if (udp->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) {
discard = PJ_TRUE;
} else {
char addr_text[80];
@ -516,7 +518,8 @@ static void on_rx_rtp( pj_ioqueue_key_t *key,
sizeof(pj_sockaddr));
PJ_LOG(4,(udp->base.name,
"Remote RTCP address switched to %s",
"Remote RTCP address switched to predicted"
" address %s",
pj_sockaddr_print(&udp->rtcp_src_addr,
addr_text,
sizeof(addr_text), 3)));
@ -526,7 +529,9 @@ static void on_rx_rtp( pj_ioqueue_key_t *key,
}
}
read_next_packet:
if (!discard && udp->attached && cb)
(*cb)(user_data, udp->rtp_pkt, bytes_read);
bytes_read = sizeof(udp->rtp_pkt);
udp->rtp_addrlen = sizeof(udp->rtp_src_addr);
status = pj_ioqueue_recvfrom(udp->rtp_key, &udp->rtp_read_op,
@ -568,18 +573,27 @@ static void on_rx_rtcp(pj_ioqueue_key_t *key,
* different.
*/
if (bytes_read>0 &&
(udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0 &&
pj_sockaddr_cmp(&udp->rem_rtcp_addr, &udp->rtcp_src_addr) != 0)
(udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0)
{
char addr_text[80];
if (pj_sockaddr_cmp(&udp->rem_rtcp_addr, &udp->rtcp_src_addr) == 0) {
/* Still receiving from rem_rtcp_addr, don't switch */
udp->rtcp_src_cnt = 0;
} else {
++udp->rtcp_src_cnt;
pj_memcpy(&udp->rem_rtcp_addr, &udp->rtcp_src_addr,
sizeof(pj_sockaddr));
if (udp->rtcp_src_cnt >= PJMEDIA_RTCP_NAT_PROBATION_CNT ) {
char addr_text[80];
PJ_LOG(4,(udp->base.name,
"Remote RTCP address switched to %s",
pj_sockaddr_print(&udp->rtcp_src_addr, addr_text,
sizeof(addr_text), 3)));
udp->rtcp_src_cnt = 0;
pj_memcpy(&udp->rem_rtcp_addr, &udp->rtcp_src_addr,
sizeof(pj_sockaddr));
PJ_LOG(4,(udp->base.name,
"Remote RTCP address switched to %s",
pj_sockaddr_print(&udp->rtcp_src_addr, addr_text,
sizeof(addr_text), 3)));
}
}
}
bytes_read = sizeof(udp->rtcp_pkt);
@ -677,6 +691,7 @@ static pj_status_t transport_attach( pjmedia_transport *tp,
pj_bzero(&udp->rtp_src_addr, sizeof(udp->rtp_src_addr));
pj_bzero(&udp->rtcp_src_addr, sizeof(udp->rtcp_src_addr));
udp->rtp_src_cnt = 0;
udp->rtcp_src_cnt = 0;
/* Unlock keys */
pj_ioqueue_unlock_key(udp->rtcp_key);

View File

@ -43,7 +43,8 @@ typedef struct test_cond_t {
int discard;
int lost;
int empty;
int delay; /**< Maximum delay, in frames. */
int delay; /**< Average delay, in frames. */
int delay_min; /**< Minimum delay, in frames. */
} test_cond_t;
static pj_bool_t parse_test_headers(char *line, test_param_t *param,
@ -69,6 +70,8 @@ static pj_bool_t parse_test_headers(char *line, test_param_t *param,
cond->burst = cond_val;
else if (pj_ansi_stricmp(cond_st, "delay") == 0)
cond->delay = cond_val;
else if (pj_ansi_stricmp(cond_st, "delay_min") == 0)
cond->delay_min = cond_val;
else if (pj_ansi_stricmp(cond_st, "discard") == 0)
cond->discard = cond_val;
else if (pj_ansi_stricmp(cond_st, "empty") == 0)
@ -217,6 +220,7 @@ int jbuf_main(void)
cond.burst = -1;
cond.delay = -1;
cond.delay_min = -1;
cond.discard = -1;
cond.empty = -1;
cond.lost = -1;
@ -313,6 +317,11 @@ int jbuf_main(void)
cond.delay, state.avg_delay/JB_PTIME);
rc |= 2;
}
if (cond.delay_min >= 0 && (int)state.min_delay/JB_PTIME > cond.delay_min) {
printf("! 'Minimum delay' should be %d, it is %d\n",
cond.delay_min, state.min_delay/JB_PTIME);
rc |= 32;
}
if (cond.discard >= 0 && (int)state.discard > cond.discard) {
printf("! 'Discard' should be %d, it is %d\n",
cond.discard, state.discard);

View File

@ -916,6 +916,23 @@ static pjmedia_port* g7221c_encode_decode(pj_pool_t *pool,
}
#endif /* PJMEDIA_HAS_G7221_CODEC */
#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
/* AMR-NB benchmark benchmark */
static pjmedia_port* amr_encode_decode(pj_pool_t *pool,
unsigned clock_rate,
unsigned channel_count,
unsigned samples_per_frame,
unsigned flags,
struct test_entry *te)
{
return codec_encode_decode(pool, "AMR/8000",
&pjmedia_codec_opencore_amrnb_init,
&pjmedia_codec_opencore_amrnb_deinit,
clock_rate, channel_count,
samples_per_frame, flags, te);
}
#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */
#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0
static pj_status_t init_l16_default(pjmedia_endpt *endpt)
{
@ -1976,6 +1993,23 @@ static pjmedia_port* create_stream_g7221c( pj_pool_t *pool,
}
#endif /* PJMEDIA_HAS_G7221_CODEC */
/* AMR-NB stream */
#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
static pjmedia_port* create_stream_amr( pj_pool_t *pool,
unsigned clock_rate,
unsigned channel_count,
unsigned samples_per_frame,
unsigned flags,
struct test_entry *te)
{
return create_stream(pool, "AMR/8000", &pjmedia_codec_opencore_amrnb_init,
&pjmedia_codec_opencore_amrnb_deinit,
PJ_FALSE, PJ_FALSE, PJ_FALSE,
clock_rate, channel_count,
samples_per_frame, flags, te);
}
#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */
/***************************************************************************/
/* Delay buffer */
enum {DELAY_BUF_MAX_DELAY = 80};
@ -2366,6 +2400,9 @@ int mips_test(void)
{ "codec encode/decode - G.722.1", OP_PUT, K16, &g7221_encode_decode},
{ "codec encode/decode - G.722.1c", OP_PUT, K32, &g7221c_encode_decode},
#endif
#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
{ "codec encode/decode - AMR-NB", OP_PUT, K8, &amr_encode_decode},
#endif
#if PJMEDIA_HAS_L16_CODEC
{ "codec encode/decode - L16/8000/1", OP_PUT, K8, &l16_8_encode_decode},
{ "codec encode/decode - L16/16000/1", OP_PUT, K16, &l16_16_encode_decode},
@ -2390,6 +2427,9 @@ int mips_test(void)
#if PJMEDIA_HAS_G7221_CODEC
{ "stream TX/RX - G.722.1", OP_PUT_GET, K16, &create_stream_g7221},
{ "stream TX/RX - G.722.1c", OP_PUT_GET, K32, &create_stream_g7221c},
#endif
#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC
{ "stream TX/RX - AMR-NB", OP_PUT_GET, K8, &create_stream_amr},
#endif
};

View File

@ -43,6 +43,17 @@ void app_perror(pj_status_t status, const char *msg)
PJ_LOG(3,(THIS_FILE, "%s: %s", msg, errbuf));
}
/* Force linking PLC stuff if G.711 is disabled. See:
* https://trac.pjsip.org/repos/ticket/1337
*/
#if PJMEDIA_HAS_G711_CODEC==0
int dummy()
{
// Dummy
return (int) &pjmedia_plc_save;
}
#endif
int test_main(void)
{
int rc = 0;

View File

@ -1201,6 +1201,12 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
PJ_STUN_SESS_LOG_RX_IND)
};
/* https://trac.pjsip.org/repos/ticket/1316 */
if (comp->turn_sock == NULL) {
/* TURN socket error */
return PJ_EINVALIDOP;
}
if (!comp->turn_log_off) {
/* Disable logging for Send/Data indications */
PJ_LOG(5,(ice_st->obj_name,

View File

@ -120,10 +120,11 @@ PJ_DEF(const char*) pj_stun_sock_op_name(pj_stun_sock_op op)
"?",
"DNS resolution",
"STUN Binding request",
"Keep-alive"
"Keep-alive",
"Mapped addr. changed"
};
return op <= PJ_STUN_SOCK_KEEP_ALIVE_OP ? names[op] : "?";
return op < PJ_ARRAY_SIZE(names) ? names[op] : "???";
};

View File

@ -619,7 +619,8 @@ static pj_status_t turn_on_send_pkt(pj_turn_session *sess,
if (turn_sock == NULL) {
/* We've been destroyed */
pj_assert(!"We should shutdown gracefully");
// https://trac.pjsip.org/repos/ticket/1316
//pj_assert(!"We should shutdown gracefully");
return PJ_EINVALIDOP;
}

View File

@ -377,6 +377,10 @@
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = ipjsua_Prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = (
"PJ_AUTOCONF=1",
USE_GUI,
);
INFOPLIST_FILE = "ipjsua-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 3.2;
LIBRARY_SEARCH_PATHS = (
@ -406,6 +410,10 @@
COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = ipjsua_Prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = (
"PJ_AUTOCONF=1",
USE_GUI,
);
INFOPLIST_FILE = "ipjsua-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 3.2;
LIBRARY_SEARCH_PATHS = (

View File

@ -148,9 +148,6 @@ static char some_buf[SOME_BUF_SIZE];
#ifdef STEREO_DEMO
static void stereo_demo();
#endif
#ifdef TRANSPORT_ADAPTER_SAMPLE
static pj_status_t transport_adapter_sample(void);
#endif
static pj_status_t create_ipv6_media_transports(void);
pj_status_t app_destroy(void);
@ -911,8 +908,8 @@ static pj_status_t parse_args(int argc, char *argv[],
break;
case OPT_100REL: /** 100rel */
cur_acc->require_100rel = PJ_TRUE;
cfg->cfg.require_100rel = PJ_TRUE;
cur_acc->require_100rel = PJSUA_100REL_MANDATORY;
cfg->cfg.require_100rel = PJSUA_100REL_MANDATORY;
break;
case OPT_TIMER: /** session timer */
@ -3221,6 +3218,35 @@ static void on_call_media_event(pjsua_call_id call_id,
#endif
}
#ifdef TRANSPORT_ADAPTER_SAMPLE
/*
* This callback is called when media transport needs to be created.
*/
static pjmedia_transport* on_create_media_transport(pjsua_call_id call_id,
unsigned media_idx,
pjmedia_transport *base_tp,
unsigned flags)
{
pjmedia_transport *adapter;
pj_status_t status;
/* Create the adapter */
status = pjmedia_tp_adapter_create(pjsua_get_pjmedia_endpt(),
NULL, base_tp,
(flags & PJSUA_MED_TP_CLOSE_MEMBER),
&adapter);
if (status != PJ_SUCCESS) {
PJ_PERROR(1,(THIS_FILE, status, "Error creating adapter"));
return NULL;
}
PJ_LOG(3,(THIS_FILE, "Media transport is created for call %d media %d",
call_id, media_idx));
return adapter;
}
#endif
/*
* Print buddy list.
*/
@ -5210,6 +5236,9 @@ pj_status_t app_init(int argc, char *argv[])
app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
app_config.cfg.cb.on_call_media_event = &on_call_media_event;
#ifdef TRANSPORT_ADAPTER_SAMPLE
app_config.cfg.cb.on_create_media_transport = &on_create_media_transport;
#endif
app_config.log_cfg.cb = log_cb;
/* Set sound device latency */
@ -5489,6 +5518,9 @@ pj_status_t app_init(int argc, char *argv[])
/* Add accounts */
for (i=0; i<app_config.acc_cnt; ++i) {
app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
app_config.acc_cfg[i].reg_retry_interval = 300;
app_config.acc_cfg[i].reg_first_retry_interval = 60;
status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
if (status != PJ_SUCCESS)
goto on_error;
@ -5521,17 +5553,12 @@ pj_status_t app_init(int argc, char *argv[])
}
/* Add RTP transports */
#ifdef TRANSPORT_ADAPTER_SAMPLE
status = transport_adapter_sample();
#else
if (app_config.ipv6)
status = create_ipv6_media_transports();
#if DISABLED_FOR_TICKET_1185
else
status = pjsua_media_transports_create(&app_config.rtp_cfg);
#endif
#endif
if (status != PJ_SUCCESS)
goto on_error;
@ -5752,47 +5779,6 @@ static void stereo_demo()
}
#endif
#ifdef TRANSPORT_ADAPTER_SAMPLE
static pj_status_t create_transport_adapter(pjmedia_endpt *med_endpt, int port,
pjmedia_transport **p_tp)
{
pjmedia_transport *udp;
pj_status_t status;
/* Create the UDP media transport */
status = pjmedia_transport_udp_create(med_endpt, NULL, port, 0, &udp);
if (status != PJ_SUCCESS)
return status;
/* Create the adapter */
status = pjmedia_tp_adapter_create(med_endpt, NULL, udp, p_tp);
if (status != PJ_SUCCESS) {
pjmedia_transport_close(udp);
return status;
}
return PJ_SUCCESS;
}
static pj_status_t transport_adapter_sample(void)
{
pjsua_media_transport tp[PJSUA_MAX_CALLS];
pj_status_t status;
int port = 7000;
unsigned i;
for (i=0; i<app_config.cfg.max_calls; ++i) {
status = create_transport_adapter(pjsua_get_pjmedia_endpt(),
port + i*10,
&tp[i].transport);
if (status != PJ_SUCCESS)
return status;
}
return pjsua_media_transports_attach(tp, i, PJ_TRUE);
}
#endif
static pj_status_t create_ipv6_media_transports(void)
{
pjsua_media_transport tp[PJSUA_MAX_CALLS];

View File

@ -1028,8 +1028,8 @@ static void icedemo_help_menu(void)
" copy-pasting the SDP to the other icedemo application\n"
" - parse remote SDP, by pasting SDP generated by the other icedemo\n"
" instance [menu \"r\"]\n"
" - begin ICE negotiation in our end [menu \"b\"]\n"
" - begin ICE negotiation in the other icedemo instance\n"
" - begin ICE negotiation in our end [menu \"b\"], and \n"
" - immediately begin ICE negotiation in the other icedemo instance\n"
" - ICE negotiation will run, and result will be printed to screen\n"
" - send application data to remote [menu \"x\"]\n"
" - end/stop ICE session [menu \"e\"]\n"

View File

@ -25,10 +25,15 @@
static const char *USAGE =
"pcaputil [options] INPUT OUTPUT\n"
"\n"
" Convert captured RTP packets in PCAP file to WAV or stream it\n"
" to remote destination.\n"
" Convert captured RTP packets in PCAP file to WAV file or play it\n"
" to audio device.\n"
"\n"
"INPUT is the PCAP file name/path\n"
" INPUT is the PCAP file name/path.\n"
" OUTPUT is the WAV file name/path to store the output, or set to \"-\",\n"
" to play the output to audio device. The program will decode\n"
" the RTP contents using codec that is available in PJMEDIA,\n"
" and optionally decrypt the content using the SRTP crypto and\n"
" keys below.\n"
"\n"
"Options to filter packets from PCAP file:\n"
"(you can always select the relevant packets from Wireshark of course!)\n"
@ -37,25 +42,27 @@ static const char *USAGE =
" --src-port=port Only include packets from this source port number\n"
" --dst-port=port Only include packets destined to this port number\n"
"\n"
"Options for saving to WAV file:\n"
"Options for RTP packet processing:\n"
""
" OUTPUT is WAV file: Set output to WAV file. The program will decode the\n"
" RTP contents to the specified WAV file using codec\n"
" that is available in PJMEDIA, and optionally decrypt\n"
" the content using the SRTP crypto and keys below.\n"
" --codec=codec_id The codec ID formatted \"name/clock-rate/channel-count\"\n"
" must be specified for codec with dynamic PT,\n"
" e.g: \"Speex/8000\"\n"
" --srtp-crypto=TAG, -c Set crypto to be used to decrypt SRTP packets. Valid\n"
" tags are: \n"
" AES_CM_128_HMAC_SHA1_80 \n"
" AES_CM_128_HMAC_SHA1_32\n"
" --srtp-key=KEY, -k Set the base64 key to decrypt SRTP packets.\n"
"\n"
"Options for playing to audio device:\n"
""
" --play-dev-id=dev_id Audio device ID for playback.\n"
"\n"
" Example:\n"
" pcaputil file.pcap output.wav\n"
" pcaputil -c AES_CM_128_HMAC_SHA1_80 \\\n"
" -k VLDONbsbGl2Puqy+0PV7w/uGfpSPKFevDpxGsxN3 \\\n"
" file.pcap output.wav\n"
" -k VLDONbsbGl2Puqy+0PV7w/uGfpSPKFevDpxGsxN3 \\\n"
" file.pcap output.wav\n"
"\n"
"Remote streaming is not supported yet."
;
static struct app
@ -66,22 +73,16 @@ static struct app
pj_pcap_file *pcap;
pjmedia_port *wav;
pjmedia_codec *codec;
pjmedia_aud_stream *aud_strm;
unsigned pt;
pjmedia_transport *srtp;
pjmedia_rtp_session rtp_sess;
pj_bool_t rtp_sess_init;
} app;
static void err_exit(const char *title, pj_status_t status)
{
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
printf("Error: %s: %s\n", title, errmsg);
} else {
printf("Error: %s\n", title);
}
static void cleanup()
{
if (app.srtp) pjmedia_transport_close(app.srtp);
if (app.wav) {
pj_ssize_t pos = pjmedia_wav_writer_port_get_pos(app.wav);
@ -102,11 +103,26 @@ static void err_exit(const char *title, pj_status_t status)
cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
pjmedia_codec_mgr_dealloc_codec(cmgr, app.codec);
}
if (app.aud_strm) {
pjmedia_aud_stream_stop(app.aud_strm);
pjmedia_aud_stream_destroy(app.aud_strm);
}
if (app.mept) pjmedia_endpt_destroy(app.mept);
if (app.pool) pj_pool_release(app.pool);
pj_caching_pool_destroy(&app.cp);
pj_shutdown();
}
static void err_exit(const char *title, pj_status_t status)
{
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
printf("Error: %s: %s\n", title, errmsg);
} else {
printf("Error: %s\n", title);
}
cleanup();
exit(1);
}
@ -212,9 +228,45 @@ static void read_rtp(pj_uint8_t *buf, pj_size_t bufsize,
}
}
static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
pjmedia_frame play_frm;
static pj_bool_t play_frm_copied, play_frm_ready;
static pj_status_t wait_play(pjmedia_frame *f)
{
play_frm_copied = PJ_FALSE;
play_frm = *f;
play_frm_ready = PJ_TRUE;
while (!play_frm_copied) {
pj_thread_sleep(1);
}
play_frm_ready = PJ_FALSE;
return PJ_SUCCESS;
}
static pj_status_t play_cb(void *user_data, pjmedia_frame *f)
{
PJ_UNUSED_ARG(user_data);
if (!play_frm_ready) {
PJ_LOG(3, ("play_cb()", "Warning! Play frame not ready"));
return PJ_SUCCESS;
}
pj_memcpy(f->buf, play_frm.buf, play_frm.size);
f->size = play_frm.size;
play_frm_copied = PJ_TRUE;
return PJ_SUCCESS;
}
static void pcap2wav(const pj_str_t *codec,
const pj_str_t *wav_filename,
pjmedia_aud_dev_index dev_id,
const pj_str_t *srtp_crypto,
const pj_str_t *srtp_key)
{
const pj_str_t WAV = {".wav", 4};
struct pkt
{
pj_uint8_t buffer[320];
@ -255,7 +307,18 @@ static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
/* Get codec info and param for the specified payload type */
app.pt = pkt0.rtp->pt;
T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
if (app.pt >=0 && app.pt < 96) {
T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
} else {
unsigned cnt = 2;
const pjmedia_codec_info *info[2];
T( pjmedia_codec_mgr_find_codecs_by_id(cmgr, codec, &cnt,
info, NULL) );
if (cnt != 1)
err_exit("Codec ID must be specified and unique!", 0);
ci = info[0];
}
T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );
/* Alloc and init codec */
@ -263,13 +326,30 @@ static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
T( pjmedia_codec_init(app.codec, app.pool) );
T( pjmedia_codec_open(app.codec, &param) );
/* Open WAV file */
/* Init audio device or WAV file */
samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
T( pjmedia_wav_writer_port_create(app.pool, wav_filename,
ci->clock_rate, ci->channel_cnt,
samples_per_frame,
param.info.pcm_bits_per_sample, 0, 0,
&app.wav) );
if (pj_strcmp2(wav_filename, "-") == 0) {
pjmedia_aud_param aud_param;
/* Open audio device */
T( pjmedia_aud_dev_default_param(dev_id, &aud_param) );
aud_param.dir = PJMEDIA_DIR_PLAYBACK;
aud_param.channel_count = ci->channel_cnt;
aud_param.clock_rate = ci->clock_rate;
aud_param.samples_per_frame = samples_per_frame;
T( pjmedia_aud_stream_create(&aud_param, NULL, &play_cb,
NULL, &app.aud_strm) );
T( pjmedia_aud_stream_start(app.aud_strm) );
} else if (pj_stristr(wav_filename, &WAV)) {
/* Open WAV file */
T( pjmedia_wav_writer_port_create(app.pool, wav_filename->ptr,
ci->clock_rate, ci->channel_cnt,
samples_per_frame,
param.info.pcm_bits_per_sample, 0, 0,
&app.wav) );
} else {
err_exit("invalid output file", PJ_EINVAL);
}
/* Loop reading PCAP and writing WAV file */
for (;;) {
@ -298,7 +378,12 @@ static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
T( pjmedia_codec_decode(app.codec, &frames[i], pcm_frame.size,
&pcm_frame) );
T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
if (app.wav) {
T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
}
if (app.aud_strm) {
T( wait_play(&pcm_frame) );
}
samples_cnt += samples_per_frame;
}
@ -321,7 +406,12 @@ static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
pj_bzero(pcm_frame.buf, pcm_frame.size);
}
T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
if (app.wav) {
T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
}
if (app.aud_strm) {
T( wait_play(&pcm_frame) );
}
ts_gap -= samples_per_frame;
}
@ -335,11 +425,15 @@ static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
int main(int argc, char *argv[])
{
pj_str_t input, output, wav, srtp_crypto, srtp_key;
pj_str_t input, output, srtp_crypto, srtp_key, codec;
pjmedia_aud_dev_index dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
pj_pcap_filter filter;
pj_status_t status;
enum { OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT };
enum {
OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT,
OPT_CODEC, OPT_PLAY_DEV_ID
};
struct pj_getopt_option long_options[] = {
{ "srtp-crypto", 1, 0, 'c' },
{ "srtp-key", 1, 0, 'k' },
@ -347,6 +441,8 @@ int main(int argc, char *argv[])
{ "dst-ip", 1, 0, OPT_DST_IP },
{ "src-port", 1, 0, OPT_SRC_PORT },
{ "dst-port", 1, 0, OPT_DST_PORT },
{ "codec", 1, 0, OPT_CODEC },
{ "play-dev-id", 1, 0, OPT_PLAY_DEV_ID },
{ NULL, 0, 0, 0}
};
int c;
@ -354,6 +450,7 @@ int main(int argc, char *argv[])
char key_bin[32];
srtp_crypto.slen = srtp_key.slen = 0;
codec.slen = 0;
pj_pcap_filter_default(&filter);
filter.link = PJ_PCAP_LINK_TYPE_ETH;
@ -398,6 +495,12 @@ int main(int argc, char *argv[])
case OPT_DST_PORT:
filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
break;
case OPT_CODEC:
codec = pj_str(pj_optarg);
break;
case OPT_PLAY_DEV_ID:
dev_id = atoi(pj_optarg);
break;
default:
puts("Error: invalid option");
return 1;
@ -417,7 +520,6 @@ int main(int argc, char *argv[])
input = pj_str(argv[pj_optind]);
output = pj_str(argv[pj_optind+1]);
wav = pj_str(".wav");
T( pj_init() );
@ -430,16 +532,9 @@ int main(int argc, char *argv[])
T( pj_pcap_open(app.pool, input.ptr, &app.pcap) );
T( pj_pcap_set_filter(app.pcap, &filter) );
if (pj_stristr(&output, &wav)) {
pcap2wav(output.ptr, &srtp_crypto, &srtp_key);
} else {
err_exit("invalid output file", PJ_EINVAL);
}
pcap2wav(&codec, &output, dev_id, &srtp_crypto, &srtp_key);
pjmedia_endpt_destroy(app.mept);
pj_pool_release(app.pool);
pj_caching_pool_destroy(&app.cp);
pj_shutdown();
cleanup();
return 0;
}

View File

@ -367,6 +367,7 @@ struct pjsip_inv_session
pjsip_status_code cause; /**< Disconnect cause. */
pj_str_t cause_text; /**< Cause text. */
pj_bool_t notify; /**< Internal. */
unsigned cb_called; /**< Cb has been called */
pjsip_dialog *dlg; /**< Underlying dialog. */
pjsip_role_e role; /**< Invite role. */
unsigned options; /**< Options in use. */

View File

@ -668,6 +668,16 @@ PJ_INLINE(pjsip_cfg_t*) pjsip_cfg(void)
# define PJSIP_POOL_TSX_INC 256
#endif
/**
* Delay for non-100 1xx retransmission, in seconds.
* Set to 0 to disable this feature.
*
* Default: 60 seconds
*/
#ifndef PJSIP_TSX_1XX_RETRANS_DELAY
# define PJSIP_TSX_1XX_RETRANS_DELAY 60
#endif
#define PJSIP_MAX_TSX_KEY_LEN (PJSIP_MAX_URL_SIZE*2)
/* User agent. */

View File

@ -56,8 +56,8 @@ PJ_BEGIN_DECL
* existing modules (such as when incoming request has unsupported method).
* - and so on..
*
* Theoritically application can have multiple instances of SIP endpoint,
* although it's not clear why application may want to do it.
* Application should only instantiate one SIP endpoint instance for every
* process.
*
* @{
*/

View File

@ -538,6 +538,22 @@ typedef pj_status_t
const pjsua_med_tp_state_info *info);
/**
* This enumeration specifies the options for custom media transport creation.
*/
typedef enum pjsua_create_media_transport_flag
{
/**
* This flag indicates that the media transport must also close its
* "member" or "child" transport when pjmedia_transport_close() is
* called. If this flag is not specified, then the media transport
* must not call pjmedia_transport_close() of its member transport.
*/
PJSUA_MED_TP_CLOSE_MEMBER = 1
} pjsua_create_media_transport_flag;
/**
* This structure describes application callback to receive various event
* notification from PJSUA-API. All of these callbacks are OPTIONAL,
@ -705,6 +721,18 @@ typedef struct pjsua_callback
pjsua_call_id new_call_id);
/**
* Notify application when registration or unregistration has been
* initiated. Note that this only notifies the initial registration
* and unregistration. Once registration session is active, subsequent
* refresh will not cause this callback to be called.
*
* @param acc_id The account ID.
* @param renew Non-zero for registration and zero for
* unregistration.
*/
void (*on_reg_started)(pjsua_acc_id acc_id, pj_bool_t renew);
/**
* Notify application when registration status has changed.
* Application may then query the account info to get the
@ -1074,6 +1102,32 @@ typedef struct pjsua_callback
unsigned med_idx,
pjmedia_event *event);
/**
* This callback can be used by application to implement custom media
* transport adapter for the call, or to replace the media transport
* with something completely new altogether.
*
* This callback is called when a new call is created. The library has
* created a media transport for the call, and it is provided as the
* \a base_tp argument of this callback. Upon returning, the callback
* must return an instance of media transport to be used by the call.
*
* @param call_id Call ID
* @param media_idx The media index in the SDP for which this media
* transport will be used.
* @param base_tp The media transport which otherwise will be
* used by the call has this callback not been
* implemented.
* @param flags Bitmask from pjsua_create_media_transport_flag.
*
* @return The callback must return an instance of media
* transport to be used by the call.
*/
pjmedia_transport* (*on_create_media_transport)(pjsua_call_id call_id,
unsigned media_idx,
pjmedia_transport *base_tp,
unsigned flags);
} pjsua_callback;
@ -1109,6 +1163,34 @@ typedef enum pjsua_sip_timer_use
} pjsua_sip_timer_use;
/**
* This constants controls the use of 100rel extension.
*/
typedef enum pjsua_100rel_use
{
/**
* Not used. For UAC, support for 100rel will be indicated in Supported
* header so that peer can opt to use it if it wants to. As UAS, this
* option will NOT cause 100rel to be used even if UAC indicates that
* it supports this feature.
*/
PJSUA_100REL_NOT_USED,
/**
* Mandatory. UAC will place 100rel in Require header, and UAS will
* reject incoming calls unless it has 100rel in Supported header.
*/
PJSUA_100REL_MANDATORY,
/**
* Optional. Similar to PJSUA_100REL_NOT_USED, except that as UAS, this
* option will cause 100rel to be used if UAC indicates that it supports it.
*/
PJSUA_100REL_OPTIONAL
} pjsua_100rel_use;
/**
* This structure describes the settings to control the API and
* user agent behavior, and can be specified when calling #pjsua_init().
@ -1245,13 +1327,13 @@ typedef struct pjsua_config
int nat_type_in_sdp;
/**
* Specify whether support for reliable provisional response (100rel and
* PRACK) should be required by default. Note that this setting can be
* Specify how the support for reliable provisional response (100rel/
* PRACK) should be used by default. Note that this setting can be
* further customized in account configuration (#pjsua_acc_config).
*
* Default: PJ_FALSE
* Default: PJSUA_100REL_NOT_USED
*/
pj_bool_t require_100rel;
pjsua_100rel_use require_100rel;
/**
* Specify the usage of Session Timers for all sessions. See the
@ -1365,6 +1447,35 @@ typedef struct pjsua_config
} pjsua_config;
/**
* Flags to be given to pjsua_destroy2()
*/
typedef enum pjsua_destroy_flag
{
/**
* Allow sending outgoing messages (such as unregistration, event
* unpublication, BYEs, unsubscription, etc.), but do not wait for
* responses. This is useful to perform "best effort" clean up
* without delaying the shutdown process waiting for responses.
*/
PJSUA_DESTROY_NO_RX_MSG = 1,
/**
* If this flag is set, do not send any outgoing messages at all.
* This flag is useful if application knows that the network which
* the messages are to be sent on is currently down.
*/
PJSUA_DESTROY_NO_TX_MSG = 2,
/**
* Do not send or receive messages during destroy. This flag is
* shorthand for PJSUA_DESTROY_NO_RX_MSG + PJSUA_DESTROY_NO_TX_MSG.
*/
PJSUA_DESTROY_NO_NETWORK = PJSUA_DESTROY_NO_RX_MSG |
PJSUA_DESTROY_NO_TX_MSG
} pjsua_destroy_flag;
/**
* Use this function to initialize pjsua config.
*
@ -1513,6 +1624,8 @@ PJ_DECL(pj_status_t) pjsua_start(void);
* Application.may safely call this function more than once if it doesn't
* keep track of it's state.
*
* @see pjsua_destroy2()
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjsua_destroy(void);
@ -1526,6 +1639,16 @@ PJ_DECL(pj_status_t) pjsua_destroy(void);
PJ_DECL(pjsua_state) pjsua_get_state(void);
/**
* Variant of destroy with additional flags.
*
* @param flags Combination of pjsua_destroy_flag enumeration.
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pjsua_destroy2(unsigned flags);
/**
* Poll pjsua for events, and if necessary block the caller thread for
* the specified maximum interval (in miliseconds).
@ -2395,12 +2518,14 @@ typedef struct pjsua_acc_config
pj_str_t contact_uri_params;
/**
* Specify whether support for reliable provisional response (100rel and
* PRACK) should be required for all sessions of this account.
* Specify how support for reliable provisional response (100rel/
* PRACK) should be used for all sessions in this account. See the
* documentation of pjsua_100rel_use enumeration for more info.
*
* Default: PJ_FALSE
* Default: The default value is taken from the value of
* require_100rel in pjsua_config.
*/
pj_bool_t require_100rel;
pjsua_100rel_use require_100rel;
/**
* Specify the usage of Session Timers for all sessions. See the
@ -2676,12 +2801,27 @@ typedef struct pjsua_acc_config
/**
* Specify interval of auto registration retry upon registration failure
* (including caused by transport problem), in second. Set to 0 to
* disable auto re-registration.
* disable auto re-registration. Note that if the registration retry
* occurs because of transport failure, the first retry will be done
* after \a reg_first_retry_interval seconds instead. Also note that
* the interval will be randomized slightly by approximately +/- ten
* seconds to avoid all clients re-registering at the same time.
*
* See also \a reg_first_retry_interval setting.
*
* Default: #PJSUA_REG_RETRY_INTERVAL
*/
unsigned reg_retry_interval;
/**
* This specifies the interval for the first registration retry. The
* registration retry is explained in \a reg_retry_interval. Note that
* the value here will also be randomized by +/- ten seconds.
*
* Default: 0
*/
unsigned reg_first_retry_interval;
/**
* Specify whether calls of the configured account should be dropped
* after registration failure and an attempt of re-registration has
@ -2721,6 +2861,16 @@ typedef struct pjsua_acc_config
* Default: PJSUA_CALL_HOLD_TYPE_DEFAULT
*/
pjsua_call_hold_type call_hold_type;
/**
* Specify whether the account should register as soon as it is
* added to the UA. Application can set this to PJ_FALSE and control
* the registration manually with pjsua_acc_set_registration().
*
* Default: PJ_TRUE
*/
pj_bool_t register_on_acc_add;
} pjsua_acc_config;

View File

@ -43,6 +43,7 @@ struct pjsua_call_media
pjmedia_type type; /**< Media type. */
unsigned idx; /**< This media index in parent call. */
pjsua_call_media_status state; /**< Media state. */
pjsua_call_media_status prev_state;/**< Previous media state. */
pjmedia_dir dir; /**< Media direction. */
/** The stream */
@ -78,6 +79,7 @@ struct pjsua_call_media
pjmedia_transport *tp_orig; /**< Original media transport */
pj_bool_t tp_auto_del; /**< May delete media transport */
pjsua_med_tp_st tp_st; /**< Media transport state */
pj_bool_t use_custom_med_tp;/**< Use custom media transport? */
pj_sockaddr rtp_addr; /**< Current RTP source address
(used to update ICE default
address) */
@ -130,6 +132,7 @@ struct pjsua_call
int secure_level;/**< Signaling security level. */
pjsua_call_hold_type call_hold_type; /**< How to do call hold. */
pj_bool_t local_hold;/**< Flag for call-hold by local. */
void *hold_msg; /**< Outgoing hold tx_data. */
unsigned med_cnt; /**< Number of media in SDP. */
pjsua_call_media media[PJSUA_MAX_CALL_MEDIA]; /**< Array of media */
@ -580,7 +583,7 @@ void pjsua_pres_update_acc(int acc_id, pj_bool_t force);
/*
* Shutdown presence.
*/
void pjsua_pres_shutdown(void);
void pjsua_pres_shutdown(unsigned flags);
/**
* Init presence for aoocunt.
@ -595,12 +598,12 @@ pj_status_t pjsua_pres_init_publish_acc(int acc_id);
/**
* Send un-PUBLISH
*/
void pjsua_pres_unpublish(pjsua_acc *acc);
void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags);
/**
* Terminate server subscription for the account
*/
void pjsua_pres_delete_acc(int acc_id);
void pjsua_pres_delete_acc(int acc_id, unsigned flags);
/**
* Init IM module handler to handle incoming MESSAGE outside dialog.
@ -635,7 +638,7 @@ pj_status_t pjsua_media_subsys_start(void);
/**
* Destroy pjsua media subsystem.
*/
pj_status_t pjsua_media_subsys_destroy(void);
pj_status_t pjsua_media_subsys_destroy(unsigned flags);
/**
* Private: check if we can accept the message.

View File

@ -295,7 +295,7 @@ static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx )
*/
PJ_DEF(void) pjsip_evsub_init_parser(void)
{
pjsip_register_hdr_parser( "Event", NULL,
pjsip_register_hdr_parser( "Event", "o",
&parse_hdr_event);
pjsip_register_hdr_parser( "Subscription-State", NULL,

View File

@ -324,15 +324,16 @@ PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st)
PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st)
{
pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC);
pj_assert(node != NULL);
if (!node)
return PJ_FALSE;
return pj_stricmp(&node->content, &OPEN)==0;
}
PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open)
{
pj_xml_node *node = pj_xml_find_node(st, &BASIC);
pj_assert(node != NULL);
node->content = open ? OPEN : CLOSED;
if (node)
node->content = open ? OPEN : CLOSED;
}
PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity)

View File

@ -105,8 +105,10 @@ typedef struct uas_state_t
/* UAC state */
typedef struct uac_state_t
{
pj_int32_t cseq;
pj_uint32_t rseq; /* Initialized to -1 */
pj_str_t tag; /* To tag */
pj_int32_t cseq;
pj_uint32_t rseq; /* Initialized to -1 */
struct uac_state_t *next; /* next call leg */
} uac_state_t;
@ -115,7 +117,7 @@ struct dlg_data
{
pjsip_inv_session *inv;
uas_state_t *uas_state;
uac_state_t *uac_state;
uac_state_t *uac_state_list;
};
@ -231,6 +233,8 @@ PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
pjsip_tx_data **p_tdata)
{
dlg_data *dd;
uac_state_t *uac_state = NULL;
const pj_str_t *to_tag = &rdata->msg_info.to->tag;
pjsip_transaction *tsx;
pjsip_msg *msg;
pjsip_generic_string_hdr *rseq_hdr;
@ -261,41 +265,51 @@ PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
if (rseq_hdr == NULL) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring provisional response with no RSeq header"));
"Ignoring 100rel response with no RSeq header"));
return PJSIP_EMISSINGHDR;
}
rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
/* Create new UAC state if we don't have one */
if (dd->uac_state == NULL) {
dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool,
uac_state_t);
dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
dd->uac_state->rseq = rseq - 1;
/* Find UAC state for the specified call leg */
uac_state = dd->uac_state_list;
while (uac_state) {
if (pj_strcmp(&uac_state->tag, to_tag)==0)
break;
uac_state = uac_state->next;
}
/* If this is from new INVITE transaction, reset UAC state */
if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) {
dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
dd->uac_state->rseq = rseq - 1;
/* Create new UAC state if we don't have one */
if (uac_state == NULL) {
uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t);
uac_state->cseq = rdata->msg_info.cseq->cseq;
uac_state->rseq = rseq - 1;
pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag);
uac_state->next = dd->uac_state_list;
dd->uac_state_list = uac_state;
}
/* If this is from new INVITE transaction, reset UAC state. */
if (rdata->msg_info.cseq->cseq != uac_state->cseq) {
uac_state->cseq = rdata->msg_info.cseq->cseq;
uac_state->rseq = rseq - 1;
}
/* Ignore provisional response retransmission */
if (rseq <= dd->uac_state->rseq) {
if (rseq <= uac_state->rseq) {
/* This should have been handled before */
return PJ_EIGNORED;
/* Ignore provisional response with out-of-order RSeq */
} else if (rseq != dd->uac_state->rseq + 1) {
} else if (rseq != uac_state->rseq + 1) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring provisional response because RSeq jump "
"Ignoring 100rel response because RSeq jump "
"(expecting %u, got %u)",
dd->uac_state->rseq+1, rseq));
uac_state->rseq+1, rseq));
return PJ_EIGNORED;
}
/* Update our RSeq */
dd->uac_state->rseq = rseq;
uac_state->rseq = rseq;
/* Create PRACK */
status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
@ -303,6 +317,26 @@ PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
if (status != PJ_SUCCESS)
return status;
/* If this response is a forked response from a different call-leg,
* update the req URI (https://trac.pjsip.org/repos/ticket/1364)
*/
if (pj_strcmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) {
const pjsip_contact_hdr *mhdr;
mhdr = (const pjsip_contact_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg,
PJSIP_H_CONTACT, NULL);
if (!mhdr || !mhdr->uri) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring 100rel response with no or "
"invalid Contact header"));
pjsip_tx_data_dec_ref(tdata);
return PJ_EIGNORED;
}
tdata->msg->line.req.uri = (pjsip_uri*)
pjsip_uri_clone(tdata->pool, mhdr->uri);
}
/* Create RAck header */
rack.ptr = rack_buf;
rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),

View File

@ -195,8 +195,18 @@ void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
pjsip_event *e)
{
pjsip_inv_state prev_state = inv->state;
pj_bool_t dont_notify = PJ_FALSE;
pj_status_t status;
/* Prevent STATE_CALLING from being reported more than once because
* of authentication
* https://trac.pjsip.org/repos/ticket/1318
*/
if (state==PJSIP_INV_STATE_CALLING &&
(inv->cb_called & (1 << PJSIP_INV_STATE_CALLING)) != 0)
{
dont_notify = PJ_TRUE;
}
/* If state is confirmed, check that SDP negotiation is done,
* otherwise disconnect the session.
@ -224,8 +234,11 @@ void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
pj_assert(inv->state != PJSIP_INV_STATE_DISCONNECTED ||
inv->cause != 0);
/* Mark the callback as called for this state */
inv->cb_called |= (1 << state);
/* Call on_state_changed() callback. */
if (mod_inv.cb.on_state_changed && inv->notify)
if (mod_inv.cb.on_state_changed && inv->notify && !dont_notify)
(*mod_inv.cb.on_state_changed)(inv, e);
/* Only decrement when previous state is not already DISCONNECTED */
@ -4116,6 +4129,16 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e)
/* Not Acceptable */
const pjsip_hdr *accept;
/* The incoming SDP is unacceptable. If the SDP negotiator
* state has just been changed, i.e: DONE -> REMOTE_OFFER,
* revert it back.
*/
if (pjmedia_sdp_neg_get_state(inv->neg) ==
PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
status = pjsip_dlg_create_response(inv->dlg, rdata,
488, NULL, &tdata);
if (status != PJ_SUCCESS)

View File

@ -81,10 +81,13 @@ static int multipart_print_body(struct pjsip_msg_body *msg_body,
/* Print optional headers */
hdr = part->hdr.next;
while (hdr != &part->hdr) {
int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p, SIZE_LEFT());
int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p,
SIZE_LEFT()-2);
if (printed < 0)
return -1;
p += printed;
*p++ = '\r';
*p++ = '\n';
hdr = hdr->next;
}

View File

@ -146,13 +146,6 @@ static pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000,
#define TIMER_INACTIVE 0
#define TIMER_ACTIVE 1
/* Delay for 1xx retransmission (should be 60 seconds).
* Specify 0 to disable this feature
*/
#ifndef PJSIP_TSX_1XX_RETRANS_DELAY
# define PJSIP_TSX_1XX_RETRANS_DELAY 60
#endif
/* Prototypes. */
static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck);
@ -2118,7 +2111,6 @@ PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx,
*/
static void tsx_resched_retransmission( pjsip_transaction *tsx )
{
pj_time_val timeout;
pj_uint32_t msec_time;
pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0);
@ -2151,11 +2143,15 @@ static void tsx_resched_retransmission( pjsip_transaction *tsx )
}
}
timeout.sec = msec_time / 1000;
timeout.msec = msec_time % 1000;
tsx->retransmit_timer.id = TIMER_ACTIVE;
pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
&timeout);
if (msec_time != 0) {
pj_time_val timeout;
timeout.sec = msec_time / 1000;
timeout.msec = msec_time % 1000;
tsx->retransmit_timer.id = TIMER_ACTIVE;
pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
&timeout);
}
}
/*
@ -2987,6 +2983,12 @@ static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx,
timeout.sec = timeout.msec = 0;
}
lock_timer(tsx);
/* In the short period above timer may have been inserted
* by set_timeout() (by CANCEL). Cancel it if necessary. See:
* https://trac.pjsip.org/repos/ticket/1374
*/
if (tsx->timeout_timer.id)
pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
tsx->timeout_timer.id = TIMER_ACTIVE;
pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
unlock_timer(tsx);

View File

@ -813,6 +813,8 @@ static pj_status_t get_dest_info(const pjsip_uri *target_uri,
if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
pjsip_uri *uri = (pjsip_uri*) target_uri;
const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
unsigned flag;
dest_info->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE);
if (url->maddr_param.slen)
pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
@ -821,6 +823,18 @@ static pj_status_t get_dest_info(const pjsip_uri *target_uri,
dest_info->addr.port = url->port;
dest_info->type =
pjsip_transport_get_type_from_name(&url->transport_param);
/* Double-check that the transport parameter match.
* Sample case: sips:host;transport=tcp
* See https://trac.pjsip.org/repos/ticket/1319
*/
flag = pjsip_transport_get_flag_from_type(dest_info->type);
if ((flag & dest_info->flag) != dest_info->flag) {
pjsip_transport_type_e t;
t = pjsip_transport_get_type_from_flag(dest_info->flag);
if (t != PJSIP_TRANSPORT_UNSPECIFIED)
dest_info->type = t;
}
} else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
pjsip_uri *uri = (pjsip_uri*) target_uri;
@ -1390,6 +1404,9 @@ static void send_raw_resolver_callback( pj_status_t status,
pj_assert(addr->count != 0);
/* Avoid tdata destroyed by pjsip_tpmgr_send_raw(). */
pjsip_tx_data_add_ref(sraw_data->tdata);
data_len = sraw_data->tdata->buf.cur - sraw_data->tdata->buf.start;
status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(sraw_data->endpt),
addr->entry[0].type,

View File

@ -470,9 +470,10 @@ PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg,
(int)cfg->id.slen, cfg->id.ptr, id));
/* If accounts has registration enabled, start registration */
if (pjsua_var.acc[id].cfg.reg_uri.slen)
pjsua_acc_set_registration(id, PJ_TRUE);
else {
if (pjsua_var.acc[id].cfg.reg_uri.slen) {
if (pjsua_var.acc[id].cfg.register_on_acc_add)
pjsua_acc_set_registration(id, PJ_TRUE);
} else {
/* Otherwise subscribe to MWI, if it's enabled */
if (pjsua_var.acc[id].cfg.mwi_enabled)
pjsua_start_mwi(&pjsua_var.acc[id]);
@ -603,7 +604,7 @@ PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id)
}
/* Delete server presence subscription */
pjsua_pres_delete_acc(acc_id);
pjsua_pres_delete_acc(acc_id, 0);
/* Release account pool */
if (pjsua_var.acc[acc_id].pool) {
@ -833,7 +834,7 @@ PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id,
if (acc->cfg.publish_enabled != cfg->publish_enabled) {
acc->cfg.publish_enabled = cfg->publish_enabled;
if (!acc->cfg.publish_enabled)
pjsua_pres_unpublish(acc);
pjsua_pres_unpublish(acc, 0);
else
update_reg = PJ_TRUE;
}
@ -992,6 +993,7 @@ PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id,
acc->cfg.unreg_timeout = cfg->unreg_timeout;
acc->cfg.allow_contact_rewrite = cfg->allow_contact_rewrite;
acc->cfg.reg_retry_interval = cfg->reg_retry_interval;
acc->cfg.reg_first_retry_interval = cfg->reg_first_retry_interval;
acc->cfg.drop_calls_on_reg_fail = cfg->drop_calls_on_reg_fail;
if (acc->cfg.reg_delay_before_refresh != cfg->reg_delay_before_refresh) {
acc->cfg.reg_delay_before_refresh = cfg->reg_delay_before_refresh;
@ -1393,7 +1395,7 @@ static pj_bool_t acc_check_nat_addr(pjsua_acc *acc,
tp->type_name,
(int)acc->cfg.contact_uri_params.slen,
acc->cfg.contact_uri_params.ptr,
ob,
(acc->cfg.use_rfc5626? ob: ""),
(int)acc->cfg.contact_params.slen,
acc->cfg.contact_params.ptr);
if (len < 1) {
@ -1691,11 +1693,14 @@ static void regc_cb(struct pjsip_regc_cbparam *param)
pjsua_acc *acc = (pjsua_acc*) param->token;
if (param->regc != acc->regc)
PJSUA_LOCK();
if (param->regc != acc->regc) {
PJSUA_UNLOCK();
return;
}
pj_log_push_indent();
PJSUA_LOCK();
/*
* Print registration status.
@ -2054,7 +2059,7 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id,
goto on_return;
}
pjsua_pres_unpublish(&pjsua_var.acc[acc_id]);
pjsua_pres_unpublish(&pjsua_var.acc[acc_id], 0);
status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata);
}
@ -2070,6 +2075,10 @@ PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id,
pjsip_regc_get_info(pjsua_var.acc[acc_id].regc, &reg_info);
pjsua_var.acc[acc_id].auto_rereg.reg_tp = reg_info.transport;
if (pjsua_var.ua_cfg.cb.on_reg_started) {
(*pjsua_var.ua_cfg.cb.on_reg_started)(acc_id, renew);
}
}
if (status != PJ_SUCCESS) {
@ -2529,10 +2538,11 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool,
/* Create the contact header */
contact->ptr = (char*)pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
"%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s",
"%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s",
(acc->display.slen?"\"" : ""),
(int)acc->display.slen,
acc->display.ptr,
(acc->display.slen?" " : ""),
(acc->display.slen?"\" " : ""),
(secure ? PJSUA_SECURE_SCHEME : "sip"),
(int)acc->user_part.slen,
acc->user_part.ptr,
@ -2545,7 +2555,7 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool,
transport_param,
(int)acc->cfg.contact_uri_params.slen,
acc->cfg.contact_uri_params.ptr,
ob,
(acc->cfg.use_rfc5626? ob: ""),
(int)acc->cfg.contact_params.slen,
acc->cfg.contact_params.ptr);
@ -2687,10 +2697,11 @@ PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool,
/* Create the contact header */
contact->ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
"%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s",
"%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s",
(acc->display.slen?"\"" : ""),
(int)acc->display.slen,
acc->display.ptr,
(acc->display.slen?" " : ""),
(acc->display.slen?"\" " : ""),
(secure ? PJSUA_SECURE_SCHEME : "sip"),
(int)acc->user_part.slen,
acc->user_part.ptr,
@ -2807,8 +2818,23 @@ static void schedule_reregistration(pjsua_acc *acc)
acc->auto_rereg.timer.user_data = acc;
/* Reregistration attempt. The first attempt will be done immediately. */
delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval : 0;
delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval :
acc->cfg.reg_first_retry_interval;
delay.msec = 0;
/* Randomize interval by +/- 10 secs */
if (delay.sec >= 10) {
delay.msec = -10000 + (pj_rand() % 20000);
} else {
delay.sec = 0;
delay.msec = (pj_rand() % 10000);
}
pj_time_val_normalize(&delay);
PJ_LOG(4,(THIS_FILE,
"Scheduling re-registration retry for acc %d in %u seconds..",
acc->index, delay.sec));
pjsua_schedule_timer(&acc->auto_rereg.timer, &delay);
}

View File

@ -971,7 +971,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
/* Verify that we can handle the request. */
options |= PJSIP_INV_SUPPORT_100REL;
options |= PJSIP_INV_SUPPORT_TIMER;
if (pjsua_var.acc[acc_id].cfg.require_100rel)
if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY)
options |= PJSIP_INV_REQUIRE_100REL;
if (pjsua_var.media_cfg.enable_ice)
options |= PJSIP_INV_SUPPORT_ICE;
@ -1047,6 +1047,19 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
options &= ~(PJSIP_INV_SUPPORT_TIMER);
}
/* If 100rel is optional and UAC supports it, use it. */
if ((options & PJSIP_INV_REQUIRE_100REL)==0 &&
pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_OPTIONAL)
{
const pj_str_t token = { "100rel", 6};
pjsip_dialog_cap_status cap_status;
cap_status = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_SUPPORTED, NULL,
&token);
if (cap_status == PJSIP_DIALOG_CAP_SUPPORTED)
options |= PJSIP_INV_REQUIRE_100REL;
}
/* Create invite session: */
status = pjsip_inv_create_uas( dlg, rdata, NULL, options, &inv);
if (status != PJ_SUCCESS) {
@ -1288,6 +1301,7 @@ pj_status_t acquire_call(const char *title,
pj_time_val time_start, timeout;
pj_gettimeofday(&time_start);
timeout.sec = 0;
timeout.msec = PJSUA_ACQUIRE_CALL_TIMEOUT;
pj_time_val_normalize(&timeout);
@ -1356,20 +1370,24 @@ pj_status_t acquire_call(const char *title,
PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
{
pjsua_call *call;
pjsua_conf_port_id port_id;
pjsip_dialog *dlg;
pj_status_t status;
pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
PJ_EINVAL);
status = acquire_call("pjsua_call_get_conf_port()", call_id, &call, &dlg);
if (status != PJ_SUCCESS)
return PJSUA_INVALID_ID;
/* Use PJSUA_LOCK() instead of acquire_call():
* https://trac.pjsip.org/repos/ticket/1371
*/
PJSUA_LOCK();
if (!pjsua_call_is_active(call_id))
goto on_return;
call = &pjsua_var.calls[call_id];
port_id = call->media[call->audio_idx].strm.a.conf_slot;
pjsip_dlg_dec_lock(dlg);
on_return:
PJSUA_UNLOCK();
return port_id;
}
@ -1383,18 +1401,23 @@ PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id,
pjsua_call_info *info)
{
pjsua_call *call;
pjsip_dialog *dlg;
unsigned mi;
pj_status_t status;
PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
PJ_EINVAL);
pj_bzero(info, sizeof(*info));
status = acquire_call("pjsua_call_get_info()", call_id, &call, &dlg);
if (status != PJ_SUCCESS) {
return status;
/* Use PJSUA_LOCK() instead of acquire_call():
* https://trac.pjsip.org/repos/ticket/1371
*/
PJSUA_LOCK();
call = &pjsua_var.calls[call_id];
if (!call->inv) {
PJSUA_UNLOCK();
return PJSIP_ESESSIONTERMINATED;
}
/* id and role */
@ -1520,7 +1543,7 @@ PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id,
PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
}
pjsip_dlg_dec_lock(dlg);
PJSUA_UNLOCK();
return PJ_SUCCESS;
}
@ -1961,10 +1984,14 @@ PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
/* Add additional headers etc */
pjsua_process_msg_data( tdata, msg_data);
/* Record the tx_data to keep track the operation */
call->hold_msg = (void*) tdata;
/* Send the request */
status = pjsip_inv_send_msg( call->inv, tdata);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
call->hold_msg = NULL;
goto on_return;
}
@ -2531,14 +2558,15 @@ PJ_DEF(void) pjsua_call_hangup_all(void)
PJ_LOG(4,(THIS_FILE, "Hangup all calls.."));
pj_log_push_indent();
PJSUA_LOCK();
// This may deadlock, see https://trac.pjsip.org/repos/ticket/1305
//PJSUA_LOCK();
for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
if (pjsua_var.calls[i].inv)
pjsua_call_hangup(i, 0, NULL, NULL);
}
PJSUA_UNLOCK();
//PJSUA_UNLOCK();
pj_log_pop_indent();
}
@ -3971,9 +3999,22 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
&tsx->status_text);
}
}
} else if (tsx->role == PJSIP_ROLE_UAC &&
tsx->last_tx == (pjsip_tx_data*)call->hold_msg &&
tsx->state >= PJSIP_TSX_STATE_COMPLETED)
{
/* Monitor the status of call hold request */
call->hold_msg = NULL;
if (tsx->status_code/100 != 2) {
/* Outgoing call hold failed */
call->local_hold = PJ_FALSE;
PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)",
call->index, tsx->status_code));
}
}
on_return:
PJSUA_UNLOCK();
pj_log_pop_indent();
}

View File

@ -234,6 +234,7 @@ PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg)
pj_list_init(&cfg->reg_hdr_list);
pj_list_init(&cfg->sub_hdr_list);
cfg->call_hold_type = PJSUA_CALL_HOLD_TYPE_DEFAULT;
cfg->register_on_acc_add = PJ_TRUE;
}
PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg)
@ -1340,7 +1341,7 @@ pj_status_t resolve_stun_server(pj_bool_t wait)
/*
* Destroy pjsua.
*/
PJ_DEF(pj_status_t) pjsua_destroy(void)
PJ_DEF(pj_status_t) pjsua_destroy2(unsigned flags)
{
int i; /* Must be signed */
@ -1365,12 +1366,14 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
if (pjsua_var.endpt) {
unsigned max_wait;
PJ_LOG(4,(THIS_FILE, "Shutting down..."));
PJ_LOG(4,(THIS_FILE, "Shutting down, flags=%d...", flags));
pj_log_push_indent();
/* Terminate all calls. */
pjsua_call_hangup_all();
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
pjsua_call_hangup_all();
}
/* Set all accounts to offline */
for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
@ -1381,10 +1384,10 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
}
/* Terminate all presence subscriptions. */
pjsua_pres_shutdown();
pjsua_pres_shutdown(flags);
/* Destroy media (to shutdown media transports etc) */
pjsua_media_subsys_destroy();
pjsua_media_subsys_destroy(flags);
/* Wait for sometime until all publish client sessions are done
* (ticket #364)
@ -1398,6 +1401,11 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
max_wait = pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec;
}
/* No waiting if RX is disabled */
if (flags & PJSUA_DESTROY_NO_RX_MSG) {
max_wait = 0;
}
/* Second stage, wait for unpublications to complete */
for (i=0; i<(int)(max_wait/50); ++i) {
unsigned j;
@ -1427,7 +1435,8 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
if (!pjsua_var.acc[i].valid)
continue;
if (pjsua_var.acc[i].regc) {
if (pjsua_var.acc[i].regc && (flags & PJSUA_DESTROY_NO_TX_MSG)==0)
{
pjsua_acc_set_registration(i, PJ_FALSE);
}
}
@ -1452,6 +1461,11 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
max_wait = pjsua_var.acc[i].cfg.unreg_timeout;
}
/* No waiting if RX is disabled */
if (flags & PJSUA_DESTROY_NO_RX_MSG) {
max_wait = 0;
}
/* Second stage, wait for unregistrations to complete */
for (i=0; i<(int)(max_wait/50); ++i) {
unsigned j;
@ -1472,8 +1486,9 @@ PJ_DEF(pj_status_t) pjsua_destroy(void)
/* Wait for some time to allow unregistration and ICE/TURN
* transports shutdown to complete:
*/
if (i < 20)
if (i < 20 && (flags & PJSUA_DESTROY_NO_RX_MSG) == 0) {
busy_sleep(1000 - i*50);
}
PJ_LOG(4,(THIS_FILE, "Destroying..."));
@ -1560,6 +1575,12 @@ PJ_DEF(pjsua_state) pjsua_get_state(void)
return pjsua_var.state;
}
PJ_DEF(pj_status_t) pjsua_destroy(void)
{
return pjsua_destroy2(0);
}
/**
* Application is recommended to call this function after all initialization
* is done, so that the library can do additional checking set up

View File

@ -396,7 +396,7 @@ pj_status_t pjsua_media_subsys_start(void)
/*
* Destroy pjsua media subsystem.
*/
pj_status_t pjsua_media_subsys_destroy(void)
pj_status_t pjsua_media_subsys_destroy(unsigned flags)
{
unsigned i;
@ -441,6 +441,10 @@ pj_status_t pjsua_media_subsys_destroy(void)
pjsua_media_channel_deinit(i);
}
if (call_med->tp && call_med->tp_auto_del) {
/* TODO: check if we're not allowed to send to network in the
* "flags", and if so do not do TURN allocation...
*/
PJ_UNUSED_ARG(flags);
pjmedia_transport_close(call_med->tp);
}
call_med->tp = NULL;
@ -1294,11 +1298,18 @@ static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
if (call_med->tp_st == PJSUA_MED_TP_CREATING)
set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
if (!call_med->tp_orig &&
pjsua_var.ua_cfg.cb.on_create_media_transport)
{
call_med->use_custom_med_tp = PJ_TRUE;
} else
call_med->use_custom_med_tp = PJ_FALSE;
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
/* This function may be called when SRTP transport already exists
* (e.g: in re-invite, update), don't need to destroy/re-create.
*/
if (!call_med->tp_orig || call_med->tp == call_med->tp_orig) {
if (!call_med->tp_orig) {
pjmedia_srtp_setting srtp_opt;
pjmedia_transport *srtp = NULL;
@ -1314,7 +1325,7 @@ static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
/* Always create SRTP adapter */
pjmedia_srtp_setting_default(&srtp_opt);
srtp_opt.close_member_tp = PJ_TRUE;
/* If media session has been ever established, let's use remote's
/* If media session has been ever established, let's use remote's
* preference in SRTP usage policy, especially when it is stricter.
*/
if (call_med->rem_srtp_use > acc->cfg.use_srtp)
@ -1519,9 +1530,25 @@ static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
call->async_call.dlg->pool);
}
status = pjmedia_transport_media_create(
call_med->tp, tmp_pool,
0, call->async_call.rem_sdp, mi);
if (call_med->use_custom_med_tp) {
unsigned custom_med_tp_flags = 0;
/* Use custom media transport returned by the application */
call_med->tp =
(*pjsua_var.ua_cfg.cb.on_create_media_transport)
(call_id, mi, call_med->tp,
custom_med_tp_flags);
if (!call_med->tp) {
status =
PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
}
}
if (call_med->tp) {
status = pjmedia_transport_media_create(
call_med->tp, tmp_pool,
0, call->async_call.rem_sdp, mi);
}
if (status != PJ_SUCCESS) {
call->med_ch_info.status = status;
call->med_ch_info.med_idx = mi;
@ -2104,6 +2131,7 @@ static void stop_media_session(pjsua_call_id call_id)
PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
call_id, mi));
call_med->prev_state = call_med->state;
call_med->state = PJSUA_CALL_MEDIA_NONE;
}
@ -2133,12 +2161,19 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
pj_log_push_indent();
for (mi=0; mi<call->med_cnt; ++mi) {
pjsua_call_media *call_med = &call->media[mi];
if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)
pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);
}
stop_media_session(call_id);
for (mi=0; mi<call->med_cnt; ++mi) {
pjsua_call_media *call_med = &call->media[mi];
if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
pjmedia_transport_media_stop(call_med->tp);
set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
}
@ -2153,6 +2188,7 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
pjmedia_transport_close(call_med->tp);
call_med->tp = call_med->tp_orig = NULL;
}
call_med->tp_orig = NULL;
}
check_snd_dev_idle();
@ -2206,6 +2242,8 @@ static pj_status_t audio_channel_update(pjsua_call_media *call_med,
if (status != PJ_SUCCESS)
goto on_return;
si->rtcp_sdes_bye_disabled = PJ_TRUE;
/* Check if no media is active */
if (si->dir == PJMEDIA_DIR_NONE) {
/* Call media state */
@ -2296,6 +2334,9 @@ static pj_status_t audio_channel_update(pjsua_call_media *call_med,
goto on_return;
}
if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
/* If DTMF callback is installed by application, install our
* callback to the session.
*/

View File

@ -1320,13 +1320,17 @@ pj_status_t pjsua_pres_init_acc(int acc_id)
/* Unpublish presence publication */
void pjsua_pres_unpublish(pjsua_acc *acc)
void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
{
if (acc->publish_sess) {
pjsua_acc_config *acc_cfg = &acc->cfg;
acc->online_status = PJ_FALSE;
send_publish(acc->index, PJ_FALSE);
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
send_publish(acc->index, PJ_FALSE);
}
/* By ticket #364, don't destroy the session yet (let the callback
destroy it)
if (acc->publish_sess) {
@ -1339,7 +1343,7 @@ void pjsua_pres_unpublish(pjsua_acc *acc)
}
/* Terminate server subscription for the account */
void pjsua_pres_delete_acc(int acc_id)
void pjsua_pres_delete_acc(int acc_id, unsigned flags)
{
pjsua_acc *acc = &pjsua_var.acc[acc_id];
pjsua_srv_pres *uapres;
@ -1361,11 +1365,15 @@ void pjsua_pres_delete_acc(int acc_id)
pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
pjsip_pres_set_status(uapres->sub, &pres_status);
if (pjsip_pres_notify(uapres->sub,
PJSIP_EVSUB_STATE_TERMINATED, NULL,
&reason, &tdata)==PJ_SUCCESS)
{
pjsip_pres_send_request(uapres->sub, tdata);
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
if (pjsip_pres_notify(uapres->sub,
PJSIP_EVSUB_STATE_TERMINATED, NULL,
&reason, &tdata)==PJ_SUCCESS)
{
pjsip_pres_send_request(uapres->sub, tdata);
}
} else {
pjsip_pres_terminate(uapres->sub, PJ_FALSE);
}
uapres = next;
@ -1376,7 +1384,7 @@ void pjsua_pres_delete_acc(int acc_id)
pj_list_init(&acc->pres_srv_list);
/* Terminate presence publication, if any */
pjsua_pres_unpublish(acc);
pjsua_pres_unpublish(acc, flags);
}
@ -2251,6 +2259,10 @@ static void pres_timer_cb(pj_timer_heap_t *th,
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
pjsua_acc *acc = &pjsua_var.acc[i];
/* Acc may not be ready yet, otherwise assertion will happen */
if (!pjsua_acc_is_valid(i))
continue;
/* Retry PUBLISH */
if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
pjsua_pres_init_publish_acc(acc->index);
@ -2324,7 +2336,7 @@ pj_status_t pjsua_pres_start(void)
/*
* Shutdown presence.
*/
void pjsua_pres_shutdown(void)
void pjsua_pres_shutdown(unsigned flags)
{
unsigned i;
@ -2339,18 +2351,20 @@ void pjsua_pres_shutdown(void)
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
if (!pjsua_var.acc[i].valid)
continue;
pjsua_pres_delete_acc(i);
pjsua_pres_delete_acc(i, flags);
}
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
pjsua_var.buddy[i].monitor = 0;
}
refresh_client_subscriptions();
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
refresh_client_subscriptions();
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
if (pjsua_var.acc[i].valid)
pjsua_pres_update_acc(i, PJ_FALSE);
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
if (pjsua_var.acc[i].valid)
pjsua_pres_update_acc(i, PJ_FALSE);
}
}
pj_log_pop_indent();

View File

@ -0,0 +1,225 @@
<?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="Forked INVITE, one of them require PRACK">
<recv request="INVITE" crlf="true">
<action>
<ereg regexp="branch=([0-9a-zA-Z\-]*)"
search_in="hdr"
header="Via"
assign_to="1,2"/>
<assign assign_to="1" variable="2"/>
<ereg regexp="CSeq: [ 0-9A-Z]+"
search_in="msg"
assign_to="4"/>
</action>
</recv>
<send>
<![CDATA[
SIP/2.0 100 Trying
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[$4]
]]>
</send>
<!-- Call leg 1 sends 180/Ringing -->
<send retrans="500">
<![CDATA[
SIP/2.0 180 Ringing1
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;branch=[$2]
[last_From:]
[last_To:];tag=UA_1
[last_Call-ID:]
[$4]
Contact: <sip:UA_1@[local_ip]:[local_port]>
Require: 100rel
RSeq: 1000
Content-Length: 0
]]>
</send>
<recv request="PRACK" crlf="true">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
]]>
</send>
<pause milliseconds="2000" />
<!-- Call leg 2: 180/Ringing -->
<send retrans="500">
<![CDATA[
SIP/2.0 180 Ringing2
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;branch=[$2]
[last_From:]
[last_To:];tag=UA_2
[last_Call-ID:]
[$4]
Contact: <sip:UA_2@[local_ip]:[local_port]>
Require: 100rel
RSeq: 2000
Content-Length: 0
]]>
</send>
<recv request="PRACK" crlf="true">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
]]>
</send>
<pause milliseconds="2000" />
<!-- Call leg 2: sends Ringing again with correct RSeq -->
<send retrans="500">
<![CDATA[
SIP/2.0 180 Ringing2b
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;branch=[$2]
[last_From:]
[last_To:];tag=UA_2
[last_Call-ID:]
[$4]
Contact: <sip:UA_2@[local_ip]:[local_port]>
Require: 100rel
RSeq: 2001
Content-Length: 0
]]>
</send>
<recv request="PRACK" crlf="true">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
]]>
</send>
<pause milliseconds="2000" />
<!-- Call leg 2: sends Ringing again with WRONG RSeq. There should be no PRACK -->
<send>
<![CDATA[
SIP/2.0 180 Ringing2c
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;branch=[$2]
[last_From:]
[last_To:];tag=UA_2
[last_Call-ID:]
[$4]
Contact: <sip:UA_2@[local_ip]:[local_port]>
Require: 100rel
RSeq: 2004
Content-Length: 0
]]>
</send>
<pause milliseconds="2000" />
<!-- Then Call leg 1 sends 180/Ringing again -->
<send retrans="500">
<![CDATA[
SIP/2.0 180 Ringing1b
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;branch=[$2]
[last_From:]
[last_To:];tag=UA_1
[last_Call-ID:]
[$4]
Contact: <sip:UA_1@[local_ip]:[local_port]>
Require: 100rel
RSeq: 1001
Content-Length: 0
]]>
</send>
<recv request="PRACK" crlf="true">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
]]>
</send>
<pause milliseconds="2000" />
<!-- 603/Decline -->
<send>
<![CDATA[
SIP/2.0 603 Decline
Via: SIP/2.0/UDP 127.0.0.1;received=127.0.0.1;rport=5080;branch=[$2]
[last_From:]
[last_To:];tag=UA_1
[last_Call-ID:]
[$4]
Content-Length: 0
]]>
</send>
<!-- Receive 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>