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:
parent
3f0ff80097
commit
e7dbbc8f30
13135
aconfigure
13135
aconfigure
File diff suppressed because it is too large
Load Diff
|
@ -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 #
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(¶m->version, (char*)HTTP_1_0);
|
||||
param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT;
|
||||
pj_time_val_normalize(¶m->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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.*/
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_ */
|
||||
|
||||
|
|
|
@ -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__ */
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 );
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
¶m->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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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] : "???";
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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, ¶m) );
|
||||
|
||||
/* 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, ¶m) );
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, ®_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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
Loading…
Reference in New Issue